UNPKG

iobroker.netatmo-energy

Version:
1,397 lines (1,345 loc) 202 kB
/* eslint-disable jsdoc/require-param-description */ 'use strict'; // Load modules const utils = require('@iobroker/adapter-core'); const abort = require('abort-controller'); const fs = require('fs'); const fetch = require('fetch'); const mytools = require('./lib/mytools'); const glob = require('./lib/globals'); //const { error } = require('console'); // Main Class class NetatmoEnergy extends utils.Adapter { /** * @param [options] */ //Class Constructor constructor(options) { super( Object.assign(options || {}, { name: 'netatmo-energy', }), ); this.on('ready', this.onReady.bind(this)); this.on('stateChange', this.onStateChange.bind(this)); //this.on('objectChange', this.onObjectChange.bind(this)); this.on('message', this.onMessage.bind(this)); this.on('unload', this.onUnload.bind(this)); //Objects this.globalDevice = null; this.globalAPIChannel = ''; this.globalAPIChannelTrigger = null; this.globalAPIChannelMessages = null; this.globalAPIStatus = null; this.globalNetatmo_AccessToken = null; this.globalRefreshToken = null; this.globalNetatmo_ExpiresIn = 0; this.globalScheduleObjects = {}; this.globalScheduleList = {}; this.globalScheduleListArray = []; this.globalRoomId = {}; this.globalRoomIdArray = []; this.globalDeviceId = {}; this.globalDeviceIdArray = []; this.globalWindowIndicators = []; //Language this.systemlang = 'de'; //Notifications this.signal = {}; this.telegram = {}; this.whatsapp = {}; this.pushover = {}; this.email = {}; //Adapter status this.FetchAbortController = new abort.AbortController(); this.RefreshTokenInterval = null; this.adapterIntervals = []; this.mySubscribedStates = []; this.AdapterStarted = false; this.SensorTimeouts = []; //Authentication this.scope = ''; this.storedOAuthData = {}; this.storedOAuthStates = {}; this.dataDir = ''; } //Is called when databases are connected and adapter received configuration async onReady() { //Check adapter configuration if (!this.config.HomeId || !this.config.ClientId || !this.config.ClientSecretID) { this.log.error('*** Adapter deactivated, missing adaper configuration !!! ***'); this.setForeignState(`system.adapter.${this.namespace}.alive`, false); return; } //Get settings this.getForeignObject('system.config', (err, obj) => { if (obj) { this.systemLang = obj.common.language; } }); await this.initAdapter(this.systemLang); await this._subscribeForeign(this.namespace, false); await this.startAdapter(); } //Subscribe foreign Sensors async _subscribeForeign(own_namespace, only_unsubscribe) { for (const sensor_attribs of this.config.sensors) { if ( // @ts-expect-error Window_sensor is available sensor_attribs.window_sensor && // @ts-expect-error Window_sensor is available sensor_attribs.window_sensor != null && // @ts-expect-error Window_sensor is available sensor_attribs.window_sensor != undefined ) { // @ts-expect-error Window_sensor is available if (sensor_attribs.window_sensor.search(own_namespace) >= 0) { //nothing to do } else { // @ts-expect-error Window_sensor is available await this.unsubscribeForeignStatesAsync(sensor_attribs.window_sensor); if (!only_unsubscribe) { // @ts-expect-error Window_sensor is available await this.subscribeForeignStatesAsync(sensor_attribs.window_sensor); } } } } } //Get THAT _getThat() { return this; } //Tocken intervall to refresh _setTokenIntervall(setTimer) { const that = this._getThat(); const refreshTokenbyTimer = function () { that._authenticate_refresh_token(that.storedOAuthData.redirect_uri, that.storedOAuthData.code); }; if (this.RefreshTokenInterval != null) { clearInterval(this.RefreshTokenInterval); this.RefreshTokenInterval = null; } if (setTimer || this.globalNetatmo_ExpiresIn > 0) { this.RefreshTokenInterval = setInterval(refreshTokenbyTimer, this.globalNetatmo_ExpiresIn); } } //Get stored token from adapter data directory async _getStoredToken() { this.dataDir = utils.getAbsoluteInstanceDataDir(this); try { if (!fs.existsSync(this.dataDir)) { fs.mkdirSync(this.dataDir); } if (fs.existsSync(`${this.dataDir}/tokens.json`)) { const tokens = JSON.parse(fs.readFileSync(`${this.dataDir}/tokens.json`, 'utf8')); if (tokens.client_id !== this.config.ClientId) { this.log.error( mytools.tl('Stored tokens belong to the different client ID', this.systemLang) + tokens.client_id + mytools.tl('and not to the configured ID ... deleting', this.systemLang), ); await this.sendRequestNotification( null, glob.ErrorNotification, 'Get Token', mytools.tl('Stored tokens belong to the different client ID', this.systemLang) + tokens.client_id + mytools.tl('and not to the configured ID ... deleting', this.systemLang), ); fs.unlinkSync(`${this.dataDir}/tokens.json`); } else { if (!tokens.access_token || !tokens.refresh_token) { this.globalNetatmo_AccessToken = null; this.globalRefreshToken = null; this.scope = ''; this.log.error( mytools.tl( 'No tokens stored - Please use authentication in adapter config!', this.systemLang, ), ); await this.sendRequestNotification( null, glob.ErrorNotification, 'Get Token', mytools.tl( 'No tokens stored - Please use authentication in adapter config!', this.systemLang, ), ); } else { this.globalNetatmo_AccessToken = tokens.access_token; this.globalRefreshToken = tokens.refresh_token; this.scope = tokens.scope; this.log.debug( mytools.tl('Using stored tokens to initialize ... ', this.systemLang) + JSON.stringify(tokens), ); if (tokens.scope !== this.scope) { this.log.warn( mytools.tl('Stored tokens have different scope', this.systemLang) + tokens.scope + mytools.tl('and not the configured scope', this.systemLang) + this.scope + mytools.tl('... If you miss data please authenticate again!', this.systemLang), ); await this.sendRequestNotification( null, glob.WarningNotification, 'Get Token', mytools.tl('Stored tokens have different scope', this.systemLang) + tokens.scope + mytools.tl('and not the configured scope', this.systemLang) + this.scope + mytools.tl('... If you miss data please authenticate again!', this.systemLang), ); } } } } else { this.globalNetatmo_AccessToken = null; this.globalRefreshToken = null; this.scope = ''; this.log.error( mytools.tl('No tokens stored - Please use authentication in adapter config!', this.systemLang), ); await this.sendRequestNotification( null, glob.ErrorNotification, 'Get Token', mytools.tl('No tokens stored - Please use authentication in adapter config!', this.systemLang), ); } } catch (err) { let _errormessage = 'Error'; if (err instanceof Error) { _errormessage = err.message; } this.log.error(mytools.tl('Error reading stored tokens: ', this.systemLang) + _errormessage); await this.sendRequestNotification( null, glob.ErrorNotification, 'Get Token', mytools.tl('Error reading stored tokens: ', this.systemLang) + _errormessage, ); } } //Save token to file system _saveToken() { const tokenData = { access_token: this.globalNetatmo_AccessToken, refresh_token: this.globalRefreshToken, scope: this.scope, client_id: this.config.ClientId, }; try { fs.writeFileSync(`${this.dataDir}/tokens.json`, JSON.stringify(tokenData), 'utf8'); this.log.debug(`${mytools.tl('Token saved', this.systemLang)}: ${this.dataDir}/tokens.json`); } catch (err) { this.log.error(mytools.tl('Cannot write token file: ', this.systemLang) + err); } } //Get token from Netatmo _getToken(HomeId, ClientId, ClientSecretID, redirect_uri, code) { this.globalNetatmo_AccessToken = null; let payload = ''; if (!this.globalRefreshToken) { payload = `code=${code}&redirect_uri=${redirect_uri}&grant_type=authorization_code${ glob.payload_client_id }${ClientId}${glob.payload_client_secret}${ClientSecretID}${glob.payload_scope}${this.scope}`; } else { payload = `grant_type=refresh_token${glob.payload_refresh_token}${this.globalRefreshToken}${ glob.payload_client_id }${ClientId}${glob.payload_client_secret}${ClientSecretID}`; } return this._myFetch(glob.Netatmo_TokenRequest_URL, payload); } //Authenticate refresh token async _authenticate_refresh_token(redirect_uri, code) { this._setTokenIntervall(false); this.log.info(mytools.tl('Start Token-Refresh', this.systemLang)); await this._getToken(this.config.HomeId, this.config.ClientId, this.config.ClientSecretID, redirect_uri, code) .then(async tokenvalues => { this.globalNetatmo_AccessToken = tokenvalues.access_token; this.globalNetatmo_ExpiresIn = tokenvalues.expires_in + new Date().getTime() / 1000 - 20; this.globalRefreshToken = tokenvalues.refresh_token; await this._saveToken(); this._setTokenIntervall(true); this.log.debug(mytools.tl('Token OK:', this.systemLang) + glob.blank + this.globalNetatmo_AccessToken); await this.startAdapter(); }) .catch(async error => { this.globalNetatmo_AccessToken = null; this.globalRefreshToken = null; this.globalNetatmo_ExpiresIn = 0; await this._saveToken(); this.log.error( mytools.tl('Did not get a tokencode:', this.systemLang) + (error !== undefined && error !== null ? `${glob.blank + error.error}: ${error.error_description}` : ''), ); await this.sendRequestNotification( null, glob.ErrorNotification, mytools.tl('API Token', this.systemLang), mytools.tl('Did not get a tokencode:', this.systemLang) + (error !== undefined && error !== null ? `${glob.blank + error.error}: ${error.error_description}` : ''), ); }); } //Start initialization adapter async initAdapter(systemLang) { // define global constants this.globalDevice = mytools.getDP([this.namespace, glob.Device_APIRequests]); this.globalAPIChannel = mytools.getDP([this.namespace, glob.Device_APIRequests, glob.Channel_APIRequests]); this.globalAPIChannelTrigger = mytools.getDP([this.namespace, glob.Device_APIRequests, glob.Channel_trigger]); this.globalAPIChannelMessages = mytools.getDP([this.namespace, glob.Device_APIRequests, glob.Channel_messages]); this.globalAPIChannelStatus = mytools.getDP([ this.namespace, glob.Device_APIRequests, glob.Channel_Status_API_running, ]); try { this.signal = { type: 'message', instance: this.config.signalInstance, systemLang, }; } catch (error) { this.log.error(mytools.tl('Signal: ', this.systemLang) + error); } try { this.telegram = { type: 'message', instance: this.config.telegramInstance, SilentNotice: this.config.telegramSilentNotice, User: this.config.telegramUser, systemLang, }; } catch (error) { this.log.error(mytools.tl('Telegram: ', this.systemLang) + error); } try { this.whatsapp = { type: 'message', instance: this.config.whatsappInstance, systemLang, }; } catch (error) { this.log.error(mytools.tl('WhatsApp: ', this.systemLang) + error); } try { this.pushover = { type: 'message', instance: this.config.pushoverInstance, SilentNotice: this.config.pushoverSilentNotice, deviceID: this.config.pushoverDeviceID, systemLang, }; } catch (error) { this.log.error(mytools.tl('Pushover: ', this.systemLang) + error); } try { this.email = { type: 'message', instance: this.config.emailInstance, emailReceiver: this.config.emailReceiver, emailSender: this.config.emailSender, systemLang, }; } catch (error) { this.log.error(mytools.tl('E-Mail: ', this.systemLang) + error); } //Get stored token await this._getStoredToken(); } //Start initialization async startAdapter() { // Check if it is neccessary if (this.AdapterStarted == true) { return; } // Set polling intervall const refreshtime = this.config.refreshstates; const that = this._getThat(); if (this.globalNetatmo_AccessToken == null || this.globalRefreshToken == null) { return; } const updateAPIStatus = async function () { that.log.debug( mytools.tl('API Request homestatus sent to API each', that.systemLang) + glob.blank + refreshtime + mytools.tl('sec', that.systemLang), ); await that.RefreshWholeStructure(true); }; if (refreshtime && refreshtime > 0) { that.log.info(mytools.tl('Refresh homestatus interval', that.systemLang) + glob.blank + refreshtime * 1000); // Timer that.adapterIntervals.push(setInterval(updateAPIStatus, refreshtime * 1000)); } //Start initial requests for adapter this.AdapterStarted = true; await this.createEnergyAPP(); await this.RefreshWholeStructure(false); } //Create APP Requests device async createEnergyAPP() { // Device energyAPP await this.createMyDevice(this.globalDevice, 'Netatmo Energy APP'); // Channel APIRequests await this.createMyChannel(this.globalAPIChannel, 'Requests for Netatmo Energy API'); // Channel setthomesdata await this.createMyChannel(mytools.getDP([this.globalAPIChannel, glob.Channel_homesdata]), 'API homesdata'); await this.createMyChannel( mytools.getDP([this.globalAPIChannel, glob.Channel_homesdata, glob.Channel_parameters]), 'parameters', ); await this.createNetatmoStructure( mytools.getDP([this.globalAPIChannel, glob.Channel_homesdata, glob.APIRequest_homesdata]), 'homesdata', false, true, 'button', true, true, '', false, false, ); await this.createNetatmoStructure( mytools.getDP([ this.globalAPIChannel, glob.Channel_homesdata, glob.Channel_parameters, glob.State_gateway_types, ]), 'gateway types', '', true, 'text', true, true, glob.List_gateway_type, false, false, ); await this.subscribeStates( mytools.getDP([this.globalAPIChannel, glob.Channel_homesdata, glob.APIRequest_homesdata]), ); // Channel setthomestatus await this.createMyChannel(mytools.getDP([this.globalAPIChannel, glob.Channel_homestatus]), 'API homesstatus'); await this.createMyChannel( mytools.getDP([this.globalAPIChannel, glob.Channel_homestatus, glob.Channel_parameters]), 'parameters', ); await this.createNetatmoStructure( mytools.getDP([ this.globalAPIChannel, glob.Channel_homestatus, glob.Channel_parameters, glob.State_device_types, ]), 'device types', '', true, 'text', true, true, glob.List_device_types, false, false, ); await this.createNetatmoStructure( mytools.getDP([this.globalAPIChannel, glob.Channel_homestatus, glob.APIRequest_homestatus]), 'homesstatus', false, true, 'button', true, true, '', false, false, ); await this.subscribeStates( mytools.getDP([this.globalAPIChannel, glob.Channel_homestatus, glob.APIRequest_homestatus]), ); // Channel setthermmode await this.createMyChannel( mytools.getDP([this.globalAPIChannel, glob.Channel_setthermmode]), 'API setthermmode', ); await this.createNetatmoStructure( mytools.getDP([ this.globalAPIChannel, glob.Channel_setthermmode, `${glob.APIRequest_setthermmode}_${glob.APIRequest_setthermmode_schedule}`, ]), 'setthermmode_schedule', false, true, 'button', true, true, '', false, false, ); await this.createNetatmoStructure( mytools.getDP([ this.globalAPIChannel, glob.Channel_setthermmode, `${glob.APIRequest_setthermmode}_${glob.APIRequest_setthermmode_hg}`, ]), 'setthermmode_hg', false, true, 'button', true, true, '', false, false, ); await this.createNetatmoStructure( mytools.getDP([ this.globalAPIChannel, glob.Channel_setthermmode, `${glob.APIRequest_setthermmode}_${glob.APIRequest_setthermmode_away}`, ]), 'setthermmode_away', false, true, 'button', true, true, '', false, false, ); await this.subscribeStates( mytools.getDP([ this.globalAPIChannel, glob.Channel_setthermmode, `${glob.APIRequest_setthermmode}_${glob.APIRequest_setthermmode_schedule}`, ]), ); await this.subscribeStates( mytools.getDP([ this.globalAPIChannel, glob.Channel_setthermmode, `${glob.APIRequest_setthermmode}_${glob.APIRequest_setthermmode_hg}`, ]), ); await this.subscribeStates( mytools.getDP([ this.globalAPIChannel, glob.Channel_setthermmode, `${glob.APIRequest_setthermmode}_${glob.APIRequest_setthermmode_away}`, ]), ); // Channel synchomeschedule await this.createMyChannel( mytools.getDP([this.globalAPIChannel, glob.Channel_synchomeschedule]), 'API synchomeschedule', ); await this.createMyChannel( mytools.getDP([this.globalAPIChannel, glob.Channel_synchomeschedule, glob.Channel_parameters]), 'parameters', ); await this.createNetatmoStructure( mytools.getDP([this.globalAPIChannel, glob.Channel_synchomeschedule, glob.APIRequest_synchomeschedule]), 'synchomeschedule', false, true, 'button', true, true, '', false, false, ); await this.createNetatmoStructure( mytools.getDP([ this.globalAPIChannel, glob.Channel_synchomeschedule, glob.Channel_parameters, glob.State_schedule_id, ]), 'Id of the schedule', '', true, 'text', true, true, '', false, false, ); await this.createNetatmoStructure( mytools.getDP([ this.globalAPIChannel, glob.Channel_synchomeschedule, glob.Channel_parameters, glob.State_zones, ]), 'Array of data used to define time periods to build a schedule. More info on the Thermostat page. id of zone | type of zone | Name of zone | Temperature', '', true, 'list', true, true, '', false, false, ); await this.createNetatmoStructure( mytools.getDP([ this.globalAPIChannel, glob.Channel_synchomeschedule, glob.Channel_parameters, glob.State_timetable, ]), 'Array describing the timetable. More info on the Thermostat page. ID of the zone - offset in minutes since Monday 00:00:01', '', true, 'list', true, true, '', false, false, ); await this.createNetatmoStructure( mytools.getDP([ this.globalAPIChannel, glob.Channel_synchomeschedule, glob.Channel_parameters, glob.State_hg_temp, ]), 'Frost guard temperature value', 7, true, 'number', true, true, '', false, false, ); await this.createNetatmoStructure( mytools.getDP([ this.globalAPIChannel, glob.Channel_synchomeschedule, glob.Channel_parameters, glob.State_away_temp, ]), 'Away temperature value', 12, true, 'number', true, true, '', false, false, ); await this.subscribeStates( mytools.getDP([this.globalAPIChannel, glob.Channel_synchomeschedule, glob.APIRequest_synchomeschedule]), ); // Channel createnewhomeschedule await this.createMyChannel( mytools.getDP([this.globalAPIChannel, glob.Channel_createnewhomeschedule]), 'API createnewhomeschedule', ); await this.createMyChannel( mytools.getDP([this.globalAPIChannel, glob.Channel_createnewhomeschedule, glob.Channel_parameters]), 'parameters', ); await this.createNetatmoStructure( mytools.getDP([ this.globalAPIChannel, glob.Channel_createnewhomeschedule, glob.APIRequest_createnewhomeschedule, ]), 'createnewhomeschedule', false, true, 'button', true, true, '', false, false, ); await this.createNetatmoStructure( mytools.getDP([ this.globalAPIChannel, glob.Channel_createnewhomeschedule, glob.Channel_parameters, glob.State_name, ]), 'Name of the schedule', '', true, 'text', true, true, '', false, false, ); await this.createNetatmoStructure( mytools.getDP([ this.globalAPIChannel, glob.Channel_createnewhomeschedule, glob.Channel_parameters, glob.State_zones, ]), 'Array of data used to define time periods to build a schedule. More info on the Thermostat page. id of zone | type of zone | Name of zone | Temperature', '', true, 'list', true, true, '', false, false, ); await this.createNetatmoStructure( mytools.getDP([ this.globalAPIChannel, glob.Channel_createnewhomeschedule, glob.Channel_parameters, glob.State_timetable, ]), 'Array describing the timetable. More info on the Thermostat page. ID of the zone - offset in minutes since Monday 00:00:01', '', true, 'list', true, true, '', false, false, ); await this.createNetatmoStructure( mytools.getDP([ this.globalAPIChannel, glob.Channel_createnewhomeschedule, glob.Channel_parameters, glob.State_hg_temp, ]), 'Frost guard temperature value', 7, true, 'number', true, true, '', false, false, ); await this.createNetatmoStructure( mytools.getDP([ this.globalAPIChannel, glob.Channel_createnewhomeschedule, glob.Channel_parameters, glob.State_away_temp, ]), 'Away temperature value', 12, true, 'number', true, true, '', false, false, ); await this.subscribeStates( mytools.getDP([ this.globalAPIChannel, glob.Channel_createnewhomeschedule, glob.APIRequest_createnewhomeschedule, ]), ); // Channel getroommeasure await this.createMyChannel( mytools.getDP([this.globalAPIChannel, glob.Channel_getroommeasure]), 'API getroommeasure', ); await this.createMyChannel( mytools.getDP([this.globalAPIChannel, glob.Channel_getroommeasure, glob.Channel_parameters]), 'parameters', ); await this.createNetatmoStructure( mytools.getDP([this.globalAPIChannel, glob.Channel_getroommeasure, glob.APIRequest_getroommeasure]), 'getroommeasure', false, true, 'button', true, true, '', false, false, ); await this.createNetatmoStructure( mytools.getDP([this.globalAPIChannel, glob.Channel_getroommeasure, glob.State_response]), 'Request response', '', true, 'text', false, true, '', false, false, ); await this.createNetatmoStructure( mytools.getDP([ this.globalAPIChannel, glob.Channel_getroommeasure, glob.Channel_parameters, glob.State_room_id, ]), 'Id of room', '', true, 'text', true, true, '', false, false, ); await this.createNetatmoStructure( mytools.getDP([ this.globalAPIChannel, glob.Channel_getroommeasure, glob.Channel_parameters, glob.State_scale, ]), 'Timelapse between two measurements', '', true, 'text', true, true, glob.List_scale, false, false, ); await this.createNetatmoStructure( mytools.getDP([ this.globalAPIChannel, glob.Channel_getroommeasure, glob.Channel_parameters, glob.State_type, ]), 'Type of data to be returned. Setpoint temperature is only available for scales from 30 to 3hours and min/max temp and dates for scales from 1day to 1month.', '', true, 'text', true, true, glob.List_type_rm, false, false, ); await this.createNetatmoStructure( mytools.getDP([ this.globalAPIChannel, glob.Channel_getroommeasure, glob.Channel_parameters, glob.State_date_begin, ]), 'Timestamp of the first measure to retrieve. Default is null', '', true, 'number', true, true, '', false, false, ); await this.createNetatmoStructure( mytools.getDP([ this.globalAPIChannel, glob.Channel_getroommeasure, glob.Channel_parameters, glob.State_date_end, ]), 'Timestamp of the last measure to retrieve (default and max are 1024). Default is null', '', true, 'number', true, true, '', false, false, ); await this.createNetatmoStructure( mytools.getDP([ this.globalAPIChannel, glob.Channel_getroommeasure, glob.Channel_parameters, glob.State_limit, ]), 'Maximum number of measurements (default and max Are 1024)', '', true, 'number', true, true, '', false, false, ); await this.createNetatmoStructure( mytools.getDP([ this.globalAPIChannel, glob.Channel_getroommeasure, glob.Channel_parameters, glob.State_optimize, ]), "Determines the format of the answer. Default is true. For mobile apps we recommend True and False if bandwidth isn't an issue as it is easier to parse", false, true, 'indicator', true, true, '', false, false, ); await this.createNetatmoStructure( mytools.getDP([ this.globalAPIChannel, glob.Channel_getroommeasure, glob.Channel_parameters, glob.State_real_time, ]), 'real_time', false, true, 'indicator', true, true, '', false, false, ); await this.subscribeStates( mytools.getDP([this.globalAPIChannel, glob.Channel_getroommeasure, glob.APIRequest_getroommeasure]), ); // Channel getmeasure await this.createMyChannel(mytools.getDP([this.globalAPIChannel, glob.Channel_getmeasure]), 'API getmeasure'); await this.createMyChannel( mytools.getDP([this.globalAPIChannel, glob.Channel_getmeasure, glob.Channel_parameters]), 'parameters', ); await this.createNetatmoStructure( mytools.getDP([this.globalAPIChannel, glob.Channel_getmeasure, glob.APIRequest_getmeasure]), 'getmeasure', false, true, 'button', true, true, '', false, false, ); await this.createNetatmoStructure( mytools.getDP([this.globalAPIChannel, glob.Channel_getmeasure, glob.State_response]), 'Request response', '', true, 'text', false, true, '', false, false, ); await this.createNetatmoStructure( mytools.getDP([ this.globalAPIChannel, glob.Channel_getmeasure, glob.Channel_parameters, glob.State_device_id, ]), 'Mac adress of the device', '', true, 'text', true, true, '', false, false, ); await this.createNetatmoStructure( mytools.getDP([this.globalAPIChannel, glob.Channel_getmeasure, glob.Channel_parameters, glob.State_scale]), 'Timelapse between two measurements', '', true, 'text', true, true, glob.List_scale, false, false, ); await this.createNetatmoStructure( mytools.getDP([this.globalAPIChannel, glob.Channel_getmeasure, glob.Channel_parameters, glob.State_type]), 'Type of data to be returned. Setpoint temperature is only available for scales from 30 to 3hours and min/max temp and dates for scales from 1day to 1month.', '', true, 'text', true, true, glob.List_type_mm, false, false, ); await this.createNetatmoStructure( mytools.getDP([ this.globalAPIChannel, glob.Channel_getmeasure, glob.Channel_parameters, glob.State_date_begin, ]), 'Timestamp of the first measure to retrieve. Default is null', '', true, 'number', true, true, '', false, false, ); await this.createNetatmoStructure( mytools.getDP([ this.globalAPIChannel, glob.Channel_getmeasure, glob.Channel_parameters, glob.State_date_end, ]), 'Timestamp of the last measure to retrieve (default and max are 1024). Default is null', '', true, 'number', true, true, '', false, false, ); await this.createNetatmoStructure( mytools.getDP([this.globalAPIChannel, glob.Channel_getmeasure, glob.Channel_parameters, glob.State_limit]), 'Maximum number of measurements (default and max Are 1024)', '', true, 'number', true, true, '', false, false, ); await this.createNetatmoStructure( mytools.getDP([ this.globalAPIChannel, glob.Channel_getmeasure, glob.Channel_parameters, glob.State_optimize, ]), "Determines the format of the answer. Default is true. For mobile apps we recommend True and False if bandwidth isn't an issue as it is easier to parse", false, true, 'indicator', true, true, '', false, false, ); await this.createNetatmoStructure( mytools.getDP([ this.globalAPIChannel, glob.Channel_getmeasure, glob.Channel_parameters, glob.State_real_time, ]), 'real_time', false, true, 'indicator', true, true, '', false, false, ); await this.subscribeStates( mytools.getDP([this.globalAPIChannel, glob.Channel_getmeasure, glob.APIRequest_getmeasure]), ); // Channel trigger await this.createMyChannel(this.globalAPIChannelTrigger, 'API trigger'); await this.createNetatmoStructure( mytools.getDP([this.globalAPIChannelTrigger, glob.Trigger_applychanges]), 'trigger to send changes to Netatmo Cloud', false, true, 'button', true, true, '', false, false, ); await this.subscribeStates(mytools.getDP([this.globalAPIChannelTrigger, glob.Trigger_applychanges])); await this.createNetatmoStructure( mytools.getDP([this.globalAPIChannelTrigger, glob.Trigger_refresh_all]), 'trigger to refresh homestructure from Netatmo Cloud', false, true, 'button', true, true, '', false, false, ); await this.subscribeStates(mytools.getDP([this.globalAPIChannelTrigger, glob.Trigger_refresh_all])); await this.createMyChannel(mytools.getDP([this.globalAPIChannelStatus]), 'API Request status'); await this.createNetatmoStructure( mytools.getDP([this.globalAPIChannelStatus, glob.State_Status_API_running]), 'API running status ', false, true, 'indicator', false, true, '', false, true, ); // Channel message await this.createMyChannel(this.globalAPIChannelMessages, 'API messages'); await this.createNetatmoStructure( mytools.getDP([this.globalAPIChannelMessages, glob.Messages_Text]), 'message from the Netatmo Energy APP', '', true, 'text', true, true, '', false, false, ); } //Send notifications async sendNotification(errortype, subject, messageText) { let MesgText = ''; let Subject = ''; if (!this.config.notificationEnabled) { return; } if (errortype != glob.SendNotification) { if ( !( (this.config.notifications.substring(0, 1) != '0' && errortype == glob.InfoNotification) || (this.config.notifications.substring(1, 2) != '0' && errortype == glob.WarningNotification) || (this.config.notifications.substring(2, 3) != '0' && errortype == glob.ErrorNotification) ) ) { return; } } switch (this.config.notificationsType) { //email case glob.NotificationEmail: if (this.email.instance !== '' && this.email.instance !== null && this.email.instance !== undefined) { MesgText = `Netatmo Energy:\n${messageText}`; Subject = subject !== undefined && subject !== null ? subject : mytools.tl('Message', this.systemLang); this.sendTo(this.email.instance, 'send', { text: MesgText, to: this.email.emailReceiver, subject: Subject, from: this.email.emailSender, }); return; } break; //pushover case glob.NotificationPushover: if ( this.pushover.instance !== '' && this.pushover.instance !== null && this.pushover.instance !== undefined ) { MesgText = `Netatmo Energy:\n${messageText}`; Subject = subject !== undefined && subject !== null ? subject : mytools.tl('Message', this.systemLang); if (this.pushover.SilentNotice === 'true' || this.pushover.SilentNotice === true) { this.sendTo(this.pushover.instance, 'send', { message: MesgText, sound: '', priority: -1, title: Subject, device: this.pushover.deviceID, }); } else { this.sendTo(this.pushover.instance, 'send', { message: MesgText, sound: '', title: Subject, device: this.pushover.deviceID, }); } } break; //telegram case glob.NotificationTelegram: if ( this.telegram.instance !== '' && this.telegram.instance !== null && this.telegram.instance !== undefined ) { MesgText = `Netatmo Energy:\n${ subject !== undefined && subject !== null ? `${subject} - ${messageText}` : messageText }`; if ( this.telegram.User && (this.telegram.User === 'allTelegramUsers' || this.telegram.User === '') ) { this.sendTo(this.telegram.instance, 'send', { text: MesgText, disable_notification: this.telegram.SilentNotice, }); } else { this.sendTo(this.telegram.instance, 'send', { user: this.telegram.User, text: MesgText, disable_notification: this.telegram.SilentNotice, }); } } break; //signal case glob.NotificationSignal: if ( this.signal.instance !== '' && this.signal.instance !== null && this.signal.instance !== undefined ) { MesgText = `Netatmo Energy: ${ subject !== undefined && subject !== null ? `${subject} - ${messageText}` : messageText }`; this.sendTo(this.signal.instance, 'send', { text: MesgText }); } break; //whatsapp case glob.NotificationWhatsapp: if ( this.whatsapp.instance !== '' && this.whatsapp.instance !== null && this.whatsapp.instance !== undefined ) { MesgText = `Netatmo Energy:\n${ subject !== undefined && subject !== null ? `${subject} - ${messageText}` : messageText }`; this.sendTo(this.whatsapp.instance, 'send', { text: MesgText }); } break; } //Message to datapoint if (MesgText && MesgText != '') { await this.setState(mytools.getDP([this.globalAPIChannelMessages, glob.Messages_Text]), MesgText, true); } } //Send notification after request async sendRequestNotification(NetatmoRequest, NotificationType, addText, longText) { switch (NetatmoRequest) { //set requests case glob.APIRequest_setroomthermpoint: await this.sendNotification( NotificationType, NetatmoRequest, mytools.tl('Target temperature changed', this.systemLang) + (this.config.NoticeType == glob.NoticeTypeLong && longText != '' ? `\n${longText}` : ''), ); break; case glob.APIRequest_setthermmode: await this.sendNotification( NotificationType, NetatmoRequest, mytools.tl('Mode for your heating system was set to', this.systemLang) + glob.blank + addText + (this.config.NoticeType == glob.NoticeTypeLong && longText != '' ? `\n${longText}` : ''), ); break; case glob.APIRequest_switchhomeschedule: await this.sendNotification( NotificationType, NetatmoRequest, mytools.tl('Changed schedule for your heating system to', this.systemLang) + glob.blank + addText + (this.config.NoticeType == glob.NoticeTypeLong && longText != '' ? `\n${longText}` : ''), ); break; case glob.APIRequest_synchomeschedule: await this.sendNotification( NotificationType, NetatmoRequest, mytools.tl('Changed weekly schedule', this.systemLang) + glob.blank + addText + glob.blank + mytools.tl('for your heating system', this.systemLang) + (this.config.NoticeType == glob.NoticeTypeLong && longText != '' ? `\n${longText}` : ''), ); break; case glob.APIRequest_createnewhomeschedule: await this.sendNotification( NotificationType, NetatmoRequest, mytools.tl('Create a thermostat weekly schedule', this.systemLang) + glob.blank + addText + glob.blank + mytools.tl('for your heating system', this.systemLang) + (this.config.NoticeType == glob.NoticeTypeLong && longText != '' ? `\n${longText}` : ''), ); break; //get requests case glob.APIRequest_getroommeasure: await this.getNameofRoom( addText.substring( addText.lastIndexOf(glob.payload_room_id) + 9, addText.lastIndexOf(glob.payload_scale), ), ); break; case glob.APIRequest_getmeasure: await this.getNameofDevice( addText.substring( addText.lastIndexOf(glob.payload_device_id) + 11, addText.lastIndexOf(glob.p