iobroker.hoover
Version:
Adapter for hoover devices
1,014 lines (983 loc) • 39.4 kB
JavaScript
"use strict";
/*
* Created with @iobroker/create-adapter v1.34.1
*/
// 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 Json2iob = require("json2iob");
const crypto = require("crypto");
const qs = require("qs");
const tough = require("tough-cookie");
const { HttpsCookieAgent } = require("http-cookie-agent/http");
const awsIot = require("aws-iot-device-sdk");
class Hoover extends utils.Adapter {
/**
* @param {Partial<utils.AdapterOptions>} [options={}]
*/
constructor(options) {
super({
...options,
name: "hoover",
});
this.on("ready", this.onReady.bind(this));
this.on("stateChange", this.onStateChange.bind(this));
this.on("unload", this.onUnload.bind(this));
this.deviceArray = [];
this.json2iob = new Json2iob(this);
this.cookieJar = new tough.CookieJar();
this.requestClient = axios.create({
withCredentials: true,
httpsAgent: new HttpsCookieAgent({
rejectUnauthorized: false,
cookies: {
jar: this.cookieJar,
},
}),
});
}
/**
* Is called when databases are connected and adapter received configuration.
*/
async onReady() {
// Reset the connection indicator during startup
this.setState("info.connection", false, true);
if (this.config.interval < 0.5) {
this.log.info("Set interval to minimum 0.5");
this.config.interval = 0.5;
}
if (!this.config.username || !this.config.password) {
this.log.error("Please set username and password in the instance settings");
return;
}
this.userAgent = "ioBroker v0.0.7";
this.updateInterval = null;
this.reLoginTimeout = null;
this.refreshTokenTimeout = null;
this.session = {};
this.subscribeStates("*");
this.log.info("starting login");
if (this.config.type !== "wizard") {
this.config.interval = 10;
}
await this.login();
if (this.session.access_token) {
this.setState("info.connection", true, true);
await this.getDeviceList();
if (this.config.type !== "wizard") {
await this.connectMqtt();
await this.updateDevices();
}
this.updateInterval = setInterval(async () => {
if (this.config.type === "wizard") {
await this.getDeviceList();
} else {
await this.updateDevices();
}
}, this.config.interval * 60 * 1000);
this.refreshTokenInterval = setInterval(() => {
this.refreshToken();
}, 2 * 60 * 60 * 1000);
}
}
extractInputsAndFormUrl(html) {
const hiddenInputs = {};
let formUrl = html.split("<form")[1].split('action="')[1].split('"')[0];
formUrl = formUrl.replace(/&/g, "&");
const inputs = html.split("<input");
for (const input of inputs) {
// if (input.includes('type="hidden"')) {
let name = input.split('id="')[1].split('"')[0];
if (input.includes('name="')) {
name = input.split('name="')[1].split('"')[0];
}
let value = "";
if (input.includes('value="')) {
value = input.split('value="')[1].split('"')[0];
}
hiddenInputs[name] = value;
// }
}
return { formUrl, hiddenInputs };
}
async login() {
let loginUrl =
"https://account2.hon-smarthome.com/services/oauth2/authorize/expid_Login?response_type=token+id_token&client_id=3MVG9QDx8IX8nP5T2Ha8ofvlmjLZl5L_gvfbT9.HJvpHGKoAS_dcMN8LYpTSYeVFCraUnV.2Ag1Ki7m4znVO6&redirect_uri=hon%3A%2F%2Fmobilesdk%2Fdetect%2Foauth%2Fdone&display=touch&scope=api%20openid%20refresh_token%20web&nonce=b8f38cb9-26f0-4aed-95b4-aa504f5e1971";
if (this.config.type === "wizard") {
loginUrl =
"https://haiereurope.my.site.com/HooverApp/services/oauth2/authorize?client_id=3MVG9QDx8IX8nP5T2Ha8ofvlmjKuido4mcuSVCv4GwStG0Lf84ccYQylvDYy9d_ZLtnyAPzJt4khJoNYn_QVB&redirect_uri=hoover://mobilesdk/detect/oauth/done&display=touch&device_id=245D4D83-98DE-4073-AEE8-1DB085DC0159&response_type=token&scope=api%20id%20refresh_token%20web%20openid";
}
const initUrl = await this.requestClient({
method: "get",
url: loginUrl,
headers: {
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "de-de",
"User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_8 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148",
},
maxRedirects: 0,
})
.then((res) => {
this.log.error("Login step #1 failed");
this.log.debug(JSON.stringify(res.data));
return "";
})
.catch((error) => {
if (error.response && error.response.status === 302) {
return error.response.headers.location;
}
this.log.error(error);
error.response && this.log.error(JSON.stringify(error.response.data));
});
if (!initUrl) {
return;
}
const initSession = qs.parse(initUrl.split("?")[1]);
let fwurl = "https://he-accounts.force.com/SmartHome/s/login/?System=IoT_Mobile_App&RegistrationSubChannel=hOn";
fwurl =
"https://account2.hon-smarthome.com/NewhOnLogin?display=touch%2F&ec=302&startURL=%2F%2Fsetup%2Fsecur%2FRemoteAccessAuthorizationPage.apexp%3Fsource%3D" +
initSession.source;
if (this.config.type === "wizard") {
fwurl =
"https://haiereurope.my.site.com/HooverApp/login?display=touch&ec=302&inst=68&startURL=%2FHooverApp%2Fsetup%2Fsecur%2FRemoteAccessAuthorizationPage.apexp%3Fsource%3D" +
initSession.source +
"%26display%3Dtouch";
}
const { formUrl, hiddenInputs } = await this.requestClient({
method: "get",
url: fwurl,
headers: {
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "de-de",
"User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_8 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148",
},
})
.then((res) => {
this.log.debug(res.data);
return this.extractInputsAndFormUrl(res.data);
})
.catch((error) => {
this.log.error("Login step #2 failed");
this.log.error(error);
error.response && this.log.error(JSON.stringify(error.response.data));
return {};
});
if (this.config.type === "wizard") {
await this.requestClient({
method: "post",
url: "https://haiereurope.my.site.com/HooverApp/login",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Connection: "keep-alive",
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_8 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148",
"Accept-Language": "de-de",
},
data: qs.stringify({
pqs:
"?startURL=%2FHooverApp%2Fsetup%2Fsecur%2FRemoteAccessAuthorizationPage.apexp%3Fsource%3" +
initSession.source +
"%26display%3Dtouch&ec=302&display=touch&inst=68",
un: this.config.username,
width: "414",
height: "736",
hasRememberUn: "true",
startURL: "/HooverApp/setup/secur/RemoteAccessAuthorizationPage.apexp?source=" + initSession.source + "&display=touch",
loginURL: "",
loginType: "",
useSecure: "true",
local: "",
lt: "standard",
qs: "",
locale: "de",
oauth_token: "",
oauth_callback: "",
login: "",
serverid: "",
display: "touch",
username: this.config.username,
pw: this.config.password,
rememberUn: "on",
}),
})
.then(async (res) => {
this.log.debug(JSON.stringify(res.data));
if (this.config.type === "wizard") {
const forwardUrl = res.data.split('<a href="')[1].split('">')[0];
const forward2Url = await this.requestClient({ method: "get", url: forwardUrl }).then((res) => {
this.log.debug(JSON.stringify(res.data));
return res.data.split("window.location.href ='")[1].split("';")[0];
});
const forward3Url = await this.requestClient({ method: "get", url: "https://haiereurope.my.site.com" + forward2Url }).then(
(res) => {
this.log.debug(JSON.stringify(res.data));
return res.data.split("window.location.href ='")[1].split(";")[0];
},
);
this.log.debug(JSON.stringify(forward3Url));
this.session = qs.parse(forward3Url.split("#")[1]);
await this.refreshToken();
} else {
if (res.data.events && res.data.events[0] && res.data.events[0].attributes && res.data.events[0].attributes) {
return res.data.events[0].attributes.values.url;
}
this.log.error("Missing step1 url");
this.log.error(JSON.stringify(res.data));
}
})
.catch((error) => {
this.log.error("Login step #3 failed");
this.log.error(error);
error.response && this.log.error(JSON.stringify(error.response.data));
});
return;
}
hiddenInputs["j_id0:loginForm:username"] = this.config.username;
hiddenInputs["j_id0:loginForm:password"] = this.config.password;
const step02Url = await this.requestClient({
method: "post",
url: formUrl,
headers: {
Accept: "*/*",
"Accept-Language": "de-de",
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
},
data: hiddenInputs,
})
.then((res) => {
this.log.debug(JSON.stringify(res.data));
if (res.data.includes("window.location.replace('")) {
return res.data.split("window.location.replace('")[1].split("')")[0];
}
this.log.error("Missing step2 url");
this.log.error(JSON.stringify(res.data));
})
.catch((error) => {
this.log.error("Login step #4 failed");
this.log.error(error);
error.response && this.log.error(JSON.stringify(error.response.data));
});
if (!step02Url) {
return;
}
const step03Url = await this.requestClient({
method: "get",
url: step02Url,
headers: {
Accept: "*/*",
"Accept-Language": "de-de",
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
},
})
.then((res) => {
this.log.debug(JSON.stringify(res.data));
if (res.data.includes(`window.location.replace("`)) {
return res.data.split(`window.location.replace("`)[1].split(`")`)[0];
}
this.log.error("Login failed please logout and login in your hON and accept new terms");
this.log.error(JSON.stringify(res.data));
})
.catch((error) => {
this.log.error("Login step #5 failed");
this.log.error(error);
error.response && this.log.error(JSON.stringify(error.response.data));
});
if (!step03Url) {
return;
}
const step03bUrl = await this.requestClient({
method: "get",
url: step03Url,
headers: {
Accept: "*/*",
"Accept-Language": "de-de",
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
},
})
.then((res) => {
this.log.debug(JSON.stringify(res.data));
if (res.data.includes(`window.location.replace('`)) {
return res.data.split(`window.location.replace('`)[1].split(`')`)[0];
}
this.log.error("Login failed please logout and login in your hON and accept new terms");
this.log.error(JSON.stringify(res.data));
})
.catch((error) => {
this.log.error("Login step #5 failed");
this.log.error(error);
error.response && this.log.error(JSON.stringify(error.response.data));
});
if (!step03bUrl) {
return;
}
const step04Url = await this.requestClient({
method: "get",
url: "https://account2.hon-smarthome.com" + step03bUrl,
headers: {
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_8 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148",
"Accept-Language": "de-de",
},
})
.then((res) => {
this.log.debug(JSON.stringify(res.data));
if (!res.data.includes("oauth_error_code") && res.data.includes("window.location.replace(")) {
return res.data.split("window.location.replace('")[1].split("')")[0];
}
this.log.error("Missing step4 url");
this.log.error(JSON.stringify(res.data));
})
.catch((error) => {
this.log.error("Login step #6 failed");
this.log.error(error);
error.response && this.log.error(JSON.stringify(error.response.data));
});
if (!step04Url) {
return;
}
this.session = qs.parse(step04Url.split("#")[1]);
const awsLogin = await this.requestClient({
method: "post",
url: "https://api-iot.he.services/auth/v1/login",
headers: {
accept: "application/json, text/plain, */*",
"content-type": "application/json;charset=utf-8",
"user-agent": "hOn/1 CFNetwork/1240.0.4 Darwin/20.6.0",
"id-token": this.session.id_token,
"accept-language": "de-de",
},
data: {
appVersion: "2.0.9",
firebaseToken:
"cvufm5cb9rI:APA91bG9jRyOd35YuAhnx-B0OW9WZ27QRJZUeYKGSfCQv9eDHr7rBHTCMt0pzY2R3HELIG844tDZ-Ip3dMA1_3jRBgYdPYt9byKcYd6XAi6jqJhiIimfQlAFeb5ZZvDmeqib_2UWl3yY",
mobileId: "245D4D83-98DE-4073-AEE8-1DB085DC0158",
osVersion: "14.8",
os: "ios",
deviceModel: "iPhone10,5",
},
})
.then((res) => {
this.log.debug("Receiving aws infos");
this.log.debug(JSON.stringify(res.data));
if (res.data.cognitoUser) {
return res.data;
}
this.log.error(JSON.stringify(res.data));
})
.catch((error) => {
this.log.error("Login step #7 failed");
this.log.error(error);
error.response && this.log.error(JSON.stringify(error.response.data));
});
if (!awsLogin) {
return;
}
this.session = { ...this.session, ...awsLogin.cognitoUser };
this.session.tokenSigned = awsLogin.tokenSigned;
const awsPayload = JSON.stringify({
IdentityId: awsLogin.cognitoUser.IdentityId,
Logins: {
"cognito-identity.amazonaws.com": awsLogin.cognitoUser.Token,
},
});
await this.requestClient({
method: "post",
url: "https://cognito-identity.eu-west-1.amazonaws.com/",
headers: {
accept: "*/*",
"content-type": "application/x-amz-json-1.1",
"x-amz-target": "AWSCognitoIdentityService.GetCredentialsForIdentity",
"user-agent": "hOn/3 CFNetwork/1240.0.4 Darwin/20.6.0",
"x-amz-content-sha256": crypto.createHash("sha256").update(awsPayload).digest("hex"),
"x-amz-user-agent": "aws-amplify/1.2.3 react-native aws-amplify/1.2.3 react-native callback",
"accept-language": "de-de",
},
data: awsPayload,
})
.then((res) => {
this.log.debug(JSON.stringify(res.data));
this.log.info("Login successful");
this.setState("info.connection", true, true);
})
.catch((error) => {
this.log.error("Login step #aws failed");
this.log.error(JSON.stringify(awsLogin));
this.log.error(error);
error.response && this.log.error(JSON.stringify(error.response.data));
});
}
async getDeviceList() {
let deviceListUrl = "https://api-iot.he.services/commands/v1/appliance";
if (this.config.type === "wizard") {
deviceListUrl = "https://simply-fi.herokuapp.com/api/v1/appliances.json?with_hidden_programs=1";
}
await this.requestClient({
method: "get",
url: deviceListUrl,
headers: {
accept: "application/json, text/plain, */*",
"id-token": this.session.id_token,
"cognito-token": this.session.Token,
"user-agent": "hOn/3 CFNetwork/1240.0.4 Darwin/20.6.0",
"accept-language": "de-de",
Authorization: "Bearer " + this.session.id_token,
"Salesforce-Auth": 1,
},
})
.then(async (res) => {
this.log.debug(JSON.stringify(res.data));
let appliances;
if (this.config.type === "wizard") {
appliances = res.data;
} else {
appliances = res.data.payload.appliances;
}
if (!appliances) {
this.log.error("No devices found");
return;
}
this.log.info(`Found ${appliances.length} devices`);
for (let device of appliances) {
if (device.appliance) {
device = device.appliance;
}
let id = device.macAddress || device.serialNumber;
if (this.config.type === "wizard") {
id = device.id;
}
this.deviceArray.push(device);
let name = device.applianceTypeName || device.appliance_model;
if (device.modelName) {
name += " " + device.modelName;
}
if (device.nickName) {
name += " " + device.nickName;
}
await this.setObjectNotExistsAsync(id, {
type: "device",
common: {
name: name,
},
native: {},
});
await this.setObjectNotExistsAsync(id + ".remote", {
type: "channel",
common: {
name: "Remote Controls",
},
native: {},
});
if (!this.config.type === "wizard") {
await this.setObjectNotExistsAsync(id + ".stream", {
type: "channel",
common: {
name: "Data from mqtt stream",
},
native: {},
});
await this.setObjectNotExistsAsync(id + ".general", {
type: "channel",
common: {
name: "General Information",
},
native: {},
});
}
const remoteArray = [
{ command: "refresh", name: "True = Refresh" },
{ command: "stopProgram", name: "True = stop" },
];
if (this.config.type === "wizard") {
remoteArray.push({
command: "send",
name: "Send a custom command",
type: "string",
role: "text",
def: `StartStop=1&Program=P2&DelayStart=0&TreinUno=1&Eco=1&MetaCarico=0&ExtraDry=0&OpzProg=0`,
});
} else {
remoteArray.push({
command: "send",
name: "Send a custom command",
type: "json",
role: "json",
def: `{
"macAddress": "id of the device set by adapter",
"timestamp": "2022-05-10T08:16:35.010Z",
"commandName": "startProgram",
"programName": "PROGRAMS.TD.CARE_45",
"transactionId": "2022-05-10T08:16:35.011Z",
"applianceOptions": {
"opt1": "anticrease",
"opt2": "dryingManager",
"opt3": "bestIroning",
"opt4": "hybrid"
},
"device": {
"mobileOs": "ios",
"mobileId": "245D4D83-98DE-4073-AEE8-1DB085DC0158",
"osVersion": "15.5",
"appVersion": "1.40.2",
"deviceModel": "iPhone10,5"
},
"attributes": {
"prStr": "Care 45",
"energyLabel": "0",
"channel": "mobileApp",
"origin": "lastProgram"
},
"ancillaryParameters": {
"dryTimeMM": "45",
"energyLabel": "0",
"functionalId": "8",
"programFamily": "[dashboard]",
"programRules": {
"opt3": {
"dryLevel": {
"2|3|4": {
"fixedValue": "0",
"typology": "fixed"
}
}
},
"dryTime": {
"dryTimeMM": {
"30": {
"fixedValue": "1",
"typology": "fixed"
},
"45": {
"fixedValue": "2",
"typology": "fixed"
},
"59": {
"fixedValue": "3",
"typology": "fixed"
},
"70": {
"fixedValue": "4",
"typology": "fixed"
},
"80": {
"fixedValue": "5",
"typology": "fixed"
},
"90": {
"fixedValue": "6",
"typology": "fixed"
},
"100": {
"fixedValue": "7",
"typology": "fixed"
},
"110": {
"fixedValue": "8",
"typology": "fixed"
},
"120": {
"fixedValue": "9",
"typology": "fixed"
},
"130": {
"fixedValue": "10",
"typology": "fixed"
},
"140": {
"fixedValue": "11",
"typology": "fixed"
},
"150": {
"fixedValue": "12",
"typology": "fixed"
},
"160": {
"fixedValue": "13",
"typology": "fixed"
},
"170": {
"fixedValue": "14",
"typology": "fixed"
},
"180": {
"fixedValue": "15",
"typology": "fixed"
},
"190": {
"fixedValue": "16",
"typology": "fixed"
},
"200": {
"fixedValue": "17",
"typology": "fixed"
},
"210": {
"fixedValue": "18",
"typology": "fixed"
},
"220": {
"fixedValue": "19",
"typology": "fixed"
}
}
},
"dryLevel": {
"opt3": {
"1": {
"fixedValue": "1",
"typology": "fixed"
}
}
}
},
"remoteActionable": "1",
"remoteVisible": "1",
"suggestedLoadD": "2"
},
"parameters": {
"dryTime": "2",
"dryingManager": "0",
"hybrid": "1",
"checkUpStatus": "0",
"anticrease": "0",
"delayTime": "0",
"prCode": "54",
"prPosition": "13",
"dryLevel": "0",
"bestIroning": "0",
"onOffStatus": "1"
},
"applianceType": "TD"
}`,
});
}
remoteArray.forEach((remote) => {
this.setObjectNotExists(id + ".remote." + remote.command, {
type: "state",
common: {
name: remote.name || "",
type: remote.type || "boolean",
role: remote.role || "boolean",
def: remote.def || false,
write: true,
read: true,
},
native: {},
});
});
if (this.config.type === "wizard") {
this.json2iob.parse(id, device);
} else {
this.json2iob.parse(id + ".general", device);
}
}
})
.catch((error) => {
this.log.error(error);
error.response && this.log.error(JSON.stringify(error.response.data));
});
}
async connectMqtt() {
this.log.info("Connecting to MQTT");
this.device = awsIot.device({
debug: false,
protocol: "wss-custom-auth",
host: "a30f6tqw0oh1x0-ats.iot.eu-west-1.amazonaws.com",
customAuthHeaders: {
"X-Amz-CustomAuthorizer-Name": "candy-iot-authorizer",
"X-Amz-CustomAuthorizer-Signature": this.session.tokenSigned,
token: this.session.id_token,
},
});
this.device.on("connect", () => {
this.log.info("mqtt connected");
for (const device of this.deviceArray) {
const id = device.macAddress || device.serialNumber;
this.log.info(`subscribe to ${id}`);
this.device.subscribe("haier/things/" + id + "/event/appliancestatus/update");
this.device.subscribe("haier/things/" + id + "/event/discovery/update");
this.device.subscribe("$aws/events/presence/connected/" + id);
}
});
this.device.on("message", (topic, payload) => {
this.log.debug(`message ${topic} ${payload.toString()}`);
try {
const message = JSON.parse(payload.toString());
const id = message.macAddress || message.serialNumber;
this.json2iob.parse(id + ".stream", message, {
preferedArrayName: "parName",
channelName: "data from mqtt stream",
});
} catch (error) {
this.log.error(error);
}
});
this.device.on("error", () => {
this.log.debug("error");
});
this.device.on("reconnect", () => {
this.log.info("reconnect");
});
this.device.on("offline", () => {
this.log.info("disconnect");
});
}
async updateDevices() {
const statusArray = [
{
path: "context",
url: "https://api-iot.he.services/commands/v1/context?macAddress=$mac&applianceType=$type&category=CYCLE",
desc: "Current context",
},
];
const headers = {
accept: "application/json, text/plain, */*",
"id-token": this.session.id_token,
"cognito-token": this.session.Token,
"user-agent": "hOn/3 CFNetwork/1240.0.4 Darwin/20.6.0",
"accept-language": "de-de",
};
for (const device of this.deviceArray) {
const id = device.macAddress || device.serialNumber;
for (const element of statusArray) {
let url = element.url.replace("$mac", id);
url = url.replace("$type", device.applianceTypeName);
await this.requestClient({
method: "get",
url: url,
headers: headers,
})
.then((res) => {
this.log.debug(JSON.stringify(res.data));
if (!res.data) {
return;
}
let data = res.data;
if (data.payload) {
data = data.payload;
}
const forceIndex = null;
const preferedArrayName = null;
this.json2iob.parse(id + "." + element.path, data, {
forceIndex: forceIndex,
preferedArrayName: preferedArrayName,
channelName: element.desc,
});
})
.catch((error) => {
if (error.response) {
if (error.response.status === 401) {
error.response && this.log.debug(JSON.stringify(error.response.data));
this.log.info(element.path + " receive 401 error. Refresh Token in 60 seconds");
this.refreshTokenTimeout && clearTimeout(this.refreshTokenTimeout);
this.refreshTokenTimeout = setTimeout(() => {
this.refreshToken();
}, 1000 * 60);
return;
}
}
this.log.error(url);
this.log.error(error);
error.response && this.log.error(JSON.stringify(error.response.data));
});
}
}
}
async refreshToken() {
if (!this.session) {
this.log.error("No session found relogin");
await this.login();
return;
}
if (this.config.type === "wizard") {
await this.requestClient({
method: "post",
maxBodyLength: Infinity,
url: "https://haiereurope.my.site.com/HooverApp/services/oauth2/token",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Connection: "keep-alive",
Accept: "*/*",
"User-Agent": "hoover-ios/762 CFNetwork/1240.0.4 Darwin/20.6.0",
"Accept-Language": "de-de",
},
data: qs.stringify({
format: "json",
redirect_uri: "hoover://mobilesdk/detect/oauth/done",
client_id: "3MVG9QDx8IX8nP5T2Ha8ofvlmjKuido4mcuSVCv4GwStG0Lf84ccYQylvDYy9d_ZLtnyAPzJt4khJoNYn_QVB",
device_id: "245D4D83-98DE-4073-AEE8-1DB085DC0158",
grant_type: "refresh_token",
refresh_token: this.session.refresh_token,
}),
}).then((res) => {
this.log.debug(JSON.stringify(res.data));
this.session = { ...this.session, ...res.data };
});
return;
}
await this.requestClient({
method: "post",
url:
"https://account2.hon-smarthome.com/services/oauth2/token?client_id=3MVG9QDx8IX8nP5T2Ha8ofvlmjLZl5L_gvfbT9.HJvpHGKoAS_dcMN8LYpTSYeVFCraUnV.2Ag1Ki7m4znVO6&refresh_token=" +
this.session.refresh_token +
"&grant_type=refresh_token",
headers: {
Accept: "application/json",
Cookie:
"BrowserId=3elRuc8OEeytLV_-N9BjLA; CookieConsentPolicy=0:1; LSKey-c$CookieConsentPolicy=0:1; oinfo=c3RhdHVzPUFDVElWRSZ0eXBlPTYmb2lkPTAwRFUwMDAwMDAwTGtjcQ==",
"User-Agent": "hOn/3 CFNetwork/1240.0.4 Darwin/20.6.0",
"Accept-Language": "de-de",
"Content-Type": "application/x-www-form-urlencoded",
},
data: qs.stringify({
"https://account2.hon-smarthome.com/services/oauth2/token?client_id":
"3MVG9QDx8IX8nP5T2Ha8ofvlmjLZl5L_gvfbT9.HJvpHGKoAS_dcMN8LYpTSYeVFCraUnV.2Ag1Ki7m4znVO6",
refresh_token: this.session.refresh_token,
grant_type: "refresh_token",
}),
})
.then((res) => {
this.log.debug(JSON.stringify(res.data));
this.session = { ...this.session, ...res.data };
this.device.updateCustomAuthHeaders({
"X-Amz-CustomAuthorizer-Name": "candy-iot-authorizer",
"X-Amz-CustomAuthorizer-Signature": this.session.tokenSigned,
token: this.session.id_token,
});
this.setState("info.connection", true, true);
})
.catch((error) => {
this.log.error("refresh token failed");
this.log.error(error);
error.response && this.log.error(JSON.stringify(error.response.data));
this.log.error("Start relogin in 1min");
this.reLoginTimeout && clearTimeout(this.reLoginTimeout);
this.reLoginTimeout = setTimeout(() => {
this.login();
}, 1000 * 60 * 1);
});
}
/**
* Is called when adapter shuts down - callback has to be called under any circumstances!
* @param {() => void} callback
*/
onUnload(callback) {
try {
this.setState("info.connection", false, true);
this.reLoginTimeout && clearTimeout(this.reLoginTimeout);
this.refreshTokenTimeout && clearTimeout(this.refreshTokenTimeout);
this.updateInterval && clearInterval(this.updateInterval);
this.refreshTokenInterval && clearInterval(this.refreshTokenInterval);
callback();
} catch (e) {
this.log.error(e);
callback();
}
}
/**
* Is called if a subscribed state changes
* @param {string} id
* @param {ioBroker.State | null | undefined} state
*/
async onStateChange(id, state) {
if (state) {
if (!state.ack) {
const deviceId = id.split(".")[2];
const command = id.split(".")[4];
let data = {};
if (id.split(".")[3] !== "remote") {
return;
}
if (command === "refresh") {
this.updateDevices();
return;
}
const dt = new Date().toISOString();
if (command === "stopProgram") {
data = "Reset=1";
if (this.config.type !== "wizard") {
data = {
macAddress: deviceId,
timestamp: dt,
commandName: "stopProgram",
transactionId: deviceId + "_" + dt,
applianceOptions: {},
device: {
mobileId: "245D4D83-98DE-4073-AEE8-1DB085DC0158",
mobileOs: "ios",
osVersion: "15.5",
appVersion: "1.40.2",
deviceModel: "iPhone10,5",
},
attributes: {
channel: "mobileApp",
origin: "standardProgram",
},
ancillaryParameters: {},
parameters: {
onOffStatus: "0",
},
applianceType: "",
};
}
}
if (command === "send") {
if (this.config.type === "wizard") {
data = state.val;
} else {
data = JSON.parse(state.val);
}
}
if (this.config.type == "wizard") {
data = {
appliance_id: deviceId,
body: data,
};
} else {
data.macAddress = deviceId;
data.timestamp = dt;
data.transactionId = deviceId + "_" + dt;
}
this.log.debug(JSON.stringify(data));
let url = "https://api-iot.he.services/commands/v1/send";
if (this.config.type === "wizard") {
url = "https://simply-fi.herokuapp.com/api/v1/commands.json";
}
await this.requestClient({
method: "post",
url: url,
headers: {
accept: "application/json, text/plain, */*",
"id-token": this.session.id_token,
"cognito-token": this.session.Token,
"user-agent": "hOn/3 CFNetwork/1240.0.4 Darwin/20.6.0",
"accept-language": "de-de",
Authorization: "Bearer " + this.session.id_token,
"Salesforce-Auth": 1,
},
data: data,
})
.then((res) => {
this.log.info(JSON.stringify(res.data));
return res.data;
})
.catch((error) => {
this.log.error(error);
if (error.response) {
this.log.error(JSON.stringify(error.response.data));
}
});
}
}
}
}
if (require.main !== module) {
// Export the constructor in compact mode
/**
* @param {Partial<utils.AdapterOptions>} [options={}]
*/
module.exports = (options) => new Hoover(options);
} else {
// otherwise start the instance directly
new Hoover();
}