UNPKG

homespun

Version:

This is the root of the homespun family of repositories: you run a server in your home that collects sensor readings and uploads them to the management cloud of your choice. At present, upload to only one cloud is available: [numerous](http://numerousapp.

276 lines (235 loc) 12.1 kB
var cadence = require('cadence/redux') , path = require('path') , querystring = require('querystring') , underscore = require('underscore') , util = require('util') , Driver = require(path.join(__dirname, 'prototype-driver.js')) require('cadence/loops') // jscs:disable requireMultipleVarDecl var Netatmo = function (config, services) { Driver.call(this, config, services) this.timestamps = {} } // jscs:enable requireMultipleVarDecl util.inherits(Netatmo, Driver); /* cf., https://dev.netatmo.com/doc/methods/devicelist */ Netatmo.prototype.initialize = cadence(function (async) { var outer = async(function () { var inner, timestamp if (this.stopP) return [ outer ] timestamp = 0 inner = async(function () { if ((!this.config.oauth2) || (!this.config.oauth2.access_token)) { this.props.status = 'configuration' return [ inner, 15 ] } this.props.status = 'network' this.ua.fetch( { method : 'GET' , url : this.config.server + '/api/devicelist?' + querystring.stringify({ access_token : this.config.oauth2.access_token }) }, async()) }, function (body, response) { var devices if ((!!body) && (!!body.error)) { this.logger.error('initialize', { event : 'fetch', body : body }) this.props.status = 'error' if (response.statusCode >= 500) return [ inner, 90 ] async(function () { this.refreshToken(async()) }, function () { return [ inner, 0 ] }) } if ((!response.okay) || (!body) || (body.status !== 'ok')) { this.logger.error('initialize' , { event : 'fetch', headers : response.headers, body : body }) this.props.status = body.status || ('HTTP error ' + response.statusCode) return [ inner, (response.statusCode < 500) ? 300 : 90 ] } this.props.status = body.status underscore.keys(this.sensors).forEach(function (sensorID) { this.sensors[sensorID].seenP = false }.bind(this)) devices = {} underscore.union(body.body.devices, body.body.modules).forEach(function (device) { if ({ NAMain : true // base station , NAModule1 : true // outdoor module , NAModule2 : true // wind gauge module , NAModule3 : true // rain gauge module , NAModule4 : true // indoor module }[device.type]) devices[device._id] = device }) async.forEach(function (netatmoID) { var capabilities, g, sensor , device = devices[netatmoID] , k2s = function (key) { capabilities.fields.push(this.sensorType(key)) return key }.bind(this) , v2s = function (value, ranges) { var i for (i = 0; i < ranges.length; i++) { if (value >= ranges[i].t) return ranges[i].s } } sensor = this.sensors[device._id] || {} sensor.seenP = true sensor.name = device.station_name || devices[device.main_device].station_name if (!!device.module_name) sensor.name += ' - ' + device.module_name if (!this.timestamps[device._id]) { this.timestamps[device._id] = [ new Date().getTime() ] } sensor.lastReading = {} capabilities = { fields : [] } if (!!device.rf_status) { sensor.lastReading.signal = v2s(device.rf_status, [ { s : 0.25, t : 90 } , { s : 0.50, t : 80 } , { s : 0.75, t : 71 } ]) || 1.00 } else { sensor.lastReading.signal = v2s(device.wifi_status, [ { s : 0.25, t : 86 } , { s : 0.50, t : 71 } ]) || 1.00 } k2s('signal') g = { NAModule1 : [ { s : 1.0, t : 5500 } , { s : 0.8, t : 5000 } , { s : 0.5, t : 4500 } , { s : 0.2, t : 4000 } ] , NAModule2 : [ { s : 1.0, t : 5590 } , { s : 0.8, t : 5180 } , { s : 0.5, t : 4770 } , { s : 0.2, t : 4360 } ] , NAModule3 : [ { s : 1.0, t : 5500 } , { s : 0.8, t : 5000 } , { s : 0.5, t : 4500 } , { s : 0.2, t : 4000 } ] , NAModule4 : [ { s : 1.0, t : 5640 } , { s : 0.8, t : 5280 } , { s : 0.5, t : 5280 } , { s : 0.2, t : 5280 } ] }[device.type] if (!!g) { sensor.lastReading.battery = v2s(device.battery_vp, g) || 0.0 k2s('battery') } underscore.keys(device.dashboard_data).forEach(function (key) { var v = device.dashboard_data[key] , f = { CO2 : function () { // pars-per-million sensor.lastReading[k2s('co2')] = v } , GustAngle : function () { // angular-degrees sensor.lastReading[k2s('gustheading')] = v } , GustStrength : function () { // kilometers/hour sensor.lastReading[k2s('gustvelocity')] = v } , Humidity : function () { // RH-% sensor.lastReading[k2s('humidity')] = v / 100 } , Noise : function () { // decibels sensor.lastReading[k2s('noise')] = v } , Pressure : function () { // millibars sensor.lastReading[k2s('pressure')] = v } , Rain : function () { // millimeters sensor.lastReading[k2s('rainfall')] = v } , Temperature : function () { // degrees-celcius sensor.lastReading[k2s('temperature')] = v } , WindAngle : function () { // angular-degrees sensor.lastReading[k2s('windheading')] = v } , WindStrength : function () { // kilometers/hour sensor.lastReading[k2s('windvelocity')] = v } , time_utc : function () { // seconds since epoch if (v <= 0) v = this.timestamps[device._id][0] / 1000 this.timestamps[device._id][1] = v * 1000 if (timestamp < v) timestamp = v } }[key] if (!!f) f.bind(this)() }.bind(this)) if (this.timestamps[device._id][0] >= this.timestamps[device._id][1]) return this.timestamps[device._id].shift() async(function () { if (!!this.sensors[device._id]) return this.register(this, sensor.name, device._id, capabilities, async()) }, function (sensorID) { if (sensorID === false) return if (!!sensorID) { this.sensors[device._id] = underscore.extend(sensor, { sensorID : sensorID }) } this.upsync(this, sensor.sensorID, sensor.lastReading, async()) }) })(underscore.keys(devices)) }, function () { async.forEach(function (sensorID) { if (!!this.sensors[sensorID].seenP) return async(function () { this.unregister(this, this.sensors[sensorID].sensorID, async()) }, function () { delete(this.sensors[sensorID]) }) })(underscore.keys(this.sensors)) }, function () { var delta if (timestamp > 0) { delta = timestamp - (new Date().getTime() / 1000) if (delta > 0) delta = 0 delta += 15 * 60 } return [ inner, delta > 0 ? delta : ((10 * 60) + 5) ] })() }, function (secs) { if (this.stopP) return [ outer ] this.props.status = 'idle' if (isNaN(secs)) secs = (10 * 60 + 5) setTimeout(async(), secs * 1000) })() }) Netatmo.prototype.finalize = cadence(function (async) {/* jshint unused: false */ this.props.status = 'finishing' this.stopP = true }) /* cf., https://dev.netatmo.com/doc/authentication/refreshtoken */ Netatmo.prototype.refreshToken = cadence(function (async) { async(function () { this.props.status = 'refresh' this.ua.fetch( { method : 'POST' , url : this.config.server + '/oauth2/token' , headers : { 'content-type' : 'application/x-www-form-urlencoded;charset=UTF-8' } , payload : new Buffer(querystring.stringify( { grant_type : 'refresh_token' , refresh_token : this.config.oauth2.refresh_token , client_id : this.config.clientID , client_secret : this.config.secret })) }, async()) }, function (body, response) { if ((response.okay) && (!!body)) underscore.extend(this.config.oauth2, body) else { this.logger.error('refreshToken' , { event : 'fetch', headers : response.headers, body : body }) this.props.status = body.status || ('HTTP error ' + response.statusCode) this.stopP = true delete(this.config.oauth2) } this.persist(this.config, async()) }) }) // TBD: add methods for /authorize, /token, and comparison to existing user account... module.exports = Netatmo