diff --git a/README.md b/README.md index 069f590..0366410 100755 --- a/README.md +++ b/README.md @@ -1,40 +1,57 @@ -# homebridge-http-temperature-humidity +# homebridge-http-humidity -Supports https devices on HomeBridge Platform +Supports http/https devices on HomeBridge Platform. +This version only supports humidity sensors returning a JSON with the data or the raw data. + +This plug-in acts as an interface between a web endpoint and homebridge only. You will still need some dedicated hardware to expose the web endpoints with the humidity information. In my case, I used an Arduino board with Wifi capabilities. # Installation -1. Install homebridge using: npm install -g homebridge -2. Install this plugin using: npm install -g homebridge-httptemperaturehumidity -3. Update your configuration file. See sample-config.json in this repository for a sample. +1. Install homebridge using: `npm install -g homebridge` +2. Install this plugin using: `npm install -g homebridge-http-humidity` +3. Update your configuration file. See sample-config.json in this repository for a sample. # Configuration +The available fields in the config.json file are: + - `url` [Mandatory] Endpoint URL. + - `name` [Mandatory] Accessory name. + - `http_method` [Optional] HTTP method used to get the humidity (Default: GET) + - `manufacturer` [Optional] Additional information for the accessory. + - `model` [Optional] Additional information for the accessory. + - `serial` [Optional] Additional information for the accessory. + - `field_name` [Optional] Field that will be used from the JSON response of the endpoint. Alternatively, if the `field_name` contains an empty string (`"field_name": ""`), the expected response is directly the current humidity value (Default: humidity). + - `timeout` [Optional] Waiting time for the endpoint response before fail (Default: 5000ms). + - `auth` [Optional] JSON with `user` and `pass` fields used to authenticate the request into the device. + - `update_interval` [Optional] If not zero, the field defines the polling period in milliseconds for the sensor state (Default is 120000ms). When the value is zero, the state is only updated when homebridge requests the current value. + - `debug` [Optional] Enable/disable debug logs (Default: false). + -Configuration sample file: +Example: ``` -"accessories": [ - "accessories": [ - { - "accessory": "HttpTemphum", - "name": "Living Room Weather", - "url": "http://192.168.1.210/weather", - "sendimmediately": "", - "http_method": "GET" - } - ] + "accessories": [ + { + "accessory": "HttpHumidity", + "name": "Outside Humidity", + "url": "http://192.168.1.210/humidity?format=json", + "http_method": "GET", + "field_name": "humidity", + "auth": { + "user": "test", + "pass": "1234" + } + } + ] ``` - -The /weather endpoint will return a json looking like this +The defined endpoint will return a json looking like this: ``` { - "temperature": 25.8, "humidity": 38 } ``` -This plugin acts as an interface between a web endpoint and homebridge only. You will still need some dedicated hardware to expose the web endpoints with the temperature and humidity information. In my case, I used a simple NodeMCU board and a DHT11 (or DHT22). \ No newline at end of file +This plugin acts as an interface between a web endpoint and homebridge only. You will still need some dedicated hardware to expose the web endpoints with the relative humidity information. In my case, I used an Arduino board with Wifi capabilities. diff --git a/index.js b/index.js index cf503b6..2f18163 100755 --- a/index.js +++ b/index.js @@ -1,96 +1,126 @@ var Service, Characteristic; -var request = require('sync-request'); +var request = require('request'); -var temperatureService; -var humidityService; -var url -var humidity = 0; -var temperature = 0; +const DEF_TIMEOUT = 5000, + DEF_INTERVAL = 120000; //120s module.exports = function (homebridge) { - Service = homebridge.hap.Service; - Characteristic = homebridge.hap.Characteristic; - homebridge.registerAccessory("homebridge-httptemperaturehumidity", "HttpTemphum", HttpTemphum); + Service = homebridge.hap.Service; + Characteristic = homebridge.hap.Characteristic; + homebridge.registerAccessory("homebridge-http-humidity", "HttpHumidity", HttpHumidity); } -function HttpTemphum(log, config) { - this.log = log; - - // url info - this.url = config["url"]; - this.http_method = config["http_method"] || "GET"; - this.sendimmediately = config["sendimmediately"] || ""; - this.name = config["name"]; - this.manufacturer = config["manufacturer"] || "Luca Manufacturer"; - this.model = config["model"] || "Luca Model"; - this.serial = config["serial"] || "Luca Serial"; +function HttpHumidity(log, config) { + this.log = log; + + this.url = config["url"]; + this.http_method = config["http_method"] || "GET"; + this.name = config["name"]; + this.manufacturer = config["manufacturer"] || "@metbosch manufacturer"; + this.model = config["model"] || "Model not available"; + this.serial = config["serial"] || "Non-defined serial"; + this.fieldName = config["field_name"] || "humidity"; + this.timeout = config["timeout"] || DEF_TIMEOUT; + this.auth = config["auth"]; + this.update_interval = Number( config["update_interval"] || DEF_INTERVAL ); + this.debug = config["debug"] || false; + + // Internal variables + this.last_value = null; + this.waiting_response = false; } -HttpTemphum.prototype = { - - httpRequest: function (url, body, method, username, password, sendimmediately, callback) { - request({ - url: url, - body: body, - method: method, - rejectUnauthorized: false - }, - function (error, response, body) { - callback(error, response, body) - }) - }, - - getStateHumidity: function(callback){ - callback(null, this.humidity); - }, - - getState: function (callback) { - var body; - - var res = request(this.http_method, this.url, {}); - if(res.statusCode > 400){ - this.log('HTTP power function failed'); - callback(error); - } else { - this.log('HTTP power function succeeded!'); - var info = JSON.parse(res.body); - - temperatureService.setCharacteristic(Characteristic.CurrentTemperature, info.temperature); - humidityService.setCharacteristic(Characteristic.CurrentRelativeHumidity, info.humidity); - - this.log(res.body); - this.log(info); - - this.temperature = info.temperature; - this.humidity = info.humidity; - - callback(null, this.temperature); - } - }, - - identify: function (callback) { - this.log("Identify requested!"); - callback(); // success - }, - - getServices: function () { - var informationService = new Service.AccessoryInformation(); - informationService - .setCharacteristic(Characteristic.Manufacturer, this.manufacturer) - .setCharacteristic(Characteristic.Model, this.model) - .setCharacteristic(Characteristic.SerialNumber, this.serial); - - temperatureService = new Service.TemperatureSensor(this.name); - temperatureService - .getCharacteristic(Characteristic.CurrentTemperature) - .on('get', this.getState.bind(this)); - - humidityService = new Service.HumiditySensor(this.name); - humidityService - .getCharacteristic(Characteristic.CurrentRelativeHumidity) - .on('get', this.getStateHumidity.bind(this)); - - return [informationService, temperatureService, humidityService]; - } +HttpHumidity.prototype = { + + logDebug: function (str) { + if (this.debug) { + this.log(str) + } + }, + + updateState: function () { + //Ensure previous call finished + if (this.waiting_response) { + this.logDebug('Avoid updateState as previous response does not arrived yet'); + return; + } + this.waiting_response = true; + this.last_value = new Promise((resolve, reject) => { + var ops = { + uri: this.url, + method: this.http_method, + timeout: this.timeout + }; + this.logDebug('Requesting humidity on "' + ops.uri + '", method ' + ops.method); + if (this.auth) { + ops.auth = { + user: this.auth.user, + pass: this.auth.pass + }; + } + request(ops, (error, res, body) => { + var value = null; + if (error) { + this.log('HTTP bad response (' + ops.uri + '): ' + error.message); + } else { + try { + value = this.fieldName === '' ? body : JSON.parse(body)[this.fieldName]; + value = Number(value); + if (value < 0 || value > 100 || isNaN(value)) { + throw new Error("Invalid value received"); + } + this.logDebug('HTTP successful response: ' + value); + } catch (parseErr) { + this.logDebug('Error processing received information: ' + parseErr.message); + error = parseErr; + } + } + if (!error) { + resolve(value); + } else { + reject(error); + } + this.waiting_response = false; + }); + }).then((value) => { + this.humidityService + .getCharacteristic(Characteristic.CurrentRelativeHumidity).updateValue(value, null); + return value; + }, (error) => { + //For now, only to avoid the NodeJS warning about uncatched rejected promises + return error; + }); + }, + + getState: function (callback) { + this.logDebug('Call to getState: waiting_response is "' + this.waiting_response + '"' ); + this.updateState(); //This sets the promise in last_value + this.last_value.then((value) => { + callback(null, value); + return value; + }, (error) => { + callback(error, null); + return error; + }); + }, + + getServices: function () { + this.informationService = new Service.AccessoryInformation(); + this.informationService + .setCharacteristic(Characteristic.Manufacturer, this.manufacturer) + .setCharacteristic(Characteristic.Model, this.model) + .setCharacteristic(Characteristic.SerialNumber, this.serial); + + this.humidityService = new Service.HumiditySensor(this.name); + this.humidityService + .getCharacteristic(Characteristic.CurrentRelativeHumidity) + .on('get', this.getState.bind(this)); + + if (this.update_interval > 0) { + this.timer = setInterval(this.updateState.bind(this), this.update_interval); + } + + return [this.informationService, this.humidityService]; + } }; diff --git a/package.json b/package.json index e0373ca..d6d8940 100755 --- a/package.json +++ b/package.json @@ -1,23 +1,25 @@ { - "name": "homebridge-httptemperaturehumidity", - "version": "0.0.14", + "name": "homebridge-http-humidity", + "version": "0.3.0", "description": "https plugin for homebridge", "license": "ISC", "keywords": [ - "homebridge-plugin" + "homebridge-plugin", + "http", + "humidity sensor" ], "engines": { "node": ">=0.12.0", "homebridge": ">=0.2.0" }, "author": { - "name": "Luca Critelli" + "name": "@metbosch" }, "repository": { "type": "git", - "url": "git://github.com/lucacri/homebridge-http-temperature-humidity.git" + "url": "git://github.com/metbosch/homebridge-http-humidity.git" }, "dependencies": { - "sync-request": "^2.1.0" + "request": "^2.5.0" } } diff --git a/sample-config.json b/sample-config.json index f843306..ad6b3b3 100755 --- a/sample-config.json +++ b/sample-config.json @@ -5,16 +5,16 @@ "port": 51826, "pin": "031-45-156" }, - + "description": "The Onion!", "platforms": [], "accessories": [ { - "accessory": "HttpTemphum", - "name": "Living Room Weather", - "url": "http://192.168.1.210/weather", + "accessory": "HttpHumidity", + "name": "Outside Humidity", + "url": "http://192.168.1.210/humidity?format=json", "http_method": "GET" } ]