UNPKG

iobroker.lg-thinq

Version:
1,100 lines (1,076 loc) 164 kB
"use strict"; /* * Created with @iobroker/create-adapter v1.34.1 * Based on https://github.com/nVuln/homebridge-lg-thinq */ // The adapter-core module gives you access to the core ioBroker functions // you need to create an adapter const utils = require("@iobroker/adapter-core"); const axios = require("axios").default; const crypto = require("crypto"); const uuid = require("uuid"); const qs = require("qs"); const { DateTime } = require("luxon"); const Json2iob = require("./lib/extractKeys"); const constants = require("./lib/constants"); const { URL } = require("url"); const helper = require("./lib/helper"); const air = require("./lib/air_conditioning"); // Device 401 const heat = require("./lib/heat_pump"); // Device 406 const awsIot = require("aws-iot-device-sdk").device; const forge = require("node-forge"); const http = require("http"); const https = require("https"); const fs = require("fs"); class LgThinq extends utils.Adapter { /** * @param options Options */ constructor(options) { super({ ...options, name: "lg-thinq", }); this.on("ready", this.onReady.bind(this)); this.on("stateChange", this.onStateChange.bind(this)); this.on("unload", this.onUnload.bind(this)); this.requestClient = axios.create({ timeout: 60000, httpAgent: new http.Agent({ keepAlive: true }), httpsAgent: new https.Agent({ keepAlive: true }), maxRedirects: 10, maxContentLength: 50 * 1000 * 1000, }); this.updateInterval = null; this.qualityInterval = null; this.refreshTokenInterval = null; this.refreshTokenTimeout = null; this.updateThinq1Interval = null; this.updateThinq1SingleInterval = null; this.updatethinq1Run = false; this.refreshTimeout = null; this.refreshCounter = {}; this.session = {}; this.modelInfos = {}; this.auth = {}; this.workIds = {}; this.available401 = {}; this.deviceControls = {}; this.json2iob = new Json2iob(this); this.targetKeys = {}; this.homes = null; this.createDataPoint = helper.createDataPoint; this.setDryerBlindStates = helper.setDryerBlindStates; this.createFridge = helper.createFridge; this.createInterval = helper.createInterval; this.createStatistic = helper.createStatistic; this.createremote = helper.createremote; this.lastDeviceCourse = helper.lastDeviceCourse; this.insertCourse = helper.insertCourse; this.setCourse = helper.setCourse; this.setFavoriteCourse = helper.setFavoriteCourse; this.checkdate = helper.checkdate; this.createWeather = helper.createWeather; this.sendStaticRequest = helper.sendStaticRequest; this.sendStaticRequestThinq1 = helper.sendStaticRequestThinq1; this.createCourse = helper.createCourse; this.refreshRemote = helper.refreshRemote; this.refrigerator = helper.refrigerator; this.getSummary = air.getSummary; this.createAirRemoteStates = air.createAirRemoteStates; this.createAirRemoteThinq1States = air.createAirRemoteThinq1States; this.sendCommandThinq1AC = air.sendCommandThinq1AC; this.updateHoliday = air.updateHoliday; this.checkHolidayDate = air.checkHolidayDate; this.createHeatRemoteStates = heat.createHeatRemoteStates; this.createHeatSchedule = heat.createHeatSchedule; this.addHeat = heat.addHeat; this.delHeat = heat.delHeat; this.sendHeat = heat.sendHeat; this.updateHeat = heat.updateHeat; this.check_reservationCtrl = heat.check_reservationCtrl; this.mqttdata = {}; this.mqttC = null; this.lang = "de"; this.deviceJson = {}; this.courseJson = {}; this.courseactual = {}; this.coursetypes = {}; this.coursedownload = {}; this.remoteValue = {}; this.mqtt_userID = ""; this.isThinq2 = false; this.thinq1Counter = 0; this.isRestart = true; this.isFinished = false; this.jsessionId = null; this.client_id = null; this.svc = constants.SVC_CODE; this.lge = "LGE"; this.app_agent = ""; this.app_device = ""; this.isAdapterUpdateFor406 = false; this.countLogin = 0; } /** * Is called when databases are connected and adapter received configuration. */ async onReady() { let isPWChanged = false; const instance = await this.getObjectAsync("session"); if (instance && instance.native && instance.native.pw != "") { if ( this.config.user === instance.native.user && this.config.password === this.decrypt(instance.native.pw) ) { this.log.debug(`User and password have not been changed!`); isPWChanged = true; } else { this.log.debug(`Save user and password!!`); this.setSession(); } if (this.config.country === instance.native.country && this.config.language === instance.native.lang) { this.log.debug(`Country and language have not been changed!`); } else { if (isPWChanged) { this.setSession(); isPWChanged = false; } } } else if (instance && instance.native) { this.log.debug(`Save user and password!`); this.setSession(); } this.app_agent = constants.APP_AGENT[Math.floor(Math.random() * constants.APP_AGENT.length)]; this.app_device = constants.APP_DEVICE[Math.floor(Math.random() * constants.APP_DEVICE.length)]; await this.setConnection(false); await this.cleanOldVersion(); if (this.config.interval < 0.5) { this.log.info("Set interval to minimum 0.5"); this.config.interval = 0.5; } if (this.config.interval_thinq1 < 0 || this.config.interval_thinq1 > 1440) { this.log.info("Set thinq1 interval to 30 seconds"); this.config.interval_thinq1 = 30; } this.refreshCounter["interval.active"] = null; this.refreshCounter["interval.inactive"] = null; this.refreshCounter["interval.inactive"] = null; this.refreshCounter["interval.status_devices"] = null; const data = await this.getForeignObjectAsync("system.config"); if (data && data.common && data.common.language) { this.lang = data.common.language === this.lang ? this.lang : "en"; } this.log.debug(this.lang); this.defaultHeaders = { "x-api-key": constants.API_KEY, "x-client-id": constants.API_CLIENT_ID, "x-thinq-app-ver": "3.5.1700", "x-thinq-app-type": "NUTS", "x-thinq-app-level": "PRD", "x-thinq-app-os": "ANDROID", "x-thinq-app-logintype": "LGE", "x-service-code": "SVC202", "x-country-code": this.config.country, "x-language-code": this.config.language, "x-service-phase": "OP", "x-origin": "app-native", "x-model-name": "samsung / SM-N950N", "x-os-version": "7.1.2", "x-app-version": "3.5.1721", "x-message-id": this.random_string(22), }; this.gateway = await this.requestClient .get(constants.GATEWAY_URL, { headers: this.defaultHeaders }) .then(res => res.data.result) .catch(error => { this.log.error(error); }); this.log.debug(JSON.stringify(this.gateway)); if (this.gateway) { this.lgeapi_url = `https://${this.gateway.countryCode.toLowerCase()}.lgeapi.com/`; let session = 0; if (isPWChanged) { session = await this.sessionCheck(); } if (session === 1) { const refreshToken = await this.refreshNewToken(true); if (refreshToken) { session = (this.session.expires_in - 100) * 1000; } else { session = 0; } } if (session === 0) { if (this.config.regProcedure) { this.log.info(`Use the old third-party login`); this.session = await this.login(this.config.user, this.config.password).catch(error => { this.log.error(error); }); } else { this.log.info(`Use the new APP login`); this.session = await this.loginNew(); } } if ( this.session != null && this.session.access_token != null && this.session.expires_in != null && this.session.refresh_token != null ) { if (session === 0) { this.session.next = new Date().getTime() + parseInt(this.session.expires_in) * 1000; await this.setState("session", { val: this.encrypt(JSON.stringify(this.session)), ack: true }); } try { if (!this.jsessionId) { const jsessionId = await this.getJSessionId(); this.log.debug(`jsessionId: ${JSON.stringify(jsessionId)}`); if (jsessionId && jsessionId.jsessionId) { this.jsessionId = jsessionId.jsessionId; } } } catch (e) { this.logError("debug", "Cannot load sessionID: ", e); } this.log.debug(JSON.stringify(this.session)); this.log.info("Login successful"); if (session === 0) { this.session.next = new Date().getTime() + parseInt(this.session.expires_in) * 1000; await this.setState("session", { val: this.encrypt(JSON.stringify(this.session)), ack: true }); this.setRefreshTokenInterval(); } else { this.log.debug(`Start refreshTokenTimeout with ${session}!`); await this.setConnection(true); this.refreshTokenTimeout = this.setTimeout(() => { this.refreshTokenTimeout = null; this.refreshNewToken(false); }, session); } this.userNumber = await this.getUserNumber(); if (this.userNumber == null) { this.log.error(`Cannot found user data`); } const hash = crypto.createHash("sha256"); const clientID = this.userNumber ? this.userNumber : constants.API_CLIENT_ID; this.mqtt_userID = hash.update(clientID + new Date().getTime()).digest("hex"); this.defaultHeaders["x-user-no"] = this.userNumber; this.defaultHeaders["x-emp-token"] = this.session.access_token; let listDevices = await this.getListDevices(); if (listDevices && listDevices === "TERMS") { await this.setConnection(false); const new_term = await this.terms(); if (new_term) { listDevices = await this.getListDevices(); } else { return; } } if (listDevices && listDevices === "TERMS") { await this.setConnection(false); return; } else if (listDevices && listDevices === "BLOCKED") { await this.setConnection(false); return; } if (!listDevices) { this.log.info("Cannot find device! Delete session data"); await this.setState("session", { val: "", ack: true }); return; } this.log.info(`Found: ${listDevices.length} devices`); let isThinq1 = false; const area = {}; if (this.userNumber) { const hash = crypto.createHash("sha256"); this.client_id = hash.update(this.userNumber + new Date().getTime()).digest("hex"); } this.subscribeStates("*"); for (const element of listDevices) { this.log.info(`Create or update datapoints for ${element.deviceId}`); this.modelInfos[element.deviceId] = await this.getDeviceModelInfo(element); if (!this.modelInfos[element.deviceId]) { this.log.error(`Missing Modelinfo for device - ${element.deviceId}. Restart adapter please!!!`); continue; } else if (this.modelInfos[element.deviceId] === "NOK") { continue; } await this.setObjectNotExistsAsync(element.deviceId, { type: "device", common: { name: element.alias, role: "state", }, native: {}, }); await this.setObjectNotExistsAsync(`${element.deviceId}.quality`, { type: "state", common: { name: { en: "Datapoint quality", de: "Datenpunktqualität", ru: "Качество Datapoint", pt: "Qualidade de Datapoint", nl: "Datapunt kwaliteit", fr: "Qualité du Datapoint", it: "Qualità dei dati", es: "Calidad del punto de datos", pl: "Jakości danych", uk: "Якість даних", "zh-cn": "数据点", }, type: "string", role: "json", desc: "Datapoints Quality", read: true, write: false, def: "", }, native: {}, }); if (element.area != null) { area[element.area] = element.deviceId; } await this.json2iob.parse(element.deviceId, element, { forceIndex: true, write: true, preferedArrayName: null, channelName: null, autoCast: true, checkvalue: false, checkType: true, firstload: true, }); this.modelInfos[element.deviceId]["thinq2"] = element.platformType; this.modelInfos[element.deviceId]["signature"] = false; this.modelInfos[element.deviceId]["deviceState"] = element.deviceState; this.modelInfos[element.deviceId]["deviceType"] = 0; if (element.platformType && element.platformType === "thinq2") { this.isThinq2 = true; if ( element.deviceType && element.deviceType == 201 && element.snapshot && element.snapshot.washerDryer && element.snapshot.washerDryer.initialTimeHour == null ) { this.modelInfos[element.deviceId]["signature"] = true; //LG Signature without reserveTimeHour, remainTimeHour and initialTimeHour } } if (element.platformType && element.platformType === "thinq1") { isThinq1 = true; ++this.thinq1Counter; } if (element.deviceType != null) { this.modelInfos[element.deviceId]["deviceType"] = element.deviceType; //this.isThinq2 = true; } await this.pollMonitor(element); //await this.sleep(2000); this.log.info(`Update raw datapoints for ${element.deviceId}`); await this.extractValues(element); } this.log.debug(JSON.stringify(listDevices)); if (this.isThinq2) { this.start_mqtt(); } if (isThinq1 && this.config.interval_thinq1 > 0) { await this.sleep(2000); await this.createInterval(); this.setState("interval.interval", this.config.interval_thinq1, true); this.setState("interval.active", 0, true); this.setState("interval.active", 0, true); this.setState("interval.last_update", 0, true); this.setState("interval.status_devices", JSON.stringify({}), true); this.startPollMonitor(); } this.log.debug(`AREA: ${JSON.stringify(area)}`); this.createWeather(area); this.updateInterval = this.setInterval( async () => { await this.updateDevices(); }, this.config.interval * 60 * 1000, ); this.qualityInterval = this.setInterval( () => { this.cleanupQuality(); }, 60 * 60 * 24 * 1000, ); } else { this.log.warn(`Missing Session Infos!`); } } } setSession() { this.extendObject("session", { native: { user: this.config.user, pw: this.encrypt(this.config.password), country: this.config.country, lang: this.config.language, }, }); } setRefreshTokenInterval() { this.log.debug(`Start refreshTokenInterval!`); if (this.session && typeof this.session.expires_in !== "number") { try { this.session.expires_in = parseInt(this.session.expires_in); } catch { this.session.expires_in = 3600; } } if (this.session && this.session.expires_in < 3600) { this.session.expires_in = 3600; } this.refreshTokenInterval && this.clearInterval(this.refreshTokenInterval); this.log.debug(this.session.expires_in); this.refreshTokenInterval = this.setInterval( () => { this.refreshNewToken(false); }, (this.session.expires_in - 100) * 1000, ); } async sessionCheck() { const obj = await this.getObjectAsync("session"); if (obj) { const check_key = await this.getStateAsync("session"); if ( check_key != null && check_key.val != null && check_key.val.toString().indexOf("aes-192-cbc") !== -1 && typeof check_key.val === "string" && check_key.val != "" ) { check_key.val = this.decrypt(check_key.val); const actual = new Date().getTime(); if (typeof check_key.val === "string") { this.log.debug(`Old session ${check_key.val}`); const val = JSON.parse(check_key.val); if (val && val.next > actual) { this.log.debug(`Use old session!`); this.session = val; return val.next - actual; } else if (val && val.refresh_token) { this.log.debug(`Use old session for refresh token!`); this.session = val; return 1; } } return 0; } return 0; } await this.setState("session", { val: "", ack: true }); return 0; } async maskingTimer() { if (typeof this.modelInfos === "object") { for (const model in this.modelInfos) { if ( this.modelInfos && this.modelInfos[model] && this.modelInfos[model]["deviceState"] === "E" && this.modelInfos[model]["thinq2"] === "thinq2" && this.modelInfos[model]["deviceType"] === 406 ) { const deviceState = { command: "Set", ctrlKey: "allEventEnable", dataKey: "airState.mon.timeout", dataValue: "70", }; this.log.debug(`Set timeout for device ${model}`); this.isAdapterUpdateFor406 = true; this.sendCommandToDevice(model, deviceState, null, true); await this.sleep(3000); this.isAdapterUpdateFor406 = false; } } } } async getJSessionId() { const memberLoginUrl = `${this.gateway.thinq1Uri}/member/login`; const headers = { "x-thinq-application-key": "wideq", "x-thinq-security-key": "nuts_securitykey", Accept: "application/json", "x-thinq-token": this.session.access_token, }; const data = { countryCode: this.gateway.countryCode, langCode: this.gateway.languageCode, loginType: "EMP", token: this.session.access_token, }; return await this.requestClient .post(memberLoginUrl, { lgedmRoot: data }, { headers }) .then(res => res.data.lgedmRoot) .then(data => data) .catch(error => { error.message && this.log.debug(`getJSessionId message: ${error.message}`); this.log.debug(`getJSessionId: ${error}`); return null; }); } getSecondsConversionTime(s) { const num = typeof s !== "number" ? parseInt(s) : s; const result = { day: 0, hour: 0, min: 0 }; result.day = num / 86400; result.hour = (num % 86400) / 3600; result.min = Math.ceil(((num % 86400) % 3600) / 60); return result; } getMinConversionTime(m) { return { day: Math.floor(parseInt(m) / (24 * 60)), hour: Math.floor(parseInt(m) / (24 * 60)) > 0 ? Math.floor(parseInt(m) / 60) - Math.floor(parseInt(m) / (24 * 60)) * 24 : Math.floor(parseInt(m) / 60), min: parseInt(m) - Math.floor(parseInt(m) / 60) * 60, }; } // Original APP header request monitorHeader() { const headers = { "User-Agent": this.app_agent, "x-model-name": this.app_device, "x-thinq-app-ver": "5.0.1000", "x-thinq-app-type": "NUTS", "x-language-code": this.gateway.languageCode, "x-thinq-app-logintype": this.lge, "x-os-version": "16.7.2", "x-client-id": this.client_id, "x-thinq-app-level": "PRD", "x-app-version": "5.0.11861", "x-user-no": this.userNumber, Connection: "keep-alive", "x-service-code": this.svc, "Accept-Language": `${this.gateway.languageCode};q=1`, "x-message-id": uuid.v4(), "x-emp-token": this.session ? this.session.access_token : "", "x-origin": "app-native", Accept: "application/json", "Content-Type": "application/json;charset=UTF-8", "x-api-key": constants.API_KEY, "x-thinq-app-os": "IOS", "x-country-code": this.gateway.countryCode, "x-service-phase": "OP", }; if (this.jsessionId) { headers["x-thinq-jsessionId"] = this.jsessionId; } this.log.debug(`HEADER: ${JSON.stringify(headers)}`); return headers; } // Original APP request async startSinglePollMonitor() { this.updateThinq1SingleInterval && this.clearInterval(this.updateThinq1SingleInterval); this.updateThinq1SingleInterval = null; this.updatethinq1Run = false; if (Object.keys(this.workIds).length < 1) { this.log.warn("Found no workID`s. Please restart adapter!"); return; } this.updateThinq1SingleInterval = this.setInterval(async () => { this.log.debug(`Status ongoing: ${this.updatethinq1Run}`); if (this.updatethinq1Run) { this.log.debug("Update thinq1 ongoing!"); return; } this.updatethinq1Run = true; this.log.debug("START MONITORING"); this.log.debug(`WORKID: ${Object.keys(this.workIds).length}`); this.log.debug(`MODEL: ${Object.keys(this.modelInfos).length}`); this.log.debug(`Counter: ${this.thinq1Counter}`); this.log.debug(`workIds: ${JSON.stringify(this.workIds)}`); if (this.thinq1Counter != Object.keys(this.workIds).length) { for (const model in this.modelInfos) { this.log.debug(`Check deviceID: ${JSON.stringify(model)}`); const devID = {}; if ( this.modelInfos[model] && this.modelInfos[model]["thinq2"] === "thinq1" && !this.workIds[model] ) { devID.platformType = this.modelInfos[model]["thinq2"]; devID.deviceId = model; await this.startMonitor(devID); this.log.debug(`Start Monitoring for ${model}`); } } } const device_status = {}; let active = 0; for (const dev in this.workIds) { const data = { platformType: "thinq1", deviceId: dev, }; if (this.workIds[dev] == null) { this.log.debug( `Restart DEV: ${dev} workid: ${this.workIds[dev]} thinq: ${this.modelInfos[dev]["thinq2"]}`, ); device_status[dev] = "Error"; await this.startMonitor(data); continue; } else { this.log.debug(`DEV: ${dev} workid: ${this.workIds[dev]}`); const result = await this.getMonResult([{ deviceId: dev, workId: this.workIds[dev] }]); this.log.debug(`RESULTS: ${JSON.stringify(result)}`); if (result == null || !result.workList) { this.log.debug(`Result is undefined! Stop Monitoring!`); device_status[dev] = "Result Error"; await this.stopMonitor(data); continue; } const device = result.workList; try { if ( device && device.returnData && (device.returnCode === "0000" || device.returnCode === "0100" || device.returnCode === "0106") ) { let resultConverted; let unit = new Uint8Array(1024); unit = Buffer.from(device.returnData, "base64"); if (this.modelInfos[device.deviceId].Monitoring.type === "BINARY(BYTE)") { resultConverted = this.decodeMonitorBinary( unit, this.modelInfos[device.deviceId].Monitoring.protocol, ); } if (this.modelInfos[device.deviceId].Monitoring.type === "JSON") { try { // @ts-expect-error nothing resultConverted = JSON.parse(unit.toString("utf-8")); } catch (e) { this.logError("debug", "Parse error! Stop Monitoring: ", e); device_status[dev] = "Parse error"; await this.stopMonitor(data); continue; } } this.log.debug(`resultConverted: ${JSON.stringify(resultConverted)}`); if (this.modelInfos[device.deviceId].Info.productType === "REF") { this.refreshRemote(resultConverted, true, device.deviceId); } await this.json2iob.parse(`${device.deviceId}.snapshot`, resultConverted, { forceIndex: true, write: true, preferedArrayName: null, channelName: null, autoCast: true, checkvalue: this.isFinished, checkType: true, }); if (device.returnCode === "0000") { device_status[device.deviceId] = "OK"; ++active; } else if (device.returnCode === "0100") { device_status[device.deviceId] = "Fail - 0100"; this.log.debug(`Fail! Stop Monitoring!`); await this.stopMonitor(data); await this.startMonitor(data); } else if (device.returnCode === "0106") { device_status[device.deviceId] = "Fail - 0106"; this.log.debug(`Not connected device! Stop Monitoring!`); await this.stopMonitor(data); await this.startMonitor(data); } else { device_status[device.deviceId] = `Error - ${device.returnCode}`; this.log.debug(`Unknown`); await this.stopMonitor(data); await this.startMonitor(data); } } else { this.log.debug(`No data:${JSON.stringify(device)} ${device.deviceId}`); device_status[device.deviceId] = `Error - ${device.returnCode}`; await this.stopMonitor(data); await this.startMonitor(data); } } catch (e) { this.logError("debug", "CATCH RESULT: ", e); if (e instanceof Error) { this.log.debug(e.message); } this.log.debug(`CATCH RESULT: ${JSON.stringify(result)}`); } } } this.log.debug(`active: ${active}`); this.setThinq1Interval(active, device_status); this.updatethinq1Run = false; }, this.config.interval_thinq1 * 1000); } async startPollMonitor() { this.updateThinq1Interval && this.clearInterval(this.updateThinq1Interval); this.updateThinq1Interval = null; this.updatethinq1Run = false; if (Object.keys(this.workIds).length < 1) { this.log.warn("Found no workID`s. Please restart adapter!"); return; } this.updateThinq1Interval = this.setInterval(async () => { this.log.debug(`Status ongoing: ${this.updatethinq1Run}`); if (this.updatethinq1Run) { this.log.debug("Update thinq1 ongoing!"); return; } this.updatethinq1Run = true; this.log.debug("START MONITORING"); this.log.debug(`WORKID: ${Object.keys(this.workIds).length}`); this.log.debug(`MODEL: ${Object.keys(this.modelInfos).length}`); this.log.debug(`Counter: ${this.thinq1Counter}`); if (this.thinq1Counter != Object.keys(this.workIds).length) { for (const model in this.modelInfos) { this.log.debug(`Check deviceID: ${JSON.stringify(model)}`); const devID = {}; if ( this.modelInfos[model] && this.modelInfos[model]["thinq2"] === "thinq1" && !this.workIds[model] ) { devID.platformType = this.modelInfos[model]["thinq2"]; devID.deviceId = model; await this.startMonitor(devID); this.log.debug(`Start Monitoring for ${model}`); } } } const all_workids = []; const device_status = {}; let active = 0; for (const dev in this.workIds) { if (!this.modelInfos[dev] || !this.modelInfos[dev]["thinq2"]) { this.log.warn(`Missing Modelinfos for ${dev}. Please restart this adapter!`); continue; } if (this.workIds[dev] == null) { const devID = { platformType: this.modelInfos[dev]["thinq2"], deviceId: dev, }; this.log.debug( `Restart DEV: ${dev} workid: ${this.workIds[dev]} thinq: ${this.modelInfos[dev]["thinq2"]}`, ); device_status[dev] = "Error"; await this.startMonitor(devID); } else { device_status[dev] = "Request"; this.log.debug(`DEV: ${dev} workid: ${this.workIds[dev]}`); all_workids.push({ deviceId: dev, workId: this.workIds[dev] }); } } if (all_workids.length === 0) { this.log.debug(`WorkID for request is empty!`); this.setThinq1Interval(0, device_status); this.updatethinq1Run = false; return; } const result = await this.getMonResult(all_workids); if (result == null || !result.workList) { this.log.debug(`Result is undefined`); this.setThinq1Interval(0, device_status); this.updatethinq1Run = false; return; } this.log.debug(`RESULTS: ${JSON.stringify(result)}`); let device_array = []; if (Object.keys(result.workList).length === 0) { this.updatethinq1Run = false; return; } else if (!Array.isArray(result.workList) && typeof result.workList === "object") { device_array.push(result.workList); } else if (Array.isArray(result.workList)) { device_array = result.workList; } else { this.log.debug(`WRONG WORKLIST: ${JSON.stringify(device_array)}`); this.updatethinq1Run = false; return; } this.log.debug(`device_array: ${JSON.stringify(device_array)}`); try { for (const device of device_array) { this.log.debug(`device: ${JSON.stringify(device)}`); const data = { platformType: "thinq1", deviceId: device.deviceId, }; if ( device && device.returnData && (device.returnCode === "0000" || device.returnCode === "0100" || device.returnCode === "0106") ) { let resultConverted; let unit = new Uint8Array(1024); unit = Buffer.from(device.returnData, "base64"); if (this.modelInfos[device.deviceId].Monitoring.type === "BINARY(BYTE)") { resultConverted = this.decodeMonitorBinary( unit, this.modelInfos[device.deviceId].Monitoring.protocol, ); } if (this.modelInfos[device.deviceId].Monitoring.type === "JSON") { try { // @ts-expect-error nothing resultConverted = JSON.parse(unit.toString("utf-8")); } catch (e) { this.logError("debug", "Parse error! Stop Monitoring: ", e); device_status[device.deviceId] = "Parse error"; await this.stopMonitor(data); continue; } } this.log.debug(`resultConverted: ${JSON.stringify(resultConverted)}`); if (this.modelInfos[device.deviceId].Info.productType === "REF") { this.refreshRemote(resultConverted, true, device.deviceId); } await this.json2iob.parse(`${device.deviceId}.snapshot`, resultConverted, { forceIndex: true, write: true, preferedArrayName: null, channelName: null, autoCast: true, checkvalue: this.isFinished, checkType: true, }); if (device.returnCode === "0000") { device_status[device.deviceId] = "OK"; ++active; } else if (device.returnCode === "0100") { device_status[device.deviceId] = "Fail - 0100"; this.log.debug(`Fail! Stop Monitoring!`); await this.stopMonitor(data); await this.startMonitor(data); } else if (device.returnCode === "0106") { device_status[device.deviceId] = "Fail - 0106"; this.log.debug(`Not connected device! Stop Monitoring!`); await this.stopMonitor(data); await this.startMonitor(data); } else { device_status[device.deviceId] = `Error - ${device.returnCode}`; this.log.debug(`Not connected device! Stop Monitoring!`); await this.stopMonitor(data); await this.startMonitor(data); } } else { this.log.debug(`No data:${JSON.stringify(device)} ${device.deviceId}`); device_status[device.deviceId] = `Error - ${device.returnCode}`; await this.stopMonitor(data); await this.startMonitor(data); } } } catch (e) { this.logError("debug", "", e); this.log.debug(`CATCH WORKLIST: ${JSON.stringify(device_array)}`); this.log.debug(`CATCH WORKLIST RESULT: ${JSON.stringify(result)}`); } this.log.debug(`active: ${active}`); this.setThinq1Interval(active, device_status); this.updatethinq1Run = false; }, this.config.interval_thinq1 * 1000); } async getWeather() { const unit_value = await this.getStateAsync("weather.unit"); const area_value = await this.getStateAsync("weather.device"); if (!unit_value || unit_value.val == null || (unit_value.val != "C" && unit_value.val != "F")) { this.log.info(`Missing unit`); return; } if (!area_value || area_value.val == null) { this.log.info(`Missing area`); return; } const req = `service/application/weather/daily?area=${area_value.val}&unit=${unit_value.val}`; this.log.debug(req); const weather = await this.getDeviceEnergy(req); this.log.debug(JSON.stringify(weather)); if (weather.temperature != null) { const temp = typeof weather.temperature === "string" ? weather.temperature : weather.temperature.toString(); this.setState("weather.temperature", temp, true); } if (weather.humidity != null) { const humi = typeof weather.humidity === "string" ? weather.humidity : weather.humidity.toString(); this.setState("weather.humidity", humi, true); } } setThinq1Interval(active, status) { this.setState("interval.last_update", Date.now(), true); if (this.refreshCounter["interval.status_devices"] != JSON.stringify(status)) { this.refreshCounter["interval.status_devices"] = JSON.stringify(status); this.setState("interval.status_devices", JSON.stringify(status), true); } if (this.refreshCounter["interval.active"] != active) { this.refreshCounter["interval.active"] = active; this.setState("interval.active", active, true); } const inactive = this.thinq1Counter - active; if (this.refreshCounter["interval.inactive"] != inactive) { this.refreshCounter["interval.inactive"] = inactive; this.setState("interval.inactive", inactive, true); } } monitorHeaders() { const monitorHeaders = { Accept: "application/json", "x-thinq-application-key": "wideq", "x-thinq-security-key": "nuts_securitykey", }; if (this.session.access_token) { monitorHeaders["x-thinq-token"] = this.session.access_token; } if (this.jsessionId) { monitorHeaders["x-thinq-jsessionId"] = this.jsessionId; } return monitorHeaders; } async getMonResult(work_id) { const headers = this.monitorHeader(); //const headers = this.monitorHeaders(); return await this.requestClient .post(`${this.gateway.thinq1Uri}/` + `rti/rtiResult`, { lgedmRoot: { workList: work_id } }, { headers }) .then(resp => resp.data.lgedmRoot) .then(data => data) .catch(error => { this.log.debug("getMonResult"); this.log.debug(error); return null; }); } async terms() { try { const showTermUrl = "common/showTerms?callback_url=lgaccount.lgsmartthinq:/updateTerms&country=VN&language=en-VN&division=ha:T20&terms_display_type=3&svc_list=SVC202"; this.log.info("New term agreement is starting..."); const showTermHtml = await this.requestClient .get(`${this.gateway.empSpxUri}/${showTermUrl}`, { headers: { "X-Login-Session": this.session.access_token, }, }) .then(res => res.data) .catch(error => { this.log.debug(`terms: ${error}`); return false; }); const headers = { Accept: "application/json", "X-Application-Key": constants.APPLICATION_KEY, "X-Client-App-Key": constants.CLIENT_ID, "X-Lge-Svccode": "SVC709", "X-Device-Type": "M01", "X-Device-Platform": "ADR", "X-Device-Language-Type": "IETF", "X-Device-Publish-Flag": "Y", "X-Device-Country": this.gateway.countryCode, "X-Device-Language": this.gateway.languageCode, "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8", "X-Login-Session": this.session.access_token, "X-Signature": showTermHtml.match(/signature\s+:\s+"([^"]+)"/)[1], "X-Timestamp": showTermHtml.match(/tStamp\s+:\s+"([^"]+)"/)[1], }; const accountTermUrl = "emp/v2.0/account/user/terms?opt_term_cond=001&term_data=SVC202&itg_terms_use_flag=Y&dummy_terms_use_flag=Y"; const accountTerms = await this.requestClient .get(`${this.gateway.empTermsUri}/${accountTermUrl}`, { headers }) .then(res => { return res.data.account.terms; }) .catch(error => { this.log.debug(`terms: ${error}`); return false; }); const termInfoUrl = "emp/v2.0/info/terms?opt_term_cond=001&only_service_terms_flag=&itg_terms_use_flag=Y&term_data=SVC202"; const infoTerms = await this.requestClient .get(`${this.gateway.empTermsUri}/${termInfoUrl}`, { headers }) .then(res => { return res.data.info.terms; }) .catch(error => { this.log.debug(`terms: ${error}`); return false; }); const newTermAgreeNeeded = infoTerms .filter(term => { return accountTerms.indexOf(term.termsID) === -1; }) .map(term => { return [term.termsType, term.termsID, term.defaultLang].join(":"); }) .join(","); if (newTermAgreeNeeded) { const updateAccountTermUrl = "emp/v2.0/account/user/terms"; await this.requestClient .post( `${this.gateway.empTermsUri}/${updateAccountTermUrl}`, qs.stringify({ terms: newTermAgreeNeeded }), { headers, }, ) .catch(error => { this.log.debug(`terms: ${error}`); return false; }); return true; } return false; } catch (e) { this.logError("debug", "terms: ", e); return false; } } async restartMqtt() { this.log.debug("Restart MQTT Connection"); if (this.mqttC) { this.mqttC.end(); this.mqttC = null; } this.isRestart = false; await this.sleep(2000); this.start_mqtt(); } async updateDevices() { const listDevices = await this.getListDevices().catch(error => { this.log.error(error); }); if (listDevices && listDevices === "TERMS") { await this.setConnection(false); this.terms(); return; } else if (listDevices && listDevices === "BLOCKED") { await this.setConnection(false); return; } if (typeof listDevices == "object") { for (const element of listDevices) { this.log.debug(`UPDATE: ${JSON.stringify(element)}`); await this.json2iob.parse(element.deviceId, element, { forceIndex: true, write: true, preferedArrayName: null, channelName: null, autoCast: true, checkvalue: this.isFinished, checkType: true, }); if (Object.keys(this.workIds).length === 0 || this.config.interval_thinq1 === 0) { await this.pollMonitor(element); } this.refreshRemote(element); } if ( this.updateThinq1Interval == null && Object.keys(this.workIds).length > 0 && this.config.interval_thinq1 > 0 ) { this.startPollMonitor();