UNPKG

iobroker.boschindego

Version:
886 lines (859 loc) 31.5 kB
'use strict'; /* * Created with @iobroker/create-adapter v2.5.0 */ const utils = require('@iobroker/adapter-core'); const axios = require('axios').default; const qs = require('qs'); const Json2iob = require('json2iob'); const tough = require('tough-cookie'); const { HttpsCookieAgent } = require('http-cookie-agent/http'); class Boschindego extends utils.Adapter { /** * @param {Partial<utils.AdapterOptions>} [options={}] */ constructor(options) { super({ ...options, name: 'boschindego', }); 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, timeout: 3 * 60 * 1000, //3min client timeout httpsAgent: new HttpsCookieAgent({ cookies: { jar: this.cookieJar, }, }), }); this.alerts = {}; this.lastState = {}; this.lastData = {}; this.lastMap = {}; this.states = { state: { 0: 'Reading status', 257: 'Trickle Charging', 258: 'Docked', 259: 'Docked - Software update', 260: 'Charging', 261: 'Docked', 262: 'Docked - Loading map', 263: 'Docked - Saving map', 265: 'Border Cut', 266: 'Leaving Dock', 513: 'Mowing', 514: 'Relocalising', 515: 'Loading map', 516: 'Learning lawn', 517: 'Paused', 518: 'Border cut', 519: 'Idle in lawn', 523: 'SpotMow', 524: 'MowingRandomly', 768: 'Returning to Dock', 769: 'Returning to Dock', 770: 'Returning to Dock', 771: 'Returning to Dock - Battery low', 772: 'Returning to dock - Calendar timeslot ended', 773: 'Returning to dock - Battery temp range', 774: 'Returning to dock', 775: 'Returning to dock - Lawn complete', 776: 'Returning to dock - Relocalising', 1025: 'Diagnostic mode', 1026: 'EOL Mode', 1281: 'Software update', 1537: 'Low power mode', 64513: 'Docked - Waking up', }, }; } /** * 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.interval > 2147483647) { this.log.info('Set interval to maximum 2147483647'); this.config.interval = 2147483647; } if (!this.config.username || !this.config.password) { this.log.error('Please set username and password in the instance settings'); return; } this.updateInterval = null; this.reLoginTimeout = null; this.refreshTokenTimeout = null; this.session = {}; this.subscribeStates('*.remote.*'); const sessionState = await this.getStateAsync('auth.session'); if (sessionState && sessionState.val) { this.session = JSON.parse(sessionState.val); this.log.info( 'Session found. If the login fails please delete boschindego.0.auth.session and restart the adapter', ); this.log.debug(JSON.stringify(this.session)); await this.refreshToken(); } else { this.log.info('Login to Bosch Indego'); await this.login(); } if (this.session.access_token) { await this.getDeviceList(); await this.updateDevices(); this.updateInterval = setInterval( async () => { await this.updateDevices(); }, this.config.interval * 60 * 1000, ); this.refreshTokenInterval = setInterval( async () => { await this.refreshToken(); }, (this.session.expires_in - 100 || 3500) * 1000, ); } } async login() { if (!this.config.captcha || !this.config.captcha.startsWith('msauth.com.bosch.indegoconnect')) { this.log.error('Please set code url in the instance settings'); return; } // const loginForm = await this.requestClient({ // method: 'get', // url: 'https://prodindego.b2clogin.com/prodindego.onmicrosoft.com/b2c_1a_signup_signin/oauth2/v2.0/authorize', // params: { // nonce: 'b_x1uhAjiy3iKMcXX1TKbJnBph18-J_Hms4vvWeE7qw', // response_type: 'code', // code_challenge_method: 'S256', // scope: // 'openid profile email https://prodindego.onmicrosoft.com/indego-mobile-api/Indego.Mower.User offline_access', // code_challenge: '5C1HXuvfGjAo-6TVzy_95lQNmpAjorsngCwiD3w3VHs', // redirect_uri: 'msauth.com.bosch.indegoconnect.cloud://auth/', // client_id: '65bb8c9d-1070-4fb4-aa95-853618acc876', // state: 'aylWn_85vBUdNlHPC_KeGoqrcsyi5VCxjQjvttvD85g', // }, // 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) Version/14.1.2 Mobile/15E148 Safari/604.1', // }, // }) // .then((res) => { // this.log.debug(JSON.stringify(res.data)); // return JSON.parse(res.data.split('var SETTINGS = ')[1].split(';')[0]); // }) // .catch((error) => { // this.log.error(error); // error.response && this.log.error(JSON.stringify(error.response.data)); // }); // if (!loginForm || !loginForm.csrf || !loginForm.transId) { // this.log.error('Could not extract login form'); // this.log.error(JSON.stringify(loginForm)); // return; // } // let formData = ''; // const loginUrl = await this.requestClient({ // method: 'get', // url: 'https://prodindego.b2clogin.com/prodindego.onmicrosoft.com/B2C_1A_signup_signin/api/CombinedSigninAndSignup/unified', // params: { // claimsexchange: 'BoschIDExchange', // csrf_token: loginForm.csrf, // tx: loginForm.transId, // p: 'B2C_1A_signup_signin', // diags: // '{"pageViewId":"281eab4f-ef89-4f5c-a546-ffad0bb1b00b","pageId":"CombinedSigninAndSignup","trace":[{"ac":"T005","acST":1699567715,"acD":1},{"ac":"T021 - URL:https://swsasharedprodb2c.blob.core.windows.net/b2c-templates/bosch/unified.html","acST":1699567715,"acD":712},{"ac":"T019","acST":1699567716,"acD":9},{"ac":"T004","acST":1699567716,"acD":4},{"ac":"T003","acST":1699567716,"acD":1},{"ac":"T035","acST":1699567716,"acD":0},{"ac":"T030Online","acST":1699567716,"acD":0},{"ac":"T002","acST":1699567791,"acD":0}]}', // }, // 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) Version/14.1.2 Mobile/15E148 Safari/604.1', // }, // }) // .then((res) => { // this.log.debug(JSON.stringify(res.data)); // formData = this.extractHidden(res.data); // return res.request.path; // }) // .catch((error) => { // this.log.error(error); // error.response && this.log.error(JSON.stringify(error.response.data)); // }); // const loginParams = qs.parse(loginUrl.split('?')[1]); // const loginUrlPath = loginUrl.split('?')[0]; // if (!loginParams || !loginParams.ReturnUrl) { // this.log.error('Could not extract login params'); // this.log.error(JSON.stringify(loginParams)); // return; // } // // const token = this.cookieJar.getCookiesSync('https://singlekey-id.com/auth/').find((cookie) => cookie.key === 'X-CSRF-FORM-TOKEN'); // const userResponse = await this.requestClient({ // method: 'post', // maxBodyLength: Infinity, // url: 'https://singlekey-id.com' + loginUrlPath, // headers: { // 'content-type': 'application/x-www-form-urlencoded', // accept: '*/*', // 'hx-request': 'true', // 'sec-fetch-site': 'same-origin', // 'hx-boosted': 'true', // 'accept-language': 'de-DE,de;q=0.9', // 'sec-fetch-mode': 'cors', // origin: 'https://singlekey-id.com', // 'user-agent': // 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_7_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1', // 'sec-fetch-dest': 'empty', // }, // params: loginParams, // data: { // 'UserIdentifierInput.EmailInput.StringValue': this.config.username, // 'h-captcha-response': this.config.captcha, // __RequestVerificationToken: formData['undefined'], // }, // }) // .then((res) => { // this.log.debug(JSON.stringify(res.data)); // return this.extractHidden(res.data); // }) // .catch((error) => { // this.log.error(error); // error.response && this.log.error(JSON.stringify(error.response.data)); // }); // if (!userResponse) { // this.log.error('Could not extract user data'); // return; // } // await this.requestClient({ // method: 'post', // maxBodyLength: Infinity, // url: 'https://singlekey-id.com' + loginUrlPath + '/password', // headers: { // 'content-type': 'application/x-www-form-urlencoded', // accept: '*/*', // 'hx-request': 'true', // 'sec-fetch-site': 'same-origin', // 'hx-boosted': 'true', // 'accept-language': 'de-DE,de;q=0.9', // 'sec-fetch-mode': 'cors', // origin: 'https://singlekey-id.com', // 'user-agent': // 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_7_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1', // 'sec-fetch-dest': 'empty', // }, // params: loginParams, // data: { // Password: this.config.password, // RememberMe: 'true', // __RequestVerificationToken: userResponse['undefined'], // }, // }).catch((error) => { // this.log.error(error); // error.response && this.log.error(JSON.stringify(error.response.data)); // }); // const htmlForm = await this.requestClient({ // method: 'get', // url: 'https://singlekey-id.com' + loginParams.ReturnUrl, // }); // const formDataAuth = this.extractHidden(htmlForm.data); // const response = await this.requestClient({ // method: 'post', // url: 'https://prodindego.b2clogin.com/prodindego.onmicrosoft.com/oauth2/authresp', // headers: { // 'content-type': 'application/x-www-form-urlencoded', // accept: 'application/json, text/plain, */*', // 'accept-language': 'de-de', // }, // data: formDataAuth, // }) // .then((res) => { // this.log.debug(JSON.stringify(res.data)); // return; // }) // .catch((error) => { // if (error && error.message.includes('Unsupported protocol')) { // return qs.parse(error.request._options.path.split('?')[1]); // } // this.log.error('Authresp step faild'); // this.log.error(error); // error.response && this.log.error(JSON.stringify(error.response.data)); // }); // if (!response) { // return; // } const response = qs.parse(this.config.captcha.split('?')[1]); await this.requestClient({ method: 'post', url: 'https://prodindego.b2clogin.com/prodindego.onmicrosoft.com/b2c_1a_signup_signin/oauth2/v2.0/token', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', Accept: '*/*', 'User-Agent': 'Bosch/15752 CFNetwork/1240.0.4 Darwin/20.6.0', 'Accept-Language': 'de-de', Authorization: 'Basic NjViYjhjOWQtMTA3MC00ZmI0LWFhOTUtODUzNjE4YWNjODc2Og==', }, data: 'code=' + response.code + '&code_verifier=nw0c1JmU5rIszzrUOFj1BFvaqOynWrZ6ZHSVOMisZ7o&redirect_uri=msauth.com.bosch.indegoconnect.cloud://auth/&grant_type=authorization_code', }) .then(async (res) => { this.log.debug(JSON.stringify(res.data)); this.session = res.data; this.log.info('Login successful'); this.setState('info.connection', true, true); await this.extendObject('auth.session', { type: 'state', common: { name: 'Session Token', type: 'string', role: 'value', read: true, write: false, }, native: {}, }); this.setState('auth.session', JSON.stringify(this.session), true); }) .catch((error) => { this.log.error('Code Exchange step faild'); this.log.error(error); error.response && this.log.error(JSON.stringify(error.response.data)); }); } extractHidden(body) { const returnObject = {}; const matches = body.matchAll(/<input (?=[^>]* name=["']([^'"]*)|)(?=[^>]* value=["']([^'"]*)|)/g); for (const match of matches) { if (match[2] != null) { returnObject[match[1]] = match[2]; } } const matcheUpper = body.matchAll(/<INPUT (?=[^>]* NAME=["']([^'"]*)|)(?=[^>]* VALUE=["']([^'"]*)|)/g); for (const match of matcheUpper) { if (match[2] != null) { returnObject[match[1]] = match[2]; } } return returnObject; } async getDeviceList() { await this.requestClient({ method: 'put', url: 'https://api.indego-cloud.iot.bosch-si.com/api/v1/devices/active', headers: { 'Content-Type': 'application/json; charset=UTF-8', Connection: 'Keep-Alive', 'User-Agent': 'Indego-Connect_4.0.3.12955', Authorization: 'Bearer ' + this.session.access_token, }, data: { accept_tc_id: '202012', accept_dpn_id: '202012', app_version: '4.0.3', device: 'eUJTd67jRcuLcceDWLnOFX:APA91bE9FVtylJgX940k8dclGV7zQPs7-yPkI48ybPWbwDtVU9VDH3AenxIINeVm1NBifYMgspjAVzBkPD54oBcFmH29R3pCVhECF5GzTBjFKRa0oiY5XIHtoqDhE42D988NT2OZLM0J', dvc_manuf: 'SAMSUNG', dvc_type: 'S901B/DS', os_type: 'Android', os_version: '13', }, }) .then(async (res) => { this.log.debug(JSON.stringify(res.data)); if (Array.isArray(res.data) === false) { if (!res.data.alm_sn) { this.log.error('No device found'); return; } res.data = [res.data]; } this.log.info(`Found ${res.data.length} devices`); for (const device of res.data) { const id = device.alm_sn; this.deviceArray.push(id); const name = device.alm_sn; await this.setObjectNotExistsAsync(id, { type: 'device', common: { name: name, }, native: {}, }); await this.setObjectNotExistsAsync(id + '.remote', { type: 'channel', common: { name: 'Remote Controls', }, native: {}, }); const remoteArray = [ { command: 'Refresh', name: 'True = Refresh' }, { command: 'state_mow', name: 'True = Mow' }, { command: 'state_pause', name: 'True = Pause' }, { command: 'state_returnToDock', name: 'True = return to dock' }, { command: 'reset_blade', name: 'True = Reset Blades' }, { command: 'reset_alerts', name: 'True = Reset Alerts' }, { command: 'calendar_get', name: 'True = Get calendar' }, { command: 'calendar_set', name: 'Set Calendar as JSON', type: 'string', role: 'json', def: `{ "sel_cal": 1, "cals": [ { "cal": 1, "days": [ { "day": 0, "slots": [ { "En": true, "EnHr": 23, "EnMin": 59, "StHr": 22, "StMin": 0 }, { "En": true, "EnHr": 8, "EnMin": 0, "StHr": 0, "StMin": 0 } ] }, { "day": 1, "slots": [ { "En": true, "EnHr": 23, "EnMin": 59, "StHr": 22, "StMin": 0 }, { "En": true, "EnHr": 8, "EnMin": 0, "StHr": 0, "StMin": 0 } ] }, { "day": 6, "slots": [ { "En": true, "EnHr": 23, "EnMin": 59, "StHr": 22, "StMin": 0 }, { "En": true, "EnHr": 8, "EnMin": 0, "StHr": 0, "StMin": 0 } ] } ] } ] } `, }, { command: 'predictive_enable', name: 'True = Enable, False Disable' }, { command: 'predictive_useradjustment', name: '-100 to 100', type: 'number', role: 'level', def: 0, min: -100, max: 100, }, ]; remoteArray.forEach((remote) => { this.extendObject(id + '.remote.' + remote.command, { type: 'state', common: { name: remote.name || '', type: remote.type || 'boolean', role: remote.role || 'boolean', def: remote.def == null ? false : remote.def, write: true, read: true, }, native: {}, }); }); this.json2iob.parse(id, device, { forceIndex: true }); } }) .catch((error) => { this.log.error(error); error.response && this.log.error(JSON.stringify(error.response.data)); }); } async updateDevices(selection) { let statusArray = [ { path: 'state', url: 'https://api.indego-cloud.iot.bosch-si.com/api/v1/alms/$id/state?longpoll=false&forceRefresh=true&timeout=0', desc: 'State', }, { path: 'alerts', url: 'https://api.indego-cloud.iot.bosch-si.com/api/v1/alerts', desc: 'Alerts', }, { path: 'config', url: 'https://api.indego-cloud.iot.bosch-si.com/api/v1/alms/$id/config', desc: 'Config', }, { path: 'predictive', url: 'https://api.indego-cloud.iot.bosch-si.com/api/v1/alms/$id/predictive', desc: 'Predictive', }, { path: 'operatingData', url: 'https://api.indego-cloud.iot.bosch-si.com/api/v1/alms/$id/operatingData', desc: 'Operating Data', }, { path: 'lastcutting', url: 'https://api.indego-cloud.iot.bosch-si.com/api/v1/alms/$id/predictive/lastcutting', desc: 'Last Cutting', }, { path: 'nextcutting', url: 'https://api.indego-cloud.iot.bosch-si.com/api/v1/alms/$id/predictive/nextcutting', desc: 'Next Cutting', }, ]; if (selection === 'calendar') { statusArray = [ { path: 'calendar', url: 'https://api.indego-cloud.iot.bosch-si.com/api/v1/alms/$id/predictive/calendar', desc: 'Calendar', }, ]; } for (const id of this.deviceArray) { if ( (this.config.getMap && this.lastState[id] == null) || (this.lastState[id] >= 500 && this.lastState[id] <= 799) ) { this.log.debug('Add map to update because of state ' + this.lastState[id]); statusArray.push({ path: 'map', url: 'https://api.indego-cloud.iot.bosch-si.com/api/v1/alms/$id/map', desc: 'Map', }); } for (const element of statusArray) { const url = element.url.replace('$id', id); this.log.debug('Fetch data from ' + url); await this.requestClient({ method: element.method || 'get', url: url, headers: { Connection: 'Keep-Alive', 'User-Agent': 'Indego-Connect_4.0.3.12955', Authorization: 'Bearer ' + this.session.access_token, 'x-im-context-id': this.session.resource, }, }) .then(async (res) => { this.log.debug(url); this.log.debug(JSON.stringify(res.data)); if (!res.data) { return; } let data = res.data; const forceIndex = true; const preferedArrayName = null; const parseOptions = { forceIndex: forceIndex, preferedArrayName: preferedArrayName, channelName: element.desc, states: this.states, }; if (element.path === 'alerts') { this.alerts[id] = data; parseOptions.deleteBeforeUpdate = true; } if (element.path === 'state') { this.lastState[id] = data.state; this.lastData[id] = data; if (data.svg_xPos && data.svg_yPos && this.lastMap[id] != null) { const map = this.addLocationtoMap(data, this.lastMap[id]); this.json2iob.parse(id + '.map', map, { forceIndex: forceIndex, preferedArrayName: preferedArrayName, channelName: element.desc, states: this.states, }); } } if (element.path === 'map') { if (!this.lastData[id].svg_xPos || !this.lastData[id].svg_yPos) { this.log.info('No mower location found to add in the map' + JSON.stringify(data)); return; } this.lastMap[id] = data; data = this.addLocationtoMap(this.lastData[id], this.lastMap[id]); } this.json2iob.parse(id + '.' + element.path, data, parseOptions); if (element.path != 'map') { await this.setObjectNotExistsAsync(id + '.' + element.path + '.json', { type: 'state', common: { name: 'Raw JSON', write: false, read: true, type: 'string', role: 'json', }, native: {}, }); } this.setState(id + '.' + element.path + '.json', JSON.stringify(data), true); }) .catch((error) => { this.log.debug(url); 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; } if (error.response.status === 504) { this.log.warn('Device is offline'); return; } } this.log.error(element.url); this.log.error(error); error.response && this.log.error(JSON.stringify(error.response.data)); }); } } } addLocationtoMap(state, map) { //add location to map map = map.replace( '</svg>', `<circle cx="${state.svg_xPos}" cy="${state.svg_yPos}" r="20" stroke="black" stroke-width="3" fill="yellow"/> </svg>`, ); //transparent background map = map.replace('ry="0" fill="#FAFAFA"', 'ry="0" fill="#00000" fill-opacity="0.0"'); return map; } async refreshToken() { this.log.debug('Refresh token'); await this.requestClient({ method: 'post', url: 'https://prodindego.b2clogin.com/prodindego.onmicrosoft.com/b2c_1a_signup_signin/oauth2/v2.0/token', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', Accept: '*/*', 'User-Agent': 'Bosch/15752 CFNetwork/1240.0.4 Darwin/20.6.0', 'Accept-Language': 'de-de', Authorization: 'Basic NjViYjhjOWQtMTA3MC00ZmI0LWFhOTUtODUzNjE4YWNjODc2Og==', }, data: qs.stringify({ refresh_token: this.session.refresh_token, grant_type: 'refresh_token', }), }) .then(async (res) => { this.log.debug(JSON.stringify(res.data)); this.session = res.data; this.log.debug('Refresh successful'); this.setState('info.connection', true, true); await this.extendObject('auth.session', { type: 'state', common: { name: 'Session Token', type: 'string', role: 'value', read: true, write: false, }, native: {}, }); this.setState('auth.session', JSON.stringify(this.session), true); }) .catch(async (error) => { this.log.error('Refresh token faild. Please delete boschindego.0.auth.session and restart the adapter'); this.log.error(error); error.response && this.log.error(JSON.stringify(error.response.data)); this.setStateAsync('info.connection', false, true); await this.login(); }); } /** * Is called when adapter shuts down - callback has to be called under any circumstances! * @param {() => void} callback */ async onUnload(callback) { try { this.setState('info.connection', false, true); this.refreshTimeout && clearTimeout(this.refreshTimeout); this.reLoginTimeout && clearTimeout(this.reLoginTimeout); this.refreshTokenTimeout && clearTimeout(this.refreshTokenTimeout); this.updateInterval && clearInterval(this.updateInterval); this.refreshTokenInterval && clearInterval(this.refreshTokenInterval); if (this.config.captcha) { const adapterSettings = await this.getForeignObjectAsync('system.adapter.' + this.namespace); adapterSettings.native.captcha = null; await this.setForeignObjectAsync('system.adapter.' + this.namespace, adapterSettings); } 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]; if (id.split('.')[3] !== 'remote') { return; } if (command === 'Refresh') { this.updateDevices(); return; } const type = command.split('_')[0]; const exec = command.split('_')[1]; let data = {}; let method = 'put'; const baseUrl = 'https://api.indego-cloud.iot.bosch-si.com/api/v1/alms/' + deviceId; const urlArray = []; if (type === 'state') { urlArray.push(baseUrl + '/state'); data = { state: exec }; } if (type === 'predictive') { if (exec === 'useradjustment') { urlArray.push(baseUrl + '/predictive/useradjustment'); data = { user_adjustment: state.val, }; } if (exec === 'enable') { urlArray.push(baseUrl + '/predictive'); data = { enabled: state.val, }; } } if (type === 'reset') { if (exec === 'blade') { urlArray.push(baseUrl); data = { needs_service: false, }; } if (exec === 'alerts') { method = 'delete'; if (this.alerts[deviceId]) { for (const alert of this.alerts[deviceId]) { const alertId = alert.id || alert.alert_id; urlArray.push('https://api.indego-cloud.iot.bosch-si.com/api/v1/alerts/' + alertId); } } else { this.log.warn('No alerts found for device ' + deviceId); } } } if (type === 'calendar') { urlArray.push(baseUrl + '/predictive/calendar'); if (exec === 'get') { this.updateDevices('calendar'); return; } if (exec === 'set') { data = JSON.parse(state.val?.toString() || '{}'); } } for (const url of urlArray) { this.log.debug(url); await this.requestClient({ method: method, url: url, headers: { 'Content-Type': 'application/json; charset=UTF-8', Connection: 'Keep-Alive', 'User-Agent': 'Indego-Connect_4.0.3.12955', Authorization: 'Bearer ' + this.session.access_token, 'x-im-context-id': this.session.resource, }, data: data, }) .then(async (res) => { this.log.debug(JSON.stringify(res.data)); }) .catch((error) => { if (error.response && error.response.status === 504) { this.log.warn('Device is offline'); return; } this.log.error(error); error.response && this.log.error(JSON.stringify(error.response.data)); }); } this.refreshTimeout && clearTimeout(this.refreshTimeout); this.refreshTimeout = setTimeout(() => { this.updateDevices(); }, 10 * 1000); } } } } if (require.main !== module) { // Export the constructor in compact mode /** * @param {Partial<utils.AdapterOptions>} [options={}] */ module.exports = (options) => new Boschindego(options); } else { // otherwise start the instance directly new Boschindego(); }