UNPKG

homebridge-eveatmo

Version:

Homebridge plugin which adds a Netatmo weatherstation as HomeKit device and tries to act like Elgato Eve Room/Weather

1,608 lines (1,311 loc) 40.3 kB
/* eslint-disable @typescript-eslint/no-require-imports */ const util = require('util'); const EventEmitter = require('events').EventEmitter; const axios = require('axios'); const moment = require('moment'); const fs = require('fs'); /* eslint-enable @typescript-eslint/no-require-imports */ const BASE_URL = 'https://api.netatmo.com'; let client_id; let client_secret; let username; let password; let scope; let access_token = null; let refresh_token = null; let authentication_failed_error = null; let filename; /** * @constructor * @param args * @param homebridge */ const netatmo = function (args, homebridge) { EventEmitter.call(this); if (args.grant_type === 'refresh_token') { client_id = args.client_id; client_secret = args.client_secret; filename = homebridge.user.storagePath() + '/netatmo-token.json'; if (fs.existsSync(filename)) { let rawData = fs.readFileSync(filename); let tokenData = JSON.parse(rawData); // access_token = tokenData.access_token; // dont read access_token because it may be already invalid on first use after restart refresh_token = tokenData.refresh_token; } else { refresh_token = args.refresh_token; } this.authenticate_refresh(); } else { this.authenticate(args, null); } }; util.inherits(netatmo, EventEmitter); netatmo.prototype.waitUntilAuthenticated = function (callback, onAuthenticated) { if (authentication_failed_error) { if (callback) { callback(authentication_failed_error); } else { this.emit('error', authentication_failed_error); } return true; } if (access_token) { return false; } let onAuthSuccess; let onAuthFailure; onAuthFailure = (error) => { this.removeListener('authenticated', onAuthSuccess); if (callback) { callback(error); } else { this.emit('error', error); } }; onAuthSuccess = () => { this.removeListener('authentication-failed', onAuthFailure); onAuthenticated.call(this); }; this.once('authenticated', onAuthSuccess); this.once('authentication-failed', onAuthFailure); return true; }; /** * handleRequestError * @param err * @param response * @param body * @param message * @param critical * @returns {Error} */ netatmo.prototype.handleRequestError = function (err, response, body, message, critical) { let errorMessage = ''; if (body && response && response.headers && response.headers['content-type'] && response.headers['content-type'].trim().toLowerCase().indexOf('application/json') !== -1) { const parsedBody = typeof body === 'string' ? JSON.parse(body) : body; errorMessage = parsedBody && (parsedBody.error?.message || parsedBody.error); } else if (response) { errorMessage = 'Status code ' + response.status; } else if (err) { errorMessage = err.message || 'Request failed'; } else { errorMessage = 'No response'; } const error = new Error(message + ': ' + errorMessage); if (critical) { this.emit('error', error); } else { this.emit('warning', error); } return error; }; /** * https://dev.netatmo.com/dev/resources/technical/guides/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) { access_token = args.access_token; return this; } 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; } if (!args.username) { this.emit('error', new Error('Authenticate \'username\' not set.')); return this; } if (!args.password) { this.emit('error', new Error('Authenticate \'password\' not set.')); return this; } username = args.username; password = args.password; client_id = args.client_id; client_secret = args.client_secret; scope = args.scope || 'read_station read_thermostat write_thermostat read_camera write_camera access_camera read_presence access_presence read_smokedetector read_homecoach'; const params = new URLSearchParams(); params.append('client_id', client_id); params.append('client_secret', client_secret); params.append('username', username); params.append('password', password); params.append('scope', scope); params.append('grant_type', 'password'); const url = util.format('%s/oauth2/token', BASE_URL); axios.post(url, params, { headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, }).then((response) => { const body = response.data; authentication_failed_error = null; access_token = body.access_token; if (body.expires_in) { setTimeout(this.authenticate_refresh.bind(this), body.expires_in * 1000, body.refresh_token); } this.emit('authenticated'); if (callback) { return callback(); } return this; }).catch((err) => { const response = err.response; const body = response ? response.data : null; const error = this.handleRequestError(err, response, body, 'Authenticate error', true); authentication_failed_error = error; this.emit('authentication-failed', error); return error; }); return this; }; /** * https://dev.netatmo.com/dev/resources/technical/guides/authentication/refreshingatoken * @param refresh_token * @returns {netatmo} */ netatmo.prototype.authenticate_refresh = function () { const params = new URLSearchParams(); params.append('grant_type', 'refresh_token'); params.append('refresh_token', refresh_token); params.append('client_id', client_id); params.append('client_secret', client_secret); const url = util.format('%s/oauth2/token', BASE_URL); axios.post(url, params, { headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, }).then((response) => { const body = response.data; authentication_failed_error = null; fs.writeFileSync(filename, JSON.stringify({ //'access_token': body.access_token, // dont store access_token because it may be already invalid on first use after restart 'refresh_token': body.refresh_token, })); refresh_token = body.refresh_token; if (access_token === null) { access_token = body.access_token; this.emit('authenticated'); } else { access_token = body.access_token; } if (body.expires_in) { setTimeout(this.authenticate_refresh.bind(this), (body.expires_in - 300) * 1000); // try refreshing the tokens 5min early } return this; }).catch((err) => { const response = err.response; const body = response ? response.data : null; if (access_token === null) { const error = this.handleRequestError(err, response, body, 'Authenticate refresh error', true); authentication_failed_error = error; this.emit('authentication-failed', error); return this; } this.handleRequestError(err, response, body, 'Authenticate refresh error'); setTimeout(this.authenticate_refresh.bind(this), 180 * 1000); // retry in 3min return this; }); return this; }; /** * https://dev.netatmo.com/dev/resources/technical/reference/weatherstation/getstationsdata * @param options * @param callback * @returns {*} */ netatmo.prototype.getStationsData = function (options, callback) { if (options != null && callback == null) { callback = options; options = null; } // Wait until authenticated. if (this.waitUntilAuthenticated(callback, () => { this.getStationsData(options, callback); })) { return this; } const url = util.format('%s/api/getstationsdata', BASE_URL); const params = new URLSearchParams(); params.append('access_token', access_token); if (options) { if (options.device_id) { params.append('device_id', options.device_id); } if (options.get_favorites) { params.append('get_favorites', options.get_favorites); } } axios.post(url, params, { headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, }).then((response) => { const body = response.data; const devices = body.body.devices; this.emit('get-stationsdata', null, devices); if (callback) { return callback(null, devices); } return this; }).catch((err) => { const response = err.response; const body = response ? response.data : null; const error = this.handleRequestError(err, response, body, 'getStationsDataError error'); if (callback) { callback(error); } return error; }); return this; }; /** * https://dev.netatmo.com/dev/resources/technical/reference/thermostat/getthermostatsdata * @param options * @param callback * @returns {*} */ netatmo.prototype.getThermostatsData = function (options, callback) { // Wait until authenticated. if (!access_token) { return this.on('authenticated', function () { this.getThermostatsData(options, callback); }); } if (options != null && callback == null) { callback = options; options = null; } let url = util.format('%s/api/getthermostatsdata?access_token=%s', BASE_URL, access_token); if (options != null) { url = util.format(url + '&device_id=%s', options.device_id); } axios.get(url).then((response) => { const body = response.data; const devices = body.body.devices; this.emit('get-thermostatsdata', null, devices); if (callback) { return callback(null, devices); } return this; }).catch((err) => { const response = err.response; const body = response ? response.data : null; return this.handleRequestError(err, response, body, 'getThermostatsDataError error'); }); return this; }; /** * https://dev.netatmo.com/dev/resources/technical/reference/common/getmeasure * @param options * @param callback * @returns {*} */ netatmo.prototype.getMeasure = function (options, callback) { // Wait until authenticated. if (!access_token) { return this.on('authenticated', function () { 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 params = new URLSearchParams(); params.append('access_token', access_token); params.append('device_id', options.device_id); params.append('scale', options.scale); params.append('type', options.type); if (options) { if (options.module_id) { params.append('module_id', options.module_id); } if (options.date_begin) { if (options.date_begin <= 1E10) { options.date_begin *= 1E3; } params.append('date_begin', moment(options.date_begin).utc().unix()); } if (options.date_end === 'last') { params.append('date_end', 'last'); } else if (options.date_end) { if (options.date_end <= 1E10) { options.date_end *= 1E3; } params.append('date_end', moment(options.date_end).utc().unix()); } if (options.limit) { let limit = parseInt(options.limit, 10); if (limit > 1024) { limit = 1024; } params.append('limit', limit); } if (options.optimize !== undefined) { params.append('optimize', !!options.optimize); } if (options.real_time !== undefined) { params.append('real_time', !!options.real_time); } } axios.post(url, params, { headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, }).then((response) => { const body = response.data; const measure = body.body; this.emit('get-measure', null, measure); if (callback) { return callback(null, measure); } return this; }).catch((err) => { const response = err.response; const body = response ? response.data : null; const error = this.handleRequestError(err, response, body, 'getMeasure error'); if (callback) { callback(error); } return; }); return this; }; /** * https://dev.netatmo.com/dev/resources/technical/reference/common/getroommeasure * @param options * @param callback * @returns {*} */ netatmo.prototype.getRoomMeasure = function (options, callback) { // Wait until authenticated. if (!access_token) { return this.on('authenticated', function () { this.getRoomMeasure(options, callback); }); } if (!options) { this.emit('error', new Error('getRoomMeasure \'options\' not set.')); return this; } if (!options.home_id) { this.emit('error', new Error('getRoomMeasure \'home_id\' not set.')); return this; } if (!options.room_id) { this.emit('error', new Error('getRoomMeasure \'room_id\' not set.')); return this; } if (!options.scale) { this.emit('error', new Error('getRoomMeasure \'scale\' not set.')); return this; } if (!options.type) { this.emit('error', new Error('getRoomMeasure \'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/getroommeasure', BASE_URL); const params = new URLSearchParams(); params.append('access_token', access_token); params.append('home_id', options.home_id); params.append('room_id', options.room_id); params.append('scale', options.scale); params.append('type', options.type); if (options) { if (options.date_begin) { if (options.date_begin <= 1E10) { options.date_begin *= 1E3; } params.append('date_begin', moment(options.date_begin).utc().unix()); } if (options.date_end === 'last') { params.append('date_end', 'last'); } else if (options.date_end) { if (options.date_end <= 1E10) { options.date_end *= 1E3; } params.append('date_end', moment(options.date_end).utc().unix()); } if (options.limit) { let limit = parseInt(options.limit, 10); if (limit > 1024) { limit = 1024; } params.append('limit', limit); } if (options.optimize !== undefined) { params.append('optimize', !!options.optimize); } if (options.real_time !== undefined) { params.append('real_time', !!options.real_time); } } axios.post(url, params, { headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, }).then((response) => { const body = response.data; const measure = body.body; this.emit('get-room-measure', null, measure); if (callback) { return callback(null, measure); } return this; }).catch((err) => { const response = err.response; const body = response ? response.data : null; const error = this.handleRequestError(err, response, body, 'getRoomMeasure error'); if (callback) { callback(error); } return; }); return this; }; /** * https://dev.netatmo.com/dev/resources/technical/reference/thermostat/syncschedule * @param options * @param callback * @returns {*} */ netatmo.prototype.setSyncSchedule = function (options, callback) { // Wait until authenticated. if (!access_token) { return this.on('authenticated', function () { 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 params = new URLSearchParams(); params.append('access_token', access_token); params.append('device_id', options.device_id); params.append('module_id', options.module_id); params.append('zones', options.zones); params.append('timetable', options.timetable); axios.post(url, params, { headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, }).then((response) => { const body = response.data; this.emit('set-syncschedule', null, body.status); if (callback) { return callback(null, body.status); } return this; }).catch((err) => { const response = err.response; const body = response ? response.data : null; return this.handleRequestError(err, response, body, 'setSyncSchedule error'); }); return this; }; /** * https://dev.netatmo.com/dev/resources/technical/reference/thermostat/setthermpoint * @param options * @param callback * @returns {*} */ netatmo.prototype.setThermpoint = function (options, callback) { // Wait until authenticated. if (!access_token) { return this.on('authenticated', function () { 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 params = new URLSearchParams(); params.append('access_token', access_token); params.append('device_id', options.device_id); params.append('module_id', options.module_id); params.append('setpoint_mode', options.setpoint_mode); if (options) { if (options.setpoint_endtime) { params.append('setpoint_endtime', options.setpoint_endtime); } if (options.setpoint_temp) { params.append('setpoint_temp', options.setpoint_temp); } } axios.post(url, params, { headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, }).then((response) => { const body = response.data; this.emit('get-thermostatsdata', null, body.status); if (callback) { return callback(null, body.status); } return this; }).catch((err) => { const response = err.response; const body = response ? response.data : null; return this.handleRequestError(err, response, body, 'setThermpoint error'); }); return this; }; /** * https://dev.netatmo.com/dev/resources/technical/reference/welcome/gethomedata * @param options * @param callback * @returns {*} */ netatmo.prototype.getHomeData = function (options, callback) { // Wait until authenticated. if (!access_token) { return this.on('authenticated', function () { this.getHomeData(options, callback); }); } const url = util.format('%s/api/gethomedata', BASE_URL); const params = new URLSearchParams(); params.append('access_token', access_token); if (options != null && callback == null) { callback = options; options = null; } if (options) { if (options.home_id) { params.append('home_id', options.home_id); } if (options.size) { params.append('size', options.size); } } axios.post(url, params, { headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, }).then((response) => { const body = response.data; this.emit('get-homedata', null, body.body); if (callback) { return callback(null, body.body); } return this; }).catch((err) => { const response = err.response; const body = response ? response.data : null; return this.handleRequestError(err, response, body, 'getHomeData error'); }); return this; }; /** * https://dev.netatmo.com/resources/technical/reference/energy/homesdata * @param options * @param callback * @returns {*} */ netatmo.prototype.getHomesData = function (options, callback) { // Wait until authenticated. if (!access_token) { return this.on('authenticated', function () { this.getHomesData(options, callback); }); } const url = util.format('%s/api/homesdata', BASE_URL); const params = new URLSearchParams(); params.append('access_token', access_token); if (options != null && callback == null) { callback = options; options = null; } if (options) { if (options.home_id) { params.append('home_id', options.home_id); } if (options.gateway_types) { params.append('gateway_types', options.gateway_types); } } axios.post(url, params, { headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, }).then((response) => { const body = response.data; this.emit('get-homesdata', null, body.body); if (callback) { return callback(null, body.body); } return this; }).catch((err) => { const response = err.response; const body = response ? response.data : null; return this.handleRequestError(err, response, body, 'getHomesData error'); }); return this; }; /** * https://dev.netatmo.com/resources/technical/reference/energy/homestatus * @param options * @param callback * @returns {*} */ netatmo.prototype.getHomeStatus = function (options, callback) { // Wait until authenticated. if (!access_token) { return this.on('authenticated', function () { this.getHomeStatus(options, callback); }); } if (!options.home_id) { this.emit('error', new Error('getHomeStatus \'home_id\' not set.')); return this; } const url = util.format('%s/api/homestatus', BASE_URL); const params = new URLSearchParams(); params.append('access_token', access_token); if (options != null && callback == null) { callback = options; options = null; } if (options) { if (options.home_id) { params.append('home_id', options.home_id); } if (options.device_types) { params.append('device_types', options.device_types); } } axios.post(url, params, { headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, }).then((response) => { const body = response.data; this.emit('get-homestatus', null, body.body); if (callback) { return callback(null, body.body); } return this; }).catch((err) => { const response = err.response; const body = response ? response.data : null; return this.handleRequestError(err, response, body, 'getHomeStatus error'); }); return this; }; /** * https://dev.netatmo.com/dev/resources/technical/reference/welcome/getnextevents * @param options * @param callback * @returns {*} */ netatmo.prototype.getNextEvents = function (options, callback) { // Wait until authenticated. if (!access_token) { return this.on('authenticated', function () { 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 params = new URLSearchParams(); params.append('access_token', access_token); params.append('home_id', options.home_id); params.append('event_id', options.event_id); if (options.size) { params.append('size', options.size); } axios.post(url, params, { headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, }).then((response) => { const body = response.data; this.emit('get-nextevents', null, body.body); if (callback) { return callback(null, body.body); } return this; }).catch((err) => { const response = err.response; const body = response ? response.data : null; return this.handleRequestError(err, response, body, 'getNextEvents error'); }); return this; }; /** * https://dev.netatmo.com/dev/resources/technical/reference/welcome/getlasteventof * @param options * @param callback * @returns {*} */ netatmo.prototype.getLastEventOf = function (options, callback) { // Wait until authenticated. if (!access_token) { return this.on('authenticated', function () { 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 params = new URLSearchParams(); params.append('access_token', access_token); params.append('home_id', options.home_id); params.append('person_id', options.person_id); if (options.offset) { params.append('offset', options.offset); } axios.post(url, params, { headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, }).then((response) => { const body = response.data; this.emit('get-lasteventof', null, body.body); if (callback) { return callback(null, body.body); } return this; }).catch((err) => { const response = err.response; const body = response ? response.data : null; return this.handleRequestError(err, response, body, 'getLastEventOf error'); }); return this; }; /** * https://dev.netatmo.com/dev/resources/technical/reference/welcome/geteventsuntil * @param options * @param callback * @returns {*} */ netatmo.prototype.getEventsUntil = function (options, callback) { // Wait until authenticated. if (!access_token) { return this.on('authenticated', function () { 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 params = new URLSearchParams(); params.append('access_token', access_token); params.append('home_id', options.home_id); params.append('event_id', options.event_id); axios.post(url, params, { headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, }).then((response) => { const body = response.data; this.emit('get-eventsuntil', null, body.body); if (callback) { return callback(null, body.body); } return this; }).catch((err) => { const response = err.response; const body = response ? response.data : null; return this.handleRequestError(err, response, body, 'getEventsUntil error'); }); return this; }; /** * https://dev.netatmo.com/dev/resources/technical/reference/welcome/getcamerapicture * @param options * @param callback * @returns {*} */ netatmo.prototype.getCameraPicture = function (options, callback) { // Wait until authenticated. if (!access_token) { return this.on('authenticated', function () { 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 params = { access_token: access_token, image_id: options.image_id, key: options.key, }; axios.get(url, { params: params, responseType: 'arraybuffer', }).then((response) => { const body = Buffer.from(response.data); this.emit('get-camerapicture', null, body); if (callback) { return callback(null, body); } return this; }).catch((err) => { const response = err.response; const body = response ? response.data : null; return this.handleRequestError(err, response, body, 'getCameraPicture error'); }); return this; }; /** * https://dev.netatmo.com/dev/resources/technical/reference/healthyhomecoach/gethomecoachsdata * @param options * @param callback * @returns {*} */ netatmo.prototype.getHealthyHomeCoachData = function (options, callback) { if (options != null && callback == null) { callback = options; options = null; } // Wait until authenticated. if (this.waitUntilAuthenticated(callback, () => { this.getHealthyHomeCoachData(options, callback); })) { return this; } let url = util.format('%s/api/gethomecoachsdata?access_token=%s', BASE_URL, access_token); if (options != null) { url = util.format(url + '&device_id=%s', options.device_id); } axios.get(url).then((response) => { const body = response.data; const devices = body.body.devices; this.emit('get-healthhomecoaches-data', null, devices); if (callback) { return callback(null, devices); } return this; }).catch((err) => { const response = err.response; const body = response ? response.data : null; const error = this.handleRequestError(err, response, body, 'getHealthyHomeCoachData error'); if (callback) { callback(error); } return error; }); return this; }; /** * https://dev.netatmo.com/resources/technical/reference/weatherapi/getpublicdata * @param options * @param callback * @returns {*} */ netatmo.prototype.getPublicData = function (options, callback) { // Wait until authenticated. if (!access_token) { return this.on('authenticated', function () { this.getPublicData(options, callback); }); } if (!options) { this.emit('error', new Error('getPublicData \'options\' not set.')); return this; } if (!options.lat_ne) { this.emit('error', new Error('getPublicData \'lat_ne\' not set.')); return this; } if (!options.lon_ne) { this.emit('error', new Error('getPublicData \'lon_ne\' not set.')); return this; } if (!options.lat_sw) { this.emit('error', new Error('getPublicData \'lat_sw\' not set.')); return this; } if (!options.lon_sw) { this.emit('error', new Error('getPublicData \'lat_sw\' not set.')); return this; } if (util.isArray(options.required_data)) { options.required_data = options.required_data.join(','); } // Remove any spaces from the type list if there is any. options.required_data = options.required_data.replace(/\s/g, '').toLowerCase(); const url = util.format('%s/api/getpublicdata', BASE_URL); const params = new URLSearchParams(); params.append('access_token', access_token); params.append('lat_ne', options.lat_ne); params.append('lon_ne', options.lon_ne); params.append('lat_sw', options.lat_sw); params.append('lon_sw', options.lon_sw); params.append('required_data', options.required_data); params.append('filter', options.filter); axios.post(url, params, { headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, }).then((response) => { const body = response.data; const measure = body.body; this.emit('get-publicdata', null, measure); if (callback) { return callback(null, measure); } return this; }).catch((err) => { const response = err.response; const body = response ? response.data : null; const error = this.handleRequestError(err, response, body, 'getPublicData error'); if (callback) { callback(error); } return; }); return this; }; /** * https://dev.netatmo.com/resources/technical/reference/energy/homesdata * @param options * @param callback * @returns {*} */ netatmo.prototype.homesData = function (options, callback) { // Wait until authenticated. if (!access_token) { return this.on('authenticated', function () { this.homesData(options, callback); }); } if (options != null && callback == null) { callback = options; options = null; } const url = util.format('%s/api/homesdata', BASE_URL); const params = new URLSearchParams(); params.append('access_token', access_token); if (options) { if (options.home_id) { params.append('home_id', options.home_id); } if (options.gateway_types) { params.append('gateway_types', options.gateway_types); } } axios.post(url, params, { headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, }).then((response) => { const body = response.data; this.emit('get-homesdata', null, body.body); if (callback) { return callback(null, body.body); } return this; }).catch((err) => { const response = err.response; const body = response ? response.data : null; return this.handleRequestError(err, response, body, 'homesData error'); }); return this; }; /** * https://dev.netatmo.com/resources/technical/reference/energy/homesdata * @param options * @param callback * @returns {*} */ netatmo.prototype.homeStatus = function (options, callback) { // Wait until authenticated. if (!access_token) { return this.on('authenticated', function () { this.homeStatus(options, callback); }); } if (!options) { this.emit('error', new Error('homeStatus \'options\' not set.')); return this; } if (!options.home_id) { this.emit('error', new Error('homeStatus \'home_id\' not set.')); return this; } const url = util.format('%s/api/homestatus', BASE_URL); const params = new URLSearchParams(); params.append('access_token', access_token); params.append('home_id', options.home_id); if (options) { if (options.device_types) { params.append('device_types', options.device_types); } } axios.post(url, params, { headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, }).then((response) => { const body = response.data; this.emit('get-homestatus', null, body.body); if (callback) { return callback(null, body.body); } return this; }).catch((err) => { const response = err.response; const body = response ? response.data : null; return this.handleRequestError(err, response, body, 'homeStatus error'); }); return this; }; /** * https://dev.netatmo.com/resources/technical/reference/energy/setthermmode * @param options * @param callback * @returns {*} */ netatmo.prototype.setThermMode = function (options, callback) { // Wait until authenticated. if (!access_token) { return this.on('authenticated', function () { this.setThermMode(options, callback); }); } if (!options) { this.emit('error', new Error('setThermMode \'options\' not set.')); return this; } if (!options.home_id) { this.emit('error', new Error('setThermMode \'home_id\' not set.')); return this; } const url = util.format('%s/api/setthermmode', BASE_URL); const params = new URLSearchParams(); params.append('access_token', access_token); params.append('home_id', options.home_id); params.append('mode', options.mode); if (options) { if (options.endtime) { params.append('endtime', options.endtime); } } axios.post(url, params, { headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, }).then((response) => { const body = response.data; this.emit('get-setthermmode', null, body); if (callback) { return callback(null, body); } return this; }).catch((err) => { const response = err.response; const body = response ? response.data : null; return this.handleRequestError(err, response, body, 'setThermMode error'); }); return this; }; /** * https://dev.netatmo.com/resources/technical/reference/energy/setroomthermpoint * @param options * @param callback * @returns {*} */ netatmo.prototype.setRoomThermPoint = function (options, callback) { // Wait until authenticated. if (!access_token) { return this.on('authenticated', function () { this.setRoomThermPoint(options, callback); }); } if (!options) { this.emit('error', new Error('setRoomThermPoint \'options\' not set.')); return this; } if (!options.home_id) { this.emit('error', new Error('setRoomThermPoint \'home_id\' not set.')); return this; } const url = util.format('%s/api/setroomthermpoint', BASE_URL); const params = new URLSearchParams(); params.append('access_token', access_token); params.append('home_id', options.home_id); params.append('room_id', options.room_id); params.append('mode', options.mode); if (options) { if (options.temp) { params.append('temp', options.temp); } if (options.endtime) { params.append('endtime', options.endtime); } } axios.post(url, params, { headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, }).then((response) => { const body = response.data; this.emit('get-setroomthermpoint', null, body); if (callback) { return callback(null, body); } return this; }).catch((err) => { const response = err.response; const body = response ? response.data : null; return this.handleRequestError(err, response, body, 'setRoomThermPoint error'); }); return this; }; /** * https://dev.netatmo.com/apidocumentation/security#setpersonsaway * @param options * @param callback * @returns {*} */ netatmo.prototype.setPersonAway = function (options, callback) { // Wait until authenticated. if (!access_token) { return this.on('authenticated', function () { this.setPersonAway(options, callback); }); } if (!options) { this.emit('error', new Error('setPersonAway \'options\' not set.')); return this; } if (!options.home_id) { this.emit('error', new Error('setPersonAway \'home_id\' not set.')); return this; } const url = util.format('%s/api/setpersonsaway', BASE_URL); const params = new URLSearchParams(); params.append('access_token', access_token); params.append('home_id', options.home_id); if (options.person_id) { params.append('person_id', options.person_id); } axios.post(url, params, { headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, }).then((response) => { const body = response.data; this.emit('set-personsaway', null, body); if (callback) { return callback(null, body); } return this; }).catch((err) => { const response = err.response; const body = response ? response.data : null; return this.handleRequestError(err, response, body, 'setPersonAway error'); }); return this; }; module.exports = netatmo;