diff --git a/HISTORY.md b/HISTORY.md index 0337d8c..c8102fe 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,8 @@ ## version history +### 0.4.0 +- added wind-sensor (still testing) + ### 0.3.7 - changed polling mechanism to prevent deadlocks on netatmo API errors diff --git a/accessory/eveatmo-wind-accessory.js b/accessory/eveatmo-wind-accessory.js new file mode 100644 index 0000000..4f16203 --- /dev/null +++ b/accessory/eveatmo-wind-accessory.js @@ -0,0 +1,114 @@ +'use strict'; + +var homebridge; +var Characteristic; +var NetatmoAccessory; +var path = require('path'); + +module.exports = function(pHomebridge) { + if (pHomebridge && !homebridge) { + homebridge = pHomebridge; + NetatmoAccessory = require("../lib/netatmo-accessory")(homebridge); + Characteristic = homebridge.hap.Characteristic; + } + + class EveatmoWindAccessory extends NetatmoAccessory { + constructor(deviceData, netatmoDevice) { + var accessoryConfig = { + "id": deviceData._id, + "model": "Eve Wind", + "netatmoType": deviceData.type, + "firmware": deviceData.firmware, + "name": deviceData._name || "Eveatmo " + netatmoDevice.deviceType + " " + deviceData._id, + "hasBattery": (deviceData.battery_vp) ? true : false, + }; + + super(homebridge, accessoryConfig, netatmoDevice); + this.buildServices(accessoryConfig); + + this.windStrength = 0.0; + this.windAngle = 0; + + this.refreshData(function(err, data) {}); + } + + buildServices(accessoryConfig) { + var serviceDir = path.dirname(__dirname) + '/service'; + try { + var EveatmoWindService = require(serviceDir + '/eveatmo-wind')(homebridge); + var serviceWind = new EveatmoWindService(this); + serviceWind.isPrimaryService = true; + this.addService(serviceWind); + + if (accessoryConfig.hasBattery) { + var EveatmoBatteryService = require(serviceDir + '/eveatmo-battery')(homebridge); + var serviceBattery = new EveatmoBatteryService(this); + this.addService(serviceBattery); + } + + } catch (err) { + this.log.warn("Could not process service files for " + accessoryConfig.name); + this.log.warn(err); + this.log.warn(err.stack); + } + } + + notifyUpdate(deviceData) { + var accessoryData = this.extractAccessoryData(deviceData); + var weatherData = this.mapAccessoryDataToWeatherData(accessoryData); + this.applyWeatherData(weatherData); + } + + mapAccessoryDataToWeatherData(accessoryData) { + var result = {}; + var dashboardData = accessoryData.dashboard_data; + if (dashboardData) { + if (dashboardData.hasOwnProperty("WindStrength")) { + result.windStrength = dashboardData.WindStrength; + } + if (dashboardData.hasOwnProperty("WindAngle")) { + result.windAngle = dashboardData.WindAngle; + } + } + + result.batteryPercent = accessoryData.battery_percent; + if (!result.batteryPercent) { + result.batteryPercent = 100; + } + result.lowBattery = (result.batteryPercent <= 20) ? true : false; + + return result; + } + + applyWeatherData(weatherData) { + var dataChanged = false; + + if (weatherData.hasOwnProperty("windStrength") && this.windStrength != weatherData.windStrength) { + this.windStrength = weatherData.windStrength; + dataChanged = true; + } + if (weatherData.hasOwnProperty("windAngle") && this.windAngle != weatherData.windAngle) { + this.windAngle = weatherData.windAngle; + dataChanged = true; + } + + if (weatherData.batteryPercent && this.batteryPercent != weatherData.batteryPercent) { + this.batteryPercent = weatherData.batteryPercent; + dataChanged = true; + } + if (weatherData.lowBattery && this.lowBattery != weatherData.lowBattery) { + this.lowBattery = weatherData.lowBattery; + dataChanged = true; + } + + if (dataChanged) { + this.getServices().forEach( + function(svc) { + var call = svc.updateCharacteristics && svc.updateCharacteristics(); + } + ); + } + } + } + return EveatmoWindAccessory; +}; diff --git a/device/weatherstation-device.js b/device/weatherstation-device.js index 589b072..c2b99fc 100644 --- a/device/weatherstation-device.js +++ b/device/weatherstation-device.js @@ -6,6 +6,7 @@ var homebridge; var EveatmoRoomAccessory; var EveatmoWeatherAccessory; var EveatmoRainAccessory; +var EveatmoWindAccessory; module.exports = function(pHomebridge) { if (pHomebridge && !homebridge) { @@ -13,6 +14,7 @@ module.exports = function(pHomebridge) { EveatmoRoomAccessory = require("../accessory/eveatmo-room-accessory")(homebridge); EveatmoWeatherAccessory = require("../accessory/eveatmo-weather-accessory")(homebridge); EveatmoRainAccessory = require("../accessory/eveatmo-rain-accessory")(homebridge); + EveatmoWindAccessory = require("../accessory/eveatmo-wind-accessory")(homebridge); } class WeatherstationDeviceType extends NetatmoDevice { @@ -59,7 +61,9 @@ module.exports = function(pHomebridge) { return new EveatmoWeatherAccessory(deviceData, this); } else if(deviceData.type == 'NAModule3') { // Rain return new EveatmoRainAccessory(deviceData, this); - } + } else if(deviceData.type == 'NAModule2') { // Wind + return new EveatmoWindAccessory(deviceData, this); + } return false; } } diff --git a/mockapi_calls/getstationsdata-eveatmo.json b/mockapi_calls/getstationsdata-eveatmo.json index c95d7f2..57b3e26 100644 --- a/mockapi_calls/getstationsdata-eveatmo.json +++ b/mockapi_calls/getstationsdata-eveatmo.json @@ -32,6 +32,97 @@ "rf_status": 67, "firmware": 43 }, + { + "_id": "10:00:00:00:00:05", + "type": "NAModule2", + "last_message": 1469639523, + "last_seen": 1469639502, + "dashboard_data": { + "WindAngle": 188, + "WindStrength": 6, + "GustAngle": 143, + "GustStrength": 15, + "time_utc": 1469905065 + }, + "WindHistoric": [ + { + "WindStrength": "6", + "WindAngle": 200, + "time_utc": 1469901739 + }, + { + "WindStrength": "6", + "WindAngle": 217, + "time_utc": 1469902040 + }, + { + "WindStrength": "6", + "WindAngle": 200, + "time_utc": 1469902342 + }, + { + "WindStrength": "4", + "WindAngle": 178, + "time_utc": 1469902643 + }, + { + "WindStrength": "7", + "WindAngle": 197, + "time_utc": 1469902944 + }, + { + "WindStrength": "9", + "WindAngle": 182, + "time_utc": 1469903245 + }, + { + "WindStrength": "10", + "WindAngle": 191, + "time_utc": 1469903546 + }, + { + "WindStrength": "8", + "WindAngle": 193, + "time_utc": 1469903848 + }, + { + "WindStrength": "6", + "WindAngle": 194, + "time_utc": 1469904155 + }, + { + "WindStrength": "7", + "WindAngle": 213, + "time_utc": 1469904457 + }, + { + "WindStrength": "6", + "WindAngle": 192, + "time_utc": 1469904764 + }, + { + "WindStrength": "6", + "WindAngle": 188, + "time_utc": 1469905065 + } + ], + "date_max_wind_str": 1469903245, + "date_max_temp": 1469851393, + "date_min_temp": 1469881855, + "min_temp": -0.8, + "max_temp": 0, + "max_wind_angle": 225, + "max_wind_str": 22, + "data_type": [ + "Wind" + ], + "module_name": "Wind Gauge", + "last_setup": 1464467149, + "battery_vp": 5940, + "battery_percent": 97, + "rf_status": 75, + "firmware": 17 + }, { "_id": "03:00:00:01:23:45", "type": "NAModule4", diff --git a/package.json b/package.json index 2f566cc..57eb107 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homebridge-eveatmo", - "version": "0.3.7", + "version": "0.4.0", "description": "Homebridge plugin which adds a Netatmo weatherstation as HomeKit device and tries to act like Elgato Eve Room/Weather", "license": "ISC", "keywords": [ diff --git a/service/eveatmo-wind.js b/service/eveatmo-wind.js new file mode 100644 index 0000000..272138f --- /dev/null +++ b/service/eveatmo-wind.js @@ -0,0 +1,121 @@ +'use strict'; + +var homebridge; +var Characteristic; + +const WIND_MEASURE_STYPE_ID = "2AFB775E-79E5-4399-B3CD-398474CAE86C"; +const WIND_STRENGTH_CTYPE_ID = "49C8AE5A-A3A5-41AB-BF1F-12D5654F9F41"; +const WIND_ANGLE_CTYPE_ID = "46F1284C-1912-421B-82F5-EB75008B167E"; + +module.exports = function(pHomebridge) { + if (pHomebridge && !homebridge) { + homebridge = pHomebridge; + Characteristic = homebridge.hap.Characteristic; + } + + class WindStrengthCharacteristic extends Characteristic { + constructor(accessory) { + super('Wind Strength', WIND_STRENGTH_CTYPE_ID); + this.setProps({ + format: Characteristic.Formats.FLOAT, + unit: "km/h", + minValue: 0, + maxValue: 100, + minStep: 0.1, + perms: [ + Characteristic.Perms.READ, + Characteristic.Perms.NOTIFY + ] + }); + this.value = this.getDefaultValue(); + } + } + + class WindAngleCharacteristic extends Characteristic { + constructor(accessory) { + super('Wind Angle', WIND_ANGLE_CTYPE_ID); + this.setProps({ + format: Characteristic.Formats.STRING, + perms: [ + Characteristic.Perms.READ, + Characteristic.Perms.NOTIFY + ] + }); + this.value = this.getDefaultValue(); + } + } + + class WindService extends homebridge.hap.Service { + constructor(accessory) { + super(accessory.name + " Wind Sensor", WIND_MEASURE_STYPE_ID); + this.accessory = accessory; + + this.addCharacteristic(WindStrengthCharacteristic) + .on('get', this.getWindStrength.bind(this)) + .eventEnabled = true; + this.addCharacteristic(WindAngleCharacteristic) + .on('get', this.getWindAngle.bind(this)) + .eventEnabled = true; + + this.addOptionalCharacteristic(Characteristic.Name); + } + + updateCharacteristics() { + this.getCharacteristic(WindStrengthCharacteristic) + .updateValue(this.accessory.windStrength); + this.getCharacteristic(WindAngleCharacteristic) + .updateValue(this.transformDirectionDegToString()); + } + + getWindStrength(callback) { + this.accessory.refreshData(function(err,data) { + callback(err, this.accessory.windStrength); + }.bind(this)); + } + + getWindAngle(callback) { + this.accessory.refreshData(function(err,data) { + callback(err, this.transformDirectionDegToString()); + }.bind(this)); + } + + transformDirectionDegToString() { + var a = this.accessory.windAngle; + if (a >= 348.75 || a < 11.25) { + return 'N'; + } else if (a >= 11.25 && a < 33.75) { + return 'NNE'; + } else if (a >= 33.75 && a < 56.25) { + return 'NE'; + } else if (a >= 56.25 && a < 78.75) { + return 'ENE'; + } else if (a >= 78.75 && a < 101.25) { + return 'E'; + } else if (a >= 101.25 && a < 123.75) { + return 'ESE'; + } else if (a >= 123.75 && a < 146.25) { + return 'SE'; + } else if (a >= 146.25 && a < 168.75) { + return 'SSE'; + } else if (a >= 168.75 && a < 191.25) { + return 'S'; + } else if (a >= 191.25 && a < 213.75) { + return 'SSW'; + } else if (a >= 213.75 && a < 236.25) { + return 'SW'; + } else if (a >= 236.25 && a < 258.75) { + return 'WSW'; + } else if (a >= 258.75 && a < 281.25) { + return 'W'; + } else if (a >= 281.25 && a < 303.75) { + return 'WNW'; + } else if (a >= 303.75 && a < 326.25) { + return 'NW'; + } else if (a >= 326.25 && a < 348.75) { + return 'NNW'; + } + } + } + + return WindService; +}; \ No newline at end of file