diff --git a/platforms/Netatmo.js b/platforms/Netatmo.js index fe532bd..ca10f10 100644 --- a/platforms/Netatmo.js +++ b/platforms/Netatmo.js @@ -8,6 +8,7 @@ // { // "platform": "Netatmo", // "name": "Netatmo Weather", +// "ttl": 10, // "auth": { // "client_id": "", // "client_secret": "", @@ -19,32 +20,99 @@ // // The default code for all HomeBridge accessories is 031-45-154. -var types = require("hap-nodejs/accessories/types.js"); +var DEFAULT_CACHE_TTL = 10; // 10 seconds caching - use config["ttl"] to override -////////////////////////////////////////////////////////////////////////////// -// DECLARE SOME UUIDS WHICH SHOUL BE IN HAP-NODEJS TYPES LIB, BUT ARE NOT YET -// REMOVE WHEN HAP LIB IS UPDATED!! -////////////////////////////////////////////////////////////////////////////// -var stPre = "000000"; -var stPost = "-0000-1000-8000-0026BB765291"; +// CUSTOM SERVICE AND CHARACTERISTIC IDS +var ATMOSPHERIC_PRESSURE_STYPE_ID = "B77831FD-D66A-46A4-B66D-FD7EE8DFE3CE"; +var ATMOSPHERIC_PRESSURE_CTYPE_ID = "28FDA6BC-9C2A-4DEA-AAFD-B49DB6D155AB"; +var NOISE_LEVEL_STYPE_ID = "8C85FD40-EB20-45EE-86C5-BCADC773E580"; +var NOISE_LEVEL_CTYPE_ID = "2CD7B6FD-419A-4740-8995-E3BFE43735AB"; -types.BATTERY_SERVICE_STYPE = stPre + "96" + stPost; -types.AIR_QUALITY_SENSOR_STYPE = stPre + "8D" + stPost; -types.CARBON_DIOXIDE_SENSOR_STYPE = stPre + "97" + stPost; +var Service; +try { + Service = require("hap-nodejs").Service; +} catch(err) { + Service = require("HAP-NodeJS").Service; +} -types.AIR_PARTICULATE_DENISITY_CTYPE = stPre + "64" + stPost; -types.CARBON_DIOXIDE_DETECTED_CTYPE = stPre + "92" + stPost; -types.CARBON_DIOXIDE_LEVEL_CTYPE = stPre + "93" + stPost; -types.AIR_QUALITY_CTYPE = stPre + "95" + stPost; -////////////////////////////////////////////////////////////////////////////// +var Characteristic; +try { + Characteristic = require("hap-nodejs").Characteristic; +} catch(err) { + Characteristic = require("HAP-NodeJS").Characteristic; +} var netatmo = require("netatmo"); var NodeCache = require("node-cache"); +var inherits = require('util').inherits; -function NetAtmoRepository(log, api) { +Characteristic.AtmosphericPressureLevel = function() { + Characteristic.call(this, 'Atmospheric Pressure', ATMOSPHERIC_PRESSURE_CTYPE_ID); + this.setProps({ + format: Characteristic.Formats.UINT8, + unit: "mbar", + minValue: 800, + maxValue: 1200, + minStep: 1, + perms: [ + Characteristic.Perms.READ, + Characteristic.Perms.NOTIFY + ] + }); + this.value = this.getDefaultValue(); +}; +inherits(Characteristic.AtmosphericPressureLevel, Characteristic); +Characteristic.NoiseLevel = function() { + Characteristic.call(this, 'Noise Level', NOISE_LEVEL_CTYPE_ID); + this.setProps({ + format: Characteristic.Formats.UINT8, + unit: "dB", + minValue: 0, + maxValue: 200, + minStep: 1, + perms: [ + Characteristic.Perms.READ, + Characteristic.Perms.NOTIFY + ] + }); + this.value = this.getDefaultValue(); +}; +inherits(Characteristic.NoiseLevel, Characteristic); + +Service.AtmosphericPressureSensor = function(displayName, subtype) { + Service.call(this, displayName, ATMOSPHERIC_PRESSURE_STYPE_ID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.AtmosphericPressureLevel); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.StatusActive); + this.addOptionalCharacteristic(Characteristic.StatusFault); + this.addOptionalCharacteristic(Characteristic.StatusLowBattery); + this.addOptionalCharacteristic(Characteristic.StatusTampered); + this.addOptionalCharacteristic(Characteristic.Name); +}; +inherits(Service.AtmosphericPressureSensor, Service); + +Service.NoiseLevelSensor = function(displayName, subtype) { + Service.call(this, displayName, NOISE_LEVEL_STYPE_ID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.NoiseLevel); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.StatusActive); + this.addOptionalCharacteristic(Characteristic.StatusFault); + this.addOptionalCharacteristic(Characteristic.StatusLowBattery); + this.addOptionalCharacteristic(Characteristic.StatusTampered); + this.addOptionalCharacteristic(Characteristic.Name); +}; +inherits(Service.NoiseLevelSensor, Service); + +function NetAtmoRepository(log, api, ttl) { this.api = api; this.log = log; - this.cache = new NodeCache(); + this.cache = new NodeCache( { stdTTL: ttl } ); } NetAtmoRepository.prototype = { @@ -71,7 +139,7 @@ NetAtmoRepository.prototype = { } } - that.cache.set( "datasource", datasource, 20 ); + that.cache.set( "datasource", datasource ); callback(datasource); }); }, @@ -92,7 +160,8 @@ NetAtmoRepository.prototype = { function NetatmoPlatform(log, config) { this.log = log; var api = new netatmo(config["auth"]); - this.repository = new NetAtmoRepository(this.log, api); + var ttl = typeof config["ttl"] !== 'undefined' ? config["ttl"] : DEFAULT_CACHE_TTL; + this.repository = new NetAtmoRepository(this.log, api, ttl); api.on("error", function(error) { this.log('ERROR - Netatmo: ' + error); }); @@ -124,7 +193,7 @@ function NetatmoAccessory(log, repository, device) { this.deviceId = device._id; this.name = device.module_name this.serial = device._id; - + this.firmware = device.firmware; this.model = device.type; this.serviceTypes = device.data_type; if (device.battery_vp) { @@ -141,243 +210,169 @@ NetatmoAccessory.prototype = { }); }, - getCurrentTemperature: function(callback) { + identify: function(callback) { + this.log("Identify requested!"); + callback(); // success + }, + + currentTemperature: function (callback) { this.getData(function(deviceData) { - callback(deviceData.dashboard_data.Temperature); - }); - }, +/* + if (error) { + callback(error); + } else { +*/ + callback(null, deviceData.dashboard_data.Temperature); + }.bind(this)); + }, - getCurrentHumidity: function(callback) { + currentRelativeHumidity: function(callback) { this.getData(function(deviceData) { - callback(deviceData.dashboard_data.Humidity); - }); + callback(null, deviceData.dashboard_data.Humidity); + }.bind(this)); }, - getAirQuality: function(callback) { + carbonDioxideDetected: function(callback) { + var that = this; + that.log ("getting CO2" + Characteristic.CarbonDioxideDetected.CO2_LEVELS_ABNORMAL); + this.getData(function(deviceData) { - var level = deviceData.dashboard_data.CO2; - var quality = 0; - if (level > 2000) quality = 5; - else if (level > 1500) quality = 4; - else if (level > 1000) quality = 3; - else if (level > 500) quality = 2; - else if (level > 250) quality = 1; - callback(quality); - }); + var result = (deviceData.dashboard_data.CO2 > 1000 ? Characteristic.CarbonDioxideDetected.CO2_LEVELS_ABNORMAL : Characteristic.CarbonDioxideDetected.CO2_LEVELS_NORMAL); + callback(null, result); + }.bind(this)); }, - getCurrentCO2Level: function(callback) { - this.log("fetching co2"); + + carbonDioxideLevel: function(callback) { this.getData(function(deviceData) { - callback(deviceData.dashboard_data.CO2); - }); + callback(null, deviceData.dashboard_data.CO2); + }.bind(this)); }, - informationCharacteristics: function() { - return [ - { - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of the accessory", - designedMaxLength: 255 - },{ - cType: types.MANUFACTURER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "Netatmo", - supportEvents: false, - supportBonjour: false, - manfDescription: "Manufacturer", - designedMaxLength: 255 - },{ - cType: types.MODEL_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.model, - supportEvents: false, - supportBonjour: false, - manfDescription: "Model", - designedMaxLength: 255 - },{ - cType: types.SERIAL_NUMBER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.serial, - supportEvents: false, - supportBonjour: false, - manfDescription: "SN", - designedMaxLength: 255 - },{ - cType: types.IDENTIFY_CTYPE, - onUpdate: null, - perms: ["pw"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Identify Accessory", - designedMaxLength: 1 - } - ] + airQuality: function(callback) { + this.getData(function(deviceData) { + var level = deviceData.dashboard_data.CO2; + var quality = Characteristic.AirQuality.UNKNOWN; + if (level > 2000) quality = Characteristic.AirQuality.POOR; + else if (level > 1500) quality = Characteristic.AirQuality.INFERIOR; + else if (level > 1000) quality = Characteristic.AirQuality.FAIR; + else if (level > 500) quality = Characteristic.AirQuality.GOOD; + else if (level > 250) quality = Characteristic.AirQuality.EXCELLENT; + callback(null, quality); + }.bind(this)); }, - humidityCharacteristics: function(that) { - var cTypes = [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name +" Humidity", - supportEvents: true, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - },{ - cType: types.CURRENT_RELATIVE_HUMIDITY_CTYPE, - onRead: function(callback) { that.getCurrentHumidity(callback); }, - onUpdate: null, - perms: ["pr","ev"], - format: "int", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Humidity" - }]; - return cTypes; + batteryLevel: function(callback) { + this.getData(function(deviceData) { + var charge = deviceData.battery_vp; + var level = charge < 3000 ? 0 : (charge - 3000)/30; + callback(null, level); + }.bind(this)); }, - temperatureCharacteristics: function(that) { - var cTypes = [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name + " Temperature", - supportEvents: true, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - },{ - cType: types.CURRENT_TEMPERATURE_CTYPE, - onRead: function(callback) { that.getCurrentTemperature(callback); }, - onUpdate: null, - perms: ["pr","ev"], - format: "float", - initialValue: 0.0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Current Temperature", - unit: "celsius" - }]; - return cTypes; + statusLowBattery: function(callback) { + this.getData(function(deviceData) { + var charge = deviceData.battery_vp; + var level = charge < 4600 ? Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; + callback(null, level); + }.bind(this)); }, - co2Characteristics: function(that) { - var cTypes = [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name + "Carbon Dioxide Level", - supportEvents: true, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - },{ - cType: types.CARBON_DIOXIDE_DETECTED_CTYPE, - //onRead: function(callback) { that.getCurrentTemperature(callback); }, - onRead: function(callback) { callback(0); }, - onUpdate: null, - perms: ["pr","ev"], - format: "uint8", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "CO2 detected" - },{ - cType: types.CARBON_DIOXIDE_LEVEL_CTYPE, - onRead: function(callback) { that.getCurrentCO2Level(callback); }, - onUpdate: null, - perms: ["pr","ev"], - format: "float", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "CO2 level " - }]; - return cTypes; + atmosphericPressure: function(callback) { + this.getData(function(deviceData) { + callback(null, deviceData.dashboard_data.Pressure); + }.bind(this)); }, - airQualityCharacteristics: function(that) { - var cTypes = [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name + " Air Quality", - supportEvents: true, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - },{ - cType: types.AIR_QUALITY_CTYPE, - onRead: function(callback) { that.getAirQuality(callback); }, - onUpdate: null, - perms: ["pr","ev"], - format: "float", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Current Air Quality", - }]; - return cTypes; + noiseLevel: function(callback) { + this.getData(function(deviceData) { + callback(null, deviceData.dashboard_data.Noise); + }.bind(this)); }, - getServices: function() { var that = this; - var services = [{ - sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: this.informationCharacteristics(), - }]; + var services = []; + + this.log("creating services for " + this.name) + + // INFORMATION /////////////////////////////////////////////////// + + var informationService = new Service.AccessoryInformation(); + var firmwareCharacteristic = informationService.getCharacteristic(Characteristic.FirmwareRevision) + || informationService.addCharacteristic(Characteristic.FirmwareRevision); + services.push( informationService ); + + informationService + .setCharacteristic(Characteristic.Manufacturer, "Netatmo") + .setCharacteristic(Characteristic.Model, this.model) + .setCharacteristic(Characteristic.Name, this.name) + .setCharacteristic(Characteristic.SerialNumber, this.serial) + .setCharacteristic(Characteristic.FirmwareRevision, this.firmware); // TEMPERATURE ////////////////////////////////////////////////// if (this.serviceTypes.indexOf("Temperature") > -1) { - var tempSensorSvc = { - sType: types.TEMPERATURE_SENSOR_STYPE, - characteristics: this.temperatureCharacteristics(that) - } - services.push(tempSensorSvc); - } - // HUMIDITY //////////////////////////////////////////////////// - if (this.serviceTypes.indexOf("Humidity") > -1) { - services.push({ - sType: types.HUMIDITY_SENSOR_STYPE, - characteristics: this.humidityCharacteristics(that) - }); - } - // CO2 SENSOR ///////////////////////////////////////////////// - if (this.serviceTypes.indexOf("CO2") > -1) { - services.push({ - sType: types.CARBON_DIOXIDE_SENSOR_STYPE, - characteristics: this.co2Characteristics(that) - }); - services.push({ - sType: types.AIR_QUALITY_SENSOR_STYPE, - characteristics: this.airQualityCharacteristics(that) - }); + var temperatureSensor = new Service.TemperatureSensor(this.name + " Temperature"); + services.push( temperatureSensor ); + temperatureSensor.getCharacteristic(Characteristic.CurrentTemperature) + .on('get', this.currentTemperature.bind(this)); } - // TODO: Pressure - // TODO: Noise - // TODO: Battery + // HUMIDITY //////////////////////////////////////////////////// + if (this.serviceTypes.indexOf("Humidity") > -1) { + var humiditySensor = new Service.HumiditySensor(this.name + " Humidity"); + services.push( humiditySensor ); + humiditySensor.getCharacteristic(Characteristic.CurrentRelativeHumidity) + .on('get', this.currentRelativeHumidity.bind(this)); + } + + + // CO2 SENSOR ///////////////////////////////////////////////// + if (this.serviceTypes.indexOf("CO2") > -1) { + var carbonDioxideSensor = new Service.CarbonDioxideSensor(this.name + " Carbon Dioxide"); + var carbonDioxideLevelCharacteristic = carbonDioxideSensor.getCharacteristic(Characteristic.CarbonDioxideLevel) + || carbonDioxideSensor.addCharacteristic(Characteristic.CarbonDioxideLevel); + + services.push( carbonDioxideSensor ); + carbonDioxideSensor.getCharacteristic(Characteristic.CarbonDioxideDetected) + .on('get', this.carbonDioxideDetected.bind(this)); + carbonDioxideLevelCharacteristic + .on('get', this.carbonDioxideLevel.bind(this)); + + var airQualitySensor = new Service.AirQualitySensor(this.name + " Air Quality"); + services.push( airQualitySensor ); + airQualitySensor.getCharacteristic(Characteristic.AirQuality) + .on('get', this.airQuality.bind(this)); + } + + // BATTERY SERVICE //////////////////////////////////////////// + + if (this.serviceTypes.indexOf("Battery") > -1) { + var batteryService = new Service.BatteryService(this.name + " Battery Level"); + services.push( batteryService ); + batteryService.getCharacteristic(Characteristic.BatteryLevel) + .on('get', this.batteryLevel.bind(this)); + batteryService.getCharacteristic(Characteristic.StatusLowBattery) + .on('get', this.statusLowBattery.bind(this)); + } + + // ATMOSPHERIC PRESSURE ///////////////////////////////////////////////////// + + if (this.serviceTypes.indexOf("Pressure") > -1) { + var atmosphericPressureSensor = new Service.AtmosphericPressureSensor(this.name + " Atmospheric Pressure"); + services.push( atmosphericPressureSensor ); + atmosphericPressureSensor.getCharacteristic(Characteristic.AtmosphericPressureLevel) + .on('get', this.atmosphericPressure.bind(this)); + } + + // NOISE LEVEL ////////////////////////////////////////////////////////////// + + if (this.serviceTypes.indexOf("Noise") > -1) { + var noiseLevelSensor = new Service.NoiseLevelSensor(this.name + " Noise Level"); + services.push( noiseLevelSensor ); + noiseLevelSensor.getCharacteristic(Characteristic.NoiseLevel) + .on('get', this.noiseLevel.bind(this)); + } + // TODO: Check Elgato Eve Characteristics (map min, max, time series, etc.)! return services;