UNPKG

iobroker.netatmo

Version:
1,856 lines (1,513 loc) 52.5 kB
/** * Based on the "netatmo" package, originally created by Ali Karbassi (https://github.com/karbassi/netatmo/) * Licensed under MIT * * The MIT License (MIT) * * Copyright (c) Ali Karbassi, Patrick Arns, Ingo Fischer * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * **/ const util = require('util'); const EventEmitter = require('events').EventEmitter; const request = require('request'); const moment = require('moment'); let glob_lib_adapter = null; const BASE_URL = 'https://api.netatmo.com'; const errorStatusTexts = { 1: 'unknown_error', 2: 'internal_error', 3: 'parser_error', 5: 'command_invalid_params', 6: 'device_unreachable', 7: 'command_error', 8: 'battery_level', 14: 'busy', 19: 'module_unreachable', 23: 'nothing_to_modify', 27: 'temporarily_banned' } /** * @constructor * @param args */ const netatmo = function (args) { EventEmitter.call(this); this.storedOAuthStates = {}; if (args) { this.authenticate(args); } }; util.inherits(netatmo, EventEmitter); /** * setAdapter * @param myAdapter */ netatmo.prototype.setAdapter = function (myAdapter) { glob_lib_adapter = myAdapter; }; /** * handleRequestError * @param err * @param response * @param body * @param message * @param critical * @param callback to give the error to * @param retry function to execute if error was recovered * @returns {Error} */ netatmo.prototype.handleRequestError = function (err, response, body, message, critical, callback, retry) { let errorMessage; if (body && response && response.headers['content-type'].indexOf('application/json') !== -1) { errorMessage = JSON.parse(body); if (this.refresh_token && errorMessage.error && (errorMessage.error.code === 2 || errorMessage.error.code === 3)) { //authConstants token is expired, refresh it and retry return this.authenticate_refresh(this.refresh_token, retry); } errorMessage = errorMessage && (errorMessage.error.message || errorMessage.error); } else if (response !== undefined) { errorMessage = `Status code${response.statusCode}`; } else { errorMessage = 'No response'; } const error = new Error(`${message}: ${errorMessage}`); if (!message.includes('getHomeData') && !errorMessage.includes('Application does not have the good scope rights')) { if (critical) { this.emit('error', error); } else { this.emit('warning', error); } } if (callback) { return callback(error); } return error; }; netatmo.prototype.getOAuth2AuthenticateStartLink = function (args) { if (!args) { this.emit('error', new Error('Authenticate "args" not set.')); return this; } const validChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; let randomState = ''; for (let i = 0; i < 40; i++) { randomState += validChars.charAt(Math.floor(Math.random() * validChars.length)); } const url = `${BASE_URL}/oauth2/authorize?client_id=${encodeURIComponent(args.client_id)}&redirect_uri=${encodeURIComponent(args.redirect_uri)}&scope=${encodeURIComponent(args.scope)}&state=${randomState}`; this.storedOAuthStates[randomState] = args; return { url, state: randomState }; } /** * http://dev.netatmo.com/doc/authentication * @param args * @param callback * @returns {netatmo} */ netatmo.prototype.authenticate = function (args, callback) { if (!args) { this.emit('error', new Error('Authenticate "args" not set.')); return this; } if (args.access_token) { this.client_id = args.client_id; this.client_secret = args.client_secret; this.scope = args.scope || 'read_homecoach read_station read_thermostat write_thermostat read_camera'; this.authenticate_refresh(args.refresh_token, err => { if (err) { this.emit('authenticated'); } if (callback) { return callback(err); } }); return this; } if (args.state && this.storedOAuthStates[args.state]) { args = Object.assign(args, this.storedOAuthStates[args.state]); delete this.storedOAuthStates[args.state]; } if (!args.client_id) { this.emit('error', new Error('Authenticate "client_id" not set.')); return this; } if (!args.client_secret) { this.emit('error', new Error('Authenticate "client_secret" not set.')); return this; } const form = {}; if (args.code) { if (!args.redirect_uri) { this.emit('error', new Error('Authenticate "code" set but "redirectUri" not set.')); return this; } Object.assign( form, { code: args.code, redirect_uri: args.redirect_uri, grant_type: 'authorization_code' } ); } else { if (!args.username || !args.password) { this.emit('error', new Error('Please Authenticate manually once using the Admin UI of this instance.')); return this; } glob_lib_adapter && glob_lib_adapter.log.info('Try one time fallback authentication with username and password. Might not work after october 2022'); this.username = args.username; this.password = args.password; Object.assign( form, { username: this.username, password: this.password, grant_type: 'password' } ); } this.client_id = args.client_id; this.client_secret = args.client_secret; this.scope = args.scope || 'read_homecoach read_station read_camera'; Object.assign( form, { client_id: this.client_id, client_secret: this.client_secret, scope: this.scope } ); const url = util.format('%s/oauth2/token', BASE_URL); glob_lib_adapter && glob_lib_adapter.log.debug(`netatmo: authenticate: ${JSON.stringify(form)}`); request({ url, method: 'POST', form, }, (err, response, body) => { glob_lib_adapter && glob_lib_adapter.log.debug(`netatmo: authenticate err ${err}`); glob_lib_adapter && glob_lib_adapter.log.debug(`netatmo: authenticate status ${response && response.statusCode}`); glob_lib_adapter && glob_lib_adapter.log.debug(`netatmo: authenticate body ${body}`); if (err || !response || response.statusCode !== 200) { return this.handleRequestError(err, response, body, 'Authenticate error', true, callback); } body = JSON.parse(body); this.access_token = body.access_token; this.refresh_token = body.refresh_token; this.emit('access_token', this.access_token); this.emit('refresh_token', this.refresh_token); if (body.expires_in) { clearTimeout(this.auth_refresh_timeout); this.auth_refresh_timeout = setTimeout(this.authenticate_refresh.bind(this), (body.expires_in - 10) * 1000, body.refresh_token); } this.emit('authenticated'); if (callback) { return callback(); } return this; }); return this; }; /** * http://dev.netatmo.com/doc/authentication * @param refresh_token * @param callback * @returns {netatmo} */ netatmo.prototype.authenticate_refresh = function (refresh_token, callback) { const form = { grant_type: 'refresh_token', refresh_token: refresh_token, client_id: this.client_id, client_secret: this.client_secret, }; const url = util.format('%s/oauth2/token', BASE_URL); glob_lib_adapter && glob_lib_adapter.log.debug(`netatmo: authenticate_refresh: ${JSON.stringify(form)}`); request({ url, method: 'POST', form, }, (err, response, body) => { if (err || !response || response.statusCode !== 200) { return this.handleRequestError(err, response, body, 'Authenticate refresh error', false, callback); } body = JSON.parse(body); this.access_token = body.access_token; this.refresh_token = body.refresh_token; this.emit('access_token', this.access_token); this.emit('refresh_token', this.refresh_token); if (body.expires_in) { clearTimeout(this.auth_refresh_timeout); this.auth_refresh_timeout = setTimeout(this.authenticate_refresh.bind(this), (body.expires_in - 10) * 1000, body.refresh_token); } if (callback) { return callback(body); } return this; }); return this; }; /** * https://dev.netatmo.com/doc/methods/getuser * @param callback * @returns {*} */ netatmo.prototype.getUser = function (callback) { // Wait until authenticated. if (!this.access_token) { return this.on('authenticated', () => { this.getUser(callback); }); } let url; if (this.scope.includes('read_homecoach')) { url = util.format('%s/api/gethomecoachsdata', BASE_URL); } else if (this.scope.includes('read_station')) { url = util.format('%s/api/getstationsdata', BASE_URL); } else if (this.scope.includes('read_thermostat')) { url = util.format('%s/api/getthermostatsdata', BASE_URL); } else if (this.scope.includes('read_camera')) { url = util.format('%s/api/gethomedata', BASE_URL); } else { this.emit('error', new Error('You do not have permission to get user data!')); } const form = { }; request({ url, method: 'POST', form, headers: { 'Authorization': `Bearer ${this.access_token}` } }, (err, response, body) => { if (err || !response || response.statusCode !== 200) { return this.handleRequestError(err, response, body, 'getUser error', false, callback, this.getUser.bind(this, callback)); } body = JSON.parse(body); this.emit('get-user', err, body.body.user); if (callback) { return callback(err, body.body.user); } return this; }); return this; }; /** * https://dev.netatmo.com/doc/methods/devicelist * @param options * @param callback * @returns {*} * @deprecated */ netatmo.prototype.getDevicelist = function (options, callback) { // Wait until authenticated. if (!this.access_token) { return this.on('authenticated', () => this.getDevicelist(options, callback)); } if (options != null && callback == null) { callback = options; options = null; } const url = util.format('%s/api/devicelist', BASE_URL); const qs = { }; if (options && options.app_type) { qs.app_type = options.app_type; } request({ url, method: 'GET', qs, headers: { 'Authorization': `Bearer ${this.access_token}` } }, (err, response, body) => { if (err || !response || response.statusCode !== 200) { return this.handleRequestError(err, response, body, 'getDevicelist error', false, callback, this.getDevicelist.bind(this, options, callback)); } body = JSON.parse(body); const devices = body.body.devices; const modules = body.body.modules; this.emit('get-devicelist', err, devices, modules); if (callback) { return callback(err, devices, modules); } return this; }); return this; }; /** * https://dev.netatmo.com/doc/methods/getstationsdata * @param options * @param callback * @returns {*} */ netatmo.prototype.getStationsData = function (options, callback) { // Wait until authenticated. if (!this.access_token) { return this.on('authenticated', () => this.getStationsData(options, callback)); } if (typeof options === 'function') { callback = options; options = {}; } const url = util.format('%s/api/getstationsdata', BASE_URL); const qs = { }; if (options.device_id) { qs.device_id = options.device_id; } if (options.get_favorites) { qs.get_favorites = !!options.get_favorites; } request({ url, method: 'GET', qs, headers: { 'Authorization': `Bearer ${this.access_token}` } }, (err, response, body) => { if (err || !response || response.statusCode !== 200) { return this.handleRequestError(err, response, body, 'getStationsDataError error', false, callback, this.getStationsData.bind(this, options, callback)); } body = JSON.parse(body); glob_lib_adapter && glob_lib_adapter.log.debug(`getStationsData Raw Response: ${JSON.stringify(body)}`); const devices = body.body.devices; this.emit('get-stationsdata', err, devices); if (callback) { return callback(err, devices); } return this; }); return this; }; /** * https://dev.netatmo.com/doc/methods/gethomecoachsdata * @param options * @param callback * @returns {*} */ netatmo.prototype.getCoachData = function (options, callback) { // Wait until authenticated. if (!this.access_token) { return this.on('authenticated', () => this.getCoachData(options, callback)); } if (options != null && callback == null) { callback = options; options = null; } const url = util.format('%s/api/gethomecoachsdata', BASE_URL); const qs = { }; if (options && options.device_id) { qs.device_id = options.device_id; } request({ url, method: 'GET', qs, headers: { 'Authorization': `Bearer ${this.access_token}` } }, (err, response, body) => { if (err || !response || response.statusCode !== 200) { return this.handleRequestError(err, response, body, 'gethomecoachsdata error', false, callback, this.getCoachData.bind(this, options, callback)); } body = JSON.parse(body); const devices = body.body.devices; this.emit('get-coachdata', err, devices); if (callback) { return callback(err, devices); } return this; }); return this; }; /** * https://dev.netatmo.com/doc/methods/getthermostatsdata * @param options * @param callback * @returns {*} */ netatmo.prototype.getThermostatsData = function (options, callback) { // Wait until authenticated. if (!this.access_token) { return this.on('authenticated', () => { this.getThermostatsData(options, callback); }); } if (options != null && callback == null) { callback = options; options = null; } let url = util.format('%s/api/getthermostatsdata', BASE_URL); const qs = { }; if (options && options.device_id) { qs.device_id = options.device_id; } request({ url, method: 'GET', qs, headers: { 'Authorization': `Bearer ${this.access_token}` } }, (err, response, body) => { if (err || !response || response.statusCode !== 200) { return this.handleRequestError(err, response, body, 'getThermostatsDataError error', false, callback, this.getThermostatsData.bind(this, options, callback)); } body = JSON.parse(body); const devices = body.body.devices; this.emit('get-thermostatsdata', err, devices); if (callback) { return callback(err, devices); } return this; }); return this; }; /** * https://dev.netatmo.com/doc/methods/getmeasure * @param options * @param callback * @returns {*} */ netatmo.prototype.getMeasure = function (options, callback) { // Wait until authenticated. if (!this.access_token) { return this.on('authenticated', () => { this.getMeasure(options, callback); }); } if (!options) { this.emit('error', new Error('getMeasure "options" not set.')); return this; } if (!options.device_id) { this.emit('error', new Error('getMeasure "device_id" not set.')); return this; } if (!options.scale) { this.emit('error', new Error('getMeasure "scale" not set.')); return this; } if (!options.type) { this.emit('error', new Error('getMeasure "type" not set.')); return this; } /* if (util.isArray(options.type)) { options.type = options.type.join(','); } // Remove any spaces from the type list if there is any. options.type = options.type.replace(/\s/g, '').toLowerCase(); */ const url = util.format('%s/api/getmeasure', BASE_URL); const qs = { device_id: options.device_id, scale: options.scale, type: options.type, }; if (options) { if (options.module_id) { qs.module_id = options.module_id; } if (options.date_begin) { if (options.date_begin <= 1E10) { options.date_begin *= 1E3; } qs.date_begin = moment(options.date_begin).utc().unix(); } if (options.date_end === 'last') { qs.date_end = 'last'; } else if (options.date_end) { if (options.date_end <= 1E10) { options.date_end *= 1E3; } qs.date_end = moment(options.date_end).utc().unix(); } if (options.limit) { qs.limit = parseInt(options.limit, 10); if (qs.limit > 1024) { qs.limit = 1024; } } if (options.optimize !== undefined) { qs.optimize = !!options.optimize; } if (options.real_time !== undefined) { qs.real_time = !!options.real_time; } } request({ url, method: 'GET', qs, qsStringifyOptions: { arrayFormat: 'indices' }, headers: { 'Authorization': `Bearer ${this.access_token}` } }, (err, response, body) => { if (err || !response || response.statusCode !== 200) { const error = this.handleRequestError(err, response, body, 'getMeasure error', false, callback, this.getMeasure.bind(this, options, callback)); if (callback) { callback(error); } return; } body = JSON.parse(body); const measure = body.body; this.emit('get-measure', err, measure); if (callback) { return callback(err, measure); } return this; }); return this; }; /** * https://dev.netatmo.com/doc/methods/getthermstate * @param options * @param callback * @returns {*} * @deprecated */ netatmo.prototype.getThermstate = function (options, callback) { // Wait until authenticated. if (!this.access_token) { return this.on('authenticated', () => this.getThermstate(options, callback)); } if (!options) { this.emit('error', new Error('getThermstate "options" not set.')); return this; } if (!options.device_id) { this.emit('error', new Error('getThermstate "device_id" not set.')); return this; } if (!options.module_id) { this.emit('error', new Error('getThermstate "module_id" not set.')); return this; } const url = util.format('%s/api/getthermstate', BASE_URL); const qs = { device_id: options.device_id, module_id: options.module_id, }; request({ url, method: 'GET', qs, headers: { 'Authorization': `Bearer ${this.access_token}` } }, (err, response, body) => { if (err || !response || response.statusCode !== 200) { return this.handleRequestError(err, response, body, 'getThermstate error', false, callback, this.getThermstate.bind(this, options, callback)); } body = JSON.parse(body); this.emit('get-thermstate', err, body.body); if (callback) { return callback(err, body.body); } return this; }); return this; }; netatmo.prototype.switchSchedule = function (options, callback) { // Wait until authenticated. if (!this.access_token) { return this.on('authenticated', () => { this.switchSchedule(options, callback); }); } if (!options) { this.emit('error', new Error('setSyncSchedule "options" not set.')); return this; } if (!options.device_id) { this.emit('error', new Error('setSyncSchedule "device_id" not set.')); return this; } if (!options.module_id) { this.emit('error', new Error('setSyncSchedule "module_id" not set.')); return this; } if (!options.schedule_id) { this.emit('error', new Error('setSyncSchedule "schedule_id" not set.')); return this; } const url = util.format('%s/api/switchschedule', BASE_URL); const qs = { device_id: options.device_id, module_id: options.module_id, schedule_id: options.schedule_id, }; request({ url, method: 'POST', qs, headers: { 'Authorization': `Bearer ${this.access_token}` } }, (err, response, body) => { if (err || !response || response.statusCode !== 200) { return this.handleRequestError(err, response, body, 'switchSchedule error', false, callback, this.switchSchedule.bind(this, options, callback)); } body = JSON.parse(body); if (callback) { return callback(err, body.status); } return this; }); return this; }; /** * https://dev.netatmo.com/doc/methods/syncschedule * @param options * @param callback * @returns {*} */ netatmo.prototype.setSyncSchedule = function (options, callback) { // Wait until authenticated. if (!this.access_token) { return this.on('authenticated', () => { this.setSyncSchedule(options, callback); }); } if (!options) { this.emit('error', new Error('setSyncSchedule "options" not set.')); return this; } if (!options.device_id) { this.emit('error', new Error('setSyncSchedule "device_id" not set.')); return this; } if (!options.module_id) { this.emit('error', new Error('setSyncSchedule "module_id" not set.')); return this; } if (!options.zones) { this.emit('error', new Error('setSyncSchedule "zones" not set.')); return this; } if (!options.timetable) { this.emit('error', new Error('setSyncSchedule "timetable" not set.')); return this; } const url = util.format('%s/api/syncschedule', BASE_URL); const qs = { device_id: options.device_id, module_id: options.module_id, zones: options.zones, timetable: options.timetable, }; request({ url, method: 'POST', qs, qsStringifyOptions: { arrayFormat: 'indices' }, headers: { 'Authorization': `Bearer ${this.access_token}` } }, (err, response, body) => { if (err || !response || response.statusCode !== 200) { return this.handleRequestError(err, response, body, 'setSyncSchedule error', false, callback, this.setSyncSchedule.bind(this, options, callback)); } body = JSON.parse(body); this.emit('set-syncschedule', err, body.status); if (callback) { return callback(err, body.status); } return this; }); return this; }; /** * https://dev.netatmo.com/doc/methods/setthermpoint * @param options * @param callback * @returns {*} */ netatmo.prototype.setThermpoint = function (options, callback) { // Wait until authenticated. if (!this.access_token) { return this.on('authenticated', () => { this.setThermpoint(options, callback); }); } if (!options) { this.emit('error', new Error('setThermpoint "options" not set.')); return this; } if (!options.device_id) { this.emit('error', new Error('setThermpoint "device_id" not set.')); return this; } if (!options.module_id) { this.emit('error', new Error('setThermpoint "module_id" not set.')); return this; } if (!options.setpoint_mode) { this.emit('error', new Error('setThermpoint "setpoint_mode" not set.')); return this; } const url = util.format('%s/api/setthermpoint', BASE_URL); const qs = { device_id: options.device_id, module_id: options.module_id, setpoint_mode: options.setpoint_mode, }; if (options) { if (options.setpoint_endtime) { qs.setpoint_endtime = options.setpoint_endtime; } if (options.setpoint_temp) { qs.setpoint_temp = options.setpoint_temp; } } request({ url, method: 'POST', qs, headers: { 'Authorization': `Bearer ${this.access_token}` } }, (err, response, body) => { if (err || !response || response.statusCode !== 200) { return this.handleRequestError(err, response, body, 'setThermpoint error', false, callback, this.setThermpoint.bind(this, options, callback)); } body = JSON.parse(body); this.emit('get-thermostatsdata', err, body.status); if (callback) { return callback(err, body.status); } return this; }); return this; }; netatmo.prototype.setState = function (homeId, moduleId, fieldName, fieldValue, bridgeId, callback) { // Wait until authenticated. if (!this.access_token) { return this.on('authenticated', () => { this.setState(home_id, moduleId, fieldName, fieldValue, bridgeId, callback); }); } if (typeof bridgeId === 'function') { callback = bridgeId; bridgeId = undefined; } if (typeof bridgeId !== 'string') { bridgeId = undefined; } if (!homeId) { this.emit('error', new Error('setState "homeId" not set.')); return this; } if (!moduleId) { this.emit('error', new Error('setState "moduleId" not set.')); return this; } if (!fieldName) { this.emit('error', new Error('setState "fieldName" not set.')); return this; } if (fieldValue === undefined || fieldValue === null) { this.emit('error', new Error('setState "fieldValue" not set.')); return this; } const url = util.format('%s/api/setstate', BASE_URL); const body = { home: { id: homeId, modules: [ { id: moduleId, bridge: bridgeId } ] } }; body.home.modules[0][fieldName] = fieldValue; request({ url: url, method: 'POST', body, json: true, headers: { 'Authorization': `Bearer ${this.access_token}` } }, (err, response, body) => { if (err || !response || response.statusCode !== 200) { return this.handleRequestError(err, response, body, 'setstate error', false, callback, this.switchSchedule.bind(this, options, callback)); } if (typeof body === 'string') { try { body = JSON.parse(body); } catch (err) { if (callback) { return callback(err); } } } if (body && typeof body === 'object') { if (callback) { return callback(err, body.status); } } else { if (callback) { return callback(err, body); } } return this; }); return this; }; /** * https://dev.netatmo.com/doc/methods/homesdata - new call for gethomedata * @param options * @param callback * @returns {*} * CHECKED */ netatmo.prototype.homesdata = function (options, callback) { // Wait until authenticated. if (!this.access_token) { return this.on('authenticated', () => { this.homesdata(options, callback); }); } const url = util.format('%s/api/homesdata', BASE_URL); const qs = { }; if (options != null && callback == null) { callback = options; options = null; } if (options) { if (options.home_id) { q.home_id = options.home_id; } if (options.gateway_types) { qs.gateway_types = options.gateway_types; } } request({ url, method: 'GET', qs, qsStringifyOptions: { arrayFormat: 'indices' }, headers: { 'Authorization': `Bearer ${this.access_token}` } }, (err, response, body) => { if (err || !response || response.statusCode !== 200) { return this.handleRequestError(err, response, body, 'homesdata error', false, callback, this.homesdata.bind(this, options, callback)); } body = JSON.parse(body); this.emit('get-homesdata', err, body.body); if (callback) { return callback(err, body.body); } return this; }); return this; } /** * The method combines homesdata, homestatus and events * @param options * @param callback * @returns {*} * CHECKED */ netatmo.prototype.homedataExtended = async function (options, callback) { // Wait until authenticated. if (!this.access_token) { return this.on('authenticated', () => { this.homedataExtended(options, callback); }); } try { const homeData = await new Promise((resolve, reject) => { this.homesdata(options, (err, data) => { if (err) { reject(err); } else { glob_lib_adapter && glob_lib_adapter.log.silly(`netatmo: homesdata: ${JSON.stringify(data)}`); resolve(data); } }); }); const legacyHomeData = await new Promise((resolve, reject) => { this.getHomeData(options, (err, data) => { if (err) { if (err.toString().includes('Application does not have the good scope rights')) { resolve({ homes: [] }); return; } reject(err); } else { glob_lib_adapter && glob_lib_adapter.log.silly(`netatmo: gethomedata: ${JSON.stringify(data)}`); resolve(data); } }); }); if (homeData.homes) { for (let i = 0; i < homeData.homes.length; i++) { const homeStatus = await new Promise((resolve, reject) => { this.homestatus(homeData.homes[i].id, options, (err, data) => { if (err) { reject(err); } else { glob_lib_adapter && glob_lib_adapter.log.silly(`netatmo: homestatus for ${homeData.homes[i].id}: ${JSON.stringify(data)}`); resolve(data); } }); }); const homeEvents = await new Promise((resolve, reject) => { this.getevents(homeData.homes[i].id, options, (err, data) => { if (err) { reject(err); } else { glob_lib_adapter && glob_lib_adapter.log.silly(`netatmo: getevents for ${homeData.homes[i].id}: ${JSON.stringify(data)}`); resolve(data); } }); }); if (homeData.homes[i].modules) { const moduleIdList = []; for (let homeModuleIndex = 0; homeModuleIndex < homeData.homes[i].modules.length; homeModuleIndex++) { moduleIdList.push(homeData.homes[i].modules[homeModuleIndex].id); if (homeStatus.home.modules) { const statusModuleIndex = homeStatus.home.modules.findIndex(statusModule => statusModule.id === homeData.homes[i].modules[homeModuleIndex].id); if (statusModuleIndex !== -1) { homeData.homes[i].modules[homeModuleIndex] = Object.assign(homeData.homes[i].modules[homeModuleIndex], homeStatus.home.modules[statusModuleIndex]); } } } if (homeStatus.errors) { for (let errorIndex = 0; errorIndex < homeStatus.errors.length; errorIndex++) { const error = homeStatus.errors[errorIndex]; const moduleIndex = homeData.homes[i].modules.findIndex(module => module.id === error.id); if (moduleIndex !== -1) { homeData.homes[i].modules[moduleIndex].errorStatus = errorStatusTexts[error.code] || `unknown_error_${error.code}`; } } } if (homeEvents && homeEvents.home && homeEvents.home.events) { homeData.homes[i].events = homeEvents.home.events.filter(event => moduleIdList.includes(event.module_id)); } } if (homeData.homes[i].persons) { const legacyHomeDetails = legacyHomeData.homes.find(legacyHome => legacyHome.id === homeData.homes[i].id); if (legacyHomeDetails && legacyHomeDetails.persons) { for (let personIndex = 0; personIndex < homeData.homes[i].persons.length; personIndex++) { const legacyPerson = legacyHomeDetails.persons.find(legacyPerson => legacyPerson.id === homeData.homes[i].persons[personIndex].id); if (legacyPerson) { homeData.homes[i].persons[personIndex] = Object.assign(homeData.homes[i].persons[personIndex], legacyPerson); } } } } } } callback && callback(null, homeData); } catch (err) { callback && callback(err); } } /** * https://dev.netatmo.com/doc/methods/homestatus - new call for gethomedata * @param home_id * @param options * @param callback * @returns {*} * CHECKED */ netatmo.prototype.homestatus = function (home_id, options, callback) { // Wait until authenticated. if (!this.access_token) { return this.on('authenticated', () => { this.homestatus(home_id, options, callback); }); } const url = util.format('%s/api/homestatus', BASE_URL); const qs = { home_id }; if (options != null && callback == null) { callback = options; options = null; } if (options) { if (options.home_id) { qs.home_id = options.home_id; } if (options.gateway_types) { qs.gateway_types = options.gateway_types; } } request({ url: url, method: 'GET', qs, qsStringifyOptions: { arrayFormat: 'indices' }, headers: { 'Authorization': `Bearer ${this.access_token}` } }, (err, response, body) => { if (err || !response || response.statusCode !== 200) { return this.handleRequestError(err, response, body, 'homestatus error', false, callback, this.homesdata.bind(this, options, callback)); } body = JSON.parse(body); this.emit('get-homestatus', err, body.body); if (callback) { return callback(err, body.body); } return this; }); return this; } /** * https://dev.netatmo.com/doc/methods/gethomedata * @param options * @param callback * @returns {*} * @deprecated Now use `Homesdata` to get topology information and `Homestatus` to get the status of the home * CHECKED */ netatmo.prototype.getHomeData = function (options, callback) { // Wait until authenticated. if (!this.access_token) { return this.on('authenticated', () => { this.getHomeData(options, callback); }); } const url = util.format('%s/api/gethomedata', BASE_URL); const qs = { }; if (options != null && callback == null) { callback = options; options = null; } if (options) { if (options.home_id) { qs.home_id = options.home_id; } if (options.size) { qs.size = options.size; } } request({ url: url, method: 'GET', qs, qsStringifyOptions: { arrayFormat: 'indices' }, headers: { 'Authorization': `Bearer ${this.access_token}` } }, (err, response, body) => { if (err || !response || response.statusCode !== 200) { return this.handleRequestError(err, response, body, 'getHomeData error', false, callback, this.getHomeData.bind(this, options, callback)); } body = JSON.parse(body); this.emit('get-homedata', err, body.body); if (callback) { return callback(err, body.body); } return this; }); return this; }; /** * https://dev.netatmo.com/doc/methods/getnextevents * @param options * @param callback * @returns {*} * @deprecated Now use `Getevents` */ netatmo.prototype.getNextEvents = function (options, callback) { // Wait until authenticated. if (!this.access_token) { return this.on('authenticated', () => { this.getNextEvents(options, callback); }); } if (!options) { this.emit('error', new Error('getNextEvents "options" not set.')); return this; } if (!options.home_id) { this.emit('error', new Error('getNextEvents "home_id" not set.')); return this; } if (!options.event_id) { this.emit('error', new Error('getNextEvents "event_id" not set.')); return this; } const url = util.format('%s/api/getnextevents', BASE_URL); const qs = { home_id: options.home_id, event_id: options.event_id, }; if (options.size) { qs.size = options.size; } request({ url: url, method: 'GET', qs, headers: { 'Authorization': `Bearer ${this.access_token}` } }, (err, response, body) => { if (err || !response || response.statusCode !== 200) { return this.handleRequestError(err, response, body, 'getNextEvents error', false, callback, this.getNextEvents.bind(this, options, callback)); } body = JSON.parse(body); this.emit('get-nextevents', err, body.body); if (callback) { return callback(err, body.body); } return this; }); return this; }; /** * https://dev.netatmo.com/doc/methods/getlasteventof * @param options * @param callback * @returns {*} * @deprecated Now use `Getevents` */ netatmo.prototype.getLastEventOf = function (options, callback) { // Wait until authenticated. if (!this.access_token) { return this.on('authenticated', () => { this.getLastEventOf(options, callback); }); } if (!options) { this.emit('error', new Error('getLastEventOf "options" not set.')); return this; } if (!options.home_id) { this.emit('error', new Error('getLastEventOf "home_id" not set.')); return this; } if (!options.person_id) { this.emit('error', new Error('getLastEventOf "person_id" not set.')); return this; } const url = util.format('%s/api/getlasteventof', BASE_URL); const qs = { home_id: options.home_id, person_id: options.person_id, }; if (options.offset) { qs.offset = options.offset; } request({ url: url, method: 'GET', qs, headers: { 'Authorization': `Bearer ${this.access_token}` } }, (err, response, body) => { if (err || !response || response.statusCode !== 200) { return this.handleRequestError(err, response, body, 'getLastEventOf error', false, callback, this.getLastEventOf.bind(this, options, callback)); } body = JSON.parse(body); this.emit('get-lasteventof', err, body.body); if (callback) { return callback(err, body.body); } return this; }); return this; }; /** * https://dev.netatmo.com/doc/methods/geteventsuntil * @param options * @param callback * @returns {*} * @deprecated Now use `Getevents` */ netatmo.prototype.getEventsUntil = function (options, callback) { // Wait until authenticated. if (!this.access_token) { return this.on('authenticated', () => { this.getEventsUntil(options, callback); }); } if (!options) { this.emit('error', new Error('getEventsUntil "options" not set.')); return this; } if (!options.home_id) { this.emit('error', new Error('getEventsUntil "home_id" not set.')); return this; } if (!options.event_id) { this.emit('error', new Error('getEventsUntil "event_id" not set.')); return this; } const url = util.format('%s/api/geteventsuntil', BASE_URL); const qs = { home_id: options.home_id, event_id: options.event_id, }; request({ url: url, method: 'GET', qs, headers: { 'Authorization': `Bearer ${this.access_token}` } }, (err, response, body) => { if (err || !response || response.statusCode !== 200) { return this.handleRequestError(err, response, body, 'getEventsUntil error', false, callback, this.getEventsUntil.bind(this, options, callback)); } body = JSON.parse(body); this.emit('get-eventsuntil', err, body.body); if (callback) { return callback(err, body.body); } return this; }); return this; }; /** * https://dev.netatmo.com/doc/methods/getcamerapicture * @param options * @param callback * @returns {*} * @deprecated Snapshots are now retrievable in the event object directly, use `Getevents` */ netatmo.prototype.getCameraPicture = function (options, callback) { // Wait until authenticated. if (!this.access_token) { return this.on('authenticated', () => { this.getCameraPicture(options, callback); }); } if (!options) { this.emit('error', new Error('getCameraPicture "options" not set.')); return this; } if (!options.image_id) { this.emit('error', new Error('getCameraPicture "image_id" not set.')); return this; } if (!options.key) { this.emit('error', new Error('getCameraPicture "key" not set.')); return this; } const url = util.format('%s/api/getcamerapicture', BASE_URL); const qs = { image_id: options.image_id, key: options.key, }; request({ url: url, method: 'GET', form: qs, encoding: null, contentType: 'image/jpg', headers: { 'Authorization': `Bearer ${this.access_token}` } }, (err, response, body) => { if (err || !response || response.statusCode !== 200) { return this.handleRequestError(err, response, body, 'getCameraPicture error', false, callback, this.getCameraPicture.bind(this, options, callback)); } this.emit('get-camerapicture', err, body); if (callback) { return callback(err, body); } return this; }); return this; }; /** * https://dev.netatmo.com/dev/resources/technical/reference/cameras/addwebhook * @param callbackUrl * @param callback * @returns {*} */ netatmo.prototype.addWebHook = function (callbackUrl, callback) { // Wait until authenticated. if (!this.access_token) { return this.on('authenticated', () => { this.addWebHook(callbackUrl, callback); }); } const url = util.format('%s/api/addwebhook', BASE_URL); const qs = { url: callbackUrl }; request({ url: url, method: 'POST', qs, encoding: null, headers: { 'Authorization': `Bearer ${this.access_token}` } }, (err, response, body) => { if (err || !response || response.statusCode !== 200) { if (callback) { callback(err, body, qs); } return; //return this.handleRequestError(err, response, body, 'addWebHook error'); } if (callback) { return callback(err, body); } return this; }); return this; }; /** * https://dev.netatmo.com/dev/resources/technical/reference/cameras/dropwebhook * @param callback * @returns {*} */ netatmo.prototype.dropWebHook = function (callback) { // Wait until authenticated. if (!this.access_token) { return this.on('authenticated', () => this.dropWebHook(callback)); } const url = util.format('%s/api/dropwebhook', BASE_URL); request({ url: url, method: 'POST', encoding: null, headers: { 'Authorization': `Bearer ${this.access_token}` } }, (err, response, body) => { if (err || !response || response.statusCode !== 200) { return this.handleRequestError(err, response, body, 'dropWebHook error'); } if (callback) { return callback(err, body); } return this; }); return this; }; /** * https://dev.netatmo.com/dev/resources/technical/reference/cameras/setpersonsaway * @param homeId * @param personsId string * @param callback * @returns {*} */ netatmo.prototype.setPersonsAway = function (homeId, personsId, callback) { // Wait until authenticated. if (!this.access_token) { return this.on('authenticated', () => { this.setPersonsAway(homeId, personsId, callback); }); } const url = util.format('%s/api/setpersonsaway', BASE_URL); const qs = { home_id: homeId }; if (personsId) qs.person_id = personsId; request({ url: url, method: 'POST', qs, qsStringifyOptions: { arrayFormat: 'indices' }, encoding: null, headers: { 'Authorization': `Bearer ${this.access_token}` } }, (err, response, body) => { if (err || !response || response.statusCode !== 200) { return this.handleRequestError(err, response, body, 'setPersonsAway error'); } if (callback) { return callback(err, body); } return this; }); return this; }; /** * https://dev.netatmo.com/dev/resources/technical/reference/cameras/setpersonshome * @param homeId * @param personsId array * @param callback * @returns {*} */ netatmo.prototype.setPersonsHome = function (homeId, personsId, callback) { // Wait until authenticated. if (!this.access_token) { return this.on('authenticated', () => { this.setPersonsHome(homeId, personsId, callback); }); } const url = util.format('%s/api/setpersonshome', BASE_URL); const qs = { home_id: homeId }; if (personsId) qs.person_ids = personsId; request({ url: url, method: 'POST', qs, qsStringifyOptions: { arrayFormat: 'indices' }, encoding: null, headers: { 'Authorization': `Bearer ${this.access_token}` } }, (err