iobroker.lg-thinq
Version:
Adapter for LG ThinQ
1,100 lines (1,076 loc) • 164 kB
JavaScript
"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();