Merge pull request #2 from nfarina/master

Update fork from nfarina/homebridge/master
This commit is contained in:
stipus
2015-10-29 12:12:10 +01:00
47 changed files with 4160 additions and 1299 deletions

4
.gitignore vendored
View File

@@ -13,4 +13,8 @@ npm-debug.log
# HomeBridge
config.json
config.test.json
persist/
.AppleDouble

View File

@@ -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 CUSTOM_PANEL_LCD_TEXT_CTYPE = "A3E7B8F9-216E-42C1-A21C-97D4E3BE52C8";

View File

@@ -1,4 +1,4 @@
var types = require("HAP-NodeJS/accessories/types.js");
var types = require("hap-nodejs/accessories/types.js");
var carwings = require("carwingsjs");
function CarwingsAccessory(log, config) {

View File

@@ -1,4 +1,4 @@
var types = require("HAP-NodeJS/accessories/types.js");
var types = require("hap-nodejs/accessories/types.js");
var elkington = require("elkington");
function ElkM1Accessory(log, config) {

View File

@@ -1,5 +1,5 @@
var Service = require("HAP-NodeJS").Service;
var Characteristic = require("HAP-NodeJS").Characteristic;
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var chokidar = require("chokidar");
var debug = require("debug")("FileSensorAccessory");
var crypto = require("crypto");

View File

@@ -1,5 +1,5 @@
var Service = require("HAP-NodeJS").Service;
var Characteristic = require("HAP-NodeJS").Characteristic;
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var SerialPort = require("serialport").SerialPort;
module.exports = {

View File

@@ -1,4 +1,4 @@
var types = require("HAP-NodeJS/accessories/types.js");
var types = require("hap-nodejs/accessories/types.js");
var request = require("request");
function HomeMatic(log, config) {

View File

@@ -1,4 +1,4 @@
var types = require("HAP-NodeJS/accessories/types.js");
var types = require("hap-nodejs/accessories/types.js");
var request = require("request");
function HomeMaticThermo(log, config) {

View File

@@ -1,5 +1,5 @@
var types = require("HAP-NodeJS/accessories/types.js");
var Characteristic = require("HAP-NodeJS").Characteristic;
var types = require("hap-nodejs/accessories/types.js");
var Characteristic = require("hap-nodejs").Characteristic;
var request = require("request");
function HomeMaticWindow(log, config) {

View File

@@ -1,5 +1,5 @@
var Service = require("HAP-NodeJS").Service;
var Characteristic = require("HAP-NodeJS").Characteristic;
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var request = require("request");
module.exports = {
@@ -14,14 +14,21 @@ function HttpAccessory(log, config) {
this.off_url = config["off_url"];
this.brightness_url = config["brightness_url"];
this.http_method = config["http_method"];
this.username = config["username"];
this.password = config["password"];
}
HttpAccessory.prototype = {
httpRequest: function(url, method, callback) {
httpRequest: function(url, method, username, password, callback) {
request({
url: url,
method: method
method: method,
auth: {
user: username,
pass: password,
sendImmediately: false
}
},
function (error, response, body) {
callback(error, response, body)
@@ -40,13 +47,17 @@ HttpAccessory.prototype = {
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) {
this.log('HTTP power function failed: %s', error.message);
callback(error);
}
else {
this.log('HTTP power function succeeded!');
this.log(response);
this.log(body);
this.log(this.username);
this.log(this.password);
callback();
}
}.bind(this));
@@ -57,7 +68,7 @@ HttpAccessory.prototype = {
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) {
this.log('HTTP brightness function failed: %s', error);
callback(error);

View File

@@ -1,5 +1,5 @@
var Service = require("HAP-NodeJS").Service;
var Characteristic = require("HAP-NodeJS").Characteristic;
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var request = require("request");
module.exports = {

View File

@@ -1,5 +1,5 @@
var Service = require("HAP-NodeJS").Service;
var Characteristic = require("HAP-NodeJS").Characteristic;
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var request = require("request");
module.exports = {

View File

@@ -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 Color = require('color');

View File

@@ -1,4 +1,4 @@
var types = require("HAP-NodeJS/accessories/types.js");
var types = require("hap-nodejs/accessories/types.js");
var request = require("request");
// 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++) {
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 (!that.requiredDeviceId) {

View File

@@ -1,5 +1,5 @@
var Service = require('HAP-NodeJS').Service;
var Characteristic = require('HAP-NodeJS').Characteristic;
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var request = require("request");
module.exports = {

View File

@@ -1,4 +1,4 @@
var types = require("HAP-NodeJS/accessories/types.js");
var types = require("hap-nodejs/accessories/types.js");
var tesla = require("teslams");
function TeslaAccessory(log, config) {

View File

@@ -1,5 +1,5 @@
var Service = require("HAP-NodeJS").Service;
var Characteristic = require("HAP-NodeJS").Characteristic;
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var wemo = require('wemo');
module.exports = {

View File

@@ -1,4 +1,4 @@
var types = require("HAP-NodeJS/accessories/types.js");
var types = require("hap-nodejs/accessories/types.js");
var request = require("request");
function X10(log, config) {

View File

@@ -1,6 +1,6 @@
var iControl = require('node-icontrol').iControl;
var Service = require('HAP-NodeJS').Service;
var Characteristic = require('HAP-NodeJS').Characteristic;
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
module.exports = {
accessory: iControlAccessory

View File

@@ -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)
*
*/
var Service = require("HAP-NodeJS").Service;
var Characteristic = require("HAP-NodeJS").Characteristic;
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var knxd = require("eibd");
var knxd_registerGA = require('../platforms/KNX.js').registerGA;
var knxd_startMonitor = require('../platforms/KNX.js').startMonitor;

View File

@@ -1,5 +1,5 @@
var Service = require("HAP-NodeJS").Service;
var Characteristic = require("HAP-NodeJS").Characteristic;
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var request = require("request");
var komponist = require('komponist')

16
app.js
View File

@@ -1,14 +1,14 @@
var fs = require('fs');
var path = require('path');
var storage = require('node-persist');
var hap = require('HAP-NodeJS');
var uuid = require('HAP-NodeJS').uuid;
var Bridge = require('HAP-NodeJS').Bridge;
var Accessory = require('HAP-NodeJS').Accessory;
var Service = require('HAP-NodeJS').Service;
var Characteristic = require('HAP-NodeJS').Characteristic;
var accessoryLoader = require('HAP-NodeJS').AccessoryLoader;
var once = require('HAP-NodeJS/lib/util/once').once;
var hap = require("hap-nodejs");
var uuid = require("hap-nodejs").uuid;
var Bridge = require("hap-nodejs").Bridge;
var Accessory = require("hap-nodejs").Accessory;
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var accessoryLoader = require("hap-nodejs").AccessoryLoader;
var once = require("hap-nodejs/lib/util/once").once;
console.log("Starting HomeBridge server...");

View File

@@ -105,6 +105,16 @@
"platform": "LIFx",
"name": "LIFx",
"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"
}
}
],

View File

@@ -12,35 +12,40 @@
"license": "ISC",
"dependencies": {
"ad2usb": "git+https://github.com/alistairg/node-ad2usb.git#local",
"async": "^1.4.2",
"carwingsjs": "0.0.x",
"chokidar": "^1.0.5",
"color": "0.10.x",
"debug": "^2.2.0",
"eibd": "^0.3.1",
"elkington": "kevinohara80/elkington",
"hap-nodejs": "git+https://github.com/KhaosT/HAP-NodeJS#4650e771f356a220868d873d16564a6be6603ff7",
"harmonyhubjs-client": "^1.1.4",
"hap-nodejs": "^0.0.3",
"harmonyhubjs-client": "^1.1.6",
"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-api": "^1.0.1",
"mdns": "^2.2.4",
"netatmo": "git+https://github.com/patricks/netatmo.git",
"node-cache": "3.0.0",
"node-hue-api": "^1.0.5",
"node-icontrol": "^0.1.5",
"node-milight-promise": "0.0.x",
"node-persist": "0.0.x",
"node-xmpp-client": "1.0.0-alpha23",
"q": "1.4.x",
"tough-cookie": "^2.0.0",
"queue": "^3.1.0",
"request": "2.49.x",
"sonos": "0.8.x",
"telldus": "0.0.9",
"telldus-live": "0.2.x",
"telldus-live": "^0.2.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",
"wink-js": "0.0.5",
"xml2js": "0.4.x",
"xmldoc": "0.1.x",
"komponist" : "0.1.0",
"yamaha-nodejs": "0.4.x",
"debug": "^2.2.0"
"yamaha-nodejs": "0.4.x"
}
}

View File

@@ -50,7 +50,7 @@
// 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 types = require("hap-nodejs/accessories/types.js");
var request = require("request");
function DomoticzPlatform(log, config){

File diff suppressed because it is too large Load Diff

View File

@@ -14,9 +14,9 @@
// 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 Service = require("HAP-NodeJS").Service;
var Characteristic = require("HAP-NodeJS").Characteristic;
var types = require("hap-nodejs/accessories/types.js");
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var request = require("request");
function FibaroHC2Platform(log, config){

View File

@@ -67,8 +67,8 @@
// 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 Service = require("HAP-NodeJS").Service;
var Characteristic = require("HAP-NodeJS").Characteristic;
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var url = require('url')
var request = require("request");

View File

@@ -180,8 +180,8 @@
// - Door
var Service = require("HAP-NodeJS").Service;
var Characteristic = require("HAP-NodeJS").Characteristic;
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var request = require("request");

View File

@@ -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 request = require('request');
var util = require('util');

552
platforms/Indigo.js Normal file
View 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;

View File

@@ -2,20 +2,19 @@
* based on Sonos platform
*/
'use strict';
var types = require("HAP-NodeJS/accessories/types.js");
//var hardware = require('myHardwareSupport'); //require any additional hardware packages
var types = require("hap-nodejs/accessories/types.js");
var knxd = require('eibd');
function KNXPlatform(log, config){
this.log = log;
this.config = config;
// this.property1 = config.property1;
// this.property2 = config.property2;
// initiate connection to bus for listening ==> done with first shim
};
}
KNXPlatform.prototype = {
accessories: function(callback) {
@@ -49,10 +48,10 @@ KNXPlatform.prototype = {
break;
default:
// do something else
this.log("unkown accessory type found")
this.log("unkown accessory type found");
}
};
}
// if done, return the array to callback function
this.log("returning "+myAccessories.length+" accessories");
callback(myAccessories);
@@ -118,7 +117,7 @@ function groupsocketlisten(opts, callback) {
var registerSingleGA = function registerSingleGA (groupAddress, callback, reverse) {
subscriptions.push({address: groupAddress, callback: callback, reverse:reverse });
}
};
/*
* public busMonitor.startMonitor()
@@ -193,7 +192,7 @@ var registerGA = function (groupAddresses, callback) {
} else {
// it's only one
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);

View File

@@ -16,8 +16,8 @@
// The default code for all HomeBridge accessories is 031-45-154.
//
var Service = require("HAP-NodeJS").Service;
var Characteristic = require("HAP-NodeJS").Characteristic;
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var lifxRemoteObj = require('lifx-api');
var lifx_remote;

View File

@@ -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 harmony = require('harmonyhubjs-client');
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) {
return array.sort(function(a, b) {
@@ -41,30 +48,37 @@ function LogitechHarmonyPlatform (log, config) {
LogitechHarmonyPlatform.prototype = {
// Find one Harmony remote hub (only support one for now)
locateHub: function (callback) {
var self = this;
accessories: function (callback) {
var plat = this;
var foundAccessories = [];
var activityAccessories = [];
var hub = null;
var hubIP = null;
var hubQueue = queue();
hubQueue.concurrency = 1;
// Connect to a Harmony hub
var createClient = function (ipAddress) {
self.log("Connecting to Logitech Harmony remote hub...");
// Get the first hub
locateHub(function (err, client, clientIP) {
if (err) throw err;
harmony(ipAddress)
.then(function (client) {
self.log("Connected to Logitech Harmony remote hub");
plat.log("Fetching Logitech Harmony devices and activites...");
callback(null, client);
hub = client;
hubIP = clientIP;
//getDevices(hub);
getActivities();
});
};
// Find one Harmony remote hub (only support one for now)
function locateHub(callback) {
// Use the ip address in configuration if available
if (this.ip_address) {
if (plat.ip_address) {
console.log("Using Logitech Harmony hub ip address from configuration");
return createClient(this.ip_address)
return createClient(plat.ip_address, callback)
}
this.log("Searching for Logitech Harmony remote hubs...");
plat.log("Searching for Logitech Harmony remote hubs...");
// Discover the harmony hub with bonjour
var discover = new harmonyDiscover(_harmonyHubPort);
@@ -72,234 +86,182 @@ LogitechHarmonyPlatform.prototype = {
// 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);
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);
createClient(hubInfo.ip, callback);
});
// Start looking for hubs
discover.start();
},
}
accessories: function (callback) {
var self = this;
var foundAccessories = [];
// Get the first hub
this.locateHub(function (err, hub) {
if (err) throw err;
self.log("Fetching Logitech Harmony devices and activites...");
//getDevices(hub);
getActivities(hub);
// 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);
});
// Get Harmony Devices
/*
var getDevices = function(hub) {
self.log("Fetching Logitech Harmony devices...");
hub.getDevices()
.then(function (devices) {
self.log("Found devices: ", devices);
var sArray = sortByKey(json['result'],"Name");
sArray.map(function(s) {
accessory = new LogitechHarmonyAccessory(self.log, self.server, self.port, false, s.idx, s.Name, s.HaveDimmer, s.MaxDimLevel, (s.SubType=="RGB")||(s.SubType=="RGBW"));
foundAccessories.push(accessory);
});
callback(foundAccessories);
});
};
*/
}
// Get Harmony Activities
var getActivities = function(hub) {
self.log("Fetching Logitech Harmony activities...");
function getActivities() {
plat.log("Fetching Logitech Harmony activities...");
hub.getActivities()
.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");
sArray.map(function(s) {
var accessory = new LogitechHarmonyAccessory(self.log, hub, s, true);
// TODO: Update the initial power state
foundAccessories.push(accessory);
});
callback(foundAccessories);
});
};
}
};
function LogitechHarmonyAccessory (log, hub, details, isActivity) {
this.log = log;
this.hub = hub;
this.details = details;
this.id = details.id;
this.name = details.label;
this.isActivity = isActivity;
this.isActivityActive = false;
};
LogitechHarmonyAccessory.prototype = {
// TODO: Somehow make this event driven so that it tells the user what activity is on
getPowerState: function (callback) {
var self = this;
if (this.isActivity) {
hub.getCurrentActivity().then(function (currentActivity) {
callback(currentActivity.id === self.id);
}).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');
var actAccessories = [];
var sArray = sortByKey(activities, "label");
sArray.map(function(s) {
var accessory = createActivityAccessory(s);
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;
});
});
}
},
setPowerState: function (state, callback) {
var self = this;
function createActivityAccessory(activity) {
var accessory = new LogitechHarmonyActivityAccessory(plat.log, activity, changeCurrentActivity.bind(plat), -1);
return accessory;
}
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)
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 () {
self.log('Finished setting activity ' + self.name + ' power state to ' + state);
callback();
cb();
isChangingActivity = false;
plat.log('Finished setting activity to ' + nextActivity);
updateCurrentActivity(nextActivity);
if (callback) callback(null, nextActivity);
})
.catch(function (err) {
self.log('Failed setting activity ' + self.name + ' power state to ' + state + ' with error ' + err);
callback(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"));
});
} 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
}
]
}
];
}
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) {
this.log = log;
this.id = details.id;
this.name = details.label;
this.isOn = 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) {
this.isOn = (currentActivity === this.id);
// Force get to trigger 'change' if needed
this.getService(Service.Switch)
.getCharacteristic(Characteristic.On)
.getValue();
};
LogitechHarmonyActivityAccessory.prototype.setPowerState = function (state, callback) {
this.changeCurrentActivity(state ? this.id : null, callback);
};
module.exports.accessory = LogitechHarmonyAccessory;
module.exports.platform = LogitechHarmonyPlatform;

View File

@@ -47,8 +47,8 @@ TODO:
*/
var Service = require("HAP-NodeJS").Service;
var Characteristic = require("HAP-NodeJS").Characteristic;
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var Milight = require('node-milight-promise').MilightController;
var commands = require('node-milight-promise').commands;

View File

@@ -1,13 +1,17 @@
var types = require("HAP-NodeJS/accessories/types.js");
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){
// auth info
this.username = config["username"];
this.password = config["password"];
this.log = log;
this.accessoryLookup = { };
}
NestPlatform.prototype = {
@@ -20,8 +24,7 @@ NestPlatform.prototype = {
nest.login(this.username, this.password, function (err, data) {
if (err) {
that.log("There was a problem authenticating with Nest.");
}
else {
} else {
nest.fetchStatus(function (data) {
for (var deviceId in data.device) {
if (data.device.hasOwnProperty(deviceId)) {
@@ -29,12 +32,28 @@ NestPlatform.prototype = {
// it's a thermostat, adjust this to detect other accessories
if (data.shared[deviceId].hasOwnProperty('current_temperature'))
{
var name = data.shared[deviceId].name
var accessory = new NestThermostatAccessory(that.log, name, device, deviceId);
var initialData = data.shared[deviceId];
var name = initialData.name;
var accessory = new NestThermostatAccessory(that.log, name, device, deviceId, initialData);
that.accessoryLookup[deviceId] = 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)
});
}
@@ -42,356 +61,204 @@ NestPlatform.prototype = {
}
}
function NestThermostatAccessory(log, name, device, deviceId) {
function NestThermostatAccessory(log, name, device, deviceId, initialData) {
// device info
if (name) {
this.name = name;
} else {
this.name = "Nest";
}
this.model = device.model_version;
this.serial = device.serial_number;
this.name = name || ("Nest" + device.serial_number);
this.deviceId = deviceId;
this.log = log;
this.device = device;
var id = uuid.generate('nest.thermostat.' + deviceId);
Accessory.call(this, name, id);
this.uuid_base = id;
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 = {
getCurrentHeatingCooling: function(callback){
NestThermostatAccessory.prototype.getServices = function() {
return this.services;
};
var that = this;
this.log("Checking current heating cooling for: " + this.name);
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;
NestThermostatAccessory.prototype.updateData = function(data) {
if (data != undefined) {
this.currentData = data;
}
that.log("Current heating for " + this.name + "is: " + currentHeatingCooling);
callback(currentHeatingCooling);
});
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;
getTargetHeatingCoooling: function(callback){
// Add threshold
var threshold = .2;
high += threshold;
low -= threshold;
var that = this;
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;
};
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) {
NestThermostatAccessory.prototype.getTargetHeatingCooling = function(){
switch(this.currentData.target_temperature_type) {
case "off":
targetHeatingCooling = 0;
break;
return Characteristic.CurrentHeatingCoolingState.OFF;
case "heat":
targetHeatingCooling = 1;
break;
return Characteristic.CurrentHeatingCoolingState.HEAT;
case "cool":
targetHeatingCooling = 2;
break;
return Characteristic.CurrentHeatingCoolingState.COOL;
case "range":
targetHeatingCooling = 3;
break;
return Characteristic.CurrentHeatingCoolingState.HEAT | Characteristic.CurrentHeatingCoolingState.COOL;
default:
targetHeatingCooling = 0;
return Characteristic.CurrentHeatingCoolingState.OFF;
}
that.log("Current target heating for " + this.name + " is: " + targetHeatingCooling);
callback(targetHeatingCooling);
});
},
};
getCurrentTemperature: function(callback){
NestThermostatAccessory.prototype.getCurrentTemperature = function(){
return this.currentData.current_temperature;
};
var that = this;
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;
}
};
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) {
NestThermostatAccessory.prototype.getTemperatureUnits = function() {
switch(this.device.temperature_scale) {
case "F":
that.log("Tempature unit for " + this.name + " is: " + "Fahrenheit");
temperatureUnits = 1;
break;
return Characteristic.TemperatureDisplayUnits.FAHRENHEIT;
case "C":
that.log("Tempature unit for " + this.name + " is: " + "Celsius");
temperatureUnits = 0;
break;
return Characteristic.TemperatureDisplayUnits.CELSIUS;
default:
temperatureUnits = 0;
return Characteristic.TemperatureDisplayUnits.CELSIUS;
}
};
callback(temperatureUnits);
});
NestThermostatAccessory.prototype.setTargetHeatingCooling = function(targetHeatingCooling, callback){
var targetTemperatureType = null;
},
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:
case Characteristic.CurrentHeatingCoolingState.HEAT:
targetTemperatureType = 'heat';
break;
case 2:
case Characteristic.CurrentHeatingCoolingState.COOL:
targetTemperatureType = 'cool';
break;
case 3:
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);
};
},
setTargetTemperature: function(targetTemperature){
var that = this;
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);
},
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",
}]
}];
break;
}
}
if (callback) callback(null, targetTemperature);
};
module.exports.accessory = NestThermostatAccessory;
module.exports.platform = NestPlatform;

388
platforms/Netatmo.js Normal file
View 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
View 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;

View File

@@ -33,7 +33,9 @@ var hue = require("node-hue-api"),
HueApi = hue.HueApi,
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) {
this.log = log;
@@ -167,7 +169,7 @@ PhilipsHueAccessory.prototype = {
// Convert 0-65535 to 0-360
hueToArcDegrees: function(value) {
value = value/65535;
value = value*100;
value = value*360;
value = Math.round(value);
return value;
},
@@ -185,9 +187,22 @@ PhilipsHueAccessory.prototype = {
value = Math.round(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
executeChange: function(api, device, characteristic, value) {
var that = this;
executeChange: function(characteristic, value, callback) {
var state = lightState.create();
switch(characteristic.toLowerCase()) {
case 'identify':
@@ -211,164 +226,112 @@ PhilipsHueAccessory.prototype = {
state.saturation(value);
break;
}
api.setLightState(device.id, state, function(err, lights) {
this.api.setLightState(this.id, state, function(err, lights) {
if (callback == null) {
return;
}
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 {
if (err.code == "ECONNRESET") {
setTimeout(function() {
that.executeChange(api, device, characteristic, value);
this.executeChange(characteristic, value, callback);
}, 300);
} 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
getServices: function() {
var that = this;
var bulb_characteristics = [
{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of service",
designedMaxLength: 255
},{
cType: types.POWER_STATE_CTYPE,
onUpdate: function(value) {
that.executeChange(that.api, that.device, "power", value);
},
perms: ["pw","pr","ev"],
format: "bool",
initialValue: that.device.state.on,
supportEvents: false,
supportBonjour: false,
manfDescription: "Turn On the Light",
designedMaxLength: 1
},{
cType: types.BRIGHTNESS_CTYPE,
onUpdate: function(value) {
that.executeChange(that.api, that.device, "brightness", value);
},
perms: ["pw","pr","ev"],
format: "int",
initialValue: that.bitsToPercentage(that.device.state.bri),
supportEvents: false,
supportBonjour: false,
manfDescription: "Adjust Brightness of Light",
designedMinValue: 0,
designedMaxValue: 100,
designedMinStep: 1,
unit: "%"
}
];
// Use HomeKit types defined in HAP node JS
var lightbulbService = new Service.Lightbulb(this.name);
// Basic light controls, common to Hue and Hue lux
lightbulbService
.getCharacteristic(Characteristic.On)
.on('get', function(callback) { that.getState("power", callback);})
.on('set', function(value, callback) { that.executeChange("power", value, callback);})
.value = this.extractValue("power", this.device);
lightbulbService
.addCharacteristic(Characteristic.Brightness)
.on('get', function(callback) { that.getState("brightness", callback);})
.on('set', function(value, callback) { that.executeChange("brightness", value, callback);})
.value = this.extractValue("brightness", this.device);
// Handle the Hue/Hue Lux divergence
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: "%"
});
if (this.device.state.hasOwnProperty('hue') && this.device.state.hasOwnProperty('sat')) {
lightbulbService
.addCharacteristic(Characteristic.Hue)
.on('get', function(callback) { that.getState("hue", callback);})
.on('set', function(value, callback) { that.executeChange("hue", value, callback);})
.value = this.extractValue("hue", this.device);
lightbulbService
.addCharacteristic(Characteristic.Saturation)
.on('get', function(callback) { that.getState("saturation", callback);})
.on('set', function(value, callback) { that.executeChange("saturation", value, callback);})
.value = this.extractValue("saturation", this.device);
}
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;
var informationService = new Service.AccessoryInformation();
informationService
.setCharacteristic(Characteristic.Manufacturer, "Philips")
.setCharacteristic(Characteristic.Model, this.model)
.setCharacteristic(Characteristic.SerialNumber, this.device.uniqueid)
.addCharacteristic(Characteristic.FirmwareRevision, this.device.swversion);
return [informationService, lightbulbService];
}
};

View File

@@ -1,7 +1,7 @@
// SmartThings JSON API SmartApp required
// 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");
function SmartThingsPlatform(log, config){

View File

@@ -1,4 +1,4 @@
var types = require("HAP-NodeJS/accessories/types.js");
var types = require("hap-nodejs/accessories/types.js");
var sonos = require('sonos');
function SonosPlatform(log, config){
@@ -34,7 +34,7 @@ SonosPlatform.prototype = {
that.log("Found device at " + device.host);
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"];
if (!roomNamesFound[roomName]) {

View File

@@ -1,4 +1,4 @@
var types = require("HAP-NodeJS/accessories/types.js");
var types = require("hap-nodejs/accessories/types.js");
var telldus = require('telldus');
function TelldusPlatform(log, config) {

View File

@@ -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");
function TelldusLivePlatform(log, config) {
@@ -58,7 +58,7 @@ var TelldusLiveAccessory = function TelldusLiveAccessory(log, cloud, device) {
this.log = log;
this.cloud = cloud;
var m = device.model.split(':');
var m = device.model ? device.model.split(':') : ['unknown', 'unknown'] ;
// Set accessory info
this.device = device;

View File

@@ -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 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 = {
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){
// auth info
this.client_id = config["client_id"];
this.client_secret = config["client_secret"];
this.username = config["username"];
this.password = config["password"];
this.log = log;
this.deviceLookup = {};
}
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) {
this.log("Fetching Wink devices.");
var that = this;
var foundAccessories = [];
this.deviceLookup = {};
var refreshLoop = function(){
setInterval(that.reloadData.bind(that), 30000);
};
wink.init({
"client_id": this.client_id,
@@ -34,219 +83,268 @@ WinkPlatform.prototype = {
that.log("There was a problem authenticating with Wink.");
} else {
// success
wink.user().devices('light_bulbs', function(devices) {
wink.user().devices(function(devices) {
for (var i=0; i<devices.data.length; i++){
device = model.light_bulbs(devices.data[i], wink)
accessory = new WinkAccessory(that.log, device);
var device = devices.data[i];
var accessory = null;
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);
});
}
});
}
}
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.lockAccessory = WinkLockAccessory;
module.exports.lightAccessory = WinkLightAccessory;
module.exports.platform = WinkPlatform;

View File

@@ -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 debug = require('debug')('YamahaAVR');
var Service = require("HAP-NodeJS").Service;
var Characteristic = require("HAP-NodeJS").Characteristic;
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var Yamaha = require('yamaha-nodejs');
var Q = require('q');
var mdns = require('mdns');

View File

@@ -1,7 +1,7 @@
var debug = require('debug')('ZWayServer');
var Service = require("HAP-NodeJS").Service;
var Characteristic = require("HAP-NodeJS").Characteristic;
var types = require("HAP-NodeJS/accessories/types.js");
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var types = require("hap-nodejs/accessories/types.js");
var request = require("request");
var tough = require('tough-cookie');
var Q = require("q");
@@ -356,7 +356,16 @@ ZWayServerAccessory.prototype = {
}
break;
case "sensorBinary.Door/Window":
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;
case "sensorMultilevel.Temperature":
services.push(new Service.TemperatureSensor(vdev.metrics.title, vdev.id));
@@ -371,6 +380,8 @@ ZWayServerAccessory.prototype = {
var stype = this.platform.getTagValue(vdev, "Service.Type");
if(stype === "MotionSensor"){
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.CurrentDoorState).UUID] = ["sensorBinary.Door/Window","sensorBinary"];
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.BatteryLevel).UUID] = ["battery.Battery"];
map[(new Characteristic.StatusLowBattery).UUID] = ["battery.Battery"];
@@ -815,6 +830,68 @@ ZWayServerAccessory.prototype = {
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){

763
platforms/isy-js.js Normal file
View 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;