mirror of
https://github.com/mtan93/homebridge.git
synced 2026-03-08 05:31:55 +00:00
Merge pull request #2 from nfarina/master
Update fork from nfarina/homebridge/master
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -13,4 +13,8 @@ npm-debug.log
|
|||||||
|
|
||||||
# HomeBridge
|
# HomeBridge
|
||||||
config.json
|
config.json
|
||||||
|
config.test.json
|
||||||
persist/
|
persist/
|
||||||
|
|
||||||
|
|
||||||
|
.AppleDouble
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
var types = require("HAP-NodeJS/accessories/types.js");
|
var types = require("hap-nodejs/accessories/types.js");
|
||||||
var AD2USB = require('ad2usb');
|
var AD2USB = require('ad2usb');
|
||||||
var CUSTOM_PANEL_LCD_TEXT_CTYPE = "A3E7B8F9-216E-42C1-A21C-97D4E3BE52C8";
|
var CUSTOM_PANEL_LCD_TEXT_CTYPE = "A3E7B8F9-216E-42C1-A21C-97D4E3BE52C8";
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
var types = require("HAP-NodeJS/accessories/types.js");
|
var types = require("hap-nodejs/accessories/types.js");
|
||||||
var carwings = require("carwingsjs");
|
var carwings = require("carwingsjs");
|
||||||
|
|
||||||
function CarwingsAccessory(log, config) {
|
function CarwingsAccessory(log, config) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
var types = require("HAP-NodeJS/accessories/types.js");
|
var types = require("hap-nodejs/accessories/types.js");
|
||||||
var elkington = require("elkington");
|
var elkington = require("elkington");
|
||||||
|
|
||||||
function ElkM1Accessory(log, config) {
|
function ElkM1Accessory(log, config) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
var Service = require("HAP-NodeJS").Service;
|
var Service = require("hap-nodejs").Service;
|
||||||
var Characteristic = require("HAP-NodeJS").Characteristic;
|
var Characteristic = require("hap-nodejs").Characteristic;
|
||||||
var chokidar = require("chokidar");
|
var chokidar = require("chokidar");
|
||||||
var debug = require("debug")("FileSensorAccessory");
|
var debug = require("debug")("FileSensorAccessory");
|
||||||
var crypto = require("crypto");
|
var crypto = require("crypto");
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
var Service = require("HAP-NodeJS").Service;
|
var Service = require("hap-nodejs").Service;
|
||||||
var Characteristic = require("HAP-NodeJS").Characteristic;
|
var Characteristic = require("hap-nodejs").Characteristic;
|
||||||
var SerialPort = require("serialport").SerialPort;
|
var SerialPort = require("serialport").SerialPort;
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
var types = require("HAP-NodeJS/accessories/types.js");
|
var types = require("hap-nodejs/accessories/types.js");
|
||||||
var request = require("request");
|
var request = require("request");
|
||||||
|
|
||||||
function HomeMatic(log, config) {
|
function HomeMatic(log, config) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
var types = require("HAP-NodeJS/accessories/types.js");
|
var types = require("hap-nodejs/accessories/types.js");
|
||||||
var request = require("request");
|
var request = require("request");
|
||||||
|
|
||||||
function HomeMaticThermo(log, config) {
|
function HomeMaticThermo(log, config) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
var types = require("HAP-NodeJS/accessories/types.js");
|
var types = require("hap-nodejs/accessories/types.js");
|
||||||
var Characteristic = require("HAP-NodeJS").Characteristic;
|
var Characteristic = require("hap-nodejs").Characteristic;
|
||||||
var request = require("request");
|
var request = require("request");
|
||||||
|
|
||||||
function HomeMaticWindow(log, config) {
|
function HomeMaticWindow(log, config) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
var Service = require("HAP-NodeJS").Service;
|
var Service = require("hap-nodejs").Service;
|
||||||
var Characteristic = require("HAP-NodeJS").Characteristic;
|
var Characteristic = require("hap-nodejs").Characteristic;
|
||||||
var request = require("request");
|
var request = require("request");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@@ -14,14 +14,21 @@ function HttpAccessory(log, config) {
|
|||||||
this.off_url = config["off_url"];
|
this.off_url = config["off_url"];
|
||||||
this.brightness_url = config["brightness_url"];
|
this.brightness_url = config["brightness_url"];
|
||||||
this.http_method = config["http_method"];
|
this.http_method = config["http_method"];
|
||||||
|
this.username = config["username"];
|
||||||
|
this.password = config["password"];
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpAccessory.prototype = {
|
HttpAccessory.prototype = {
|
||||||
|
|
||||||
httpRequest: function(url, method, callback) {
|
httpRequest: function(url, method, username, password, callback) {
|
||||||
request({
|
request({
|
||||||
url: url,
|
url: url,
|
||||||
method: method
|
method: method,
|
||||||
|
auth: {
|
||||||
|
user: username,
|
||||||
|
pass: password,
|
||||||
|
sendImmediately: false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
function (error, response, body) {
|
function (error, response, body) {
|
||||||
callback(error, response, body)
|
callback(error, response, body)
|
||||||
@@ -40,13 +47,17 @@ HttpAccessory.prototype = {
|
|||||||
this.log("Setting power state to off");
|
this.log("Setting power state to off");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.httpRequest(url, this.http_method, function(error, response, body) {
|
this.httpRequest(url, this.http_method, this.username, this.password, function(error, response, body) {
|
||||||
if (error) {
|
if (error) {
|
||||||
this.log('HTTP power function failed: %s', error.message);
|
this.log('HTTP power function failed: %s', error.message);
|
||||||
callback(error);
|
callback(error);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.log('HTTP power function succeeded!');
|
this.log('HTTP power function succeeded!');
|
||||||
|
this.log(response);
|
||||||
|
this.log(body);
|
||||||
|
this.log(this.username);
|
||||||
|
this.log(this.password);
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
@@ -57,7 +68,7 @@ HttpAccessory.prototype = {
|
|||||||
|
|
||||||
this.log("Setting brightness to %s", level);
|
this.log("Setting brightness to %s", level);
|
||||||
|
|
||||||
this.httpRequest(url, this.http_method, function(error, response, body) {
|
this.httpRequest(url, this.http_method, this.username, this.password, function(error, response, body) {
|
||||||
if (error) {
|
if (error) {
|
||||||
this.log('HTTP brightness function failed: %s', error);
|
this.log('HTTP brightness function failed: %s', error);
|
||||||
callback(error);
|
callback(error);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
var Service = require("HAP-NodeJS").Service;
|
var Service = require("hap-nodejs").Service;
|
||||||
var Characteristic = require("HAP-NodeJS").Characteristic;
|
var Characteristic = require("hap-nodejs").Characteristic;
|
||||||
var request = require("request");
|
var request = require("request");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
var Service = require("HAP-NodeJS").Service;
|
var Service = require("hap-nodejs").Service;
|
||||||
var Characteristic = require("HAP-NodeJS").Characteristic;
|
var Characteristic = require("hap-nodejs").Characteristic;
|
||||||
var request = require("request");
|
var request = require("request");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
var types = require("HAP-NodeJS/accessories/types.js");
|
var types = require("hap-nodejs/accessories/types.js");
|
||||||
var net = require('net');
|
var net = require('net');
|
||||||
var Color = require('color');
|
var Color = require('color');
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
var types = require("HAP-NodeJS/accessories/types.js");
|
var types = require("hap-nodejs/accessories/types.js");
|
||||||
var request = require("request");
|
var request = require("request");
|
||||||
|
|
||||||
// This seems to be the "id" of the official LiftMaster iOS app
|
// This seems to be the "id" of the official LiftMaster iOS app
|
||||||
@@ -89,7 +89,7 @@ LiftMasterAccessory.prototype = {
|
|||||||
for (var i=0; i<devices.length; i++) {
|
for (var i=0; i<devices.length; i++) {
|
||||||
var device = devices[i];
|
var device = devices[i];
|
||||||
|
|
||||||
if (device["MyQDeviceTypeName"] == "GarageDoorOpener") {
|
if (device["MyQDeviceTypeName"] == "GarageDoorOpener" || device["MyQDeviceTypeName"] == "VGDO") {
|
||||||
|
|
||||||
// If we haven't explicity specified a door ID, we'll loop to make sure we don't have multiple openers, which is confusing
|
// If we haven't explicity specified a door ID, we'll loop to make sure we don't have multiple openers, which is confusing
|
||||||
if (!that.requiredDeviceId) {
|
if (!that.requiredDeviceId) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
var Service = require('HAP-NodeJS').Service;
|
var Service = require("hap-nodejs").Service;
|
||||||
var Characteristic = require('HAP-NodeJS').Characteristic;
|
var Characteristic = require("hap-nodejs").Characteristic;
|
||||||
var request = require("request");
|
var request = require("request");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
var types = require("HAP-NodeJS/accessories/types.js");
|
var types = require("hap-nodejs/accessories/types.js");
|
||||||
var tesla = require("teslams");
|
var tesla = require("teslams");
|
||||||
|
|
||||||
function TeslaAccessory(log, config) {
|
function TeslaAccessory(log, config) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
var Service = require("HAP-NodeJS").Service;
|
var Service = require("hap-nodejs").Service;
|
||||||
var Characteristic = require("HAP-NodeJS").Characteristic;
|
var Characteristic = require("hap-nodejs").Characteristic;
|
||||||
var wemo = require('wemo');
|
var wemo = require('wemo');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
var types = require("HAP-NodeJS/accessories/types.js");
|
var types = require("hap-nodejs/accessories/types.js");
|
||||||
var request = require("request");
|
var request = require("request");
|
||||||
|
|
||||||
function X10(log, config) {
|
function X10(log, config) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
var iControl = require('node-icontrol').iControl;
|
var iControl = require('node-icontrol').iControl;
|
||||||
var Service = require('HAP-NodeJS').Service;
|
var Service = require("hap-nodejs").Service;
|
||||||
var Characteristic = require('HAP-NodeJS').Characteristic;
|
var Characteristic = require("hap-nodejs").Characteristic;
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
accessory: iControlAccessory
|
accessory: iControlAccessory
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ New 2015-10-07:
|
|||||||
- Accept uuid_base parameter from config.json to use as unique identifier in UUIDs instead of name (optional)
|
- Accept uuid_base parameter from config.json to use as unique identifier in UUIDs instead of name (optional)
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
var Service = require("HAP-NodeJS").Service;
|
var Service = require("hap-nodejs").Service;
|
||||||
var Characteristic = require("HAP-NodeJS").Characteristic;
|
var Characteristic = require("hap-nodejs").Characteristic;
|
||||||
var knxd = require("eibd");
|
var knxd = require("eibd");
|
||||||
var knxd_registerGA = require('../platforms/KNX.js').registerGA;
|
var knxd_registerGA = require('../platforms/KNX.js').registerGA;
|
||||||
var knxd_startMonitor = require('../platforms/KNX.js').startMonitor;
|
var knxd_startMonitor = require('../platforms/KNX.js').startMonitor;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
var Service = require("HAP-NodeJS").Service;
|
var Service = require("hap-nodejs").Service;
|
||||||
var Characteristic = require("HAP-NodeJS").Characteristic;
|
var Characteristic = require("hap-nodejs").Characteristic;
|
||||||
var request = require("request");
|
var request = require("request");
|
||||||
var komponist = require('komponist')
|
var komponist = require('komponist')
|
||||||
|
|
||||||
|
|||||||
16
app.js
16
app.js
@@ -1,14 +1,14 @@
|
|||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var storage = require('node-persist');
|
var storage = require('node-persist');
|
||||||
var hap = require('HAP-NodeJS');
|
var hap = require("hap-nodejs");
|
||||||
var uuid = require('HAP-NodeJS').uuid;
|
var uuid = require("hap-nodejs").uuid;
|
||||||
var Bridge = require('HAP-NodeJS').Bridge;
|
var Bridge = require("hap-nodejs").Bridge;
|
||||||
var Accessory = require('HAP-NodeJS').Accessory;
|
var Accessory = require("hap-nodejs").Accessory;
|
||||||
var Service = require('HAP-NodeJS').Service;
|
var Service = require("hap-nodejs").Service;
|
||||||
var Characteristic = require('HAP-NodeJS').Characteristic;
|
var Characteristic = require("hap-nodejs").Characteristic;
|
||||||
var accessoryLoader = require('HAP-NodeJS').AccessoryLoader;
|
var accessoryLoader = require("hap-nodejs").AccessoryLoader;
|
||||||
var once = require('HAP-NodeJS/lib/util/once').once;
|
var once = require("hap-nodejs/lib/util/once").once;
|
||||||
|
|
||||||
console.log("Starting HomeBridge server...");
|
console.log("Starting HomeBridge server...");
|
||||||
|
|
||||||
|
|||||||
@@ -105,6 +105,16 @@
|
|||||||
"platform": "LIFx",
|
"platform": "LIFx",
|
||||||
"name": "LIFx",
|
"name": "LIFx",
|
||||||
"access_token": "XXXXXXXX generate at https://cloud.lifx.com/settings"
|
"access_token": "XXXXXXXX generate at https://cloud.lifx.com/settings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"platform": "Netatmo",
|
||||||
|
"name": "Netatmo Weather",
|
||||||
|
"auth": {
|
||||||
|
"client_id": "XXXXX Create at https://dev.netatmo.com/",
|
||||||
|
"client_secret": "XXXXX Create at https://dev.netatmo.com/",
|
||||||
|
"username": "your netatmo username",
|
||||||
|
"password": "your netatmo password"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
25
package.json
25
package.json
@@ -12,35 +12,40 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ad2usb": "git+https://github.com/alistairg/node-ad2usb.git#local",
|
"ad2usb": "git+https://github.com/alistairg/node-ad2usb.git#local",
|
||||||
|
"async": "^1.4.2",
|
||||||
"carwingsjs": "0.0.x",
|
"carwingsjs": "0.0.x",
|
||||||
"chokidar": "^1.0.5",
|
"chokidar": "^1.0.5",
|
||||||
"color": "0.10.x",
|
"color": "0.10.x",
|
||||||
|
"debug": "^2.2.0",
|
||||||
"eibd": "^0.3.1",
|
"eibd": "^0.3.1",
|
||||||
"elkington": "kevinohara80/elkington",
|
"elkington": "kevinohara80/elkington",
|
||||||
"hap-nodejs": "git+https://github.com/KhaosT/HAP-NodeJS#4650e771f356a220868d873d16564a6be6603ff7",
|
"hap-nodejs": "^0.0.3",
|
||||||
"harmonyhubjs-client": "^1.1.4",
|
"harmonyhubjs-client": "^1.1.6",
|
||||||
"harmonyhubjs-discover": "git+https://github.com/swissmanu/harmonyhubjs-discover.git",
|
"harmonyhubjs-discover": "git+https://github.com/swissmanu/harmonyhubjs-discover.git",
|
||||||
"lifx-api": "^1.0.1",
|
"isy-js": "",
|
||||||
|
"komponist": "0.1.0",
|
||||||
"lifx": "git+https://github.com/magicmonkey/lifxjs.git",
|
"lifx": "git+https://github.com/magicmonkey/lifxjs.git",
|
||||||
|
"lifx-api": "^1.0.1",
|
||||||
"mdns": "^2.2.4",
|
"mdns": "^2.2.4",
|
||||||
|
"netatmo": "git+https://github.com/patricks/netatmo.git",
|
||||||
|
"node-cache": "3.0.0",
|
||||||
"node-hue-api": "^1.0.5",
|
"node-hue-api": "^1.0.5",
|
||||||
"node-icontrol": "^0.1.5",
|
"node-icontrol": "^0.1.5",
|
||||||
"node-milight-promise": "0.0.x",
|
"node-milight-promise": "0.0.x",
|
||||||
"node-persist": "0.0.x",
|
"node-persist": "0.0.x",
|
||||||
|
"node-xmpp-client": "1.0.0-alpha23",
|
||||||
"q": "1.4.x",
|
"q": "1.4.x",
|
||||||
"tough-cookie": "^2.0.0",
|
"queue": "^3.1.0",
|
||||||
"request": "2.49.x",
|
"request": "2.49.x",
|
||||||
"sonos": "0.8.x",
|
"sonos": "0.8.x",
|
||||||
"telldus": "0.0.9",
|
"telldus-live": "^0.2.1",
|
||||||
"telldus-live": "0.2.x",
|
|
||||||
"teslams": "1.0.1",
|
"teslams": "1.0.1",
|
||||||
"unofficial-nest-api": "git+https://github.com/hachidorii/unofficial_nodejs_nest.git#d8d48edc952b049ff6320ef99afa7b2f04cdee98",
|
"tough-cookie": "^2.0.0",
|
||||||
|
"unofficial-nest-api": "git+https://github.com/kraigm/unofficial_nodejs_nest.git#3cbd337adc32fab3b481659b38d86f9fcd6a9c02",
|
||||||
"wemo": "0.2.x",
|
"wemo": "0.2.x",
|
||||||
"wink-js": "0.0.5",
|
"wink-js": "0.0.5",
|
||||||
"xml2js": "0.4.x",
|
"xml2js": "0.4.x",
|
||||||
"xmldoc": "0.1.x",
|
"xmldoc": "0.1.x",
|
||||||
"komponist" : "0.1.0",
|
"yamaha-nodejs": "0.4.x"
|
||||||
"yamaha-nodejs": "0.4.x",
|
|
||||||
"debug": "^2.2.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@
|
|||||||
// When you attempt to add a device, it will ask for a "PIN code".
|
// When you attempt to add a device, it will ask for a "PIN code".
|
||||||
// The default code for all HomeBridge accessories is 031-45-154.
|
// The default code for all HomeBridge accessories is 031-45-154.
|
||||||
//
|
//
|
||||||
var types = require("HAP-NodeJS/accessories/types.js");
|
var types = require("hap-nodejs/accessories/types.js");
|
||||||
var request = require("request");
|
var request = require("request");
|
||||||
|
|
||||||
function DomoticzPlatform(log, config){
|
function DomoticzPlatform(log, config){
|
||||||
|
|||||||
1111
platforms/FHEM.js
1111
platforms/FHEM.js
File diff suppressed because it is too large
Load Diff
@@ -14,9 +14,9 @@
|
|||||||
// When you attempt to add a device, it will ask for a "PIN code".
|
// When you attempt to add a device, it will ask for a "PIN code".
|
||||||
// The default code for all HomeBridge accessories is 031-45-154.
|
// The default code for all HomeBridge accessories is 031-45-154.
|
||||||
|
|
||||||
var types = require("HAP-NodeJS/accessories/types.js");
|
var types = require("hap-nodejs/accessories/types.js");
|
||||||
var Service = require("HAP-NodeJS").Service;
|
var Service = require("hap-nodejs").Service;
|
||||||
var Characteristic = require("HAP-NodeJS").Characteristic;
|
var Characteristic = require("hap-nodejs").Characteristic;
|
||||||
var request = require("request");
|
var request = require("request");
|
||||||
|
|
||||||
function FibaroHC2Platform(log, config){
|
function FibaroHC2Platform(log, config){
|
||||||
|
|||||||
@@ -67,8 +67,8 @@
|
|||||||
// When you attempt to add a device, it will ask for a "PIN code".
|
// When you attempt to add a device, it will ask for a "PIN code".
|
||||||
// The default code for all HomeBridge accessories is 031-45-154.
|
// The default code for all HomeBridge accessories is 031-45-154.
|
||||||
|
|
||||||
var Service = require("HAP-NodeJS").Service;
|
var Service = require("hap-nodejs").Service;
|
||||||
var Characteristic = require("HAP-NodeJS").Characteristic;
|
var Characteristic = require("hap-nodejs").Characteristic;
|
||||||
var url = require('url')
|
var url = require('url')
|
||||||
var request = require("request");
|
var request = require("request");
|
||||||
|
|
||||||
|
|||||||
@@ -180,8 +180,8 @@
|
|||||||
// - Door
|
// - Door
|
||||||
|
|
||||||
|
|
||||||
var Service = require("HAP-NodeJS").Service;
|
var Service = require("hap-nodejs").Service;
|
||||||
var Characteristic = require("HAP-NodeJS").Characteristic;
|
var Characteristic = require("hap-nodejs").Characteristic;
|
||||||
var request = require("request");
|
var request = require("request");
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
var types = require("HAP-NodeJS/accessories/types.js");
|
var types = require("hap-nodejs/accessories/types.js");
|
||||||
var xml2js = require('xml2js');
|
var xml2js = require('xml2js');
|
||||||
var request = require('request');
|
var request = require('request');
|
||||||
var util = require('util');
|
var util = require('util');
|
||||||
|
|||||||
552
platforms/Indigo.js
Normal file
552
platforms/Indigo.js
Normal file
@@ -0,0 +1,552 @@
|
|||||||
|
// Indigo Platform Shim for HomeBridge
|
||||||
|
// Written by Mike Riccio (https://github.com/webdeck)
|
||||||
|
// Based on many of the other HomeBridge plartform modules
|
||||||
|
// See http://www.indigodomo.com/ for more info on Indigo
|
||||||
|
//
|
||||||
|
// Remember to add platform to config.json. Example:
|
||||||
|
// "platforms": [
|
||||||
|
// {
|
||||||
|
// "platform": "Indigo", // required
|
||||||
|
// "name": "Indigo", // required
|
||||||
|
// "host": "127.0.0.1", // required
|
||||||
|
// "port": "8176", // required
|
||||||
|
// "username": "username", // optional
|
||||||
|
// "password": "password" // optional
|
||||||
|
// }
|
||||||
|
// ],
|
||||||
|
//
|
||||||
|
// When you attempt to add a device, it will ask for a "PIN code".
|
||||||
|
// The default code for all HomeBridge accessories is 031-45-154.
|
||||||
|
//
|
||||||
|
|
||||||
|
var types = require("hap-nodejs/accessories/types.js");
|
||||||
|
var Characteristic = require("hap-nodejs").Characteristic;
|
||||||
|
var request = require('request');
|
||||||
|
var async = require('async');
|
||||||
|
|
||||||
|
|
||||||
|
function IndigoPlatform(log, config) {
|
||||||
|
this.log = log;
|
||||||
|
|
||||||
|
this.baseURL = "http://" + config["host"] + ":" + config["port"];
|
||||||
|
|
||||||
|
if (config["username"] && config["password"]) {
|
||||||
|
this.auth = {
|
||||||
|
'user': config["username"],
|
||||||
|
'pass': config["password"],
|
||||||
|
'sendImmediately': false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IndigoPlatform.prototype = {
|
||||||
|
accessories: function(callback) {
|
||||||
|
var that = this;
|
||||||
|
this.log("Discovering Indigo Devices.");
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
url: this.baseURL + "/devices.json/",
|
||||||
|
method: 'GET'
|
||||||
|
};
|
||||||
|
if (this.auth) {
|
||||||
|
options['auth'] = this.auth;
|
||||||
|
}
|
||||||
|
this.foundAccessories = [];
|
||||||
|
this.callback = callback;
|
||||||
|
|
||||||
|
request(options, function(error, response, body) {
|
||||||
|
if (error) {
|
||||||
|
console.trace("Requesting Indigo devices.");
|
||||||
|
that.log(error);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cheesy hack because response may have an extra comma at the start of the array, which is invalid
|
||||||
|
var firstComma = body.indexOf(",");
|
||||||
|
if (firstComma < 10) {
|
||||||
|
body = "[" + body.substr(firstComma + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
var json = JSON.parse(body);
|
||||||
|
async.eachSeries(json, function(item, asyncCallback) {
|
||||||
|
var deviceURL = that.baseURL + item.restURL;
|
||||||
|
var deviceOptions = {
|
||||||
|
url: deviceURL,
|
||||||
|
method: 'GET'
|
||||||
|
};
|
||||||
|
if (that.auth) {
|
||||||
|
deviceOptions['auth'] = that.auth;
|
||||||
|
}
|
||||||
|
|
||||||
|
request(deviceOptions, function(deviceError, deviceResponse, deviceBody) {
|
||||||
|
if (deviceError) {
|
||||||
|
console.trace("Requesting Indigo device info: " + deviceURL + "\nError: " + deviceError + "\nResponse: " + deviceBody);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
var deviceJson = JSON.parse(deviceBody);
|
||||||
|
that.log("Discovered " + deviceJson.type + ": " + deviceJson.name);
|
||||||
|
that.foundAccessories.push(
|
||||||
|
new IndigoAccessory(that.log, that.auth, deviceURL, deviceJson));
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
that.log("Error parsing Indigo device info: " + deviceURL + "\nException: " + e + "\nResponse: " + deviceBody);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
asyncCallback();
|
||||||
|
});
|
||||||
|
}, function(asyncError) {
|
||||||
|
// This will be called after all the requests complete
|
||||||
|
if (asyncError) {
|
||||||
|
console.trace("Requesting Indigo device info.");
|
||||||
|
that.log(asyncError);
|
||||||
|
}
|
||||||
|
|
||||||
|
that.callback(that.foundAccessories.sort(function (a,b) {
|
||||||
|
return (a.name > b.name) - (a.name < b.name);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function IndigoAccessory(log, auth, deviceURL, json) {
|
||||||
|
this.log = log;
|
||||||
|
this.auth = auth;
|
||||||
|
this.deviceURL = deviceURL;
|
||||||
|
|
||||||
|
for (var prop in json) {
|
||||||
|
if (json.hasOwnProperty(prop)) {
|
||||||
|
this[prop] = json[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IndigoAccessory.prototype = {
|
||||||
|
getStatus: function(callback) {
|
||||||
|
var that = this;
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
url: this.deviceURL,
|
||||||
|
method: 'GET'
|
||||||
|
};
|
||||||
|
if (this.auth) {
|
||||||
|
options['auth'] = this.auth;
|
||||||
|
}
|
||||||
|
|
||||||
|
request(options, function(error, response, body) {
|
||||||
|
if (error) {
|
||||||
|
console.trace("Requesting Device Status.");
|
||||||
|
that.log(error);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
that.log("getStatus of " + that.name + ": " + body);
|
||||||
|
try {
|
||||||
|
var json = JSON.parse(body);
|
||||||
|
callback(json);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.trace("Requesting Device Status.");
|
||||||
|
that.log("Exception: " + e + "\nResponse: " + body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
updateStatus: function(params) {
|
||||||
|
var that = this;
|
||||||
|
var options = {
|
||||||
|
url: this.deviceURL + "?" + params,
|
||||||
|
method: 'PUT'
|
||||||
|
};
|
||||||
|
if (this.auth) {
|
||||||
|
options['auth'] = this.auth;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.log("updateStatus of " + that.name + ": " + params);
|
||||||
|
request(options, function(error, response, body) {
|
||||||
|
if (error) {
|
||||||
|
console.trace("Updating Device Status.");
|
||||||
|
that.log(error);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
query: function(prop, callback) {
|
||||||
|
this.getStatus(function(json) {
|
||||||
|
callback(json[prop]);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
turnOn: function() {
|
||||||
|
if (this.typeSupportsOnOff) {
|
||||||
|
this.updateStatus("isOn=1");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
turnOff: function() {
|
||||||
|
if (this.typeSupportsOnOff) {
|
||||||
|
this.updateStatus("isOn=0");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setBrightness: function(brightness) {
|
||||||
|
if (this.typeSupportsDim && brightness >= 0 && brightness <= 100) {
|
||||||
|
this.updateStatus("brightness=" + brightness);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setSpeedIndex: function(speedIndex) {
|
||||||
|
if (this.typeSupportsSpeedControl && speedIndex >= 0 && speedIndex <= 3) {
|
||||||
|
this.updateStatus("speedIndex=" + speedIndex);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getCurrentHeatingCooling: function(callback) {
|
||||||
|
this.getStatus(function(json) {
|
||||||
|
var mode = 0;
|
||||||
|
if (json["hvacOperatonModeIsHeat"]) {
|
||||||
|
mode = 1;
|
||||||
|
}
|
||||||
|
else if (json["hvacOperationModeIsCool"]) {
|
||||||
|
mode = 2;
|
||||||
|
}
|
||||||
|
else if (json["hvacOperationModeIsAuto"]) {
|
||||||
|
mode = 3;
|
||||||
|
}
|
||||||
|
callback(mode);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
setTargetHeatingCooling: function(mode) {
|
||||||
|
if (mode == 0) {
|
||||||
|
param = "Off";
|
||||||
|
}
|
||||||
|
else if (mode == 1) {
|
||||||
|
param = "Heat";
|
||||||
|
}
|
||||||
|
else if (mode == 2) {
|
||||||
|
param = "Cool";
|
||||||
|
}
|
||||||
|
else if (mode == 3) {
|
||||||
|
param = "Auto";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (param) {
|
||||||
|
this.updateStatus("hvacOperationModeIs" + param + "=true");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Note: HomeKit wants all temperature values to be in celsius
|
||||||
|
getCurrentTemperature: function(callback) {
|
||||||
|
this.query("displayRawState", function(temperature) {
|
||||||
|
callback((temperature - 32.0) * 5.0 / 9.0);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getTargetTemperature: function(callback) {
|
||||||
|
this.getStatus(function(json) {
|
||||||
|
var temperature;
|
||||||
|
if (json["hvacOperatonModeIsHeat"]) {
|
||||||
|
temperature = json["setpointHeat"];
|
||||||
|
}
|
||||||
|
else if (json["hvacOperationModeIsCool"]) {
|
||||||
|
temperature = json["setpointCool"];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
temperature = (json["setpointHeat"] + json["setpointCool"]) / 2.0;
|
||||||
|
}
|
||||||
|
callback((temperature - 32.0) * 5.0 / 9.0);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
setTargetTemperature: function(temperature) {
|
||||||
|
var that = this;
|
||||||
|
var t = (temperature * 9.0 / 5.0) + 32.0;
|
||||||
|
this.getStatus(function(json) {
|
||||||
|
if (json["hvacOperatonModeIsHeat"]) {
|
||||||
|
that.updateStatus("setpointHeat=" + t);
|
||||||
|
}
|
||||||
|
else if (json["hvacOperationModeIsCool"]) {
|
||||||
|
that.updateStatus("setpointCool=" + t);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var cool = t + 5;
|
||||||
|
var heat = t - 5;
|
||||||
|
that.updateStatus("setpointCool=" + cool + "&setpointHeat=" + heat);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
informationCharacteristics: function() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
cType: types.NAME_CTYPE,
|
||||||
|
onUpdate: null,
|
||||||
|
perms: [Characteristic.Perms.READ],
|
||||||
|
format: Characteristic.Formats.STRING,
|
||||||
|
initialValue: this.name,
|
||||||
|
supportEvents: false,
|
||||||
|
supportBonjour: false,
|
||||||
|
manfDescription: "Name of the accessory",
|
||||||
|
designedMaxLength: 255
|
||||||
|
},{
|
||||||
|
cType: types.MANUFACTURER_CTYPE,
|
||||||
|
onUpdate: null,
|
||||||
|
perms: [Characteristic.Perms.READ],
|
||||||
|
format: Characteristic.Formats.STRING,
|
||||||
|
initialValue: "Indigo",
|
||||||
|
supportEvents: false,
|
||||||
|
supportBonjour: false,
|
||||||
|
manfDescription: "Manufacturer",
|
||||||
|
designedMaxLength: 255
|
||||||
|
},{
|
||||||
|
cType: types.MODEL_CTYPE,
|
||||||
|
onUpdate: null,
|
||||||
|
perms: [Characteristic.Perms.READ],
|
||||||
|
format: Characteristic.Formats.STRING,
|
||||||
|
initialValue: this.type,
|
||||||
|
supportEvents: false,
|
||||||
|
supportBonjour: false,
|
||||||
|
manfDescription: "Model",
|
||||||
|
designedMaxLength: 255
|
||||||
|
},{
|
||||||
|
cType: types.SERIAL_NUMBER_CTYPE,
|
||||||
|
onUpdate: null,
|
||||||
|
perms: [Characteristic.Perms.READ],
|
||||||
|
format: Characteristic.Formats.STRING,
|
||||||
|
initialValue: this.addressStr,
|
||||||
|
supportEvents: false,
|
||||||
|
supportBonjour: false,
|
||||||
|
manfDescription: "SN",
|
||||||
|
designedMaxLength: 255
|
||||||
|
},{
|
||||||
|
cType: types.IDENTIFY_CTYPE,
|
||||||
|
onUpdate: null,
|
||||||
|
perms: [Characteristic.Perms.WRITE],
|
||||||
|
format: Characteristic.Formats.BOOL,
|
||||||
|
initialValue: false,
|
||||||
|
supportEvents: false,
|
||||||
|
supportBonjour: false,
|
||||||
|
manfDescription: "Identify Accessory",
|
||||||
|
designedMaxLength: 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
controlCharacteristics: function(that) {
|
||||||
|
var hasAType = false;
|
||||||
|
|
||||||
|
var cTypes = [{
|
||||||
|
cType: types.NAME_CTYPE,
|
||||||
|
onUpdate: null,
|
||||||
|
perms: [Characteristic.Perms.READ],
|
||||||
|
format: Characteristic.Formats.STRING,
|
||||||
|
initialValue: that.name,
|
||||||
|
supportEvents: false,
|
||||||
|
supportBonjour: false,
|
||||||
|
manfDescription: "Name of the accessory",
|
||||||
|
designedMaxLength: 255
|
||||||
|
}];
|
||||||
|
|
||||||
|
if (that.typeSupportsDim) {
|
||||||
|
hasAType = true;
|
||||||
|
cTypes.push({
|
||||||
|
cType: types.BRIGHTNESS_CTYPE,
|
||||||
|
perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY],
|
||||||
|
format: Characteristic.Formats.INT,
|
||||||
|
initialValue: that.brightness,
|
||||||
|
supportEvents: false,
|
||||||
|
supportBonjour: false,
|
||||||
|
manfDescription: "Adjust Brightness of Light",
|
||||||
|
designedMinValue: 0,
|
||||||
|
designedMaxValue: 100,
|
||||||
|
designedMinStep: 1,
|
||||||
|
unit: Characteristic.Units.PERCENTAGE,
|
||||||
|
onUpdate: function(value) {
|
||||||
|
that.setBrightness(value);
|
||||||
|
},
|
||||||
|
onRead: function(callback) {
|
||||||
|
that.query("brightness", callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (that.typeSupportsSpeedControl) {
|
||||||
|
hasAType = true;
|
||||||
|
cTypes.push({
|
||||||
|
cType: types.ROTATION_SPEED_CTYPE,
|
||||||
|
perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY],
|
||||||
|
format: Characteristic.Formats.INT,
|
||||||
|
initialValue: 0,
|
||||||
|
supportEvents: false,
|
||||||
|
supportBonjour: false,
|
||||||
|
manfDescription: "Change the speed of the fan",
|
||||||
|
designedMaxLength: 1,
|
||||||
|
designedMinValue: 0,
|
||||||
|
designedMaxValue: 3,
|
||||||
|
designedMinStep: 1,
|
||||||
|
onUpdate: function(value) {
|
||||||
|
that.setSpeedIndex(value);
|
||||||
|
},
|
||||||
|
onRead: function(callback) {
|
||||||
|
that.query("speedIndex", callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (that.typeSupportsHVAC) {
|
||||||
|
hasAType = true;
|
||||||
|
cTypes.push({
|
||||||
|
cType: types.CURRENTHEATINGCOOLING_CTYPE,
|
||||||
|
perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY],
|
||||||
|
format: Characteristic.Formats.INT,
|
||||||
|
initialValue: 0,
|
||||||
|
supportEvents: false,
|
||||||
|
supportBonjour: false,
|
||||||
|
manfDescription: "Current Mode",
|
||||||
|
designedMaxLength: 1,
|
||||||
|
designedMinValue: 0,
|
||||||
|
designedMaxValue: 3,
|
||||||
|
designedMinStep: 1,
|
||||||
|
onUpdate: null,
|
||||||
|
onRead: function(callback) {
|
||||||
|
that.getCurrentHeatingCooling(callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cTypes.push({
|
||||||
|
cType: types.TARGETHEATINGCOOLING_CTYPE,
|
||||||
|
perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY],
|
||||||
|
format: Characteristic.Formats.INT,
|
||||||
|
initialValue: 0,
|
||||||
|
supportEvents: false,
|
||||||
|
supportBonjour: false,
|
||||||
|
manfDescription: "Target Mode",
|
||||||
|
designedMaxLength: 1,
|
||||||
|
designedMinValue: 0,
|
||||||
|
designedMaxValue: 3,
|
||||||
|
designedMinStep: 1,
|
||||||
|
onUpdate: function(value) {
|
||||||
|
that.setTargetHeatingCooling(value);
|
||||||
|
},
|
||||||
|
onRead: function(callback) {
|
||||||
|
that.getCurrentHeatingCooling(callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cTypes.push({
|
||||||
|
cType: types.CURRENT_TEMPERATURE_CTYPE,
|
||||||
|
perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY],
|
||||||
|
format: Characteristic.Formats.INT,
|
||||||
|
designedMinValue: 16,
|
||||||
|
designedMaxValue: 38,
|
||||||
|
designedMinStep: 1,
|
||||||
|
initialValue: 20,
|
||||||
|
supportEvents: false,
|
||||||
|
supportBonjour: false,
|
||||||
|
manfDescription: "Current Temperature",
|
||||||
|
unit: Characteristic.Units.FAHRENHEIT,
|
||||||
|
onUpdate: null,
|
||||||
|
onRead: function(callback) {
|
||||||
|
that.getCurrentTemperature(callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cTypes.push({
|
||||||
|
cType: types.TARGET_TEMPERATURE_CTYPE,
|
||||||
|
perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY],
|
||||||
|
format: Characteristic.Formats.INT,
|
||||||
|
designedMinValue: 16,
|
||||||
|
designedMaxValue: 38,
|
||||||
|
designedMinStep: 1,
|
||||||
|
initialValue: 20,
|
||||||
|
supportEvents: false,
|
||||||
|
supportBonjour: false,
|
||||||
|
manfDescription: "Target Temperature",
|
||||||
|
unit: Characteristic.Units.FAHRENHEIT,
|
||||||
|
onUpdate: function(value) {
|
||||||
|
that.setTargetTemperature(value);
|
||||||
|
},
|
||||||
|
onRead: function(callback) {
|
||||||
|
that.getTargetTemperature(callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cTypes.push({
|
||||||
|
cType: types.TEMPERATURE_UNITS_CTYPE,
|
||||||
|
perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY],
|
||||||
|
format: Characteristic.Formats.INT,
|
||||||
|
initialValue: 1,
|
||||||
|
supportEvents: false,
|
||||||
|
supportBonjour: false,
|
||||||
|
manfDescription: "Unit",
|
||||||
|
onUpdate: null,
|
||||||
|
onRead: function(callback) {
|
||||||
|
callback(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (that.typeSupportsOnOff || !hasAType) {
|
||||||
|
cTypes.push({
|
||||||
|
cType: types.POWER_STATE_CTYPE,
|
||||||
|
perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY],
|
||||||
|
format: Characteristic.Formats.BOOL,
|
||||||
|
initialValue: (that.isOn) ? 1 : 0,
|
||||||
|
supportEvents: false,
|
||||||
|
supportBonjour: false,
|
||||||
|
manfDescription: "Change the power state",
|
||||||
|
designedMaxLength: 1,
|
||||||
|
onUpdate: function(value) {
|
||||||
|
if (value == 0) {
|
||||||
|
that.turnOff();
|
||||||
|
} else {
|
||||||
|
that.turnOn();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRead: function(callback) {
|
||||||
|
that.query("isOn", function(isOn) {
|
||||||
|
callback((isOn) ? 1 : 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return cTypes;
|
||||||
|
},
|
||||||
|
|
||||||
|
sType: function() {
|
||||||
|
if (this.typeSupportsHVAC) {
|
||||||
|
return types.THERMOSTAT_STYPE;
|
||||||
|
} else if (this.typeSupportsDim) {
|
||||||
|
return types.LIGHTBULB_STYPE;
|
||||||
|
} else if (this.typeSupportsSpeedControl) {
|
||||||
|
return types.FAN_STYPE;
|
||||||
|
} else if (this.typeSupportsOnOff) {
|
||||||
|
return types.SWITCH_STYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.SWITCH_STYPE;
|
||||||
|
},
|
||||||
|
|
||||||
|
getServices: function() {
|
||||||
|
var that = this;
|
||||||
|
var services = [{
|
||||||
|
sType: types.ACCESSORY_INFORMATION_STYPE,
|
||||||
|
characteristics: that.informationCharacteristics(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sType: that.sType(),
|
||||||
|
characteristics: that.controlCharacteristics(that)
|
||||||
|
}];
|
||||||
|
|
||||||
|
that.log("Loaded services for " + that.name);
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.accessory = IndigoAccessory;
|
||||||
|
module.exports.platform = IndigoPlatform;
|
||||||
@@ -2,20 +2,19 @@
|
|||||||
* based on Sonos platform
|
* based on Sonos platform
|
||||||
*/
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
var types = require("HAP-NodeJS/accessories/types.js");
|
var types = require("hap-nodejs/accessories/types.js");
|
||||||
//var hardware = require('myHardwareSupport'); //require any additional hardware packages
|
|
||||||
var knxd = require('eibd');
|
var knxd = require('eibd');
|
||||||
|
|
||||||
function KNXPlatform(log, config){
|
function KNXPlatform(log, config){
|
||||||
this.log = log;
|
this.log = log;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
// this.property1 = config.property1;
|
|
||||||
// this.property2 = config.property2;
|
|
||||||
|
|
||||||
|
|
||||||
// initiate connection to bus for listening ==> done with first shim
|
// initiate connection to bus for listening ==> done with first shim
|
||||||
|
|
||||||
};
|
}
|
||||||
|
|
||||||
KNXPlatform.prototype = {
|
KNXPlatform.prototype = {
|
||||||
accessories: function(callback) {
|
accessories: function(callback) {
|
||||||
@@ -49,10 +48,10 @@ KNXPlatform.prototype = {
|
|||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// do something else
|
// do something else
|
||||||
this.log("unkown accessory type found")
|
this.log("unkown accessory type found");
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
}
|
||||||
// if done, return the array to callback function
|
// if done, return the array to callback function
|
||||||
this.log("returning "+myAccessories.length+" accessories");
|
this.log("returning "+myAccessories.length+" accessories");
|
||||||
callback(myAccessories);
|
callback(myAccessories);
|
||||||
@@ -118,7 +117,7 @@ function groupsocketlisten(opts, callback) {
|
|||||||
|
|
||||||
var registerSingleGA = function registerSingleGA (groupAddress, callback, reverse) {
|
var registerSingleGA = function registerSingleGA (groupAddress, callback, reverse) {
|
||||||
subscriptions.push({address: groupAddress, callback: callback, reverse:reverse });
|
subscriptions.push({address: groupAddress, callback: callback, reverse:reverse });
|
||||||
}
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* public busMonitor.startMonitor()
|
* public busMonitor.startMonitor()
|
||||||
@@ -193,7 +192,7 @@ var registerGA = function (groupAddresses, callback) {
|
|||||||
} else {
|
} else {
|
||||||
// it's only one
|
// it's only one
|
||||||
if (groupAddresses.match(/(\d*\/\d*\/\d*)/)) {
|
if (groupAddresses.match(/(\d*\/\d*\/\d*)/)) {
|
||||||
registerSingleGA (groupAddresses.match(/(\d*\/\d*\/\d*)/)[0], callback, groupAddresses[i].match(/\d*\/\d*\/\d*(R)/) ? true:false);
|
registerSingleGA (groupAddresses.match(/(\d*\/\d*\/\d*)/)[0], callback, groupAddresses.match(/\d*\/\d*\/\d*(R)/) ? true:false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// console.log("listeners now: " + subscriptions.length);
|
// console.log("listeners now: " + subscriptions.length);
|
||||||
|
|||||||
@@ -16,8 +16,8 @@
|
|||||||
// The default code for all HomeBridge accessories is 031-45-154.
|
// The default code for all HomeBridge accessories is 031-45-154.
|
||||||
//
|
//
|
||||||
|
|
||||||
var Service = require("HAP-NodeJS").Service;
|
var Service = require("hap-nodejs").Service;
|
||||||
var Characteristic = require("HAP-NodeJS").Characteristic;
|
var Characteristic = require("hap-nodejs").Characteristic;
|
||||||
var lifxRemoteObj = require('lifx-api');
|
var lifxRemoteObj = require('lifx-api');
|
||||||
var lifx_remote;
|
var lifx_remote;
|
||||||
|
|
||||||
|
|||||||
@@ -17,13 +17,20 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
|
|
||||||
var types = require('HAP-NodeJS/accessories/types.js');
|
var types = require('hap-nodejs/accessories/types.js');
|
||||||
|
|
||||||
var harmonyDiscover = require('harmonyhubjs-discover');
|
var harmonyDiscover = require('harmonyhubjs-discover');
|
||||||
var harmony = require('harmonyhubjs-client');
|
var harmony = require('harmonyhubjs-client');
|
||||||
|
|
||||||
var _harmonyHubPort = 61991;
|
var _harmonyHubPort = 61991;
|
||||||
|
|
||||||
|
var Service = require("hap-nodejs").Service;
|
||||||
|
var Characteristic = require("hap-nodejs").Characteristic;
|
||||||
|
var Accessory = require("hap-nodejs").Accessory;
|
||||||
|
var uuid = require("hap-nodejs").uuid;
|
||||||
|
var inherits = require('util').inherits;
|
||||||
|
var queue = require('queue');
|
||||||
|
|
||||||
|
|
||||||
function sortByKey (array, key) {
|
function sortByKey (array, key) {
|
||||||
return array.sort(function(a, b) {
|
return array.sort(function(a, b) {
|
||||||
@@ -41,265 +48,220 @@ function LogitechHarmonyPlatform (log, config) {
|
|||||||
|
|
||||||
LogitechHarmonyPlatform.prototype = {
|
LogitechHarmonyPlatform.prototype = {
|
||||||
|
|
||||||
// Find one Harmony remote hub (only support one for now)
|
|
||||||
locateHub: function (callback) {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
// Connect to a Harmony hub
|
|
||||||
var createClient = function (ipAddress) {
|
|
||||||
self.log("Connecting to Logitech Harmony remote hub...");
|
|
||||||
|
|
||||||
harmony(ipAddress)
|
|
||||||
.then(function (client) {
|
|
||||||
self.log("Connected to Logitech Harmony remote hub");
|
|
||||||
|
|
||||||
callback(null, client);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Use the ip address in configuration if available
|
|
||||||
if (this.ip_address) {
|
|
||||||
console.log("Using Logitech Harmony hub ip address from configuration");
|
|
||||||
|
|
||||||
return createClient(this.ip_address)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.log("Searching for Logitech Harmony remote hubs...");
|
|
||||||
|
|
||||||
// Discover the harmony hub with bonjour
|
|
||||||
var discover = new harmonyDiscover(_harmonyHubPort);
|
|
||||||
|
|
||||||
// TODO: Support update event with some way to add accessories
|
|
||||||
// TODO: Have some kind of timeout with an error message. Right now this searches forever until it finds one hub.
|
|
||||||
discover.on('online', function (hubInfo) {
|
|
||||||
self.log("Found Logitech Harmony remote hub: " + hubInfo.ip);
|
|
||||||
|
|
||||||
// Stop looking for hubs once we find the first one
|
|
||||||
// TODO: Support multiple hubs
|
|
||||||
discover.stop();
|
|
||||||
|
|
||||||
createClient(hubInfo.ip);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Start looking for hubs
|
|
||||||
discover.start();
|
|
||||||
},
|
|
||||||
|
|
||||||
accessories: function (callback) {
|
accessories: function (callback) {
|
||||||
var self = this;
|
var plat = this;
|
||||||
var foundAccessories = [];
|
var foundAccessories = [];
|
||||||
|
var activityAccessories = [];
|
||||||
|
var hub = null;
|
||||||
|
var hubIP = null;
|
||||||
|
var hubQueue = queue();
|
||||||
|
hubQueue.concurrency = 1;
|
||||||
|
|
||||||
// Get the first hub
|
// Get the first hub
|
||||||
this.locateHub(function (err, hub) {
|
locateHub(function (err, client, clientIP) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
|
|
||||||
self.log("Fetching Logitech Harmony devices and activites...");
|
plat.log("Fetching Logitech Harmony devices and activites...");
|
||||||
|
|
||||||
|
hub = client;
|
||||||
|
hubIP = clientIP;
|
||||||
//getDevices(hub);
|
//getDevices(hub);
|
||||||
getActivities(hub);
|
getActivities();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get Harmony Devices
|
// Find one Harmony remote hub (only support one for now)
|
||||||
/*
|
function locateHub(callback) {
|
||||||
var getDevices = function(hub) {
|
// Use the ip address in configuration if available
|
||||||
self.log("Fetching Logitech Harmony devices...");
|
if (plat.ip_address) {
|
||||||
|
console.log("Using Logitech Harmony hub ip address from configuration");
|
||||||
|
|
||||||
hub.getDevices()
|
return createClient(plat.ip_address, callback)
|
||||||
.then(function (devices) {
|
}
|
||||||
self.log("Found devices: ", devices);
|
|
||||||
|
|
||||||
var sArray = sortByKey(json['result'],"Name");
|
plat.log("Searching for Logitech Harmony remote hubs...");
|
||||||
|
|
||||||
sArray.map(function(s) {
|
// Discover the harmony hub with bonjour
|
||||||
accessory = new LogitechHarmonyAccessory(self.log, self.server, self.port, false, s.idx, s.Name, s.HaveDimmer, s.MaxDimLevel, (s.SubType=="RGB")||(s.SubType=="RGBW"));
|
var discover = new harmonyDiscover(_harmonyHubPort);
|
||||||
foundAccessories.push(accessory);
|
|
||||||
|
// TODO: Support update event with some way to add accessories
|
||||||
|
// TODO: Have some kind of timeout with an error message. Right now this searches forever until it finds one hub.
|
||||||
|
discover.on('online', function (hubInfo) {
|
||||||
|
plat.log("Found Logitech Harmony remote hub: " + hubInfo.ip);
|
||||||
|
|
||||||
|
// Stop looking for hubs once we find the first one
|
||||||
|
// TODO: Support multiple hubs
|
||||||
|
discover.stop();
|
||||||
|
|
||||||
|
createClient(hubInfo.ip, callback);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start looking for hubs
|
||||||
|
discover.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect to a Harmony hub
|
||||||
|
function createClient(ipAddress, callback) {
|
||||||
|
plat.log("Connecting to Logitech Harmony remote hub...");
|
||||||
|
harmony(ipAddress)
|
||||||
|
.then(function (client) {
|
||||||
|
plat.log("Connected to Logitech Harmony remote hub");
|
||||||
|
callback(null, client, ipAddress);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
callback(foundAccessories);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Get Harmony Activities
|
// Get Harmony Activities
|
||||||
var getActivities = function(hub) {
|
function getActivities() {
|
||||||
self.log("Fetching Logitech Harmony activities...");
|
plat.log("Fetching Logitech Harmony activities...");
|
||||||
|
|
||||||
hub.getActivities()
|
hub.getActivities()
|
||||||
.then(function (activities) {
|
.then(function (activities) {
|
||||||
self.log("Found activities: \n" + activities.map(function (a) { return "\t" + a.label; }).join("\n"));
|
plat.log("Found activities: \n" + activities.map(function (a) { return "\t" + a.label; }).join("\n"));
|
||||||
|
|
||||||
var sArray = sortByKey(activities, "label");
|
hub.getCurrentActivity().then(function (currentActivity) {
|
||||||
|
var actAccessories = [];
|
||||||
sArray.map(function(s) {
|
var sArray = sortByKey(activities, "label");
|
||||||
var accessory = new LogitechHarmonyAccessory(self.log, hub, s, true);
|
sArray.map(function(s) {
|
||||||
// TODO: Update the initial power state
|
var accessory = createActivityAccessory(s);
|
||||||
foundAccessories.push(accessory);
|
if (accessory.id > 0) {
|
||||||
|
accessory.updateActivityState(currentActivity);
|
||||||
|
actAccessories.push(accessory);
|
||||||
|
foundAccessories.push(accessory);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
activityAccessories = actAccessories;
|
||||||
|
keepAliveRefreshLoop();
|
||||||
|
callback(foundAccessories);
|
||||||
|
}).catch(function (err) {
|
||||||
|
plat.log('Unable to get current activity with error', err);
|
||||||
|
throw err;
|
||||||
});
|
});
|
||||||
|
|
||||||
callback(foundAccessories);
|
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
|
function createActivityAccessory(activity) {
|
||||||
|
var accessory = new LogitechHarmonyActivityAccessory(plat.log, activity, changeCurrentActivity.bind(plat), -1);
|
||||||
|
return accessory;
|
||||||
|
}
|
||||||
|
|
||||||
|
var isChangingActivity = false;
|
||||||
|
function changeCurrentActivity(nextActivity, callback) {
|
||||||
|
if (!nextActivity) {
|
||||||
|
nextActivity = -1;
|
||||||
|
}
|
||||||
|
plat.log('Queue activity to ' + nextActivity);
|
||||||
|
executeOnHub(function(h, cb) {
|
||||||
|
plat.log('Set activity to ' + nextActivity);
|
||||||
|
h.startActivity(nextActivity)
|
||||||
|
.then(function () {
|
||||||
|
cb();
|
||||||
|
isChangingActivity = false;
|
||||||
|
plat.log('Finished setting activity to ' + nextActivity);
|
||||||
|
updateCurrentActivity(nextActivity);
|
||||||
|
if (callback) callback(null, nextActivity);
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
cb();
|
||||||
|
isChangingActivity = false;
|
||||||
|
plat.log('Failed setting activity to ' + nextActivity + ' with error ' + err);
|
||||||
|
if (callback) callback(err);
|
||||||
|
});
|
||||||
|
}, function(){
|
||||||
|
callback(Error("Set activity failed too many times"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCurrentActivity(currentActivity) {
|
||||||
|
var actAccessories = activityAccessories;
|
||||||
|
if (actAccessories instanceof Array) {
|
||||||
|
actAccessories.map(function(a) { a.updateActivityState(currentActivity); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// prevent connection from closing
|
||||||
|
function keepAliveRefreshLoop() {
|
||||||
|
setTimeout(function() {
|
||||||
|
setInterval(function() {
|
||||||
|
executeOnHub(function(h, cb) {
|
||||||
|
plat.log("Refresh Status");
|
||||||
|
h.getCurrentActivity()
|
||||||
|
.then(function(currentActivity){
|
||||||
|
cb();
|
||||||
|
updateCurrentActivity(currentActivity);
|
||||||
|
})
|
||||||
|
.catch(cb);
|
||||||
|
});
|
||||||
|
}, 20000);
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function executeOnHub(func, funcMaxTimeout)
|
||||||
|
{
|
||||||
|
if (!func) return;
|
||||||
|
hubQueue.push(function(cb) {
|
||||||
|
var tout = setTimeout(function(){
|
||||||
|
plat.log("Reconnecting to Hub " + hubIP);
|
||||||
|
createClient(hubIP, function(err, newHub){
|
||||||
|
if (err) throw err;
|
||||||
|
hub = newHub;
|
||||||
|
if (funcMaxTimeout) {
|
||||||
|
funcMaxTimeout();
|
||||||
|
}
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
}, 30000);
|
||||||
|
func(hub, function(){
|
||||||
|
clearTimeout(tout);
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if (!hubQueue.running){
|
||||||
|
hubQueue.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function LogitechHarmonyActivityAccessory (log, details, changeCurrentActivity) {
|
||||||
function LogitechHarmonyAccessory (log, hub, details, isActivity) {
|
|
||||||
this.log = log;
|
this.log = log;
|
||||||
this.hub = hub;
|
|
||||||
this.details = details;
|
|
||||||
this.id = details.id;
|
this.id = details.id;
|
||||||
this.name = details.label;
|
this.name = details.label;
|
||||||
this.isActivity = isActivity;
|
this.isOn = false;
|
||||||
this.isActivityActive = false;
|
this.changeCurrentActivity = changeCurrentActivity;
|
||||||
|
Accessory.call(this, this.name, uuid.generate(this.id));
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
this.getService(Service.AccessoryInformation)
|
||||||
|
.setCharacteristic(Characteristic.Manufacturer, "Logitech")
|
||||||
|
.setCharacteristic(Characteristic.Model, "Harmony")
|
||||||
|
// TODO: Add hub unique id to this for people with multiple hubs so that it is really a guid.
|
||||||
|
.setCharacteristic(Characteristic.SerialNumber, this.id);
|
||||||
|
|
||||||
|
this.addService(Service.Switch)
|
||||||
|
.getCharacteristic(Characteristic.On)
|
||||||
|
.on('get', function(callback) {
|
||||||
|
// Refreshed automatically by platform
|
||||||
|
callback(null, self.isOn);
|
||||||
|
})
|
||||||
|
.on('set', this.setPowerState.bind(this));
|
||||||
|
|
||||||
|
}
|
||||||
|
inherits(LogitechHarmonyActivityAccessory, Accessory);
|
||||||
|
LogitechHarmonyActivityAccessory.prototype.parent = Accessory.prototype;
|
||||||
|
LogitechHarmonyActivityAccessory.prototype.getServices = function() {
|
||||||
|
return this.services;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
LogitechHarmonyActivityAccessory.prototype.updateActivityState = function (currentActivity) {
|
||||||
LogitechHarmonyAccessory.prototype = {
|
this.isOn = (currentActivity === this.id);
|
||||||
|
// Force get to trigger 'change' if needed
|
||||||
// TODO: Somehow make this event driven so that it tells the user what activity is on
|
this.getService(Service.Switch)
|
||||||
getPowerState: function (callback) {
|
.getCharacteristic(Characteristic.On)
|
||||||
var self = this;
|
.getValue();
|
||||||
|
};
|
||||||
if (this.isActivity) {
|
|
||||||
hub.getCurrentActivity().then(function (currentActivity) {
|
LogitechHarmonyActivityAccessory.prototype.setPowerState = function (state, callback) {
|
||||||
callback(currentActivity.id === self.id);
|
this.changeCurrentActivity(state ? this.id : null, callback);
|
||||||
}).except(function (err) {
|
|
||||||
self.log('Unable to get current activity with error', err);
|
|
||||||
callback(false);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// TODO: Support onRead for devices
|
|
||||||
this.log('TODO: Support onRead for devices');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setPowerState: function (state, callback) {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
if (this.isActivity) {
|
|
||||||
this.log('Set activity ' + this.name + ' power state to ' + state);
|
|
||||||
|
|
||||||
// Activity id -1 is turn off all devices
|
|
||||||
var id = state ? this.id : -1;
|
|
||||||
|
|
||||||
this.hub.startActivity(id)
|
|
||||||
.then(function () {
|
|
||||||
self.log('Finished setting activity ' + self.name + ' power state to ' + state);
|
|
||||||
callback();
|
|
||||||
})
|
|
||||||
.catch(function (err) {
|
|
||||||
self.log('Failed setting activity ' + self.name + ' power state to ' + state + ' with error ' + err);
|
|
||||||
callback(err);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// TODO: Support setting device power
|
|
||||||
this.log('TODO: Support setting device power');
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getServices: function () {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
sType: types.ACCESSORY_INFORMATION_STYPE,
|
|
||||||
characteristics: [
|
|
||||||
{
|
|
||||||
cType: types.NAME_CTYPE,
|
|
||||||
onUpdate: null,
|
|
||||||
perms: ["pr"],
|
|
||||||
format: "string",
|
|
||||||
initialValue: self.name,
|
|
||||||
supportEvents: false,
|
|
||||||
supportBonjour: false,
|
|
||||||
manfDescription: "Name of the accessory",
|
|
||||||
designedMaxLength: 255
|
|
||||||
},
|
|
||||||
{
|
|
||||||
cType: types.MANUFACTURER_CTYPE,
|
|
||||||
onUpdate: null,
|
|
||||||
perms: ["pr"],
|
|
||||||
format: "string",
|
|
||||||
initialValue: "Logitech",
|
|
||||||
supportEvents: false,
|
|
||||||
supportBonjour: false,
|
|
||||||
manfDescription: "Manufacturer",
|
|
||||||
designedMaxLength: 255
|
|
||||||
},
|
|
||||||
{
|
|
||||||
cType: types.MODEL_CTYPE,
|
|
||||||
onUpdate: null,
|
|
||||||
perms: ["pr"],
|
|
||||||
format: "string",
|
|
||||||
initialValue: "Harmony",
|
|
||||||
supportEvents: false,
|
|
||||||
supportBonjour: false,
|
|
||||||
manfDescription: "Model",
|
|
||||||
designedMaxLength: 255
|
|
||||||
},
|
|
||||||
{
|
|
||||||
cType: types.SERIAL_NUMBER_CTYPE,
|
|
||||||
onUpdate: null,
|
|
||||||
perms: ["pr"],
|
|
||||||
format: "string",
|
|
||||||
// TODO: Add hub unique id to this for people with multiple hubs so that it is really a guid.
|
|
||||||
initialValue: self.id,
|
|
||||||
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
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sType: types.SWITCH_STYPE,
|
|
||||||
characteristics: [
|
|
||||||
{
|
|
||||||
cType: types.NAME_CTYPE,
|
|
||||||
onUpdate: null,
|
|
||||||
perms: ["pr"],
|
|
||||||
format: "string",
|
|
||||||
initialValue: self.name,
|
|
||||||
supportEvents: true,
|
|
||||||
supportBonjour: false,
|
|
||||||
manfDescription: "Name of service",
|
|
||||||
designedMaxLength: 255
|
|
||||||
},
|
|
||||||
{
|
|
||||||
cType: types.POWER_STATE_CTYPE,
|
|
||||||
onUpdate: function (value) {
|
|
||||||
self.setPowerState(value)
|
|
||||||
},
|
|
||||||
onRead: self.getPowerState,
|
|
||||||
perms: ["pw","pr","ev"],
|
|
||||||
format: "bool",
|
|
||||||
initialValue: 0,
|
|
||||||
supportEvents: true,
|
|
||||||
supportBonjour: false,
|
|
||||||
manfDescription: "Change the power state",
|
|
||||||
designedMaxLength: 1
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.accessory = LogitechHarmonyAccessory;
|
|
||||||
module.exports.platform = LogitechHarmonyPlatform;
|
module.exports.platform = LogitechHarmonyPlatform;
|
||||||
|
|
||||||
|
|||||||
@@ -47,8 +47,8 @@ TODO:
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var Service = require("HAP-NodeJS").Service;
|
var Service = require("hap-nodejs").Service;
|
||||||
var Characteristic = require("HAP-NodeJS").Characteristic;
|
var Characteristic = require("hap-nodejs").Characteristic;
|
||||||
var Milight = require('node-milight-promise').MilightController;
|
var Milight = require('node-milight-promise').MilightController;
|
||||||
var commands = require('node-milight-promise').commands;
|
var commands = require('node-milight-promise').commands;
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
var types = require("HAP-NodeJS/accessories/types.js");
|
|
||||||
var nest = require('unofficial-nest-api');
|
var nest = require('unofficial-nest-api');
|
||||||
|
var Service = require("hap-nodejs").Service;
|
||||||
|
var Characteristic = require("hap-nodejs").Characteristic;
|
||||||
|
var Accessory = require("hap-nodejs").Accessory;
|
||||||
|
var uuid = require("hap-nodejs").uuid;
|
||||||
|
var inherits = require('util').inherits;
|
||||||
|
|
||||||
function NestPlatform(log, config){
|
function NestPlatform(log, config){
|
||||||
|
// auth info
|
||||||
|
this.username = config["username"];
|
||||||
|
this.password = config["password"];
|
||||||
|
|
||||||
// auth info
|
this.log = log;
|
||||||
this.username = config["username"];
|
this.accessoryLookup = { };
|
||||||
this.password = config["password"];
|
|
||||||
|
|
||||||
this.log = log;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NestPlatform.prototype = {
|
NestPlatform.prototype = {
|
||||||
@@ -20,8 +24,7 @@ NestPlatform.prototype = {
|
|||||||
nest.login(this.username, this.password, function (err, data) {
|
nest.login(this.username, this.password, function (err, data) {
|
||||||
if (err) {
|
if (err) {
|
||||||
that.log("There was a problem authenticating with Nest.");
|
that.log("There was a problem authenticating with Nest.");
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
nest.fetchStatus(function (data) {
|
nest.fetchStatus(function (data) {
|
||||||
for (var deviceId in data.device) {
|
for (var deviceId in data.device) {
|
||||||
if (data.device.hasOwnProperty(deviceId)) {
|
if (data.device.hasOwnProperty(deviceId)) {
|
||||||
@@ -29,12 +32,28 @@ NestPlatform.prototype = {
|
|||||||
// it's a thermostat, adjust this to detect other accessories
|
// it's a thermostat, adjust this to detect other accessories
|
||||||
if (data.shared[deviceId].hasOwnProperty('current_temperature'))
|
if (data.shared[deviceId].hasOwnProperty('current_temperature'))
|
||||||
{
|
{
|
||||||
var name = data.shared[deviceId].name
|
var initialData = data.shared[deviceId];
|
||||||
var accessory = new NestThermostatAccessory(that.log, name, device, deviceId);
|
var name = initialData.name;
|
||||||
|
var accessory = new NestThermostatAccessory(that.log, name, device, deviceId, initialData);
|
||||||
|
that.accessoryLookup[deviceId] = accessory;
|
||||||
foundAccessories.push(accessory);
|
foundAccessories.push(accessory);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function subscribe() {
|
||||||
|
nest.subscribe(subscribeDone, ['shared']);
|
||||||
|
}
|
||||||
|
|
||||||
|
function subscribeDone(deviceId, data, type) {
|
||||||
|
// data if set, is also stored here: nest.lastStatus.shared[thermostatID]
|
||||||
|
if (deviceId && that.accessoryLookup[deviceId]) {
|
||||||
|
that.log('Update to Device: ' + deviceId + " type: " + type);
|
||||||
|
that.accessoryLookup[deviceId].updateData(data);
|
||||||
|
}
|
||||||
|
setTimeout(subscribe, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribe();
|
||||||
callback(foundAccessories)
|
callback(foundAccessories)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -42,356 +61,204 @@ NestPlatform.prototype = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function NestThermostatAccessory(log, name, device, deviceId) {
|
function NestThermostatAccessory(log, name, device, deviceId, initialData) {
|
||||||
// device info
|
// device info
|
||||||
if (name) {
|
this.name = name || ("Nest" + device.serial_number);
|
||||||
this.name = name;
|
this.deviceId = deviceId;
|
||||||
} else {
|
this.log = log;
|
||||||
this.name = "Nest";
|
this.device = device;
|
||||||
}
|
|
||||||
this.model = device.model_version;
|
var id = uuid.generate('nest.thermostat.' + deviceId);
|
||||||
this.serial = device.serial_number;
|
Accessory.call(this, name, id);
|
||||||
this.deviceId = deviceId;
|
this.uuid_base = id;
|
||||||
this.log = log;
|
|
||||||
|
this.currentData = initialData;
|
||||||
|
|
||||||
|
this.getService(Service.AccessoryInformation)
|
||||||
|
.setCharacteristic(Characteristic.Manufacturer, "Nest")
|
||||||
|
.setCharacteristic(Characteristic.Model, device.model_version)
|
||||||
|
.setCharacteristic(Characteristic.SerialNumber, device.serial_number);
|
||||||
|
|
||||||
|
this.addService(Service.Thermostat, name);
|
||||||
|
|
||||||
|
this.getService(Service.Thermostat)
|
||||||
|
.getCharacteristic(Characteristic.TemperatureDisplayUnits)
|
||||||
|
.on('get', function(callback) {
|
||||||
|
var units = this.getTemperatureUnits();
|
||||||
|
var unitsName = units == Characteristic.TemperatureDisplayUnits.FAHRENHEIT ? "Fahrenheit" : "Celsius";
|
||||||
|
this.log("Tempature unit for " + this.name + " is: " + unitsName);
|
||||||
|
if (callback) callback(null, units);
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
this.getService(Service.Thermostat)
|
||||||
|
.getCharacteristic(Characteristic.CurrentTemperature)
|
||||||
|
.on('get', function(callback) {
|
||||||
|
var curTemp = this.getCurrentTemperature();
|
||||||
|
this.log("Current temperature for " + this.name + " is: " + curTemp);
|
||||||
|
if (callback) callback(null, curTemp);
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
this.getService(Service.Thermostat)
|
||||||
|
.getCharacteristic(Characteristic.CurrentHeatingCoolingState)
|
||||||
|
.on('get', function(callback) {
|
||||||
|
var curHeatingCooling = this.getCurrentHeatingCooling();
|
||||||
|
this.log("Current heating for " + this.name + " is: " + curHeatingCooling);
|
||||||
|
if (callback) callback(null, curHeatingCooling);
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
this.getService(Service.Thermostat)
|
||||||
|
.getCharacteristic(Characteristic.TargetTemperature)
|
||||||
|
.on('get', function(callback) {
|
||||||
|
var targetTemp = this.getTargetTemperature();
|
||||||
|
this.log("Target temperature for " + this.name + " is: " + targetTemp);
|
||||||
|
if (callback) callback(null, targetTemp);
|
||||||
|
}.bind(this))
|
||||||
|
.on('set', this.setTargetTemperature.bind(this));
|
||||||
|
|
||||||
|
this.getService(Service.Thermostat)
|
||||||
|
.getCharacteristic(Characteristic.TargetHeatingCoolingState)
|
||||||
|
.on('get', function(callback) {
|
||||||
|
var targetHeatingCooling = this.getTargetHeatingCooling();
|
||||||
|
this.log("Target heating for " + this.name + " is: " + targetHeatingCooling);
|
||||||
|
if (callback) callback(null, targetHeatingCooling);
|
||||||
|
}.bind(this))
|
||||||
|
.on('set', this.setTargetHeatingCooling.bind(this));
|
||||||
|
|
||||||
|
this.updateData(initialData);
|
||||||
}
|
}
|
||||||
|
inherits(NestThermostatAccessory, Accessory);
|
||||||
|
NestThermostatAccessory.prototype.parent = Accessory.prototype;
|
||||||
|
|
||||||
NestThermostatAccessory.prototype = {
|
NestThermostatAccessory.prototype.getServices = function() {
|
||||||
getCurrentHeatingCooling: function(callback){
|
return this.services;
|
||||||
|
};
|
||||||
|
|
||||||
var that = this;
|
NestThermostatAccessory.prototype.updateData = function(data) {
|
||||||
|
if (data != undefined) {
|
||||||
this.log("Checking current heating cooling for: " + this.name);
|
this.currentData = data;
|
||||||
nest.fetchStatus(function (data) {
|
|
||||||
var device = data.device[that.deviceId];
|
|
||||||
|
|
||||||
var currentHeatingCooling = 0;
|
|
||||||
switch(device.current_schedule_mode) {
|
|
||||||
case "OFF":
|
|
||||||
targetHeatingCooling = 0;
|
|
||||||
break;
|
|
||||||
case "HEAT":
|
|
||||||
currentHeatingCooling = 1;
|
|
||||||
break;
|
|
||||||
case "COOL":
|
|
||||||
currentHeatingCooling = 2;
|
|
||||||
break;
|
|
||||||
case "RANGE":
|
|
||||||
currentHeatingCooling = 3;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
currentHeatingCooling = 0;
|
|
||||||
}
|
|
||||||
that.log("Current heating for " + this.name + "is: " + currentHeatingCooling);
|
|
||||||
callback(currentHeatingCooling);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
getTargetHeatingCoooling: function(callback){
|
|
||||||
|
|
||||||
var that = this;
|
|
||||||
|
|
||||||
this.log("Checking target heating cooling for: " + this.name);
|
|
||||||
nest.fetchStatus(function (data) {
|
|
||||||
var device = data.device[that.deviceId];
|
|
||||||
|
|
||||||
var targetHeatingCooling = 0;
|
|
||||||
switch(device.target_temperature_type) {
|
|
||||||
case "off":
|
|
||||||
targetHeatingCooling = 0;
|
|
||||||
break;
|
|
||||||
case "heat":
|
|
||||||
targetHeatingCooling = 1;
|
|
||||||
break;
|
|
||||||
case "cool":
|
|
||||||
targetHeatingCooling = 2;
|
|
||||||
break;
|
|
||||||
case "range":
|
|
||||||
targetHeatingCooling = 3;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
targetHeatingCooling = 0;
|
|
||||||
}
|
|
||||||
that.log("Current target heating for " + this.name + " is: " + targetHeatingCooling);
|
|
||||||
callback(targetHeatingCooling);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
getCurrentTemperature: function(callback){
|
|
||||||
|
|
||||||
var that = this;
|
|
||||||
|
|
||||||
nest.fetchStatus(function (data) {
|
|
||||||
var device = data.shared[that.deviceId];
|
|
||||||
that.log("Current temperature for " + this.name + " is: " + device.current_temperature);
|
|
||||||
callback(device.current_temperature);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
getTargetTemperature: function(callback){
|
|
||||||
|
|
||||||
var that = this;
|
|
||||||
|
|
||||||
nest.fetchStatus(function (data) {
|
|
||||||
var device = data.shared[that.deviceId];
|
|
||||||
that.log("Target temperature for " + this.name + " is: " + device.target_temperature);
|
|
||||||
callback(device.target_temperature);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
getTemperatureUnits: function(callback){
|
|
||||||
|
|
||||||
var that = this;
|
|
||||||
|
|
||||||
nest.fetchStatus(function (data) {
|
|
||||||
var device = data.device[that.deviceId];
|
|
||||||
var temperatureUnits = 0;
|
|
||||||
switch(device.temperature_scale) {
|
|
||||||
case "F":
|
|
||||||
that.log("Tempature unit for " + this.name + " is: " + "Fahrenheit");
|
|
||||||
temperatureUnits = 1;
|
|
||||||
break;
|
|
||||||
case "C":
|
|
||||||
that.log("Tempature unit for " + this.name + " is: " + "Celsius");
|
|
||||||
temperatureUnits = 0;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
temperatureUnits = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(temperatureUnits);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
getCurrentRelativeHumidity: function(callback){
|
|
||||||
|
|
||||||
var that = this;
|
|
||||||
|
|
||||||
nest.fetchStatus(function (data) {
|
|
||||||
var device = data.device[that.deviceId];
|
|
||||||
that.log("Humidity for " + this.name + " is: " + device.current_humidity);
|
|
||||||
callback(device.current_humidity);
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
setTargetHeatingCooling: function(targetHeatingCooling){
|
|
||||||
|
|
||||||
var that = this;
|
|
||||||
|
|
||||||
var targetTemperatureType = 'off';
|
|
||||||
switch(targetHeatingCooling) {
|
|
||||||
case 0:
|
|
||||||
targetTemperatureType = 'off';
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
targetTemperatureType = 'heat';
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
targetTemperatureType = 'cool';
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
targetTemperatureType = 'range';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
targetTemperatureType = 'off';
|
|
||||||
}
|
|
||||||
|
|
||||||
this.log("Setting target heating cooling for " + this.name + " to: " + targetTemperatureType);
|
|
||||||
nest.setTargetTemperatureType(this.deviceId, targetTemperatureType);
|
|
||||||
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
setTargetTemperature: function(targetTemperature){
|
|
||||||
|
|
||||||
var that = this;
|
|
||||||
|
|
||||||
this.log("Setting target temperature for " + this.name + " to: " + targetTemperature);
|
|
||||||
nest.setTemperature(this.deviceId, targetTemperature);
|
|
||||||
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
getServices: function() {
|
|
||||||
var that = this;
|
|
||||||
return [{
|
|
||||||
sType: types.ACCESSORY_INFORMATION_STYPE,
|
|
||||||
characteristics: [{
|
|
||||||
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: "Nest",
|
|
||||||
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
|
|
||||||
}]
|
|
||||||
},{
|
|
||||||
sType: types.THERMOSTAT_STYPE,
|
|
||||||
characteristics: [{
|
|
||||||
cType: types.NAME_CTYPE,
|
|
||||||
onUpdate: null,
|
|
||||||
perms: ["pr"],
|
|
||||||
format: "string",
|
|
||||||
initialValue: this.name,
|
|
||||||
supportEvents: false,
|
|
||||||
supportBonjour: false,
|
|
||||||
manfDescription: "Name of thermostat",
|
|
||||||
designedMaxLength: 255
|
|
||||||
},{
|
|
||||||
cType: types.CURRENTHEATINGCOOLING_CTYPE,
|
|
||||||
onUpdate: null,
|
|
||||||
onRead: function(callback) {
|
|
||||||
that.getCurrentHeatingCooling(function(currentHeatingCooling){
|
|
||||||
callback(currentHeatingCooling);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
perms: ["pr","ev"],
|
|
||||||
format: "int",
|
|
||||||
initialValue: 0,
|
|
||||||
supportEvents: false,
|
|
||||||
supportBonjour: false,
|
|
||||||
manfDescription: "Current Mode",
|
|
||||||
designedMaxLength: 1,
|
|
||||||
designedMinValue: 0,
|
|
||||||
designedMaxValue: 2,
|
|
||||||
designedMinStep: 1,
|
|
||||||
},{
|
|
||||||
cType: types.TARGETHEATINGCOOLING_CTYPE,
|
|
||||||
onUpdate: function(value) {
|
|
||||||
that.setTargetHeatingCooling(value);
|
|
||||||
},
|
|
||||||
onRead: function(callback) {
|
|
||||||
that.getTargetHeatingCoooling(function(targetHeatingCooling){
|
|
||||||
callback(targetHeatingCooling);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
perms: ["pw","pr","ev"],
|
|
||||||
format: "int",
|
|
||||||
initialValue: 0,
|
|
||||||
supportEvents: false,
|
|
||||||
supportBonjour: false,
|
|
||||||
manfDescription: "Target Mode",
|
|
||||||
designedMinValue: 0,
|
|
||||||
designedMaxValue: 3,
|
|
||||||
designedMinStep: 1,
|
|
||||||
},{
|
|
||||||
cType: types.CURRENT_TEMPERATURE_CTYPE,
|
|
||||||
onUpdate: null,
|
|
||||||
onRead: function(callback) {
|
|
||||||
that.getCurrentTemperature(function(currentTemperature){
|
|
||||||
callback(currentTemperature);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
perms: ["pr","ev"],
|
|
||||||
format: "int",
|
|
||||||
initialValue: 20,
|
|
||||||
supportEvents: false,
|
|
||||||
supportBonjour: false,
|
|
||||||
manfDescription: "Current Temperature",
|
|
||||||
unit: "celsius"
|
|
||||||
},{
|
|
||||||
cType: types.TARGET_TEMPERATURE_CTYPE,
|
|
||||||
onUpdate: function(value) {
|
|
||||||
that.setTargetTemperature(value);
|
|
||||||
},
|
|
||||||
onRead: function(callback) {
|
|
||||||
that.getTargetTemperature(function(targetTemperature){
|
|
||||||
callback(targetTemperature);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
perms: ["pw","pr","ev"],
|
|
||||||
format: "int",
|
|
||||||
initialValue: 20,
|
|
||||||
supportEvents: false,
|
|
||||||
supportBonjour: false,
|
|
||||||
manfDescription: "Target Temperature",
|
|
||||||
designedMinValue: 16,
|
|
||||||
designedMaxValue: 38,
|
|
||||||
designedMinStep: 1,
|
|
||||||
unit: "celsius"
|
|
||||||
},{
|
|
||||||
cType: types.TEMPERATURE_UNITS_CTYPE,
|
|
||||||
onUpdate: null,
|
|
||||||
onRead: function(callback) {
|
|
||||||
that.getTemperatureUnits(function(temperatureUnits){
|
|
||||||
callback(temperatureUnits);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
perms: ["pr","ev"],
|
|
||||||
format: "int",
|
|
||||||
initialValue: 0,
|
|
||||||
supportEvents: false,
|
|
||||||
supportBonjour: false,
|
|
||||||
manfDescription: "Unit",
|
|
||||||
},{
|
|
||||||
cType: types.CURRENT_RELATIVE_HUMIDITY_CTYPE,
|
|
||||||
onUpdate: null,
|
|
||||||
onRead: function(callback) {
|
|
||||||
that.getCurrentRelativeHumidity(function(currentRelativeHumidity){
|
|
||||||
callback(currentRelativeHumidity);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
perms: ["pr","ev"],
|
|
||||||
format: "int",
|
|
||||||
initialValue: 0,
|
|
||||||
supportEvents: false,
|
|
||||||
supportBonjour: false,
|
|
||||||
manfDescription: "Humidity",
|
|
||||||
}]
|
|
||||||
}];
|
|
||||||
}
|
}
|
||||||
}
|
var thermostat = this.getService(Service.Thermostat);
|
||||||
|
thermostat.getCharacteristic(Characteristic.TemperatureDisplayUnits).getValue();
|
||||||
|
thermostat.getCharacteristic(Characteristic.CurrentTemperature).getValue();
|
||||||
|
thermostat.getCharacteristic(Characteristic.CurrentHeatingCoolingState).getValue();
|
||||||
|
thermostat.getCharacteristic(Characteristic.TargetHeatingCoolingState).getValue();
|
||||||
|
thermostat.getCharacteristic(Characteristic.TargetTemperature).getValue();
|
||||||
|
};
|
||||||
|
|
||||||
|
NestThermostatAccessory.prototype.getCurrentHeatingCooling = function(){
|
||||||
|
var current = this.getCurrentTemperature();
|
||||||
|
var state = this.getTargetHeatingCooling();
|
||||||
|
|
||||||
|
var isRange = state == (Characteristic.CurrentHeatingCoolingState.HEAT | Characteristic.CurrentHeatingCoolingState.COOL);
|
||||||
|
var high = isRange ? this.currentData.target_temperature_high : this.currentData.target_temperature;
|
||||||
|
var low = isRange ? this.currentData.target_temperature_low : this.currentData.target_temperature;
|
||||||
|
|
||||||
|
// Add threshold
|
||||||
|
var threshold = .2;
|
||||||
|
high += threshold;
|
||||||
|
low -= threshold;
|
||||||
|
|
||||||
|
if ((state & Characteristic.CurrentHeatingCoolingState.COOL) && this.currentData.can_cool && high < current) {
|
||||||
|
return Characteristic.CurrentHeatingCoolingState.COOL;
|
||||||
|
}
|
||||||
|
if ((state & Characteristic.CurrentHeatingCoolingState.HEAT) && this.currentData.can_heat && low > current) {
|
||||||
|
return Characteristic.CurrentHeatingCoolingState.HEAT;
|
||||||
|
}
|
||||||
|
return Characteristic.CurrentHeatingCoolingState.OFF;
|
||||||
|
};
|
||||||
|
|
||||||
|
NestThermostatAccessory.prototype.getTargetHeatingCooling = function(){
|
||||||
|
switch(this.currentData.target_temperature_type) {
|
||||||
|
case "off":
|
||||||
|
return Characteristic.CurrentHeatingCoolingState.OFF;
|
||||||
|
case "heat":
|
||||||
|
return Characteristic.CurrentHeatingCoolingState.HEAT;
|
||||||
|
case "cool":
|
||||||
|
return Characteristic.CurrentHeatingCoolingState.COOL;
|
||||||
|
case "range":
|
||||||
|
return Characteristic.CurrentHeatingCoolingState.HEAT | Characteristic.CurrentHeatingCoolingState.COOL;
|
||||||
|
default:
|
||||||
|
return Characteristic.CurrentHeatingCoolingState.OFF;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
NestThermostatAccessory.prototype.getCurrentTemperature = function(){
|
||||||
|
return this.currentData.current_temperature;
|
||||||
|
};
|
||||||
|
|
||||||
|
NestThermostatAccessory.prototype.getTargetTemperature = function() {
|
||||||
|
switch (this.getTargetHeatingCooling()) {
|
||||||
|
case Characteristic.CurrentHeatingCoolingState.HEAT | Characteristic.CurrentHeatingCoolingState.COOL:
|
||||||
|
// Choose closest target as single target
|
||||||
|
var high = this.currentData.target_temperature_high;
|
||||||
|
var low = this.currentData.target_temperature_low;
|
||||||
|
var cur = this.currentData.current_temperature;
|
||||||
|
return Math.abs(high - cur) < Math.abs(cur - low) ? high : low;
|
||||||
|
default:
|
||||||
|
return this.currentData.target_temperature;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
NestThermostatAccessory.prototype.getTemperatureUnits = function() {
|
||||||
|
switch(this.device.temperature_scale) {
|
||||||
|
case "F":
|
||||||
|
return Characteristic.TemperatureDisplayUnits.FAHRENHEIT;
|
||||||
|
case "C":
|
||||||
|
return Characteristic.TemperatureDisplayUnits.CELSIUS;
|
||||||
|
default:
|
||||||
|
return Characteristic.TemperatureDisplayUnits.CELSIUS;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
NestThermostatAccessory.prototype.setTargetHeatingCooling = function(targetHeatingCooling, callback){
|
||||||
|
var targetTemperatureType = null;
|
||||||
|
|
||||||
|
switch(targetHeatingCooling) {
|
||||||
|
case Characteristic.CurrentHeatingCoolingState.HEAT:
|
||||||
|
targetTemperatureType = 'heat';
|
||||||
|
break;
|
||||||
|
case Characteristic.CurrentHeatingCoolingState.COOL:
|
||||||
|
targetTemperatureType = 'cool';
|
||||||
|
break;
|
||||||
|
case Characteristic.CurrentHeatingCoolingState.HEAT | Characteristic.CurrentHeatingCoolingState.COOL:
|
||||||
|
targetTemperatureType = 'range';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
targetTemperatureType = 'off';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.log("Setting target heating cooling for " + this.name + " to: " + targetTemperatureType);
|
||||||
|
nest.setTargetTemperatureType(this.deviceId, targetTemperatureType);
|
||||||
|
|
||||||
|
if (callback) callback(null, targetTemperatureType);
|
||||||
|
};
|
||||||
|
|
||||||
|
NestThermostatAccessory.prototype.setTargetTemperature = function(targetTemperature, callback){
|
||||||
|
|
||||||
|
switch (this.getTargetHeatingCooling()) {
|
||||||
|
case Characteristic.CurrentHeatingCoolingState.HEAT | Characteristic.CurrentHeatingCoolingState.COOL:
|
||||||
|
// Choose closest target as single target
|
||||||
|
var high = this.currentData.target_temperature_high;
|
||||||
|
var low = this.currentData.target_temperature_low;
|
||||||
|
var cur = this.currentData.current_temperature;
|
||||||
|
var isHighTemp = Math.abs(high - cur) < Math.abs(cur - low);
|
||||||
|
if (isHighTemp) {
|
||||||
|
high = targetTemperature;
|
||||||
|
} else {
|
||||||
|
low = targetTemperature;
|
||||||
|
}
|
||||||
|
this.log("Setting " + (isHighTemp ? "high" : "low") + " target temperature for " + this.name + " to: " + targetTemperature);
|
||||||
|
nest.setTemperatureRange(this.deviceId, low, high);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.log("Setting target temperature for " + this.name + " to: " + targetTemperature);
|
||||||
|
nest.setTemperature(this.deviceId, targetTemperature);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callback) callback(null, targetTemperature);
|
||||||
|
};
|
||||||
|
|
||||||
module.exports.accessory = NestThermostatAccessory;
|
module.exports.accessory = NestThermostatAccessory;
|
||||||
module.exports.platform = NestPlatform;
|
module.exports.platform = NestPlatform;
|
||||||
|
|||||||
388
platforms/Netatmo.js
Normal file
388
platforms/Netatmo.js
Normal file
@@ -0,0 +1,388 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Netatmo weatherstation for HomeBridge
|
||||||
|
// Wriiten by planetk (https://github.com/planetk)
|
||||||
|
//
|
||||||
|
// Remember to add platform to config.json. Example:
|
||||||
|
// "platforms": [
|
||||||
|
// {
|
||||||
|
// "platform": "Netatmo",
|
||||||
|
// "name": "Netatmo Weather",
|
||||||
|
// "auth": {
|
||||||
|
// "client_id": "",
|
||||||
|
// "client_secret": "",
|
||||||
|
// "username": "",
|
||||||
|
// "password": ""
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// ],
|
||||||
|
//
|
||||||
|
// The default code for all HomeBridge accessories is 031-45-154.
|
||||||
|
|
||||||
|
var types = require("hap-nodejs/accessories/types.js");
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// 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";
|
||||||
|
|
||||||
|
types.BATTERY_SERVICE_STYPE = stPre + "96" + stPost;
|
||||||
|
types.AIR_QUALITY_SENSOR_STYPE = stPre + "8D" + stPost;
|
||||||
|
types.CARBON_DIOXIDE_SENSOR_STYPE = stPre + "97" + stPost;
|
||||||
|
|
||||||
|
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 netatmo = require("netatmo");
|
||||||
|
var NodeCache = require("node-cache");
|
||||||
|
|
||||||
|
function NetAtmoRepository(log, api) {
|
||||||
|
this.api = api;
|
||||||
|
this.log = log;
|
||||||
|
this.cache = new NodeCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
NetAtmoRepository.prototype = {
|
||||||
|
refresh: function(callback) {
|
||||||
|
var datasource = {
|
||||||
|
modules: {}
|
||||||
|
};
|
||||||
|
var that = this;
|
||||||
|
that.api.getStationsData(function(err, devices) {
|
||||||
|
|
||||||
|
// querying for the device infos and the main module
|
||||||
|
for (var device of devices) {
|
||||||
|
device.module_name = device.station_name + " " + device.module_name
|
||||||
|
|
||||||
|
that.log("refreshing device " + device._id + " (" + device.module_name + ")");
|
||||||
|
datasource.modules[device._id] = device;
|
||||||
|
|
||||||
|
// querying for the extra modules
|
||||||
|
for (var module of device.modules) {
|
||||||
|
module.module_name = device.station_name + " " + module.module_name
|
||||||
|
|
||||||
|
that.log("refreshing device " + module._id + " (" + module.module_name + ")");
|
||||||
|
datasource.modules[module._id] = module;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
that.cache.set( "datasource", datasource, 20 );
|
||||||
|
callback(datasource);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
load: function(callback) {
|
||||||
|
var that = this;
|
||||||
|
this.cache.get( "datasource", function(err, datasource) {
|
||||||
|
if(!err) {
|
||||||
|
if (datasource == undefined) {
|
||||||
|
that.refresh(callback);
|
||||||
|
} else {
|
||||||
|
callback(datasource)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function NetatmoPlatform(log, config) {
|
||||||
|
this.log = log;
|
||||||
|
var api = new netatmo(config["auth"]);
|
||||||
|
this.repository = new NetAtmoRepository(this.log, api);
|
||||||
|
api.on("error", function(error) {
|
||||||
|
this.log('ERROR - Netatmo: ' + error);
|
||||||
|
});
|
||||||
|
api.on("warning", function(error) {
|
||||||
|
this.log('WARN - Netatmo: ' + error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
NetatmoPlatform.prototype = {
|
||||||
|
accessories: function(callback) {
|
||||||
|
|
||||||
|
var that = this;
|
||||||
|
var foundAccessories = [];
|
||||||
|
|
||||||
|
this.repository.load(function(datasource) {
|
||||||
|
for (var id in datasource.modules) {
|
||||||
|
var device = datasource.modules[id];
|
||||||
|
var accessory = new NetatmoAccessory(that.log, that.repository, device);
|
||||||
|
foundAccessories.push(accessory);
|
||||||
|
}
|
||||||
|
callback(foundAccessories);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function NetatmoAccessory(log, repository, device) {
|
||||||
|
this.log = log;
|
||||||
|
this.repository = repository;
|
||||||
|
this.deviceId = device._id;
|
||||||
|
this.name = device.module_name
|
||||||
|
this.serial = device._id;
|
||||||
|
|
||||||
|
this.model = device.type;
|
||||||
|
this.serviceTypes = device.data_type;
|
||||||
|
if (device.battery_vp) {
|
||||||
|
this.serviceTypes.push("Battery");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NetatmoAccessory.prototype = {
|
||||||
|
|
||||||
|
getData: function(callback) {
|
||||||
|
var that = this;
|
||||||
|
this.repository.load(function(datasource) {
|
||||||
|
callback(datasource.modules[that.deviceId]);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getCurrentTemperature: function(callback) {
|
||||||
|
this.getData(function(deviceData) {
|
||||||
|
callback(deviceData.dashboard_data.Temperature);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getCurrentHumidity: function(callback) {
|
||||||
|
this.getData(function(deviceData) {
|
||||||
|
callback(deviceData.dashboard_data.Humidity);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getAirQuality: function(callback) {
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getCurrentCO2Level: function(callback) {
|
||||||
|
this.log("fetching co2");
|
||||||
|
this.getData(function(deviceData) {
|
||||||
|
callback(deviceData.dashboard_data.CO2);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
getServices: function() {
|
||||||
|
var that = this;
|
||||||
|
var services = [{
|
||||||
|
sType: types.ACCESSORY_INFORMATION_STYPE,
|
||||||
|
characteristics: this.informationCharacteristics(),
|
||||||
|
}];
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Pressure
|
||||||
|
// TODO: Noise
|
||||||
|
// TODO: Battery
|
||||||
|
// TODO: Check Elgato Eve Characteristics (map min, max, time series, etc.)!
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.accessory = NetatmoAccessory;
|
||||||
|
module.exports.platform = NetatmoPlatform;
|
||||||
573
platforms/Openhab.js
Normal file
573
platforms/Openhab.js
Normal file
@@ -0,0 +1,573 @@
|
|||||||
|
// OpenHAB 1 Platform Shim for HomeBridge
|
||||||
|
// Written by Tommaso Marchionni
|
||||||
|
// Based on many of the other HomeBridge platform modules
|
||||||
|
//
|
||||||
|
// Revisions:
|
||||||
|
//
|
||||||
|
// 17 October 2015 [tommasomarchionni]
|
||||||
|
// - Initial release
|
||||||
|
//
|
||||||
|
// 25 October 2015 [tommasomarchionni]
|
||||||
|
// - Added WS listener and new OOP structure
|
||||||
|
//
|
||||||
|
// Remember to add platform to config.json. Example:
|
||||||
|
// "platforms": [
|
||||||
|
// {
|
||||||
|
// "platform": "Openhab",
|
||||||
|
// "name": "Openhab",
|
||||||
|
// "server": "127.0.0.1",
|
||||||
|
// "port": "8080",
|
||||||
|
// "sitemap": "demo"
|
||||||
|
// }
|
||||||
|
// ],
|
||||||
|
//
|
||||||
|
// Example of sitemap in OpenHAB:
|
||||||
|
// sitemap homekit label="HomeKit" {
|
||||||
|
// Switch item=Light_1 label="Light 1"
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Rollershutter is tested with this binding in OpenHAB:
|
||||||
|
// command=SWITCH_MULTILEVEL,invert_percent=true,invert_state=false"
|
||||||
|
// When you attempt to add a device, it will ask for a "PIN code".
|
||||||
|
// The default code for all HomeBridge accessories is 031-45-154.
|
||||||
|
//
|
||||||
|
|
||||||
|
//////// LIBS /////////
|
||||||
|
|
||||||
|
var WebSocket = require('ws');
|
||||||
|
var request = require("request");
|
||||||
|
var Service = require("hap-nodejs/lib/Service.js").Service;
|
||||||
|
var Characteristic = require("hap-nodejs").Characteristic;
|
||||||
|
var currentModule = this;
|
||||||
|
var util = require('core-util-is');
|
||||||
|
util.inherits = require('inherits');
|
||||||
|
|
||||||
|
//////// PLATFORM /////////
|
||||||
|
|
||||||
|
function OpenhabPlatform(log, config){
|
||||||
|
this.log = log;
|
||||||
|
this.user = config["user"];
|
||||||
|
this.password = config["password"];
|
||||||
|
this.server = config["server"];
|
||||||
|
this.port = config["port"];
|
||||||
|
this.protocol = "http";
|
||||||
|
this.sitemap = "demo";
|
||||||
|
if (typeof config["sitemap"] != 'undefined') {
|
||||||
|
this.sitemap = config["sitemap"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenhabPlatform.prototype = {
|
||||||
|
accessories: function(callback) {
|
||||||
|
var that = this;
|
||||||
|
this.log("Platform - Fetching OpenHAB devices.");
|
||||||
|
var itemFactory = new ItemFactory(this);
|
||||||
|
url = itemFactory.sitemapUrl();
|
||||||
|
this.log("Platform - Connecting to " + url);
|
||||||
|
request.get({
|
||||||
|
url: url,
|
||||||
|
json: true
|
||||||
|
}, function(err, response, json) {
|
||||||
|
if (!err && response.statusCode == 200) {
|
||||||
|
callback(itemFactory.parseSitemap(json));
|
||||||
|
} else {
|
||||||
|
that.log("Platform - There was a problem connecting to OpenHAB.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//////// END PLATFORM /////////
|
||||||
|
|
||||||
|
///////// ACCESSORY /////////
|
||||||
|
|
||||||
|
function OpenhabAccessory(widget,platform) {}
|
||||||
|
|
||||||
|
///////// ABSTRACT ITEM /////////
|
||||||
|
|
||||||
|
function AbstractItem(widget,platform){
|
||||||
|
|
||||||
|
AbstractItem.super_.call(this,widget,platform);
|
||||||
|
|
||||||
|
this.widget = widget;
|
||||||
|
this.label = widget.label;
|
||||||
|
this.name = widget.item.name;
|
||||||
|
this.url = widget.item.link;
|
||||||
|
this.state = widget.item.state;
|
||||||
|
this.platform = platform;
|
||||||
|
this.log = platform.log;
|
||||||
|
|
||||||
|
this.setInitialState = false;
|
||||||
|
this.setFromOpenHAB = false;
|
||||||
|
this.informationService = undefined;
|
||||||
|
this.otherService = undefined;
|
||||||
|
this.listener = undefined;
|
||||||
|
this.ws = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
util.inherits(AbstractItem, OpenhabAccessory);
|
||||||
|
|
||||||
|
AbstractItem.prototype.getInformationServices = function() {
|
||||||
|
informationService = new Service.AccessoryInformation();
|
||||||
|
|
||||||
|
informationService
|
||||||
|
.setCharacteristic(Characteristic.Manufacturer, "OpenHAB")
|
||||||
|
.setCharacteristic(Characteristic.Model, this.constructor.name)
|
||||||
|
.setCharacteristic(Characteristic.SerialNumber, "N/A")
|
||||||
|
.setCharacteristic(Characteristic.Name, this.name);
|
||||||
|
return informationService;
|
||||||
|
}
|
||||||
|
|
||||||
|
AbstractItem.prototype.checkListener = function() {
|
||||||
|
|
||||||
|
if (typeof this.listener == 'undefined' || typeof this.ws == 'undefined') {
|
||||||
|
this.ws = undefined;
|
||||||
|
this.listener = new WSListener(this, this.updateCharacteristics.bind(this));
|
||||||
|
this.listener.startListener();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
///////// END ABSTRACT ITEM /////////
|
||||||
|
|
||||||
|
///////// SWITCH ITEM /////////
|
||||||
|
|
||||||
|
function SwitchItem(widget,platform){
|
||||||
|
SwitchItem.super_.call(this, widget,platform);
|
||||||
|
};
|
||||||
|
|
||||||
|
util.inherits(SwitchItem, AbstractItem);
|
||||||
|
|
||||||
|
SwitchItem.prototype.getServices = function() {
|
||||||
|
|
||||||
|
this.checkListener();
|
||||||
|
this.setInitialState = true;
|
||||||
|
this.informationService = this.getInformationServices();
|
||||||
|
|
||||||
|
this.otherService = new Service.Lightbulb();
|
||||||
|
this.otherService.getCharacteristic(Characteristic.On)
|
||||||
|
.on('set', this.setItem.bind(this))
|
||||||
|
.on('get', this.getItemPowerState.bind(this))
|
||||||
|
.setValue(this.state === 'ON');
|
||||||
|
|
||||||
|
return [this.informationService, this.otherService];
|
||||||
|
};
|
||||||
|
|
||||||
|
SwitchItem.prototype.updateCharacteristics = function(message) {
|
||||||
|
|
||||||
|
this.setFromOpenHAB = true;
|
||||||
|
this.otherService
|
||||||
|
.getCharacteristic(Characteristic.On)
|
||||||
|
.setValue(message === 'ON' ? true : false,
|
||||||
|
function() {
|
||||||
|
this.setFromOpenHAB = false;
|
||||||
|
}.bind(this)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
SwitchItem.prototype.getItemPowerState = function(callback) {
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
this.checkListener();
|
||||||
|
|
||||||
|
this.log("iOS - request power state from " + this.name);
|
||||||
|
request(this.url + '/state?type=json', function (error, response, body) {
|
||||||
|
if (!error && response.statusCode == 200) {
|
||||||
|
self.log("OpenHAB HTTP - response from " + self.name + ": " + body);
|
||||||
|
callback(undefined,body == "ON" ? true : false);
|
||||||
|
} else {
|
||||||
|
self.log("OpenHAB HTTP - error from " + self.name + ": " + error);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
SwitchItem.prototype.setItem = function(value, callback) {
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
this.checkListener();
|
||||||
|
|
||||||
|
if (this.setInitialState) {
|
||||||
|
this.setInitialState = false;
|
||||||
|
callback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.setFromOpenHAB) {
|
||||||
|
callback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.log("iOS - send message to " + this.name + ": " + value);
|
||||||
|
var command = value ? 'ON' : 'OFF';
|
||||||
|
request.post(
|
||||||
|
this.url,
|
||||||
|
{ body: command },
|
||||||
|
function (error, response, body) {
|
||||||
|
if (!error && response.statusCode == 201) {
|
||||||
|
self.log("OpenHAB HTTP - response from " + self.name + ": " + body);
|
||||||
|
} else {
|
||||||
|
self.log("OpenHAB HTTP - error from " + self.name + ": " + error);
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
///////// END SWITCH ITEM /////////
|
||||||
|
|
||||||
|
///////// DIMMER ITEM /////////
|
||||||
|
|
||||||
|
function DimmerItem(widget,platform){
|
||||||
|
DimmerItem.super_.call(this, widget,platform);
|
||||||
|
};
|
||||||
|
|
||||||
|
util.inherits(DimmerItem, AbstractItem);
|
||||||
|
|
||||||
|
DimmerItem.prototype.getServices = function() {
|
||||||
|
|
||||||
|
this.checkListener();
|
||||||
|
this.setInitialState = true;
|
||||||
|
|
||||||
|
this.informationService = this.getInformationServices();
|
||||||
|
|
||||||
|
this.otherService = new Service.Lightbulb();
|
||||||
|
this.otherService.getCharacteristic(Characteristic.On)
|
||||||
|
.on('set', this.setItem.bind(this))
|
||||||
|
.on('get', this.getItemPowerState.bind(this))
|
||||||
|
.setValue(+this.state > 0);
|
||||||
|
|
||||||
|
this.setInitialState = true;
|
||||||
|
|
||||||
|
this.otherService.addCharacteristic(Characteristic.Brightness)
|
||||||
|
.on('set', this.setItem.bind(this))
|
||||||
|
.on('get', this.getItemBrightnessState.bind(this))
|
||||||
|
.setValue(+this.state);
|
||||||
|
|
||||||
|
return [this.informationService, this.otherService];
|
||||||
|
};
|
||||||
|
|
||||||
|
DimmerItem.prototype.updateCharacteristics = function(message) {
|
||||||
|
|
||||||
|
this.setFromOpenHAB = true;
|
||||||
|
var brightness = +message;
|
||||||
|
var steps = 2;
|
||||||
|
if (brightness >= 0) {
|
||||||
|
this.otherService.getCharacteristic(Characteristic.Brightness)
|
||||||
|
.setValue(brightness,
|
||||||
|
function() {
|
||||||
|
steps--;
|
||||||
|
if (!steps) {
|
||||||
|
this.setFromOpenHAB = false;
|
||||||
|
}
|
||||||
|
}.bind(this));
|
||||||
|
this.otherService.getCharacteristic(Characteristic.On)
|
||||||
|
.setValue(brightness > 0 ? true : false,
|
||||||
|
function() {
|
||||||
|
steps--;
|
||||||
|
if (!steps) {
|
||||||
|
this.setFromOpenHAB = false;
|
||||||
|
}
|
||||||
|
}.bind(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DimmerItem.prototype.getItemPowerState = function(callback) {
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
this.checkListener();
|
||||||
|
|
||||||
|
this.log("iOS - request power state from " + this.name);
|
||||||
|
request(this.url + '/state?type=json', function (error, response, body) {
|
||||||
|
if (!error && response.statusCode == 200) {
|
||||||
|
self.log("OpenHAB HTTP - response from " + self.name + ": " + body);
|
||||||
|
callback(undefined,+body > 0 ? true : false);
|
||||||
|
} else {
|
||||||
|
self.log("OpenHAB HTTP - error from " + self.name + ": " + error);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
DimmerItem.prototype.setItem = function(value, callback) {
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
this.checkListener();
|
||||||
|
|
||||||
|
if (this.setInitialState) {
|
||||||
|
this.setInitialState = false;
|
||||||
|
callback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.setFromOpenHAB) {
|
||||||
|
callback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.log("iOS - send message to " + this.name + ": " + value);
|
||||||
|
var command = 0;
|
||||||
|
if (typeof value === 'boolean') {
|
||||||
|
command = value ? '100' : '0';
|
||||||
|
} else {
|
||||||
|
command = "" + value;
|
||||||
|
}
|
||||||
|
request.post(
|
||||||
|
this.url,
|
||||||
|
{
|
||||||
|
body: command,
|
||||||
|
headers: {'Content-Type': 'text/plain'}
|
||||||
|
},
|
||||||
|
function (error, response, body) {
|
||||||
|
if (!error && response.statusCode == 201) {
|
||||||
|
self.log("OpenHAB HTTP - response from " + self.name + ": " + body);
|
||||||
|
} else {
|
||||||
|
self.log("OpenHAB HTTP - error from " + self.name + ": " + error);
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
DimmerItem.prototype.getItemBrightnessState = function(callback) {
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
this.log("iOS - request brightness state from " + this.name);
|
||||||
|
request(this.url + '/state?type=json', function (error, response, body) {
|
||||||
|
if (!error && response.statusCode == 200) {
|
||||||
|
self.log("OpenHAB HTTP - response from " + self.name + ": " + body);
|
||||||
|
callback(undefined,+body);
|
||||||
|
} else {
|
||||||
|
self.log("OpenHAB HTTP - error from " + self.name + ": " + error);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
///////// END DIMMER ITEM /////////
|
||||||
|
|
||||||
|
///////// ROLLERSHUTTER ITEM /////////
|
||||||
|
|
||||||
|
function RollershutterItem(widget,platform){
|
||||||
|
RollershutterItem.super_.call(this, widget,platform);
|
||||||
|
this.positionState = Characteristic.PositionState.STOPPED;
|
||||||
|
this.currentPosition = 100;
|
||||||
|
this.targetPosition = 100;
|
||||||
|
this.startedPosition = 100;
|
||||||
|
};
|
||||||
|
|
||||||
|
util.inherits(RollershutterItem, AbstractItem);
|
||||||
|
|
||||||
|
RollershutterItem.prototype.getServices = function() {
|
||||||
|
|
||||||
|
this.checkListener();
|
||||||
|
|
||||||
|
this.informationService = this.getInformationServices();
|
||||||
|
|
||||||
|
this.otherService = new Service.WindowCovering();
|
||||||
|
|
||||||
|
this.otherService.getCharacteristic(Characteristic.CurrentPosition)
|
||||||
|
.on('get', this.getItemCurrentPosition.bind(this))
|
||||||
|
.setValue(this.currentPosition);
|
||||||
|
|
||||||
|
this.setInitialState = true;
|
||||||
|
|
||||||
|
this.otherService.getCharacteristic(Characteristic.TargetPosition)
|
||||||
|
.on('set', this.setItem.bind(this))
|
||||||
|
.on('get', this.getItemTargetPosition.bind(this))
|
||||||
|
.setValue(this.currentPosition);
|
||||||
|
|
||||||
|
this.otherService.getCharacteristic(Characteristic.PositionState)
|
||||||
|
.on('get', this.getItemPositionState.bind(this))
|
||||||
|
.setValue(this.positionState);
|
||||||
|
|
||||||
|
return [this.informationService, this.otherService];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
RollershutterItem.prototype.updateCharacteristics = function(message) {
|
||||||
|
|
||||||
|
console.log(message);
|
||||||
|
console.log(this.targetPosition);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (parseInt(message) == this.targetPosition) {
|
||||||
|
var ps = Characteristic.PositionState.STOPPED;
|
||||||
|
var cs = parseInt(message);
|
||||||
|
} else if (parseInt(message) > this.targetPosition){
|
||||||
|
var ps = Characteristic.PositionState.INCREASING;
|
||||||
|
var cs = this.startedPosition;
|
||||||
|
} else {
|
||||||
|
var ps = Characteristic.PositionState.DECREASING;
|
||||||
|
var cs = this.startedPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.otherService
|
||||||
|
.getCharacteristic(Characteristic.PositionState)
|
||||||
|
.setValue(ps);
|
||||||
|
|
||||||
|
this.otherService
|
||||||
|
.getCharacteristic(Characteristic.CurrentPosition)
|
||||||
|
.setValue(parseInt(cs));
|
||||||
|
this.currentPosition = parseInt(cs);
|
||||||
|
};
|
||||||
|
|
||||||
|
RollershutterItem.prototype.setItem = function(value, callback) {
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
this.checkListener();
|
||||||
|
|
||||||
|
if (this.setInitialState) {
|
||||||
|
this.setInitialState = false;
|
||||||
|
callback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.startedPosition = this.currentPosition;
|
||||||
|
|
||||||
|
this.log("iOS - send message to " + this.name + ": " + value);
|
||||||
|
|
||||||
|
var command = 0;
|
||||||
|
if (typeof value === 'boolean') {
|
||||||
|
command = value ? '100' : '0';
|
||||||
|
} else {
|
||||||
|
command = "" + value;
|
||||||
|
}
|
||||||
|
request.post(
|
||||||
|
this.url,
|
||||||
|
{
|
||||||
|
body: command,
|
||||||
|
headers: {'Content-Type': 'text/plain'}
|
||||||
|
},
|
||||||
|
function (error, response, body) {
|
||||||
|
if (!error && response.statusCode == 201) {
|
||||||
|
self.log("OpenHAB HTTP - response from " + self.name + ": " + body);
|
||||||
|
self.targetPosition = parseInt(value);
|
||||||
|
} else {
|
||||||
|
self.log("OpenHAB HTTP - error from " + self.name + ": " + error);
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
RollershutterItem.prototype.getItemPositionState = function(callback) {
|
||||||
|
this.log("iOS - request position state from " + this.name);
|
||||||
|
this.log("Platform - response from " + this.name + ": " + this.positionState);
|
||||||
|
callback(undefined,this.positionState);
|
||||||
|
};
|
||||||
|
|
||||||
|
RollershutterItem.prototype.getItemTargetPosition = function(callback) {
|
||||||
|
this.log("iOS - get target position state from " + this.name);
|
||||||
|
this.log("Platform - response from " + this.name + ": " + this.targetPosition);
|
||||||
|
callback(undefined,this.targetPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
RollershutterItem.prototype.getItemCurrentPosition = function(callback) {
|
||||||
|
var self = this;
|
||||||
|
this.log("iOS - request current position state from " + this.name);
|
||||||
|
|
||||||
|
request(this.url + '/state?type=json', function (error, response, body) {
|
||||||
|
if (!error && response.statusCode == 200) {
|
||||||
|
|
||||||
|
self.log("OpenHAB HTTP - response from " + self.name + ": " +body);
|
||||||
|
self.currentPosition = parseInt(body);
|
||||||
|
callback(undefined,parseInt(body));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
self.log("OpenHAB HTTP - error from " + self.name + ": " + error);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
///////// END ROLLERSHUTTER ITEM /////////
|
||||||
|
|
||||||
|
///////// ITEM UTILITY /////////
|
||||||
|
|
||||||
|
function ItemFactory(openhabPlatform){
|
||||||
|
this.platform = openhabPlatform;
|
||||||
|
this.log = this.platform.log;
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemFactory.prototype = {
|
||||||
|
sitemapUrl: function() {
|
||||||
|
var serverString = this.platform.server;
|
||||||
|
//TODO da verificare
|
||||||
|
if (this.platform.user && this.platform.password) {
|
||||||
|
serverString = this.platform.user + ":" + this.platform.password + "@" + serverString;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.platform.protocol + "://" + serverString + ":" + this.platform.port + "/rest/sitemaps/" + this.platform.sitemap + "?type=json";
|
||||||
|
},
|
||||||
|
|
||||||
|
parseSitemap: function(jsonSitemap) {
|
||||||
|
var widgets = [].concat(jsonSitemap.homepage.widget);
|
||||||
|
|
||||||
|
var result = [];
|
||||||
|
for (var i = 0; i < widgets.length; i++) {
|
||||||
|
var widget = widgets[i];
|
||||||
|
if (!widget.item) {
|
||||||
|
//TODO to handle frame
|
||||||
|
this.log("Platform - The widget '" + widget.label + "' is not an item.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentModule[widget.item.type] != undefined) {
|
||||||
|
var accessory = new currentModule[widget.item.type](widget,this.platform);
|
||||||
|
} else {
|
||||||
|
this.log("Platform - The widget '" + widget.label + "' of type "+widget.item.type+" is an item not handled.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.log("Platform - Accessory Found: " + widget.label);
|
||||||
|
result.push(accessory);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
///////// END ITEM UTILITY /////////
|
||||||
|
|
||||||
|
///////// WS LISTENER /////////
|
||||||
|
|
||||||
|
function WSListener(item, callback){
|
||||||
|
this.item = item;
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
WSListener.prototype = {
|
||||||
|
startListener: function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (typeof this.item.ws == 'undefined') {
|
||||||
|
this.item.ws = new WebSocket(this.item.url.replace('http:', 'ws:') + '/state?type=json');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.item.ws.on('open', function() {
|
||||||
|
self.item.log("OpenHAB WS - new connection for "+self.item.name);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.item.ws.on('message', function(message) {
|
||||||
|
self.item.log("OpenHAB WS - message from " +self.item.name+": "+ message);
|
||||||
|
self.callback(message);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.item.ws.on('close', function close() {
|
||||||
|
self.item.log("OpenHAB WS - closed connection for "+self.item.name);
|
||||||
|
self.item.listener = undefined;
|
||||||
|
self.item.ws = undefined;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
///////// END WS LISTENER /////////
|
||||||
|
|
||||||
|
///////// SUPPORTED ITEMS /////////
|
||||||
|
module.exports.SwitchItem = SwitchItem;
|
||||||
|
module.exports.DimmerItem = DimmerItem;
|
||||||
|
module.exports.RollershutterItem = RollershutterItem;
|
||||||
|
///////// END SUPPORTED ITEMS /////////
|
||||||
|
|
||||||
|
module.exports.accessory = OpenhabAccessory;
|
||||||
|
module.exports.platform = OpenhabPlatform;
|
||||||
@@ -33,7 +33,9 @@ var hue = require("node-hue-api"),
|
|||||||
HueApi = hue.HueApi,
|
HueApi = hue.HueApi,
|
||||||
lightState = hue.lightState;
|
lightState = hue.lightState;
|
||||||
|
|
||||||
var types = require("HAP-NodeJS/accessories/types.js");
|
var Service = require("hap-nodejs").Service;
|
||||||
|
var Characteristic = require("hap-nodejs").Characteristic;
|
||||||
|
|
||||||
|
|
||||||
function PhilipsHuePlatform(log, config) {
|
function PhilipsHuePlatform(log, config) {
|
||||||
this.log = log;
|
this.log = log;
|
||||||
@@ -167,7 +169,7 @@ PhilipsHueAccessory.prototype = {
|
|||||||
// Convert 0-65535 to 0-360
|
// Convert 0-65535 to 0-360
|
||||||
hueToArcDegrees: function(value) {
|
hueToArcDegrees: function(value) {
|
||||||
value = value/65535;
|
value = value/65535;
|
||||||
value = value*100;
|
value = value*360;
|
||||||
value = Math.round(value);
|
value = Math.round(value);
|
||||||
return value;
|
return value;
|
||||||
},
|
},
|
||||||
@@ -185,9 +187,22 @@ PhilipsHueAccessory.prototype = {
|
|||||||
value = Math.round(value);
|
value = Math.round(value);
|
||||||
return value;
|
return value;
|
||||||
},
|
},
|
||||||
|
extractValue: function(characteristic, status) {
|
||||||
|
switch(characteristic.toLowerCase()) {
|
||||||
|
case 'power':
|
||||||
|
return status.state.on ? 1 : 0;
|
||||||
|
case 'hue':
|
||||||
|
return this.hueToArcDegrees(status.state.hue);
|
||||||
|
case 'brightness':
|
||||||
|
return this.bitsToPercentage(status.state.bri);
|
||||||
|
case 'saturation':
|
||||||
|
return this.bitsToPercentage(status.state.sat);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
// Create and set a light state
|
// Create and set a light state
|
||||||
executeChange: function(api, device, characteristic, value) {
|
executeChange: function(characteristic, value, callback) {
|
||||||
var that = this;
|
|
||||||
var state = lightState.create();
|
var state = lightState.create();
|
||||||
switch(characteristic.toLowerCase()) {
|
switch(characteristic.toLowerCase()) {
|
||||||
case 'identify':
|
case 'identify':
|
||||||
@@ -211,164 +226,112 @@ PhilipsHueAccessory.prototype = {
|
|||||||
state.saturation(value);
|
state.saturation(value);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
api.setLightState(device.id, state, function(err, lights) {
|
this.api.setLightState(this.id, state, function(err, lights) {
|
||||||
|
if (callback == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!err) {
|
if (!err) {
|
||||||
that.log(device.name + ", characteristic: " + characteristic + ", value: " + value + ".");
|
if (callback) callback(); // Success
|
||||||
|
callback = null;
|
||||||
|
this.log("Set " + this.device.name + ", characteristic: " + characteristic + ", value: " + value + ".");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (err.code == "ECONNRESET") {
|
if (err.code == "ECONNRESET") {
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
that.executeChange(api, device, characteristic, value);
|
this.executeChange(characteristic, value, callback);
|
||||||
}, 300);
|
}, 300);
|
||||||
} else {
|
} else {
|
||||||
that.log(err);
|
this.log(err);
|
||||||
|
callback(new Error(err));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
// Read light state
|
||||||
|
// TODO: implement clever polling/update and caching
|
||||||
|
// maybe a better NodeJS hue API exists for this
|
||||||
|
getState: function(characteristic, callback) {
|
||||||
|
this.api.lightStatus(this.id, function(err, status) {
|
||||||
|
if (callback == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
if (err.code == "ECONNRESET") {
|
||||||
|
setTimeout(function() {
|
||||||
|
this.getState(characteristic, callback);
|
||||||
|
}.bind(this), 300);
|
||||||
|
} else {
|
||||||
|
this.log(err);
|
||||||
|
callback(new Error(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
var newValue = this.extractValue(characteristic, status);
|
||||||
|
if (newValue != undefined) {
|
||||||
|
callback(null, newValue);
|
||||||
|
} else {
|
||||||
|
// this.log("Device " + that.device.name + " does not support reading characteristic " + characteristic);
|
||||||
|
// callback(Error("Device " + that.device.name + " does not support reading characteristic " + characteristic) );
|
||||||
|
}
|
||||||
|
|
||||||
|
callback = null;
|
||||||
|
|
||||||
|
//this.log("Get " + that.device.name + ", characteristic: " + characteristic + ", value: " + value + ".");
|
||||||
|
}
|
||||||
|
}.bind(this));
|
||||||
|
},
|
||||||
|
|
||||||
|
// Respond to identify request
|
||||||
|
identify: function(callback) {
|
||||||
|
this.executeChange("identify", true, callback);
|
||||||
|
},
|
||||||
|
|
||||||
// Get Services
|
// Get Services
|
||||||
getServices: function() {
|
getServices: function() {
|
||||||
var that = this;
|
var that = this;
|
||||||
var bulb_characteristics = [
|
|
||||||
{
|
// Use HomeKit types defined in HAP node JS
|
||||||
cType: types.NAME_CTYPE,
|
var lightbulbService = new Service.Lightbulb(this.name);
|
||||||
onUpdate: null,
|
|
||||||
perms: ["pr"],
|
// Basic light controls, common to Hue and Hue lux
|
||||||
format: "string",
|
lightbulbService
|
||||||
initialValue: this.name,
|
.getCharacteristic(Characteristic.On)
|
||||||
supportEvents: false,
|
.on('get', function(callback) { that.getState("power", callback);})
|
||||||
supportBonjour: false,
|
.on('set', function(value, callback) { that.executeChange("power", value, callback);})
|
||||||
manfDescription: "Name of service",
|
.value = this.extractValue("power", this.device);
|
||||||
designedMaxLength: 255
|
|
||||||
},{
|
lightbulbService
|
||||||
cType: types.POWER_STATE_CTYPE,
|
.addCharacteristic(Characteristic.Brightness)
|
||||||
onUpdate: function(value) {
|
.on('get', function(callback) { that.getState("brightness", callback);})
|
||||||
that.executeChange(that.api, that.device, "power", value);
|
.on('set', function(value, callback) { that.executeChange("brightness", value, callback);})
|
||||||
},
|
.value = this.extractValue("brightness", this.device);
|
||||||
perms: ["pw","pr","ev"],
|
|
||||||
format: "bool",
|
// Handle the Hue/Hue Lux divergence
|
||||||
initialValue: that.device.state.on,
|
if (this.device.state.hasOwnProperty('hue') && this.device.state.hasOwnProperty('sat')) {
|
||||||
supportEvents: false,
|
lightbulbService
|
||||||
supportBonjour: false,
|
.addCharacteristic(Characteristic.Hue)
|
||||||
manfDescription: "Turn On the Light",
|
.on('get', function(callback) { that.getState("hue", callback);})
|
||||||
designedMaxLength: 1
|
.on('set', function(value, callback) { that.executeChange("hue", value, callback);})
|
||||||
},{
|
.value = this.extractValue("hue", this.device);
|
||||||
cType: types.BRIGHTNESS_CTYPE,
|
|
||||||
onUpdate: function(value) {
|
lightbulbService
|
||||||
that.executeChange(that.api, that.device, "brightness", value);
|
.addCharacteristic(Characteristic.Saturation)
|
||||||
},
|
.on('get', function(callback) { that.getState("saturation", callback);})
|
||||||
perms: ["pw","pr","ev"],
|
.on('set', function(value, callback) { that.executeChange("saturation", value, callback);})
|
||||||
format: "int",
|
.value = this.extractValue("saturation", this.device);
|
||||||
initialValue: that.bitsToPercentage(that.device.state.bri),
|
}
|
||||||
supportEvents: false,
|
|
||||||
supportBonjour: false,
|
var informationService = new Service.AccessoryInformation();
|
||||||
manfDescription: "Adjust Brightness of Light",
|
|
||||||
designedMinValue: 0,
|
informationService
|
||||||
designedMaxValue: 100,
|
.setCharacteristic(Characteristic.Manufacturer, "Philips")
|
||||||
designedMinStep: 1,
|
.setCharacteristic(Characteristic.Model, this.model)
|
||||||
unit: "%"
|
.setCharacteristic(Characteristic.SerialNumber, this.device.uniqueid)
|
||||||
}
|
.addCharacteristic(Characteristic.FirmwareRevision, this.device.swversion);
|
||||||
];
|
|
||||||
// Handle the Hue/Hue Lux divergence
|
return [informationService, lightbulbService];
|
||||||
if (that.device.state.hasOwnProperty('hue') && that.device.state.hasOwnProperty('sat')) {
|
|
||||||
bulb_characteristics.push({
|
|
||||||
cType: types.HUE_CTYPE,
|
|
||||||
onUpdate: function(value) {
|
|
||||||
that.executeChange(that.api, that.device, "hue", value);
|
|
||||||
},
|
|
||||||
perms: ["pw","pr","ev"],
|
|
||||||
format: "int",
|
|
||||||
initialValue: that.hueToArcDegrees(that.device.state.hue),
|
|
||||||
supportEvents: false,
|
|
||||||
supportBonjour: false,
|
|
||||||
manfDescription: "Adjust Hue of Light",
|
|
||||||
designedMinValue: 0,
|
|
||||||
designedMaxValue: 360,
|
|
||||||
designedMinStep: 1,
|
|
||||||
unit: "arcdegrees"
|
|
||||||
});
|
|
||||||
bulb_characteristics.push({
|
|
||||||
cType: types.SATURATION_CTYPE,
|
|
||||||
onUpdate: function(value) {
|
|
||||||
that.executeChange(that.api, that.device, "saturation", value);
|
|
||||||
},
|
|
||||||
perms: ["pw","pr","ev"],
|
|
||||||
format: "int",
|
|
||||||
initialValue: that.bitsToPercentage(that.device.state.sat),
|
|
||||||
supportEvents: false,
|
|
||||||
supportBonjour: false,
|
|
||||||
manfDescription: "Adjust Saturation of Light",
|
|
||||||
designedMinValue: 0,
|
|
||||||
designedMaxValue: 100,
|
|
||||||
designedMinStep: 1,
|
|
||||||
unit: "%"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
var accessory_data = [
|
|
||||||
{
|
|
||||||
sType: types.ACCESSORY_INFORMATION_STYPE,
|
|
||||||
characteristics: [
|
|
||||||
{
|
|
||||||
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: "Philips",
|
|
||||||
supportEvents: false,
|
|
||||||
supportBonjour: false,
|
|
||||||
manfDescription: "Manufacturer",
|
|
||||||
designedMaxLength: 255
|
|
||||||
},{
|
|
||||||
cType: types.MODEL_CTYPE,
|
|
||||||
onUpdate: null,
|
|
||||||
perms: ["pr"],
|
|
||||||
format: "string",
|
|
||||||
initialValue: that.model,
|
|
||||||
supportEvents: false,
|
|
||||||
supportBonjour: false,
|
|
||||||
manfDescription: "Model",
|
|
||||||
designedMaxLength: 255
|
|
||||||
},{
|
|
||||||
cType: types.SERIAL_NUMBER_CTYPE,
|
|
||||||
onUpdate: null,
|
|
||||||
perms: ["pr"],
|
|
||||||
format: "string",
|
|
||||||
initialValue: that.device.uniqueid,
|
|
||||||
supportEvents: false,
|
|
||||||
supportBonjour: false,
|
|
||||||
manfDescription: "SN",
|
|
||||||
designedMaxLength: 255
|
|
||||||
},{
|
|
||||||
cType: types.IDENTIFY_CTYPE,
|
|
||||||
onUpdate: function(value) {
|
|
||||||
that.executeChange(that.api, that.device, "identify", value);
|
|
||||||
},
|
|
||||||
perms: ["pw"],
|
|
||||||
format: "bool",
|
|
||||||
initialValue: false,
|
|
||||||
supportEvents: false,
|
|
||||||
supportBonjour: false,
|
|
||||||
manfDescription: "Identify Accessory",
|
|
||||||
designedMaxLength: 1
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},{
|
|
||||||
sType: types.LIGHTBULB_STYPE,
|
|
||||||
// `bulb_characteristics` defined based on bulb type
|
|
||||||
characteristics: bulb_characteristics
|
|
||||||
}
|
|
||||||
];
|
|
||||||
return accessory_data;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// SmartThings JSON API SmartApp required
|
// SmartThings JSON API SmartApp required
|
||||||
// https://github.com/jnewland/SmartThings/blob/master/JSON.groovy
|
// https://github.com/jnewland/SmartThings/blob/master/JSON.groovy
|
||||||
//
|
//
|
||||||
var types = require("HAP-NodeJS/accessories/types.js");
|
var types = require("hap-nodejs/accessories/types.js");
|
||||||
var request = require("request");
|
var request = require("request");
|
||||||
|
|
||||||
function SmartThingsPlatform(log, config){
|
function SmartThingsPlatform(log, config){
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
var types = require("HAP-NodeJS/accessories/types.js");
|
var types = require("hap-nodejs/accessories/types.js");
|
||||||
var sonos = require('sonos');
|
var sonos = require('sonos');
|
||||||
|
|
||||||
function SonosPlatform(log, config){
|
function SonosPlatform(log, config){
|
||||||
@@ -34,7 +34,7 @@ SonosPlatform.prototype = {
|
|||||||
that.log("Found device at " + device.host);
|
that.log("Found device at " + device.host);
|
||||||
|
|
||||||
device.deviceDescription(function (err, description) {
|
device.deviceDescription(function (err, description) {
|
||||||
if (description["zoneType"] != '11' && description["zoneType"] != '8') { // 8 is the Sonos SUB
|
if (description["zoneType"] != '11' && description["zoneType"] != '8' && description["zoneType"] != '4') { // 8 is the Sonos SUB, 4 is the Sonos Bridge
|
||||||
var roomName = description["roomName"];
|
var roomName = description["roomName"];
|
||||||
|
|
||||||
if (!roomNamesFound[roomName]) {
|
if (!roomNamesFound[roomName]) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
var types = require("HAP-NodeJS/accessories/types.js");
|
var types = require("hap-nodejs/accessories/types.js");
|
||||||
var telldus = require('telldus');
|
var telldus = require('telldus');
|
||||||
|
|
||||||
function TelldusPlatform(log, config) {
|
function TelldusPlatform(log, config) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
var types = require("HAP-NodeJS/accessories/types.js");
|
var types = require("hap-nodejs/accessories/types.js");
|
||||||
var TellduAPI = require("telldus-live");
|
var TellduAPI = require("telldus-live");
|
||||||
|
|
||||||
function TelldusLivePlatform(log, config) {
|
function TelldusLivePlatform(log, config) {
|
||||||
@@ -58,7 +58,7 @@ var TelldusLiveAccessory = function TelldusLiveAccessory(log, cloud, device) {
|
|||||||
this.log = log;
|
this.log = log;
|
||||||
this.cloud = cloud;
|
this.cloud = cloud;
|
||||||
|
|
||||||
var m = device.model.split(':');
|
var m = device.model ? device.model.split(':') : ['unknown', 'unknown'] ;
|
||||||
|
|
||||||
// Set accessory info
|
// Set accessory info
|
||||||
this.device = device;
|
this.device = device;
|
||||||
|
|||||||
@@ -1,28 +1,77 @@
|
|||||||
var types = require("HAP-NodeJS/accessories/types.js");
|
var types = require("hap-nodejs/accessories/types.js");
|
||||||
var wink = require('wink-js');
|
var wink = require('wink-js');
|
||||||
|
var Service = require("hap-nodejs").Service;
|
||||||
|
var Characteristic = require("hap-nodejs").Characteristic;
|
||||||
|
var Accessory = require("hap-nodejs").Accessory;
|
||||||
|
var uuid = require("hap-nodejs").uuid;
|
||||||
|
var inherits = require('util').inherits;
|
||||||
|
|
||||||
|
process.env.WINK_NO_CACHE = true;
|
||||||
|
|
||||||
var model = {
|
var model = {
|
||||||
light_bulbs: require('wink-js/lib/model/light')
|
light_bulbs: require('wink-js/lib/model/light'),
|
||||||
|
refreshUntil: function(that, maxTimes, predicate, callback, interval, incrementInterval) {
|
||||||
|
if (!interval) {
|
||||||
|
interval = 500;
|
||||||
|
}
|
||||||
|
if (!incrementInterval) {
|
||||||
|
incrementInterval = 500;
|
||||||
|
}
|
||||||
|
setTimeout(function() {
|
||||||
|
that.reloadData(function() {
|
||||||
|
if (predicate == undefined || predicate(that.device) == true) {
|
||||||
|
if (callback) callback(true);
|
||||||
|
} else if (maxTimes > 0) {
|
||||||
|
maxTimes = maxTimes - 1;
|
||||||
|
interval += incrementInterval;
|
||||||
|
model.refreshUntil(that, maxTimes, predicate, callback, interval, incrementInterval);
|
||||||
|
} else {
|
||||||
|
if (callback) callback(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, interval);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
function WinkPlatform(log, config){
|
function WinkPlatform(log, config){
|
||||||
|
|
||||||
// auth info
|
// auth info
|
||||||
this.client_id = config["client_id"];
|
this.client_id = config["client_id"];
|
||||||
this.client_secret = config["client_secret"];
|
this.client_secret = config["client_secret"];
|
||||||
|
|
||||||
this.username = config["username"];
|
this.username = config["username"];
|
||||||
this.password = config["password"];
|
this.password = config["password"];
|
||||||
|
|
||||||
this.log = log;
|
this.log = log;
|
||||||
|
this.deviceLookup = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
WinkPlatform.prototype = {
|
WinkPlatform.prototype = {
|
||||||
|
reloadData: function(callback) {
|
||||||
|
this.log("Refreshing Wink Data");
|
||||||
|
var that = this;
|
||||||
|
wink.user().devices(function(devices) {
|
||||||
|
for (var i=0; i<devices.data.length; i++){
|
||||||
|
var device = devices.data[i];
|
||||||
|
var accessory = that.deviceLookup[device.lock_id | device.light_bulb_id | ""];
|
||||||
|
if (accessory != undefined) {
|
||||||
|
accessory.device = device;
|
||||||
|
accessory.loadData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (callback) callback();
|
||||||
|
});
|
||||||
|
},
|
||||||
accessories: function(callback) {
|
accessories: function(callback) {
|
||||||
this.log("Fetching Wink devices.");
|
this.log("Fetching Wink devices.");
|
||||||
|
|
||||||
var that = this;
|
var that = this;
|
||||||
var foundAccessories = [];
|
var foundAccessories = [];
|
||||||
|
this.deviceLookup = {};
|
||||||
|
|
||||||
|
var refreshLoop = function(){
|
||||||
|
setInterval(that.reloadData.bind(that), 30000);
|
||||||
|
};
|
||||||
|
|
||||||
wink.init({
|
wink.init({
|
||||||
"client_id": this.client_id,
|
"client_id": this.client_id,
|
||||||
@@ -34,219 +83,268 @@ WinkPlatform.prototype = {
|
|||||||
that.log("There was a problem authenticating with Wink.");
|
that.log("There was a problem authenticating with Wink.");
|
||||||
} else {
|
} else {
|
||||||
// success
|
// success
|
||||||
wink.user().devices('light_bulbs', function(devices) {
|
wink.user().devices(function(devices) {
|
||||||
for (var i=0; i<devices.data.length; i++){
|
for (var i=0; i<devices.data.length; i++){
|
||||||
device = model.light_bulbs(devices.data[i], wink)
|
var device = devices.data[i];
|
||||||
accessory = new WinkAccessory(that.log, device);
|
var accessory = null;
|
||||||
foundAccessories.push(accessory);
|
if (device.light_bulb_id !== undefined) {
|
||||||
|
accessory = new WinkLightAccessory(that.log, device);
|
||||||
|
} else if (device.lock_id !== undefined) {
|
||||||
|
accessory = new WinkLockAccessory(that.log, device);
|
||||||
|
}
|
||||||
|
if (accessory != undefined) {
|
||||||
|
that.deviceLookup[accessory.deviceId] = accessory;
|
||||||
|
foundAccessories.push(accessory);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
refreshLoop();
|
||||||
callback(foundAccessories);
|
callback(foundAccessories);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function WinkAccessory(log, device) {
|
|
||||||
// device info
|
|
||||||
this.name = device.name;
|
|
||||||
this.device = device;
|
|
||||||
this.log = log;
|
|
||||||
}
|
|
||||||
|
|
||||||
WinkAccessory.prototype = {
|
|
||||||
getPowerState: function(callback){
|
|
||||||
if (!this.device) {
|
|
||||||
this.log("No '"+this.name+"' device found (yet?)");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var that = this;
|
|
||||||
|
|
||||||
this.log("checking power state for: " + this.name);
|
|
||||||
wink.user().device(this.name, function(light_obj){
|
|
||||||
powerState = light_obj.desired_state.powered
|
|
||||||
that.log("power state for " + that.name + " is: " + powerState)
|
|
||||||
callback(powerState);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
getBrightness: function(callback){
|
|
||||||
if (!this.device) {
|
|
||||||
this.log("No '"+this.name+"' device found (yet?)");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var that = this;
|
|
||||||
|
|
||||||
this.log("checking brightness level for: " + this.name);
|
|
||||||
wink.user().device(this.name, function(light_obj){
|
|
||||||
level = light_obj.desired_state.brightness * 100
|
|
||||||
that.log("brightness level for " + that.name + " is: " + level)
|
|
||||||
callback(level);
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
setPowerState: function(powerOn) {
|
|
||||||
if (!this.device) {
|
|
||||||
this.log("No '"+this.name+"' device found (yet?)");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var that = this;
|
|
||||||
|
|
||||||
if (powerOn) {
|
|
||||||
this.log("Setting power state on the '"+this.name+"' to on");
|
|
||||||
this.device.power.on(function(response) {
|
|
||||||
if (response === undefined) {
|
|
||||||
that.log("Error setting power state on the '"+that.name+"'")
|
|
||||||
} else {
|
|
||||||
that.log("Successfully set power state on the '"+that.name+"' to on");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}else{
|
|
||||||
this.log("Setting power state on the '"+this.name+"' to off");
|
|
||||||
this.device.power.off(function(response) {
|
|
||||||
if (response === undefined) {
|
|
||||||
that.log("Error setting power state on the '"+that.name+"'")
|
|
||||||
} else {
|
|
||||||
that.log("Successfully set power state on the '"+that.name+"' to off");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
setBrightness: function(level) {
|
|
||||||
if (!this.device) {
|
|
||||||
this.log("No '"+this.name+"' device found (yet?)");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var that = this;
|
|
||||||
|
|
||||||
this.log("Setting brightness on the '"+this.name+"' to " + level);
|
|
||||||
this.device.brightness(level, function(response) {
|
|
||||||
if (response === undefined) {
|
|
||||||
that.log("Error setting brightness on the '"+that.name+"'")
|
|
||||||
} else {
|
|
||||||
that.log("Successfully set brightness on the '"+that.name+"' to " + level);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
getServices: function() {
|
|
||||||
var that = this;
|
|
||||||
return [{
|
|
||||||
sType: types.ACCESSORY_INFORMATION_STYPE,
|
|
||||||
characteristics: [{
|
|
||||||
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: "Wink",
|
|
||||||
supportEvents: false,
|
|
||||||
supportBonjour: false,
|
|
||||||
manfDescription: "Manufacturer",
|
|
||||||
designedMaxLength: 255
|
|
||||||
},{
|
|
||||||
cType: types.MODEL_CTYPE,
|
|
||||||
onUpdate: null,
|
|
||||||
perms: ["pr"],
|
|
||||||
format: "string",
|
|
||||||
initialValue: "Rev-1",
|
|
||||||
supportEvents: false,
|
|
||||||
supportBonjour: false,
|
|
||||||
manfDescription: "Model",
|
|
||||||
designedMaxLength: 255
|
|
||||||
},{
|
|
||||||
cType: types.SERIAL_NUMBER_CTYPE,
|
|
||||||
onUpdate: null,
|
|
||||||
perms: ["pr"],
|
|
||||||
format: "string",
|
|
||||||
initialValue: "A1S2NASF88EW",
|
|
||||||
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
|
|
||||||
}]
|
|
||||||
},{
|
|
||||||
sType: types.LIGHTBULB_STYPE,
|
|
||||||
characteristics: [{
|
|
||||||
cType: types.NAME_CTYPE,
|
|
||||||
onUpdate: null,
|
|
||||||
perms: ["pr"],
|
|
||||||
format: "string",
|
|
||||||
initialValue: this.name,
|
|
||||||
supportEvents: true,
|
|
||||||
supportBonjour: false,
|
|
||||||
manfDescription: "Name of service",
|
|
||||||
designedMaxLength: 255
|
|
||||||
},{
|
|
||||||
cType: types.POWER_STATE_CTYPE,
|
|
||||||
onUpdate: function(value) {
|
|
||||||
that.setPowerState(value);
|
|
||||||
},
|
|
||||||
onRead: function(callback) {
|
|
||||||
that.getPowerState(function(powerState){
|
|
||||||
callback(powerState);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
perms: ["pw","pr","ev"],
|
|
||||||
format: "bool",
|
|
||||||
initialValue: 0,
|
|
||||||
supportEvents: true,
|
|
||||||
supportBonjour: false,
|
|
||||||
manfDescription: "Change the power state of the Bulb",
|
|
||||||
designedMaxLength: 1
|
|
||||||
},{
|
|
||||||
cType: types.BRIGHTNESS_CTYPE,
|
|
||||||
onUpdate: function(value) {
|
|
||||||
that.setBrightness(value);
|
|
||||||
},
|
|
||||||
onRead: function(callback) {
|
|
||||||
that.getBrightness(function(level){
|
|
||||||
callback(level);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
perms: ["pw","pr","ev"],
|
|
||||||
format: "int",
|
|
||||||
initialValue: 0,
|
|
||||||
supportEvents: true,
|
|
||||||
supportBonjour: false,
|
|
||||||
manfDescription: "Adjust Brightness of Light",
|
|
||||||
designedMinValue: 0,
|
|
||||||
designedMaxValue: 100,
|
|
||||||
designedMinStep: 1,
|
|
||||||
unit: "%"
|
|
||||||
}]
|
|
||||||
}];
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Base Accessory
|
||||||
|
*/
|
||||||
|
|
||||||
|
function WinkAccessory(log, device, type, typeId) {
|
||||||
|
// construct base
|
||||||
|
this.device = device;
|
||||||
|
this.name = device.name;
|
||||||
|
this.log = log;
|
||||||
|
if (typeId == undefined) {
|
||||||
|
typeId = this.name;
|
||||||
|
log("WARN: Unable to find id of " + this.name + " so using name instead");
|
||||||
|
}
|
||||||
|
this.deviceGroup = type + 's';
|
||||||
|
this.deviceId = typeId;
|
||||||
|
var idKey = 'hbdev:wink:' + type + ':' + typeId;
|
||||||
|
var id = uuid.generate(idKey);
|
||||||
|
Accessory.call(this, this.name, id);
|
||||||
|
this.uuid_base = id;
|
||||||
|
|
||||||
|
this.control = wink.device_group(this.deviceGroup).device_id(this.deviceId);
|
||||||
|
|
||||||
|
// set some basic properties (these values are arbitrary and setting them is optional)
|
||||||
|
this
|
||||||
|
.getService(Service.AccessoryInformation)
|
||||||
|
.setCharacteristic(Characteristic.Manufacturer, this.device.device_manufacturer)
|
||||||
|
.setCharacteristic(Characteristic.Model, this.device.model_name);
|
||||||
|
|
||||||
|
WinkAccessory.prototype.loadData.call(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
inherits(WinkAccessory, Accessory);
|
||||||
|
WinkAccessory.prototype.parent = Accessory.prototype;
|
||||||
|
|
||||||
|
WinkAccessory.prototype.getServices = function() {
|
||||||
|
return this.services;
|
||||||
|
};
|
||||||
|
|
||||||
|
WinkAccessory.prototype.loadData = function() {
|
||||||
|
};
|
||||||
|
|
||||||
|
WinkAccessory.prototype.handleResponse = function(res) {
|
||||||
|
if (!res) {
|
||||||
|
return Error("No response from Wink");
|
||||||
|
} else if (res.errors && res.errors.length > 0) {
|
||||||
|
return res.errors[0];
|
||||||
|
} else if (res.data) {
|
||||||
|
this.device = res.data;
|
||||||
|
this.loadData();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
WinkAccessory.prototype.reloadData = function(callback){
|
||||||
|
var that = this;
|
||||||
|
this.control.get(function(res) {
|
||||||
|
callback(that.handleResponse(res));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Light Accessory
|
||||||
|
*/
|
||||||
|
|
||||||
|
function WinkLightAccessory(log, device) {
|
||||||
|
// construct base
|
||||||
|
WinkAccessory.call(this, log, device, 'light_bulb', device.light_bulb_id);
|
||||||
|
|
||||||
|
// accessor
|
||||||
|
var that = this;
|
||||||
|
|
||||||
|
that.device = device;
|
||||||
|
that.deviceControl = model.light_bulbs(device, wink);
|
||||||
|
|
||||||
|
this
|
||||||
|
.addService(Service.Lightbulb)
|
||||||
|
.getCharacteristic(Characteristic.On)
|
||||||
|
.on('get', function(callback) {
|
||||||
|
var powerState = that.device.desired_state.powered;
|
||||||
|
that.log("power state for " + that.name + " is: " + powerState);
|
||||||
|
callback(null, powerState != undefined ? powerState : false);
|
||||||
|
})
|
||||||
|
.on('set', function(powerOn, callback) {
|
||||||
|
if (powerOn) {
|
||||||
|
that.log("Setting power state on the '"+that.name+"' to on");
|
||||||
|
that.deviceControl.power.on(function(response) {
|
||||||
|
if (response === undefined) {
|
||||||
|
that.log("Error setting power state on the '"+that.name+"'");
|
||||||
|
callback(Error("Error setting power state on the '"+that.name+"'"));
|
||||||
|
} else {
|
||||||
|
that.log("Successfully set power state on the '"+that.name+"' to on");
|
||||||
|
callback(null, powerOn);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
that.log("Setting power state on the '"+that.name+"' to off");
|
||||||
|
that.deviceControl.power.off(function(response) {
|
||||||
|
if (response === undefined) {
|
||||||
|
that.log("Error setting power state on the '"+that.name+"'");
|
||||||
|
callback(Error("Error setting power state on the '"+that.name+"'"));
|
||||||
|
} else {
|
||||||
|
that.log("Successfully set power state on the '"+that.name+"' to off");
|
||||||
|
callback(null, powerOn);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this
|
||||||
|
.getService(Service.Lightbulb)
|
||||||
|
.getCharacteristic(Characteristic.Brightness)
|
||||||
|
.on('get', function(callback) {
|
||||||
|
var level = that.device.desired_state.brightness * 100;
|
||||||
|
that.log("brightness level for " + that.name + " is: " + level);
|
||||||
|
callback(null, level);
|
||||||
|
})
|
||||||
|
.on('set', function(level, callback) {
|
||||||
|
that.log("Setting brightness on the '"+this.name+"' to " + level);
|
||||||
|
that.deviceControl.brightness(level, function(response) {
|
||||||
|
if (response === undefined) {
|
||||||
|
that.log("Error setting brightness on the '"+that.name+"'");
|
||||||
|
callback(Error("Error setting brightness on the '"+that.name+"'"));
|
||||||
|
} else {
|
||||||
|
that.log("Successfully set brightness on the '"+that.name+"' to " + level);
|
||||||
|
callback(null, level);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
WinkLightAccessory.prototype.loadData.call(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
inherits(WinkLightAccessory, WinkAccessory);
|
||||||
|
WinkLightAccessory.prototype.parent = WinkAccessory.prototype;
|
||||||
|
|
||||||
|
WinkLightAccessory.prototype.loadData = function() {
|
||||||
|
this.parent.loadData.call(this);
|
||||||
|
this.getService(Service.Lightbulb)
|
||||||
|
.getCharacteristic(Characteristic.On)
|
||||||
|
.getValue();
|
||||||
|
this.getService(Service.Lightbulb)
|
||||||
|
.getCharacteristic(Characteristic.Brightness)
|
||||||
|
.getValue();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Lock Accessory
|
||||||
|
*/
|
||||||
|
|
||||||
|
function WinkLockAccessory(log, device) {
|
||||||
|
// construct base
|
||||||
|
WinkAccessory.call(this, log, device, 'lock', device.lock_id);
|
||||||
|
|
||||||
|
// accessor
|
||||||
|
var that = this;
|
||||||
|
|
||||||
|
this
|
||||||
|
.addService(Service.LockMechanism)
|
||||||
|
.getCharacteristic(Characteristic.LockTargetState)
|
||||||
|
.on('get', function(callback) {
|
||||||
|
callback(null, that.isLockTarget());
|
||||||
|
})
|
||||||
|
.on('set', function(value, callback) {
|
||||||
|
var locked = that.fromLockState(value);
|
||||||
|
|
||||||
|
if (locked == undefined) {
|
||||||
|
callback(Error("Unsupported"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
that.log("Changing target lock state of " + that.name + " to " + (locked ? "locked" : "unlocked"));
|
||||||
|
|
||||||
|
var update = function(retry) {
|
||||||
|
that.control.update({ "desired_state": { "locked": locked } }, function(res) {
|
||||||
|
var err = that.handleResponse(res);
|
||||||
|
if (!err) {
|
||||||
|
model.refreshUntil(that, 5,
|
||||||
|
function() { return that.isLocked() == that.isLockTarget(); },
|
||||||
|
function(completed) {
|
||||||
|
if (completed) {
|
||||||
|
that.log("Successfully changed lock status to " + (that.isLocked() ? "locked" : "unlocked"));
|
||||||
|
} else if (retry) {
|
||||||
|
that.log("Unable to determine if update was successful. Retrying update.");
|
||||||
|
retry();
|
||||||
|
} else {
|
||||||
|
that.log("Unable to determine if update was successful.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (callback)
|
||||||
|
{
|
||||||
|
callback(err);
|
||||||
|
callback = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
update(update);
|
||||||
|
});
|
||||||
|
|
||||||
|
WinkLockAccessory.prototype.loadData.call(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
inherits(WinkLockAccessory, WinkAccessory);
|
||||||
|
WinkLockAccessory.prototype.parent = WinkAccessory.prototype;
|
||||||
|
|
||||||
|
WinkLockAccessory.prototype.loadData = function() {
|
||||||
|
this.parent.loadData.call(this);
|
||||||
|
this.getService(Service.LockMechanism)
|
||||||
|
.setCharacteristic(Characteristic.LockCurrentState, this.isLocked());
|
||||||
|
this.getService(Service.LockMechanism)
|
||||||
|
.getCharacteristic(Characteristic.LockTargetState)
|
||||||
|
.getValue();
|
||||||
|
};
|
||||||
|
|
||||||
|
WinkLockAccessory.prototype.toLockState= function(isLocked) {
|
||||||
|
return isLocked ? Characteristic.LockCurrentState.SECURED : Characteristic.LockCurrentState.UNSECURED;
|
||||||
|
};
|
||||||
|
|
||||||
|
WinkLockAccessory.prototype.fromLockState= function(lockState) {
|
||||||
|
switch (lockState) {
|
||||||
|
case Characteristic.LockCurrentState.SECURED:
|
||||||
|
return true;
|
||||||
|
case Characteristic.LockCurrentState.UNSECURED:
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
WinkLockAccessory.prototype.isLockTarget= function() {
|
||||||
|
return this.toLockState(this.device.desired_state.locked);
|
||||||
|
};
|
||||||
|
|
||||||
|
WinkLockAccessory.prototype.isLocked= function() {
|
||||||
|
return this.toLockState(this.device.last_reading.locked);
|
||||||
|
};
|
||||||
|
|
||||||
module.exports.accessory = WinkAccessory;
|
module.exports.accessory = WinkAccessory;
|
||||||
|
module.exports.lockAccessory = WinkLockAccessory;
|
||||||
|
module.exports.lightAccessory = WinkLightAccessory;
|
||||||
module.exports.platform = WinkPlatform;
|
module.exports.platform = WinkPlatform;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
var types = require("HAP-NodeJS/accessories/types.js");
|
var types = require("hap-nodejs/accessories/types.js");
|
||||||
var inherits = require('util').inherits;
|
var inherits = require('util').inherits;
|
||||||
var debug = require('debug')('YamahaAVR');
|
var debug = require('debug')('YamahaAVR');
|
||||||
var Service = require("HAP-NodeJS").Service;
|
var Service = require("hap-nodejs").Service;
|
||||||
var Characteristic = require("HAP-NodeJS").Characteristic;
|
var Characteristic = require("hap-nodejs").Characteristic;
|
||||||
var Yamaha = require('yamaha-nodejs');
|
var Yamaha = require('yamaha-nodejs');
|
||||||
var Q = require('q');
|
var Q = require('q');
|
||||||
var mdns = require('mdns');
|
var mdns = require('mdns');
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
var debug = require('debug')('ZWayServer');
|
var debug = require('debug')('ZWayServer');
|
||||||
var Service = require("HAP-NodeJS").Service;
|
var Service = require("hap-nodejs").Service;
|
||||||
var Characteristic = require("HAP-NodeJS").Characteristic;
|
var Characteristic = require("hap-nodejs").Characteristic;
|
||||||
var types = require("HAP-NodeJS/accessories/types.js");
|
var types = require("hap-nodejs/accessories/types.js");
|
||||||
var request = require("request");
|
var request = require("request");
|
||||||
var tough = require('tough-cookie');
|
var tough = require('tough-cookie');
|
||||||
var Q = require("q");
|
var Q = require("q");
|
||||||
@@ -356,7 +356,16 @@ ZWayServerAccessory.prototype = {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "sensorBinary.Door/Window":
|
case "sensorBinary.Door/Window":
|
||||||
services.push(new Service.GarageDoorOpener(vdev.metrics.title, vdev.id));
|
var stype = this.platform.getTagValue(vdev, "Service.Type");
|
||||||
|
if(stype === "ContactSensor"){
|
||||||
|
services.push(new Service.ContactSensor(vdev.metrics.title, vdev.id));
|
||||||
|
} else if(stype === "GarageDoorOpener"){
|
||||||
|
services.push(new Service.GarageDoorOpener(vdev.metrics.title, vdev.id));
|
||||||
|
} else if(stype === "Window"){
|
||||||
|
services.push(new Service.GarageDoorOpener(vdev.metrics.title, vdev.id));
|
||||||
|
} else {
|
||||||
|
services.push(new Service.Door(vdev.metrics.title, vdev.id));
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case "sensorMultilevel.Temperature":
|
case "sensorMultilevel.Temperature":
|
||||||
services.push(new Service.TemperatureSensor(vdev.metrics.title, vdev.id));
|
services.push(new Service.TemperatureSensor(vdev.metrics.title, vdev.id));
|
||||||
@@ -371,6 +380,8 @@ ZWayServerAccessory.prototype = {
|
|||||||
var stype = this.platform.getTagValue(vdev, "Service.Type");
|
var stype = this.platform.getTagValue(vdev, "Service.Type");
|
||||||
if(stype === "MotionSensor"){
|
if(stype === "MotionSensor"){
|
||||||
services.push(new Service.MotionSensor(vdev.metrics.title, vdev.id));
|
services.push(new Service.MotionSensor(vdev.metrics.title, vdev.id));
|
||||||
|
} else {
|
||||||
|
services.push(new Service.ContactSensor(vdev.metrics.title, vdev.id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -412,6 +423,10 @@ ZWayServerAccessory.prototype = {
|
|||||||
map[(new Characteristic.TargetHeatingCoolingState).UUID] = ["thermostat"]; //TODO: Always a fixed result
|
map[(new Characteristic.TargetHeatingCoolingState).UUID] = ["thermostat"]; //TODO: Always a fixed result
|
||||||
map[(new Characteristic.CurrentDoorState).UUID] = ["sensorBinary.Door/Window","sensorBinary"];
|
map[(new Characteristic.CurrentDoorState).UUID] = ["sensorBinary.Door/Window","sensorBinary"];
|
||||||
map[(new Characteristic.TargetDoorState).UUID] = ["sensorBinary.Door/Window","sensorBinary"]; //TODO: Always a fixed result
|
map[(new Characteristic.TargetDoorState).UUID] = ["sensorBinary.Door/Window","sensorBinary"]; //TODO: Always a fixed result
|
||||||
|
map[(new Characteristic.ContactSensorState).UUID] = ["sensorBinary"];
|
||||||
|
map[(new Characteristic.CurrentPosition).UUID] = ["sensorBinary.Door/Window","sensorBinary"];
|
||||||
|
map[(new Characteristic.TargetPosition).UUID] = ["sensorBinary.Door/Window","sensorBinary"];
|
||||||
|
map[(new Characteristic.PositionState).UUID] = ["sensorBinary.Door/Window","sensorBinary"];
|
||||||
map[(new Characteristic.ObstructionDetected).UUID] = ["sensorBinary.Door/Window","sensorBinary"]; //TODO: Always a fixed result
|
map[(new Characteristic.ObstructionDetected).UUID] = ["sensorBinary.Door/Window","sensorBinary"]; //TODO: Always a fixed result
|
||||||
map[(new Characteristic.BatteryLevel).UUID] = ["battery.Battery"];
|
map[(new Characteristic.BatteryLevel).UUID] = ["battery.Battery"];
|
||||||
map[(new Characteristic.StatusLowBattery).UUID] = ["battery.Battery"];
|
map[(new Characteristic.StatusLowBattery).UUID] = ["battery.Battery"];
|
||||||
@@ -815,6 +830,68 @@ ZWayServerAccessory.prototype = {
|
|||||||
return cx;
|
return cx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(cx instanceof Characteristic.ContactSensorState){
|
||||||
|
cx.zway_getValueFromVDev = function(vdev){
|
||||||
|
var boolval = vdev.metrics.level === "off" ? false : true;
|
||||||
|
boolval = accessory.platform.getTagValue(vdev, "ContactSensorState.Invert") ? !boolval : boolval;
|
||||||
|
return boolval ? Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : Characteristic.ContactSensorState.CONTACT_DETECTED;
|
||||||
|
};
|
||||||
|
cx.value = cx.zway_getValueFromVDev(vdev);
|
||||||
|
cx.on('get', function(callback, context){
|
||||||
|
debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"...");
|
||||||
|
this.getVDev(vdev).then(function(result){
|
||||||
|
debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + ".");
|
||||||
|
callback(false, cx.zway_getValueFromVDev(result.data));
|
||||||
|
});
|
||||||
|
}.bind(this));
|
||||||
|
cx.on('change', function(ev){
|
||||||
|
debug("Device " + vdev.metrics.title + ", characteristic " + cx.displayName + " changed from " + ev.oldValue + " to " + ev.newValue);
|
||||||
|
});
|
||||||
|
return cx;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(cx instanceof Characteristic.CurrentPosition){
|
||||||
|
cx.zway_getValueFromVDev = function(vdev){
|
||||||
|
return vdev.metrics.level === "off" ? 0 : 100 ;
|
||||||
|
};
|
||||||
|
cx.value = cx.zway_getValueFromVDev(vdev);
|
||||||
|
cx.on('get', function(callback, context){
|
||||||
|
debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"...");
|
||||||
|
this.getVDev(vdev).then(function(result){
|
||||||
|
debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + ".");
|
||||||
|
callback(false, cx.zway_getValueFromVDev(result.data));
|
||||||
|
});
|
||||||
|
}.bind(this));
|
||||||
|
cx.on('change', function(ev){
|
||||||
|
debug("Device " + vdev.metrics.title + ", characteristic " + cx.displayName + " changed from " + ev.oldValue + " to " + ev.newValue);
|
||||||
|
});
|
||||||
|
return cx;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(cx instanceof Characteristic.TargetPosition){
|
||||||
|
//TODO: Currently only Door sensors, so always return 0.
|
||||||
|
cx.zway_getValueFromVDev = function(vdev){
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
cx.value = cx.zway_getValueFromVDev(vdev);
|
||||||
|
cx.on('get', function(callback, context){
|
||||||
|
debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"...");
|
||||||
|
callback(false, cx.zway_getValueFromVDev(vdev));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if(cx instanceof Characteristic.PositionState){
|
||||||
|
//TODO: Currently only Door sensors, so always return STOPPED.
|
||||||
|
cx.zway_getValueFromVDev = function(vdev){
|
||||||
|
return Characteristic.PositionState.STOPPED;
|
||||||
|
};
|
||||||
|
cx.value = cx.zway_getValueFromVDev(vdev);
|
||||||
|
cx.on('get', function(callback, context){
|
||||||
|
debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"...");
|
||||||
|
callback(false, cx.zway_getValueFromVDev(vdev));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
,
|
,
|
||||||
configureService: function(service, vdev){
|
configureService: function(service, vdev){
|
||||||
|
|||||||
763
platforms/isy-js.js
Normal file
763
platforms/isy-js.js
Normal file
@@ -0,0 +1,763 @@
|
|||||||
|
/*
|
||||||
|
ISY-JS
|
||||||
|
|
||||||
|
ISY-99 REST / WebSockets based HomeBridge shim.
|
||||||
|
|
||||||
|
Supports the following Insteon devices: Lights (dimmable and non-dimmable), Fans, Outlets, Door/Window Sensors, MorningLinc locks, Inline Lincs and I/O Lincs.
|
||||||
|
Also supports ZWave based locks. If elkEnabled is set to true then this will also expose your Elk Alarm Panel and all of your Elk Sensors.
|
||||||
|
|
||||||
|
Turns out that HomeBridge platforms can only return a maximum of 100 devices. So if you end up exposing more then 100 devices through HomeBridge the HomeKit
|
||||||
|
software will fail adding the HomeBridge to your HomeKit network. To address this issue this platform provides an option to screen out devices based on
|
||||||
|
criteria specified in the config.
|
||||||
|
|
||||||
|
Configuration sample:
|
||||||
|
|
||||||
|
"platforms": [
|
||||||
|
{
|
||||||
|
"platform": "isy-js",
|
||||||
|
"name": "isy-js",
|
||||||
|
"host": "10.0.1.12",
|
||||||
|
"username": "admin",
|
||||||
|
"password": "password",
|
||||||
|
"elkEnabled": true,
|
||||||
|
"ignoreDevices": [
|
||||||
|
{ "nameContains": "ApplianceLinc", "lastAddressDigit": "", "address": ""},
|
||||||
|
{ "nameContains": "Bedroom.Side Gate", "lastAddressDigit": "", "address": ""},
|
||||||
|
{ "nameContains": "Remote", "lastAddressDigit": "", "address": "" },
|
||||||
|
{ "nameContains": "Keypad", "lastAddressDigit": "2", "address": "" },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
Fields:
|
||||||
|
"platform" - Must be set to isy-js
|
||||||
|
"name" - Can be set to whatever you want
|
||||||
|
"host" - IP address of the ISY
|
||||||
|
"username" - Your ISY username
|
||||||
|
"password" - Your ISY password
|
||||||
|
"elkEnabled" - true if there is an elk alarm panel connected to your ISY
|
||||||
|
"ignoreDevices" - Array of objects specifying criteria for screening out devices from the network. nameContains is the only required criteria. If the other criteria
|
||||||
|
are blank all devices will match those criteria (providing they match the name criteria).
|
||||||
|
"nameContains" - Specifies a substring to check against the names of the ISY devices. Required field for the criteria.
|
||||||
|
"lastAddressDigit" - Specifies a single digit in the ISY address of a device which should be used to match the device. Example use of this is for composite
|
||||||
|
devices like keypads so you can screen out the non-main buttons.
|
||||||
|
"address" - ISY address to match.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
{ "nameContains": "Keypad", "lastAddressDigit": "2", "address": "" } - Ignore all devices which have the word Keypad in their name and whose last address digit is 2.
|
||||||
|
{ "nameContains": "Remote", "lastAddressDigit": "", "address": "" } - Ignore all devices which have the word Remote in their name
|
||||||
|
{ "nameContains": "", "lastAddressDigit": "", "address": "15 5 3 2"} - Ignore the device with an ISY address of 15 5 3 2.
|
||||||
|
|
||||||
|
TODOS: Implement identify functions (beep perhaps?) and more device types.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
var types = require("hap-nodejs/accessories/types.js");
|
||||||
|
var isy = require('isy-js');
|
||||||
|
var Service = require("hap-nodejs").Service;
|
||||||
|
var Characteristic = require("hap-nodejs").Characteristic;
|
||||||
|
var inherits = require('util').inherits;
|
||||||
|
|
||||||
|
// Global device map. Needed to map incoming notifications to the corresponding HomeKit device for update.
|
||||||
|
var deviceMap = {};
|
||||||
|
|
||||||
|
// This function responds to changes in devices from the isy-js library. Uses the global device map to update
|
||||||
|
// the state.
|
||||||
|
// TODO: Move this to a member function of the ISYPlatform object so we don't need a global map.
|
||||||
|
function ISYChangeHandler(isy,device) {
|
||||||
|
var deviceToUpdate = deviceMap[device.address];
|
||||||
|
if(deviceToUpdate != null) {
|
||||||
|
deviceToUpdate.handleExternalChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to have ISYJSDEBUG control if debug output appears
|
||||||
|
function ISYJSDebugMessage(isy,message) {
|
||||||
|
if(process.env.ISYJSDEBUG != undefined) {
|
||||||
|
isy.log(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// PLATFORM
|
||||||
|
|
||||||
|
// Construct the ISY platform. log = Logger, config = homebridge cofnig
|
||||||
|
function ISYPlatform(log,config) {
|
||||||
|
this.log = log;
|
||||||
|
this.config = config;
|
||||||
|
this.host = config.host;
|
||||||
|
this.username = config.username;
|
||||||
|
this.password = config.password;
|
||||||
|
this.elkEnabled = config.elkEnabled;
|
||||||
|
this.isy = new isy.ISY(this.host, this.username,this.password, config.elkEnabled, ISYChangeHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks the device against the configuration to see if it should be ignored.
|
||||||
|
ISYPlatform.prototype.shouldIgnore = function(device) {
|
||||||
|
var deviceAddress = device.address;
|
||||||
|
var deviceName = device.name;
|
||||||
|
for(var index = 0; index < this.config.ignoreDevices.length; index++) {
|
||||||
|
var rule = this.config.ignoreDevices[index];
|
||||||
|
if(rule.nameContains != "") {
|
||||||
|
if(deviceName.indexOf(rule.nameContains) == -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(rule.lastAddressDigit != "") {
|
||||||
|
if(deviceAddress.indexOf(rule.lastAddressDigit,deviceAddress.length-2) == -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(rule.address != "") {
|
||||||
|
if(deviceAddress != rule.address) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ISYJSDebugMessage(this,"Ignoring device: "+deviceName+" ["+deviceAddress+"] because of rule ["+rule.nameContains+"] ["+rule.lastAddressDigit+"] ["+rule.address+"]");
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calls the isy-js library, retrieves the list of devices, and maps them to appropriate ISYXXXXAccessory devices.
|
||||||
|
ISYPlatform.prototype.accessories = function(callback) {
|
||||||
|
var that = this;
|
||||||
|
this.isy.initialize(function() {
|
||||||
|
var results = [];
|
||||||
|
var deviceList = that.isy.getDeviceList();
|
||||||
|
for(var index = 0; index < deviceList.length; index++) {
|
||||||
|
var device = deviceList[index];
|
||||||
|
var homeKitDevice = null;
|
||||||
|
if(!that.shouldIgnore(device)) {
|
||||||
|
|
||||||
|
if(device.deviceType == that.isy.DEVICE_TYPE_LIGHT || device.deviceType == that.isy.DEVICE_TYPE_DIMMABLE_LIGHT) {
|
||||||
|
homeKitDevice = new ISYLightAccessory(that.log,device);
|
||||||
|
} else if(device.deviceType == that.isy.DEVICE_TYPE_LOCK || device.deviceType == that.isy.DEVICE_TYPE_SECURE_LOCK) {
|
||||||
|
homeKitDevice = new ISYLockAccessory(that.log,device);
|
||||||
|
} else if(device.deviceType == that.isy.DEVICE_TYPE_OUTLET) {
|
||||||
|
homeKitDevice = new ISYOutletAccessory(that.log,device);
|
||||||
|
} else if(device.deviceType == that.isy.DEVICE_TYPE_FAN) {
|
||||||
|
homeKitDevice = new ISYFanAccessory(that.log,device);
|
||||||
|
} else if(device.deviceType == that.isy.DEVICE_TYPE_DOOR_WINDOW_SENSOR) {
|
||||||
|
homeKitDevice = new ISYDoorWindowSensorAccessory(that.log,device);
|
||||||
|
} else if(device.deviceType == that.isy.DEVICE_TYPE_ALARM_DOOR_WINDOW_SENSOR) {
|
||||||
|
homeKitDevice = new ISYDoorWindowSensorAccessory(that.log,device);
|
||||||
|
} else if(device.deviceType == that.isy.DEVICE_TYPE_ALARM_PANEL) {
|
||||||
|
homeKitDevice = new ISYElkAlarmPanelAccessory(that.log,device);
|
||||||
|
}
|
||||||
|
if(homeKitDevice != null) {
|
||||||
|
// Make sure the device is address to the global map
|
||||||
|
deviceMap[device.address] = homeKitDevice;
|
||||||
|
results.push(homeKitDevice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(that.isy.elkEnabled) {
|
||||||
|
var panelDevice = that.isy.getElkAlarmPanel();
|
||||||
|
var panelDeviceHK = new ISYElkAlarmPanelAccessory(that.log,panelDevice);
|
||||||
|
deviceMap[panelDevice.address] = panelDeviceHK;
|
||||||
|
results.push(panelDeviceHK);
|
||||||
|
}
|
||||||
|
ISYJSDebugMessage(that,"Filtered device has: "+results.length+" devices");
|
||||||
|
callback(results);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// BASE FOR ALL DEVICES
|
||||||
|
|
||||||
|
// Provides common constructor tasks
|
||||||
|
function ISYAccessoryBaseSetup(accessory,log,device) {
|
||||||
|
accessory.log = log;
|
||||||
|
accessory.device = device;
|
||||||
|
accessory.address = device.address;
|
||||||
|
accessory.name = device.name;
|
||||||
|
accessory.uuid_base = device.isy.address+":"+device.address;
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// FANS - ISYFanAccessory
|
||||||
|
// Implemetnts the fan service for an isy fan device.
|
||||||
|
|
||||||
|
// Constructs a fan accessory object. device is the isy-js device object and log is the logger.
|
||||||
|
function ISYFanAccessory(log,device) {
|
||||||
|
ISYAccessoryBaseSetup(this,log,device);
|
||||||
|
}
|
||||||
|
|
||||||
|
ISYFanAccessory.prototype.identify = function(callback) {
|
||||||
|
// Do the identify action
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translates the fan speed as an isy-js string into the corresponding homekit constant level.
|
||||||
|
// Homekit doesn't have steps for the fan speed and needs to have a value from 0 to 100. We
|
||||||
|
// split the range into 4 steps and map them to the 4 isy-js levels.
|
||||||
|
ISYFanAccessory.prototype.translateFanSpeedToHK = function(fanSpeed) {
|
||||||
|
if(fanSpeed == this.device.FAN_LEVEL_OFF) {
|
||||||
|
return 0;
|
||||||
|
} else if(fanSpeed == this.device.FAN_LEVEL_LOW) {
|
||||||
|
return 32;
|
||||||
|
} else if(fanSpeed == this.device.FAN_LEVEL_MEDIUM) {
|
||||||
|
return 67;
|
||||||
|
} else if(fanSpeed == this.device.FAN_LEVEL_HIGH) {
|
||||||
|
return 100;
|
||||||
|
} else {
|
||||||
|
ISYJSDebugMessage(this,"!!!! ERROR: Unknown fan speed: "+fanSpeed);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translates the fan level from homebridge into the isy-js level. Maps from the 0-100
|
||||||
|
// to the four isy-js fan speed levels.
|
||||||
|
ISYFanAccessory.prototype.translateHKToFanSpeed = function(fanStateHK) {
|
||||||
|
if(fanStateHK == 0) {
|
||||||
|
return this.device.FAN_LEVEL_OFF;
|
||||||
|
} else if(fanStateHK > 0 && fanStateHK <=32) {
|
||||||
|
return this.device.FAN_LEVEL_LOW;
|
||||||
|
} else if(fanStateHK >= 33 && fanStateHK <= 67) {
|
||||||
|
return this.device.FAN_LEVEL_MEDIUM;
|
||||||
|
} else if(fanStateHK > 67) {
|
||||||
|
return this.device.FAN_LEVEL_HIGH;
|
||||||
|
} else {
|
||||||
|
ISYJSDebugMessage(this,"ERROR: Unknown fan state!");
|
||||||
|
return this.device.FAN_LEVEL_OFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the current state of the fan from the isy-js level to the 0-100 level of HK.
|
||||||
|
ISYFanAccessory.prototype.getFanRotationSpeed = function(callback) {
|
||||||
|
callback(null,this.translateFanSpeedToHK(this.device.getCurrentFanState()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets the current state of the fan from the 0-100 level of HK to the isy-js level.
|
||||||
|
ISYFanAccessory.prototype.setFanRotationSpeed = function(fanStateHK,callback) {
|
||||||
|
var newFanState = this.translateHKToFanSpeed(fanStateHK);
|
||||||
|
ISYJSDebugMessage(this,"Sending command to set fan state to: "+newFanState);
|
||||||
|
if(newFanState != this.device.getCurrentFanState()) {
|
||||||
|
this.device.sendFanCommand(newFanState, function(result) {
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ISYJSDebugMessage(this,"Fan command does not change actual speed");
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the fan is on
|
||||||
|
ISYFanAccessory.prototype.getIsFanOn = function() {
|
||||||
|
return (this.device.getCurrentFanState() != "Off");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the state of the fan to the homebridge system for the On characteristic
|
||||||
|
ISYFanAccessory.prototype.getFanOnState = function(callback) {
|
||||||
|
callback(null,this.getIsFanOn());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets the fan state based on the value of the On characteristic. Default to Medium for on.
|
||||||
|
ISYFanAccessory.prototype.setFanOnState = function(onState,callback) {
|
||||||
|
if(onState != this.getIsFanOn()) {
|
||||||
|
if(onState) {
|
||||||
|
this.setFanRotationSpeed(this.translateFanSpeedToHK(this.device.FAN_LEVEL_MEDIUM), callback);
|
||||||
|
} else {
|
||||||
|
this.setFanRotationSpeed(this.translateFanSpeedToHK(this.device.FAN_LEVEL_OFF), callback);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ISYJSDebugMessage(this,"Fan command does not change actual state");
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mirrors change in the state of the underlying isj-js device object.
|
||||||
|
ISYFanAccessory.prototype.handleExternalChange = function() {
|
||||||
|
this.fanService
|
||||||
|
.setCharacteristic(Characteristic.On, this.getIsFanOn());
|
||||||
|
|
||||||
|
this.fanService
|
||||||
|
.setCharacteristic(Characteristic.RotationSpeed, this.translateFanSpeedToHK(this.device.getCurrentFanState()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the services supported by the fan device.
|
||||||
|
ISYFanAccessory.prototype.getServices = function() {
|
||||||
|
var informationService = new Service.AccessoryInformation();
|
||||||
|
|
||||||
|
informationService
|
||||||
|
.setCharacteristic(Characteristic.Manufacturer, "SmartHome")
|
||||||
|
.setCharacteristic(Characteristic.Model, this.device.deviceFriendlyName)
|
||||||
|
.setCharacteristic(Characteristic.SerialNumber, this.device.address);
|
||||||
|
|
||||||
|
var fanService = new Service.Fan();
|
||||||
|
|
||||||
|
this.fanService = fanService;
|
||||||
|
this.informationService = informationService;
|
||||||
|
|
||||||
|
fanService
|
||||||
|
.getCharacteristic(Characteristic.On)
|
||||||
|
.on('set', this.setFanOnState.bind(this));
|
||||||
|
|
||||||
|
fanService
|
||||||
|
.getCharacteristic(Characteristic.On)
|
||||||
|
.on('get', this.getFanOnState.bind(this));
|
||||||
|
|
||||||
|
fanService
|
||||||
|
.addCharacteristic(new Characteristic.RotationSpeed())
|
||||||
|
.on('get', this.getFanRotationSpeed.bind(this));
|
||||||
|
|
||||||
|
fanService
|
||||||
|
.getCharacteristic(Characteristic.RotationSpeed)
|
||||||
|
.on('set', this.setFanRotationSpeed.bind(this));
|
||||||
|
|
||||||
|
return [informationService, fanService];
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// OUTLETS - ISYOutletAccessory
|
||||||
|
// Implements the Outlet service for ISY devices.
|
||||||
|
|
||||||
|
// Constructs an outlet. log = HomeBridge logger, device = isy-js device to wrap
|
||||||
|
function ISYOutletAccessory(log,device) {
|
||||||
|
ISYAccessoryBaseSetup(this,log,device);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles the identify command
|
||||||
|
ISYOutletAccessory.prototype.identify = function(callback) {
|
||||||
|
// Do the identify action
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles a request to set the outlet state. Ignores redundant sets based on current states.
|
||||||
|
ISYOutletAccessory.prototype.setOutletState = function(outletState,callback) {
|
||||||
|
ISYJSDebugMessage(this,"Sending command to set outlet state to: "+outletState);
|
||||||
|
if(outletState != this.device.getCurrentOutletState()) {
|
||||||
|
this.device.sendOutletCommand(outletState, function(result) {
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles a request to get the current outlet state based on underlying isy-js device object.
|
||||||
|
ISYOutletAccessory.prototype.getOutletState = function(callback) {
|
||||||
|
callback(null,this.device.getCurrentOutletState());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles a request to get the current in use state of the outlet. We set this to true always as
|
||||||
|
// there is no way to deterine this through the isy.
|
||||||
|
ISYOutletAccessory.prototype.getOutletInUseState = function(callback) {
|
||||||
|
callback(null, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mirrors change in the state of the underlying isj-js device object.
|
||||||
|
ISYOutletAccessory.prototype.handleExternalChange = function() {
|
||||||
|
this.outletService
|
||||||
|
.setCharacteristic(Characteristic.On, this.device.getCurrentOutletState());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the set of services supported by this object.
|
||||||
|
ISYOutletAccessory.prototype.getServices = function() {
|
||||||
|
var informationService = new Service.AccessoryInformation();
|
||||||
|
|
||||||
|
informationService
|
||||||
|
.setCharacteristic(Characteristic.Manufacturer, "SmartHome")
|
||||||
|
.setCharacteristic(Characteristic.Model, this.device.deviceFriendlyName)
|
||||||
|
.setCharacteristic(Characteristic.SerialNumber, this.device.address);
|
||||||
|
|
||||||
|
var outletService = new Service.Outlet();
|
||||||
|
|
||||||
|
this.outletService = outletService;
|
||||||
|
this.informationService = informationService;
|
||||||
|
|
||||||
|
outletService
|
||||||
|
.getCharacteristic(Characteristic.On)
|
||||||
|
.on('set', this.setOutletState.bind(this));
|
||||||
|
|
||||||
|
outletService
|
||||||
|
.getCharacteristic(Characteristic.On)
|
||||||
|
.on('get', this.getOutletState.bind(this));
|
||||||
|
|
||||||
|
outletService
|
||||||
|
.getCharacteristic(Characteristic.OutletInUse)
|
||||||
|
.on('get', this.getOutletInUseState.bind(this));
|
||||||
|
|
||||||
|
return [informationService, outletService];
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// LOCKS - ISYLockAccessory
|
||||||
|
// Implements the lock service for isy-js devices.
|
||||||
|
|
||||||
|
// Constructs a lock accessory. log = homebridge logger, device = isy-js device object being wrapped
|
||||||
|
function ISYLockAccessory(log,device) {
|
||||||
|
ISYAccessoryBaseSetup(this,log,device);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles an identify request
|
||||||
|
ISYLockAccessory.prototype.identify = function(callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles a set to the target lock state. Will ignore redundant commands.
|
||||||
|
ISYLockAccessory.prototype.setTargetLockState = function(lockState,callback) {
|
||||||
|
ISYJSDebugMessage(this,"Sending command to set lock state to: "+lockState);
|
||||||
|
if(lockState != this.getDeviceCurrentStateAsHK()) {
|
||||||
|
var targetLockValue = (lockState == 0) ? false : true;
|
||||||
|
this.device.sendLockCommand(targetLockValue, function(result) {
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translates underlying lock state into the corresponding homekit state
|
||||||
|
ISYLockAccessory.prototype.getDeviceCurrentStateAsHK = function() {
|
||||||
|
return (this.device.getCurrentLockState() ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles request to get the current lock state for homekit
|
||||||
|
ISYLockAccessory.prototype.getLockCurrentState = function(callback) {
|
||||||
|
callback(null, this.getDeviceCurrentStateAsHK());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles request to get the target lock state for homekit
|
||||||
|
ISYLockAccessory.prototype.getTargetLockState = function(callback) {
|
||||||
|
this.getLockCurrentState(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mirrors change in the state of the underlying isj-js device object.
|
||||||
|
ISYLockAccessory.prototype.handleExternalChange = function() {
|
||||||
|
this.lockService
|
||||||
|
.setCharacteristic(Characteristic.LockTargetState, this.getDeviceCurrentStateAsHK());
|
||||||
|
this.lockService
|
||||||
|
.setCharacteristic(Characteristic.LockCurrentState, this.getDeviceCurrentStateAsHK());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the set of services supported by this object.
|
||||||
|
ISYLockAccessory.prototype.getServices = function() {
|
||||||
|
var informationService = new Service.AccessoryInformation();
|
||||||
|
|
||||||
|
informationService
|
||||||
|
.setCharacteristic(Characteristic.Manufacturer, "SmartHome")
|
||||||
|
.setCharacteristic(Characteristic.Model, this.device.deviceFriendlyName)
|
||||||
|
.setCharacteristic(Characteristic.SerialNumber, this.device.address);
|
||||||
|
|
||||||
|
var lockMechanismService = new Service.LockMechanism();
|
||||||
|
|
||||||
|
this.lockService = lockMechanismService;
|
||||||
|
this.informationService = informationService;
|
||||||
|
|
||||||
|
lockMechanismService
|
||||||
|
.getCharacteristic(Characteristic.LockTargetState)
|
||||||
|
.on('set', this.setTargetLockState.bind(this));
|
||||||
|
|
||||||
|
lockMechanismService
|
||||||
|
.getCharacteristic(Characteristic.LockTargetState)
|
||||||
|
.on('get', this.getTargetLockState.bind(this));
|
||||||
|
|
||||||
|
lockMechanismService
|
||||||
|
.getCharacteristic(Characteristic.LockCurrentState)
|
||||||
|
.on('get', this.getLockCurrentState.bind(this));
|
||||||
|
|
||||||
|
return [informationService, lockMechanismService];
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// LIGHTS
|
||||||
|
// Implements the Light service for homekit based on an underlying isy-js device. Is dimmable or not depending
|
||||||
|
// on if the underlying device is dimmable.
|
||||||
|
|
||||||
|
// Constructs the light accessory. log = homebridge logger, device = isy-js device object being wrapped
|
||||||
|
function ISYLightAccessory(log,device) {
|
||||||
|
ISYAccessoryBaseSetup(this,log,device);
|
||||||
|
this.dimmable = (this.device.deviceType == "DimmableLight");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles the identify command
|
||||||
|
ISYLightAccessory.prototype.identify = function(callback) {
|
||||||
|
this.device.sendLightCommand(true, function(result) {
|
||||||
|
this.device.sendLightCommand(false, function(result) {
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles request to set the current powerstate from homekit. Will ignore redundant commands.
|
||||||
|
ISYLightAccessory.prototype.setPowerState = function(powerOn,callback) {
|
||||||
|
ISYJSDebugMessage(this,"Setting powerstate to %s", powerOn);
|
||||||
|
if(powerOn != this.device.getCurrentLightState()) {
|
||||||
|
ISYJSDebugMessage(this,"Changing powerstate to "+powerOn);
|
||||||
|
this.device.sendLightCommand(powerOn, function(result) {
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ISYJSDebugMessage(this,"Ignoring redundant setPowerState");
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mirrors change in the state of the underlying isj-js device object.
|
||||||
|
ISYLightAccessory.prototype.handleExternalChange = function() {
|
||||||
|
ISYJSDebugMessage(this,"Handling external change for light");
|
||||||
|
this.lightService
|
||||||
|
.setCharacteristic(Characteristic.On, this.device.getCurrentLightState());
|
||||||
|
if(this.device.deviceType == this.device.isy.DEVICE_TYPE_DIMMABLE_LIGHT) {
|
||||||
|
this.lightService
|
||||||
|
.setCharacteristic(Characteristic.Brightness, this.device.getCurrentLightDimState() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles request to get the current on state
|
||||||
|
ISYLightAccessory.prototype.getPowerState = function(callback) {
|
||||||
|
callback(null,this.device.getCurrentLightState());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles request to set the brightness level of dimmable lights. Ignore redundant commands.
|
||||||
|
ISYLightAccessory.prototype.setBrightness = function(level,callback) {
|
||||||
|
ISYJSDebugMessage(this,"Setting brightness to %s", level);
|
||||||
|
if(level != this.device.getCurrentLightDimState()) {
|
||||||
|
ISYJSDebugMessage(this,"Changing Brightness to "+level);
|
||||||
|
this.device.sendLightDimCommand(level, function(result) {
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ISYJSDebugMessage(this,"Ignoring redundant setBrightness");
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles a request to get the current brightness level for dimmable lights.
|
||||||
|
ISYLightAccessory.prototype.getBrightness = function(callback) {
|
||||||
|
callback(null,this.device.getCurrentLightDimState());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the set of services supported by this object.
|
||||||
|
ISYLightAccessory.prototype.getServices = function() {
|
||||||
|
var informationService = new Service.AccessoryInformation();
|
||||||
|
|
||||||
|
informationService
|
||||||
|
.setCharacteristic(Characteristic.Manufacturer, "SmartHome")
|
||||||
|
.setCharacteristic(Characteristic.Model, this.device.deviceFriendlyName)
|
||||||
|
.setCharacteristic(Characteristic.SerialNumber, this.device.address);
|
||||||
|
|
||||||
|
var lightBulbService = new Service.Lightbulb();
|
||||||
|
|
||||||
|
this.informationService = informationService;
|
||||||
|
this.lightService = lightBulbService;
|
||||||
|
|
||||||
|
lightBulbService
|
||||||
|
.getCharacteristic(Characteristic.On)
|
||||||
|
.on('set', this.setPowerState.bind(this));
|
||||||
|
|
||||||
|
lightBulbService
|
||||||
|
.getCharacteristic(Characteristic.On)
|
||||||
|
.on('get', this.getPowerState.bind(this));
|
||||||
|
|
||||||
|
if(this.dimmable) {
|
||||||
|
lightBulbService
|
||||||
|
.addCharacteristic(new Characteristic.Brightness())
|
||||||
|
.on('get', this.getBrightness.bind(this));
|
||||||
|
|
||||||
|
lightBulbService
|
||||||
|
.getCharacteristic(Characteristic.Brightness)
|
||||||
|
.on('set', this.setBrightness.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
return [informationService, lightBulbService];
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// CONTACT SENSOR - ISYDoorWindowSensorAccessory
|
||||||
|
// Implements the ContactSensor service.
|
||||||
|
|
||||||
|
// Constructs a Door Window Sensor (contact sensor) accessory. log = HomeBridge logger, device = wrapped isy-js device.
|
||||||
|
function ISYDoorWindowSensorAccessory(log,device) {
|
||||||
|
ISYAccessoryBaseSetup(this,log,device);
|
||||||
|
this.doorWindowState = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles the identify command.
|
||||||
|
ISYDoorWindowSensorAccessory.prototype.identify = function(callback) {
|
||||||
|
// Do the identify action
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translates the state of the underlying device object into the corresponding homekit compatible state
|
||||||
|
ISYDoorWindowSensorAccessory.prototype.translateCurrentDoorWindowState = function() {
|
||||||
|
return (this.device.getCurrentDoorWindowState()) ? Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : Characteristic.ContactSensorState.CONTACT_DETECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles the request to get he current door window state.
|
||||||
|
ISYDoorWindowSensorAccessory.prototype.getCurrentDoorWindowState = function(callback) {
|
||||||
|
callback(null,this.translateCurrentDoorWindowState());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mirrors change in the state of the underlying isj-js device object.
|
||||||
|
ISYDoorWindowSensorAccessory.prototype.handleExternalChange = function() {
|
||||||
|
this.sensorService
|
||||||
|
.setCharacteristic(Characteristic.ContactSensorState, this.translateCurrentDoorWindowState());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the set of services supported by this object.
|
||||||
|
ISYDoorWindowSensorAccessory.prototype.getServices = function() {
|
||||||
|
var informationService = new Service.AccessoryInformation();
|
||||||
|
|
||||||
|
informationService
|
||||||
|
.setCharacteristic(Characteristic.Manufacturer, "SmartHome")
|
||||||
|
.setCharacteristic(Characteristic.Model, this.device.deviceFriendlyName)
|
||||||
|
.setCharacteristic(Characteristic.SerialNumber, this.device.address);
|
||||||
|
|
||||||
|
var sensorService = new Service.ContactSensor();
|
||||||
|
|
||||||
|
this.sensorService = sensorService;
|
||||||
|
this.informationService = informationService;
|
||||||
|
|
||||||
|
sensorService
|
||||||
|
.getCharacteristic(Characteristic.ContactSensorState)
|
||||||
|
.on('get', this.getCurrentDoorWindowState.bind(this));
|
||||||
|
|
||||||
|
return [informationService, sensorService];
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// ELK SENSOR PANEL - ISYElkAlarmPanelAccessory
|
||||||
|
// Implements the SecuritySystem service for an elk security panel connected to the isy system
|
||||||
|
|
||||||
|
// Constructs the alarm panel accessory. log = HomeBridge logger, device = underlying isy-js device being wrapped
|
||||||
|
function ISYElkAlarmPanelAccessory(log,device) {
|
||||||
|
ISYAccessoryBaseSetup(this,log,device);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles the identify command
|
||||||
|
ISYElkAlarmPanelAccessory.prototype.identify = function(callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles the request to set the alarm target state
|
||||||
|
ISYElkAlarmPanelAccessory.prototype.setAlarmTargetState = function(targetStateHK,callback) {
|
||||||
|
ISYJSDebugMessage(this,"Sending command to set alarm panel state to: "+targetStateHK);
|
||||||
|
var targetState = this.translateHKToAlarmTargetState(targetStateHK);
|
||||||
|
ISYJSDebugMessage(this,"Would send the target state of: "+targetState);
|
||||||
|
if(this.device.getAlarmMode() != targetState) {
|
||||||
|
this.device.sendSetAlarmModeCommand(targetState, function(result) {
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ISYJSDebugMessage(this,"Redundant command, already in that state.");
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translates from the current state of the elk alarm system into a homekit compatible state. The elk panel has a lot more
|
||||||
|
// possible states then can be directly represented by homekit so we map them. If the alarm is going off then it is tripped.
|
||||||
|
// If it is arming or armed it is considered armed. Stay maps to the state state, away to the away state, night to the night
|
||||||
|
// state.
|
||||||
|
ISYElkAlarmPanelAccessory.prototype.translateAlarmCurrentStateToHK = function() {
|
||||||
|
var tripState = this.device.getAlarmTripState();
|
||||||
|
var sourceAlarmState = this.device.getAlarmState();
|
||||||
|
var sourceAlarmMode = this.device.getAlarmMode();
|
||||||
|
|
||||||
|
if(tripState >= this.device.ALARM_TRIP_STATE_TRIPPED) {
|
||||||
|
return Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED;
|
||||||
|
} else if(sourceAlarmState == this.device.ALARM_STATE_NOT_READY_TO_ARM ||
|
||||||
|
sourceAlarmState == this.device.ALARM_STATE_READY_TO_ARM ||
|
||||||
|
sourceAlarmState == this.device.ALARM_STATE_READY_TO_ARM_VIOLATION) {
|
||||||
|
return Characteristic.SecuritySystemCurrentState.DISARMED;
|
||||||
|
} else {
|
||||||
|
if(sourceAlarmMode == this.device.ALARM_MODE_STAY || sourceAlarmMode == this.device.ALARM_MODE_STAY_INSTANT ) {
|
||||||
|
return Characteristic.SecuritySystemCurrentState.STAY_ARM;
|
||||||
|
} else if(sourceAlarmMode == this.device.ALARM_MODE_AWAY || sourceAlarmMode == this.device.ALARM_MODE_VACATION) {
|
||||||
|
return Characteristic.SecuritySystemCurrentState.AWAY_ARM;
|
||||||
|
} else if(sourceAlarmMode == this.device.ALARM_MODE_NIGHT || sourceAlarmMode == this.device.ALARM_MODE_NIGHT_INSTANT) {
|
||||||
|
return Characteristic.SecuritySystemCurrentState.NIGHT_ARM;
|
||||||
|
} else {
|
||||||
|
ISYJSDebugMessage(this,"Setting to disarmed because sourceAlarmMode is "+sourceAlarmMode);
|
||||||
|
return Characteristic.SecuritySystemCurrentState.DISARMED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translates the current target state of hthe underlying alarm into the appropriate homekit value
|
||||||
|
ISYElkAlarmPanelAccessory.prototype.translateAlarmTargetStateToHK = function() {
|
||||||
|
var sourceAlarmState = this.device.getAlarmMode();
|
||||||
|
if(sourceAlarmState == this.device.ALARM_MODE_STAY || sourceAlarmState == this.device.ALARM_MODE_STAY_INSTANT ) {
|
||||||
|
return Characteristic.SecuritySystemTargetState.STAY_ARM;
|
||||||
|
} else if(sourceAlarmState == this.device.ALARM_MODE_AWAY || sourceAlarmState == this.device.ALARM_MODE_VACATION) {
|
||||||
|
return Characteristic.SecuritySystemTargetState.AWAY_ARM;
|
||||||
|
} else if(sourceAlarmState == this.device.ALARM_MODE_NIGHT || sourceAlarmState == this.device.ALARM_MODE_NIGHT_INSTANT) {
|
||||||
|
return Characteristic.SecuritySystemTargetState.NIGHT_ARM;
|
||||||
|
} else {
|
||||||
|
return Characteristic.SecuritySystemTargetState.DISARM;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translates the homekit version of the alarm target state into the appropriate elk alarm panel state
|
||||||
|
ISYElkAlarmPanelAccessory.prototype.translateHKToAlarmTargetState = function(state) {
|
||||||
|
if(state == Characteristic.SecuritySystemTargetState.STAY_ARM) {
|
||||||
|
return this.device.ALARM_MODE_STAY;
|
||||||
|
} else if(state == Characteristic.SecuritySystemTargetState.AWAY_ARM) {
|
||||||
|
return this.device.ALARM_MODE_AWAY;
|
||||||
|
} else if(state == Characteristic.SecuritySystemTargetState.NIGHT_ARM) {
|
||||||
|
return this.device.ALARM_MODE_NIGHT;
|
||||||
|
} else {
|
||||||
|
return this.device.ALARM_MODE_DISARMED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles request to get the target alarm state
|
||||||
|
ISYElkAlarmPanelAccessory.prototype.getAlarmTargetState = function(callback) {
|
||||||
|
callback(null,this.translateAlarmTargetStateToHK());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles request to get the current alarm state
|
||||||
|
ISYElkAlarmPanelAccessory.prototype.getAlarmCurrentState = function(callback) {
|
||||||
|
callback(null,this.translateAlarmCurrentStateToHK());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mirrors change in the state of the underlying isj-js device object.
|
||||||
|
ISYElkAlarmPanelAccessory.prototype.handleExternalChange = function() {
|
||||||
|
ISYJSDebugMessage(this,"Source device. Currenty state locally -"+this.device.getAlarmStatusAsText());
|
||||||
|
ISYJSDebugMessage(this,"Got alarm change notification. Setting HK target state to: "+this.translateAlarmTargetStateToHK()+" Setting HK Current state to: "+this.translateAlarmCurrentStateToHK());
|
||||||
|
this.alarmPanelService
|
||||||
|
.setCharacteristic(Characteristic.SecuritySystemTargetState, this.translateAlarmTargetStateToHK());
|
||||||
|
this.alarmPanelService
|
||||||
|
.setCharacteristic(Characteristic.SecuritySystemCurrentState, this.translateAlarmCurrentStateToHK());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the set of services supported by this object.
|
||||||
|
ISYElkAlarmPanelAccessory.prototype.getServices = function() {
|
||||||
|
var informationService = new Service.AccessoryInformation();
|
||||||
|
|
||||||
|
informationService
|
||||||
|
.setCharacteristic(Characteristic.Manufacturer, "SmartHome")
|
||||||
|
.setCharacteristic(Characteristic.Model, this.device.deviceFriendlyName)
|
||||||
|
.setCharacteristic(Characteristic.SerialNumber, this.device.address);
|
||||||
|
|
||||||
|
var alarmPanelService = new Service.SecuritySystem();
|
||||||
|
|
||||||
|
this.alarmPanelService = alarmPanelService;
|
||||||
|
this.informationService = informationService;
|
||||||
|
|
||||||
|
alarmPanelService
|
||||||
|
.getCharacteristic(Characteristic.SecuritySystemTargetState)
|
||||||
|
.on('set', this.setAlarmTargetState.bind(this));
|
||||||
|
|
||||||
|
alarmPanelService
|
||||||
|
.getCharacteristic(Characteristic.SecuritySystemTargetState)
|
||||||
|
.on('get', this.getAlarmTargetState.bind(this));
|
||||||
|
|
||||||
|
alarmPanelService
|
||||||
|
.getCharacteristic(Characteristic.SecuritySystemCurrentState)
|
||||||
|
.on('get', this.getAlarmCurrentState.bind(this));
|
||||||
|
|
||||||
|
return [informationService, alarmPanelService];
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.platform = ISYPlatform;
|
||||||
|
module.exports.accessory = ISYFanAccessory;
|
||||||
|
module.exports.accessory = ISYLightAccessory;
|
||||||
|
module.exports.accessory = ISYLockAccessory;
|
||||||
|
module.exports.accessory = ISYOutletAccessory;
|
||||||
|
module.exports.accessory = ISYDoorWindowSensorAccessory;
|
||||||
|
module.exports.accessory = ISYElkAlarmPanelAccessory;
|
||||||
Reference in New Issue
Block a user