From a3c0df1c7c6612dcafb7433ec063b9b3b11f50b3 Mon Sep 17 00:00:00 2001 From: Nick Farina Date: Sun, 18 Oct 2015 16:34:21 -0700 Subject: [PATCH] Plugin support - Homebridge is now designed to be `npm install`d globally and executed via "homebridge" script - Remove all specific accessories/platforms except for an example - New internal structure and "cli" --- .gitignore | 8 - .gitmodules | 0 accessories/AD2USB.js | 245 --- accessories/Carwings.js | 126 -- accessories/ELKM1.js | 126 -- accessories/FileSensor.js | 76 - accessories/GenericRS232Device.js | 58 - accessories/HomeMatic.js | 141 -- accessories/HomeMaticThermo.js | 264 --- accessories/HomeMaticWindow.js | 123 -- accessories/Http.js | 100 -- accessories/HttpHygrometer.js | 71 - accessories/HttpThermometer.js | 79 - accessories/Hyperion.js | 221 --- accessories/LiftMaster.js | 306 ---- accessories/Tesla.js | 119 -- accessories/WeMo.js | 169 -- accessories/X10.js | 151 -- accessories/iControl.js | 130 -- accessories/knxdevice.js | 1032 ----------- accessories/mpdclient.js | 89 - app.js | 223 --- bin/homebridge | 17 + .../homebridge-lockitron/README.md | 3 + .../homebridge-lockitron/index.js | 4 +- .../homebridge-lockitron/package.json | 15 + lib/cli.js | 29 + lib/logger.js | 64 + lib/plugin.js | 176 ++ lib/server.js | 297 ++++ lib/user.js | 35 + lib/version.js | 12 + package.json | 58 +- platforms/Domoticz.js | 378 ---- platforms/FHEM.js | 1570 ----------------- platforms/FibaroHC2.js | 253 --- platforms/HomeAssistant.js | 542 ------ platforms/HomeSeer.js | 1066 ----------- platforms/ISY.js | 385 ---- platforms/Indigo.js | 552 ------ platforms/KNX-sample-config.json | 156 -- platforms/KNX.js | 206 --- platforms/KNX.md | 213 --- platforms/LIFx.js | 302 ---- platforms/LogitechHarmony.js | 305 ---- platforms/MiLight.js | 242 --- platforms/Nest.js | 397 ----- platforms/PhilipsHue.js | 375 ---- platforms/SmartThings.js | 242 --- platforms/Sonos.js | 188 -- platforms/Telldus.js | 265 --- platforms/TelldusLive.js | 265 --- platforms/Wink.js | 252 --- platforms/YamahaAVR.js | 238 --- platforms/ZWayServer.js | 935 ---------- 55 files changed, 675 insertions(+), 13219 deletions(-) delete mode 100644 .gitmodules delete mode 100644 accessories/AD2USB.js delete mode 100644 accessories/Carwings.js delete mode 100644 accessories/ELKM1.js delete mode 100644 accessories/FileSensor.js delete mode 100644 accessories/GenericRS232Device.js delete mode 100644 accessories/HomeMatic.js delete mode 100644 accessories/HomeMaticThermo.js delete mode 100644 accessories/HomeMaticWindow.js delete mode 100644 accessories/Http.js delete mode 100644 accessories/HttpHygrometer.js delete mode 100644 accessories/HttpThermometer.js delete mode 100644 accessories/Hyperion.js delete mode 100644 accessories/LiftMaster.js delete mode 100644 accessories/Tesla.js delete mode 100644 accessories/WeMo.js delete mode 100644 accessories/X10.js delete mode 100644 accessories/iControl.js delete mode 100644 accessories/knxdevice.js delete mode 100644 accessories/mpdclient.js delete mode 100644 app.js create mode 100755 bin/homebridge create mode 100644 example-plugins/homebridge-lockitron/README.md rename accessories/Lockitron.js => example-plugins/homebridge-lockitron/index.js (97%) create mode 100644 example-plugins/homebridge-lockitron/package.json create mode 100644 lib/cli.js create mode 100644 lib/logger.js create mode 100644 lib/plugin.js create mode 100644 lib/server.js create mode 100644 lib/user.js create mode 100644 lib/version.js delete mode 100644 platforms/Domoticz.js delete mode 100644 platforms/FHEM.js delete mode 100644 platforms/FibaroHC2.js delete mode 100644 platforms/HomeAssistant.js delete mode 100644 platforms/HomeSeer.js delete mode 100644 platforms/ISY.js delete mode 100644 platforms/Indigo.js delete mode 100644 platforms/KNX-sample-config.json delete mode 100644 platforms/KNX.js delete mode 100644 platforms/KNX.md delete mode 100644 platforms/LIFx.js delete mode 100644 platforms/LogitechHarmony.js delete mode 100644 platforms/MiLight.js delete mode 100644 platforms/Nest.js delete mode 100644 platforms/PhilipsHue.js delete mode 100644 platforms/SmartThings.js delete mode 100644 platforms/Sonos.js delete mode 100644 platforms/Telldus.js delete mode 100644 platforms/TelldusLive.js delete mode 100644 platforms/Wink.js delete mode 100644 platforms/YamahaAVR.js delete mode 100644 platforms/ZWayServer.js diff --git a/.gitignore b/.gitignore index 81a1589..2f5c281 100644 --- a/.gitignore +++ b/.gitignore @@ -6,11 +6,3 @@ node_modules/ npm-debug.log .node-version - -# Intellij -.idea/ -*.iml - -# HomeBridge -config.json -persist/ diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e69de29..0000000 diff --git a/accessories/AD2USB.js b/accessories/AD2USB.js deleted file mode 100644 index bcef82d..0000000 --- a/accessories/AD2USB.js +++ /dev/null @@ -1,245 +0,0 @@ -var types = require("hap-nodejs/accessories/types.js"); -var AD2USB = require('ad2usb'); -var CUSTOM_PANEL_LCD_TEXT_CTYPE = "A3E7B8F9-216E-42C1-A21C-97D4E3BE52C8"; - -function AD2USBAccessory(log, config) { - - this.log = log; - this.name = config["name"]; - this.host = config["host"]; - this.port = config["port"]; - this.pin = config["pin"]; - var that = this; - this.currentArmState = 2; - this.currentStateCharacteristic = undefined; - this.targetStateCharacteristic = undefined; - this.lcdCharacteristic = undefined; - - var alarm = AD2USB.connect(this.host, this.port, function() { - - // Send an initial empty character to get status - alarm.send(''); - - // Armed Away - alarm.on('armedAway', function() { - - that.log("Armed to AWAY"); - if (that.currentStateCharacteristic) { - that.currentStateCharacteristic.updateValue(0, null); - } - if (that.targetStateCharacteristic) { - that.targetStateCharacteristic.updateValue(1, null); - } - - }); - - // Armed Stay - alarm.on('armedStay', function() { - - that.log("Armed to STAY"); - if (that.currentStateCharacteristic) { - that.currentStateCharacteristic.updateValue(0, null); - } - if (that.targetStateCharacteristic) { - that.targetStateCharacteristic.updateValue(0, null); - } - - }); - - // Armed Night - alarm.on('armedNight', function() { - - that.log("Armed to NIGHT"); - if (that.currentStateCharacteristic) { - that.currentStateCharacteristic.updateValue(0, null); - } - if (that.targetStateCharacteristic) { - that.targetStateCharacteristic.updateValue(2, null); - } - - }); - - // Disarmed - alarm.on('disarmed', function() { - - that.log("Disarmed"); - if (that.currentStateCharacteristic) { - that.currentStateCharacteristic.updateValue(1, null); - } - if (that.targetStateCharacteristic) { - that.targetStateCharacteristic.updateValue(3, null); - } - - }); - - // Text Change - alarm.on('lcdtext', function(newText) { - - that.log("LCD: " + newText); - if (that.lcdCharacteristic) { - that.lcdCharacteristic.updateValue(newText, null); - } - - }); - - - }); - this.alarm = alarm; - -} - -AD2USBAccessory.prototype = { - - setArmState: function(targetArmState) { - - var that = this; - that.log("Desired target arm state: " + targetArmState); - - // TARGET - // 0 - Stay - // 1 - Away - // 2 - Night - // 3 - Disarm - if (targetArmState == 0) { - that.alarm.armStay(that.pin); - } - else if (targetArmState == 1) { - that.alarm.armAway(that.pin); - } - else if (targetArmState == 2) { - that.alarm.armNight(that.pin); - } - else if (targetArmState == 3) { - that.alarm.disarm(that.pin); - } - - - // CURRENT - // 0 - Armed - // 1 - Disarmed - // 2 - Hold - - }, - - 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: "Nutech", - supportEvents: false, - supportBonjour: false, - manfDescription: "Manufacturer", - designedMaxLength: 255 - },{ - cType: types.MODEL_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "AD2USB", - supportEvents: false, - supportBonjour: false, - manfDescription: "Model", - designedMaxLength: 255 - },{ - cType: types.SERIAL_NUMBER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "AD2USBIF", - 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.ALARM_STYPE, - 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.ALARM_CURRENT_STATE_CTYPE, - onUpdate: null, - onRegister: function(characteristic) { - - that.currentStateCharacteristic = characteristic; - characteristic.eventEnabled = true; - - }, - perms: ["pr","ev"], - format: "int", - initialValue: 2, - supportEvents: true, - supportBonjour: false, - manfDescription: "Alarm current arm state", - designedMaxLength: 1 - },{ - cType: types.ALARM_TARGET_STATE_CTYPE, - onUpdate: function(value) { that.setArmState(value); }, - onRegister: function(characteristic) { - - that.targetStateCharacteristic = characteristic; - characteristic.eventEnabled = true; - - }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: 1, - supportEvents: true, - supportBonjour: false, - manfDescription: "Alarm target arm state", - designedMaxLength: 1 - }, - { - cType: CUSTOM_PANEL_LCD_TEXT_CTYPE, - onUpdate: null, - onRegister: function(characteristic) { - - that.lcdCharacteristic = characteristic; - characteristic.eventEnabled = true; - - }, - perms: ["pr","ev"], - format: "string", - initialValue: "Unknown", - supportEvents: false, - supportBonjour: false, - manfDescription: "Keypad Text", - designedMaxLength: 64 - }] - }]; - } -}; - -module.exports.accessory = AD2USBAccessory; diff --git a/accessories/Carwings.js b/accessories/Carwings.js deleted file mode 100644 index 7d0cd82..0000000 --- a/accessories/Carwings.js +++ /dev/null @@ -1,126 +0,0 @@ -var types = require("hap-nodejs/accessories/types.js"); -var carwings = require("carwingsjs"); - -function CarwingsAccessory(log, config) { - this.log = log; - this.name = config["name"]; - this.username = config["username"]; - this.password = config["password"]; -} - -CarwingsAccessory.prototype = { - - setPowerState: function(powerOn) { - var that = this; - - carwings.login(this.username, this.password, function(err, result) { - if (!err) { - that.vin = result.vin; - that.log("Got VIN: " + that.vin); - - if (powerOn) { - carwings.startClimateControl(that.vin, null, function(err, result) { - if (!err) - that.log("Started climate control."); - else - that.log("Error starting climate control: " + err); - }); - } - else { - carwings.stopClimateControl(that.vin, function(err, result) { - if (!err) - that.log("Stopped climate control."); - else - that.log("Error stopping climate control: " + err); - }); - } - } - else { - that.log("Error logging in: " + err); - } - }); - }, - - 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: "Nissan", - 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.SWITCH_STYPE, - 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.setPowerState(value); }, - perms: ["pw","pr","ev"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Change the power state of the car", - designedMaxLength: 1 - }] - }]; - } -}; - -module.exports.accessory = CarwingsAccessory; diff --git a/accessories/ELKM1.js b/accessories/ELKM1.js deleted file mode 100644 index ead0f17..0000000 --- a/accessories/ELKM1.js +++ /dev/null @@ -1,126 +0,0 @@ -var types = require("hap-nodejs/accessories/types.js"); -var elkington = require("elkington"); - -function ElkM1Accessory(log, config) { - this.log = log; - this.name = config["name"]; - this.zone = config["zone"]; - this.host = config["host"]; - this.port = config["port"]; - this.pin = config["pin"]; - this.arm = config["arm"]; -} - -ElkM1Accessory.prototype = { - setPowerState: function(alarmOn) { - var that = this; - - if (!alarmOn) - { - return; - } - - var elk = elkington.createConnection({ - port: that.port, - host: that.host, - }); - - switch (that.arm) - { - case 'Away': - elk.armAway({area: that.zone, code: that.pin}); - break; - case 'Stay': - elk.armStay({area: that.zone, code: that.pin}); - break; - case 'Night': - elk.armNightInstant({area: that.zone, code: that.pin}); - break; - default: - break; - } - }, - - 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: "Elk", - supportEvents: false, - supportBonjour: false, - manfDescription: "Manufacturer", - designedMaxLength: 255 - },{ - cType: types.MODEL_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "M1", - 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.SWITCH_STYPE, - 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.setPowerState(value); }, - perms: ["pw","pr","ev"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Alarm the Zone", - designedMaxLength: 1 - }] - }]; - } -}; - -module.exports.accessory = ElkM1Accessory; diff --git a/accessories/FileSensor.js b/accessories/FileSensor.js deleted file mode 100644 index 112089e..0000000 --- a/accessories/FileSensor.js +++ /dev/null @@ -1,76 +0,0 @@ -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"); - -module.exports = { - accessory: FileSensorAccessory -} - -function FileSensorAccessory(log, config) { - this.log = log; - - // url info - this.name = config["name"]; - this.path = config["path"]; - this.window_seconds = config["window_seconds"] || 5; - this.sensor_type = config["sensor_type"] || "m"; - this.inverse = config["inverse"] || false; - - if(config["sn"]){ - this.sn = config["sn"]; - } else { - var shasum = crypto.createHash('sha1'); - shasum.update(this.path); - this.sn = shasum.digest('base64'); - debug('Computed SN ' + this.sn); - } -} - -FileSensorAccessory.prototype = { - - getServices: function() { - - // you can OPTIONALLY create an information service if you wish to override - // the default values for things like serial number, model, etc. - var informationService = new Service.AccessoryInformation(); - - informationService - .setCharacteristic(Characteristic.Name, this.name) - .setCharacteristic(Characteristic.Manufacturer, "Homebridge") - .setCharacteristic(Characteristic.Model, "File Sensor") - .setCharacteristic(Characteristic.SerialNumber, this.sn); - - var service, changeAction; - if(this.sensor_type === "c"){ - service = new Service.ContactSensor(); - changeAction = function(newState){ - service.getCharacteristic(Characteristic.ContactSensorState) - .setValue(newState ? Characteristic.ContactSensorState.CONTACT_DETECTED : Characteristic.ContactSensorState.CONTACT_NOT_DETECTED); - }; - } else { - service = new Service.MotionSensor(); - changeAction = function(newState){ - service.getCharacteristic(Characteristic.MotionDetected) - .setValue(newState); - }; - } - - var changeHandler = function(path, stats){ - var d = new Date(); - if(d.getTime() - stats.mtime.getTime() <= (this.window_seconds * 1000)){ - var newState = this.inverse ? false : true; - changeAction(newState); - if(this.timer !== undefined) clearTimeout(this.timer); - this.timer = setTimeout(function(){changeAction(!newState);}, this.window_seconds * 1000); - } - }.bind(this); - - var watcher = chokidar.watch(this.path, {alwaysStat: true}); - watcher.on('add', changeHandler); - watcher.on('change', changeHandler); - - return [informationService, service]; - } -}; diff --git a/accessories/GenericRS232Device.js b/accessories/GenericRS232Device.js deleted file mode 100644 index 01fe80c..0000000 --- a/accessories/GenericRS232Device.js +++ /dev/null @@ -1,58 +0,0 @@ -var Service = require("hap-nodejs").Service; -var Characteristic = require("hap-nodejs").Characteristic; -var SerialPort = require("serialport").SerialPort; - -module.exports = { - accessory: GenericRS232DeviceAccessory -} - -function GenericRS232DeviceAccessory(log, config) { - this.log = log; - this.id = config["id"]; - this.name = config["name"]; - this.model_name = config["model_name"]; - this.manufacturer = config["manufacturer"]; - this.on_command = config["on_command"]; - this.off_command = config["off_command"]; - this.device = config["device"]; - this.baudrate = config["baudrate"]; -} - -GenericRS232DeviceAccessory.prototype = { - setPowerState: function(powerOn, callback) { - var that = this; - var command = powerOn ? that.on_command : that.off_command; - var serialPort = new SerialPort(that.device, { baudrate: that.baudrate }, false); - serialPort.open(function (error) { - if (error) { - callback(new Error('Can not communicate with ' + that.name + " (" + error + ")")) - } else { - serialPort.write(command, function(err, results) { - if (error) { - callback(new Error('Can not send power command to ' + that.name + " (" + err + ")")) - } else { - callback() - } - }); - } - }); - }, - - getServices: function() { - var switchService = new Service.Switch(this.name); - var informationService = new Service.AccessoryInformation(); - - informationService - .setCharacteristic(Characteristic.Manufacturer, this.manufacturer) - .setCharacteristic(Characteristic.Model, this.model_name) - .setCharacteristic(Characteristic.SerialNumber, this.id); - - switchService - .getCharacteristic(Characteristic.On) - .on('set', this.setPowerState.bind(this)); - - return [informationService, switchService]; - } -} - -module.exports.accessory = GenericRS232DeviceAccessory; \ No newline at end of file diff --git a/accessories/HomeMatic.js b/accessories/HomeMatic.js deleted file mode 100644 index 23a7ecb..0000000 --- a/accessories/HomeMatic.js +++ /dev/null @@ -1,141 +0,0 @@ -var types = require("hap-nodejs/accessories/types.js"); -var request = require("request"); - -function HomeMatic(log, config) { - this.log = log; - this.name = config["name"]; - this.ccuID = config["ccu_id"]; - this.ccuIP = config["ccu_ip"]; -} - -HomeMatic.prototype = { - - setPowerState: function(powerOn) { - - var binaryState = powerOn ? 1 : 0; - var that = this; - - this.log("Setting power state of CCU to " + powerOn); - this.log(this.ccuID+ powerOn); - - request.put({ - url: "http://"+this.ccuIP+"/config/xmlapi/statechange.cgi?ise_id="+this.ccuID+"&new_value="+ powerOn, - }, function(err, response, body) { - - if (!err && response.statusCode == 200) { - that.log("State change complete."); - } - else { - that.log("Error '"+err+"' setting lock state: " + body); - } - }); - }, - getPowerState: function(callback) { - var that = this; - - this.log("Getting Power State of CCU"); - request.get({ - url: "http://"+this.ccuIP+"/config/xmlapi/state.cgi?datapoint_id="+this.ccuID, - }, function(err, response, body) { - - if (!err && response.statusCode == 200) { - - //that.log("Response:"+response.body); - var responseString = response.body.substring(83,87); - //that.log(responseString); - switch(responseString){ - case "true": {modvalue = "1";break;} - case "fals": {modvalue = "0";break;} - } - callback(parseInt(modvalue)); - that.log("Getting Power State complete."); - } - else { - that.log("Error '"+err+"' getting Power State: " + body); - } - }); - }, - 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: "WeMo", - 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.SWITCH_STYPE, - 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.setPowerState(value); }, - onRead: function(callback) { that.getPowerState(callback); }, - perms: ["pw","pr","ev"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Change the power state of a Variable", - designedMaxLength: 1 - }] - }]; - } -}; - -module.exports.accessory = HomeMatic; diff --git a/accessories/HomeMaticThermo.js b/accessories/HomeMaticThermo.js deleted file mode 100644 index 86db229..0000000 --- a/accessories/HomeMaticThermo.js +++ /dev/null @@ -1,264 +0,0 @@ -var types = require("hap-nodejs/accessories/types.js"); -var request = require("request"); - -function HomeMaticThermo(log, config) { - this.log = log; - this.name = config["name"]; - this.ccuIDTargetTemp = config["ccu_id_TargetTemp"]; - this.ccuIDCurrentTemp = config["ccu_id_CurrentTemp"]; - this.ccuIDControlMode = config["ccu_id_ControlMode"]; - this.ccuIDManuMode = config["ccu_id_ManuMode"]; - this.ccuIDAutoMode = config["ccu_id_AutoMode"]; - this.ccuIP = config["ccu_ip"]; -} - -HomeMaticThermo.prototype = { - - setTargetTemperature: function(value) { - - var that = this; - - this.log("Setting target Temperature of CCU to " + value); - this.log(this.ccuIDTargetTemp + " " + value); - - request.put({ - url: "http://"+this.ccuIP+"/config/xmlapi/statechange.cgi?ise_id="+this.ccuIDTargetTemp+"&new_value="+ value, - }, function(err, response, body) { - - if (!err && response.statusCode == 200) { - that.log("State change complete."); - } - else { - that.log("Error '"+err+"' setting Temperature: " + body); - } - }); - }, - getCurrentTemperature: function(callback) { - - var that = this; - - this.log("Getting current Temperature of CCU"); - request.get({ - url: "http://"+this.ccuIP+"/config/xmlapi/state.cgi?datapoint_id="+this.ccuIDCurrentTemp, - }, function(err, response, body) { - - if (!err && response.statusCode == 200) { - - //that.log("Response:"+response.body); - var responseString = response.body.substring(83,87); - //that.log(responseString); - callback(parseFloat(responseString)); - //that.log("Getting current temperature complete."); - } - else { - that.log("Error '"+err+"' getting Temperature: " + body); - } - }); - }, - getTargetTemperature: function(callback) { - - var that = this; - -this.log("Getting target Temperature of CCU"); - request.get({ - url: "http://"+this.ccuIP+"/config/xmlapi/state.cgi?datapoint_id="+this.ccuIDTargetTemp, - }, function(err, response, body) { - - if (!err && response.statusCode == 200) { - - //that.log("Response:"+response.body); - var responseString = response.body.substring(83,87); - //that.log(responseString); - callback(parseFloat(responseString)); - //that.log("Getting target temperature complete."); - } - else { - that.log("Error '"+err+"' getting Temperature: " + body); - } - }); - }, - getMode: function(callback) { - - var that = this; - - //this.log("Getting target Mode of CCU"); - //this.log(this.ccuID+ value); - - request.get({ - url: "http://"+this.ccuIP+"/config/xmlapi/state.cgi?datapoint_id="+this.ccuIDControlMode, - }, function(err, response, body) { - - if (!err && response.statusCode == 200) { - - //that.log("Response:"+response.body); - var responseInt = response.body.substring(83,84); - //that.log(responseString); - if (responseInt == 1) - { callback(parseInt("0")); } - if (responseInt == 0) - { callback(parseInt("1")); } - //that.log("Getting mode complete."); - } - else { - that.log("Error '"+err+"' getting Mode: " + body); - } - }); - }, - setMode: function(value) { - - var that = this; - - //this.log("Seting target Mode of CCU:" + value); - var modvalue; - var dpID; - switch(value) { - case 3: {modvalue = "true";dpID=this.ccuIDAutoMode;break;} //auto - case 1: {modvalue = "true";dpID=this.ccuIDAutoMode;break;} //heating => auto - default: {modvalue = "1";dpID=this.ccuIDManuMode;} //default => off (manual) - } - - request.put({ - url: "http://"+this.ccuIP+"/config/xmlapi/statechange.cgi?ise_id="+dpID+"&new_value="+ modvalue, - }, function(err, response, body) { - - if (!err && response.statusCode == 200) { - //that.log("Setting Mode complete."); - } - else { - that.log("Error '"+err+"' setting Mode: " + body); - } - }); - }, - 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: "test", - supportEvents: false, - supportBonjour: false, - manfDescription: "Manufacturer", - designedMaxLength: 255 - },{ - cType: types.MODEL_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "test", - supportEvents: false, - supportBonjour: false, - manfDescription: "Model", - designedMaxLength: 255 - },{ - cType: types.SERIAL_NUMBER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "A1S2NREF88EW", - 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 service", - designedMaxLength: 255 - },{ - cType: types.CURRENTHEATINGCOOLING_CTYPE, - onRead: function(callback) { that.getMode(callback); }, - 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, - onRead: function(callback) { that.getMode(callback); }, - onUpdate: function(value) { that.setMode(value);}, - 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, - onRead: function(callback) { that.getCurrentTemperature(callback); }, - onUpdate: null, - perms: ["pr","ev"], - format: "float", - initialValue: 13.0, - 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(callback); }, - perms: ["pw","pr","ev"], - format: "float", - initialValue: 19.0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Target Temperature", - designedMinValue: 4, - designedMaxValue: 25, - designedMinStep: 0.1, - unit: "celsius" - },{ - cType: types.TEMPERATURE_UNITS_CTYPE, - onUpdate: null, - perms: ["pr","ev"], - format: "int", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Unit" - }] - }]; - } -}; - -module.exports.accessory = HomeMaticThermo; diff --git a/accessories/HomeMaticWindow.js b/accessories/HomeMaticWindow.js deleted file mode 100644 index 6dcaf78..0000000 --- a/accessories/HomeMaticWindow.js +++ /dev/null @@ -1,123 +0,0 @@ -var types = require("hap-nodejs/accessories/types.js"); -var Characteristic = require("hap-nodejs").Characteristic; -var request = require("request"); - -function HomeMaticWindow(log, config) { - this.log = log; - this.name = config["name"]; - this.ccuID = config["ccu_id"]; - this.ccuIP = config["ccu_ip"]; -} - -HomeMaticWindow.prototype = { - - - getPowerState: function(callback) { - var that = this; - - this.log("Getting Window State of CCU"); - request.get({ - url: "http://"+this.ccuIP+"/config/xmlapi/state.cgi?datapoint_id="+this.ccuID, - }, function(err, response, body) { - - if (!err && response.statusCode == 200) { - - //that.log("Response:"+response.body); - var responseString = response.body.substring(83,84); - //that.log(responseString); - switch(responseString){ - case "0": {callback(Characteristic.ContactSensorState.CONTACT_DETECTED);break;} - case "1": {callback(Characteristic.ContactSensorState.CONTACT_NOT_DETECTED);break;} - case "2": {callback(Characteristic.ContactSensorState.CONTACT_NOT_DETECTED);break;} - } - that.log("Getting Window State complete."); - } - else { - that.log("Error '"+err+"' getting Window State: " + body); - } - }); - }, - - 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: "Homematic", - supportEvents: false, - supportBonjour: false, - manfDescription: "Manufacturer", - designedMaxLength: 255 - },{ - cType: types.MODEL_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "HM-Sec-RHS", - 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.CONTACT_SENSOR_STYPE, - 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.CONTACT_SENSOR_STATE_CTYPE, - onRead: function(callback) { that.getPowerState(callback); }, - perms: ["pr","ev"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Get Window state of a Variable", - designedMaxLength: 1 - }] - }]; - } -}; - -module.exports.accessory = HomeMaticWindow; diff --git a/accessories/Http.js b/accessories/Http.js deleted file mode 100644 index fb708e3..0000000 --- a/accessories/Http.js +++ /dev/null @@ -1,100 +0,0 @@ -var Service = require("hap-nodejs").Service; -var Characteristic = require("hap-nodejs").Characteristic; -var request = require("request"); - -module.exports = { - accessory: HttpAccessory -} - -function HttpAccessory(log, config) { - this.log = log; - - // url info - this.on_url = config["on_url"]; - this.off_url = config["off_url"]; - this.brightness_url = config["brightness_url"]; - this.http_method = config["http_method"]; -} - -HttpAccessory.prototype = { - - httpRequest: function(url, method, callback) { - request({ - url: url, - method: method - }, - function (error, response, body) { - callback(error, response, body) - }) - }, - - setPowerState: function(powerOn, callback) { - var url; - - if (powerOn) { - url = this.on_url; - this.log("Setting power state to on"); - } - else { - url = this.off_url; - this.log("Setting power state to off"); - } - - this.httpRequest(url, this.http_method, function(error, response, body) { - if (error) { - this.log('HTTP power function failed: %s', error.message); - callback(error); - } - else { - this.log('HTTP power function succeeded!'); - callback(); - } - }.bind(this)); - }, - - setBrightness: function(level, callback) { - var url = this.brightness_url.replace("%b", level) - - this.log("Setting brightness to %s", level); - - this.httpRequest(url, this.http_method, function(error, response, body) { - if (error) { - this.log('HTTP brightness function failed: %s', error); - callback(error); - } - else { - this.log('HTTP brightness function succeeded!'); - callback(); - } - }.bind(this)); - }, - - identify: function(callback) { - this.log("Identify requested!"); - callback(); // success - }, - - getServices: function() { - - // you can OPTIONALLY create an information service if you wish to override - // the default values for things like serial number, model, etc. - var informationService = new Service.AccessoryInformation(); - - informationService - .setCharacteristic(Characteristic.Manufacturer, "HTTP Manufacturer") - .setCharacteristic(Characteristic.Model, "HTTP Model") - .setCharacteristic(Characteristic.SerialNumber, "HTTP Serial Number"); - - var lightbulbService = new Service.Lightbulb(); - - lightbulbService - .getCharacteristic(Characteristic.On) - .on('set', this.setPowerState.bind(this)); - - lightbulbService - .addCharacteristic(new Characteristic.Brightness()) - .on('set', this.setBrightness.bind(this)); - - return [informationService, lightbulbService]; - } -}; diff --git a/accessories/HttpHygrometer.js b/accessories/HttpHygrometer.js deleted file mode 100644 index f722fe8..0000000 --- a/accessories/HttpHygrometer.js +++ /dev/null @@ -1,71 +0,0 @@ -var Service = require("hap-nodejs").Service; -var Characteristic = require("hap-nodejs").Characteristic; -var request = require("request"); - -module.exports = { - accessory: HygrometerAccessory -} - -function HygrometerAccessory(log, config) { - this.log = log; - - // url info - this.url = config["url"]; - this.http_method = config["http_method"]; -} - - -HygrometerAccessory.prototype = { - - httpRequest: function(url, method, callback) { - request({ - url: url, - method: method - }, - function (error, response, body) { - callback(error, response, body) - }) - }, - - - identify: function(callback) { - this.log("Identify requested!"); - callback(); // success - }, - - getCurrentRelativeHumidity: function (callback) { - var that = this; - that.log ("getting CurrentCurrentRelativeHumidity"); - - this.httpRequest(this.url, this.http_method, function(error, response, body) { - if (error) { - this.log('HTTP function failed: %s', error); - callback(error); - } - else { - this.log('HTTP function succeeded - %s', body); - callback(null, Number(body)); - } - }.bind(this)); - }, - - getServices: function() { - - // you can OPTIONALLY create an information service if you wish to override - // the default values for things like serial number, model, etc. - var informationService = new Service.AccessoryInformation(); - - informationService - .setCharacteristic(Characteristic.Manufacturer, "HTTP Manufacturer") - .setCharacteristic(Characteristic.Model, "HTTP Hygrometer") - .setCharacteristic(Characteristic.SerialNumber, "HTTP Serial Number"); - - var humidityService = new Service.HumiditySensor(); - - humidityService - .getCharacteristic(Characteristic.CurrentRelativeHumidity) - .on('get', this.getCurrentRelativeHumidity.bind(this)); - - return [informationService, humidityService]; - } -}; diff --git a/accessories/HttpThermometer.js b/accessories/HttpThermometer.js deleted file mode 100644 index 9235a57..0000000 --- a/accessories/HttpThermometer.js +++ /dev/null @@ -1,79 +0,0 @@ -var Service = require("hap-nodejs").Service; -var Characteristic = require("hap-nodejs").Characteristic; -var request = require("request"); - -module.exports = { - accessory: ThermometerAccessory -} - -function ThermometerAccessory(log, config) { - this.log = log; - - // url info - this.url = config["url"]; - this.http_method = config["http_method"]; -} - - -ThermometerAccessory.prototype = { - - httpRequest: function(url, method, callback) { - request({ - url: url, - method: method - }, - function (error, response, body) { - callback(error, response, body) - }) - }, - - - identify: function(callback) { - this.log("Identify requested!"); - callback(); // success - }, - - getCurrentTemperature: function (callback) { - var that = this; - that.log ("getting CurrentTemperature"); - - - this.httpRequest(this.url, this.http_method, function(error, response, body) { - if (error) { - this.log('HTTP function failed: %s', error); - callback(error); - } - else { - this.log('HTTP function succeeded - %s', body); - callback(null, Number(body)); - } - }.bind(this)); - }, - - getTemperatureUnits: function (callback) { - var that = this; - that.log ("getTemperature Units"); - // 1 = F and 0 = C - callback (null, 0); - }, - - getServices: function() { - - // you can OPTIONALLY create an information service if you wish to override - // the default values for things like serial number, model, etc. - var informationService = new Service.AccessoryInformation(); - - informationService - .setCharacteristic(Characteristic.Manufacturer, "HTTP Manufacturer") - .setCharacteristic(Characteristic.Model, "HTTP Thermometer") - .setCharacteristic(Characteristic.SerialNumber, "HTTP Serial Number"); - - var temperatureService = new Service.TemperatureSensor(); - - temperatureService - .getCharacteristic(Characteristic.CurrentTemperature) - .on('get', this.getCurrentTemperature.bind(this)); - - return [informationService, temperatureService]; - } -}; diff --git a/accessories/Hyperion.js b/accessories/Hyperion.js deleted file mode 100644 index 8767b19..0000000 --- a/accessories/Hyperion.js +++ /dev/null @@ -1,221 +0,0 @@ -var types = require("hap-nodejs/accessories/types.js"); -var net = require('net'); -var Color = require('color'); - -function HyperionAccessory(log, config) { - this.log = log; - this.host = config["host"]; - this.port = config["port"]; - this.name = config["name"]; - this.color = Color().hsv([0, 0, 0]); - this.prevColor = Color().hsv([0,0,100]); -} - - -HyperionAccessory.prototype = { - - sendHyperionCommand: function(command, cmdParams, priority) { - var that = this; - var client = new net.Socket(); - var data = {}; - - if (typeof priority === 'undefined') { priority = 100; } - - switch (command) { - case 'color': - data = {"command":"color", "priority":priority,"color":cmdParams}; - break; - case 'blacklevel': - data = {"command":"transform","transform":{"blacklevel":cmdParams}} - break; - default: - that.log("Hyperion command not found"); - return; - } - - //that.log(JSON.stringify(data)); - - client.connect(that.port, that.host, function() { - client.write(JSON.stringify(data) + "\n"); - }); - - client.on('data', function(data){ - that.log("Response: " + data.toString().trim()); - that.log("***** Color HSV:" + that.color.hsvArray() + "*****"); - that.log("***** Color RGB:" + that.color.rgbArray() + "*****"); - client.end(); - }); - }, - - setPowerState: function(powerOn) { - var that = this; - - if (powerOn) { - that.log("Setting power state on the '"+that.name+"' to on"); - that.color.rgb(that.prevColor.rgb()); - that.sendHyperionCommand('color', that.color.rgbArray()); - } else { - that.log("Setting power state on the '"+that.name+"' to off"); - that.prevColor.rgb(that.color.rgb()); - that.color.value(0); - that.sendHyperionCommand('color', that.color.rgbArray()); - that.sendHyperionCommand('blacklevel', [0,0,0]); - } - - }, - - setBrightness: function(level) { - var that = this; - - that.color.value(level); - that.log("Setting brightness on the '"+that.name+"' to '" + level + "'"); - that.sendHyperionCommand('color', that.color.rgbArray()); - - }, - - setHue: function(level) { - var that = this; - - that.color.hue(level); - that.prevColor.hue(level); - that.log("Setting hue on the '"+that.name+"' to '" + level + "'"); - that.sendHyperionCommand('color', that.color.rgbArray()); - - }, - - setSaturation: function(level) { - var that = this; - - that.color.saturationv(level); - that.prevColor.saturationv(level); - that.log("Setting saturation on the '"+that.name+"' to '" + level + "'"); - that.sendHyperionCommand('color', that.color.rgbArray()); - - }, - - getServices: function() { - var that = this; - return [{ - sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: that.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of the accessory", - designedMaxLength: 255 - },{ - cType: types.MANUFACTURER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "Hyperion", - 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: "DEADBEEF", - 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: that.name, - supportEvents: true, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - },{ - cType: types.POWER_STATE_CTYPE, - onUpdate: function(value) { that.setPowerState(value); }, - onRead: ((that.color.value() > 0) ? true : false), - perms: ["pw","pr","ev"], - format: "bool", - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Turn on the light", - designedMaxLength: 1 - },{ - cType: types.BRIGHTNESS_CTYPE, - onUpdate: function(value) { that.setBrightness(value); }, - onRead: that.color.value(), - perms: ["pw","pr","ev"], - format: "int", - initialValue: that.color.value(), - supportEvents: false, - supportBonjour: false, - manfDescription: "Adjust Brightness", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - },{ - cType: types.HUE_CTYPE, - onUpdate: function(value) { that.setHue(value) }, - onRead: that.color.hue(), - perms: ["pw","pr","ev"], - format: "int", - initialValue: that.color.hue(), - supportEvents: false, - supportBonjour: false, - manfDescription: "Adjust Hue", - designedMinValue: 0, - designedMaxValue: 360, - designedMinStep: 1, - unit: "arcdegrees" - },{ - cType: types.SATURATION_CTYPE, - onUpdate: function(value) { that.setSaturation(value) }, - onRead: that.color.saturationv(), - perms: ["pw","pr","ev"], - format: "int", - initialValue: that.color.saturationv(), - supportEvents: false, - supportBonjour: false, - manfDescription: "Adjust Saturation", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - }] - }]; - } -}; - -module.exports.accessory = HyperionAccessory; diff --git a/accessories/LiftMaster.js b/accessories/LiftMaster.js deleted file mode 100644 index c239f24..0000000 --- a/accessories/LiftMaster.js +++ /dev/null @@ -1,306 +0,0 @@ -var types = require("hap-nodejs/accessories/types.js"); -var request = require("request"); - -// This seems to be the "id" of the official LiftMaster iOS app -var APP_ID = "JVM/G9Nwih5BwKgNCjLxiFUQxQijAebyyg8QUHr7JOrP+tuPb8iHfRHKwTmDzHOu" - -function LiftMasterAccessory(log, config) { - this.log = log; - this.name = config["name"]; - this.username = config["username"]; - this.password = config["password"]; - this.requiredDeviceId = config["requiredDeviceId"]; -} - -LiftMasterAccessory.prototype = { - - setState: function(state) { - this.targetState = state; - this.login(); - }, - - login: function() { - var that = this; - - // reset our logged-in state hint until we're logged in - this.deviceId = null; - - // querystring params - var query = { - appId: APP_ID, - username: this.username, - password: this.password, - culture: "en" - }; - - // login to liftmaster - request.get({ - url: "https://myqexternal.myqdevice.com/api/user/validatewithculture", - qs: query - }, function(err, response, body) { - - if (!err && response.statusCode == 200) { - - // parse and interpret the response - var json = JSON.parse(body); - that.userId = json["UserId"]; - that.securityToken = json["SecurityToken"]; - that.log("Logged in with user ID " + that.userId); - that.getDevice(); - } - else { - that.log("Error '"+err+"' logging in: " + body); - } - }); - }, - - // find your garage door ID - getDevice: function() { - var that = this; - - // querystring params - var query = { - appId: APP_ID, - SecurityToken: this.securityToken, - filterOn: "true" - }; - - // some necessary duplicated info in the headers - var headers = { - MyQApplicationId: APP_ID, - SecurityToken: this.securityToken - }; - - // request details of all your devices - request.get({ - url: "https://myqexternal.myqdevice.com/api/v4/userdevicedetails/get", - qs: query, - headers: headers - }, function(err, response, body) { - - if (!err && response.statusCode == 200) { - - // parse and interpret the response - var json = JSON.parse(body); - var devices = json["Devices"]; - var foundDoors = []; - - // look through the array of devices for an opener - for (var i=0; i 1) { - that.log("WARNING: You have multiple doors on your MyQ account."); - that.log("WARNING: Specify the ID of the door you want to control using the 'requiredDeviceId' property in your config.json file."); - that.log("WARNING: You can have multiple liftmaster accessories to cover your multiple doors"); - - for (var j = 0; j < foundDoors.length; j++) { - that.log("Found Door: " + foundDoors[j]); - } - - throw "FATAL: Please specify which specific door this Liftmaster accessory should control - you have multiples on your account"; - - } - - // Did we get a device ID? - if (that.deviceId) { - that.log("Found an opener with ID " + that.deviceId +". Ready to send command..."); - that.setTargetState(); - } - else - { - that.log("Error: Couldn't find a door device, or the ID you specified isn't associated with your account"); - } - } - else { - that.log("Error '"+err+"' getting devices: " + body); - } - }); - }, - - setTargetState: function() { - - var that = this; - var liftmasterState = (this.targetState + "") == "1" ? "0" : "1"; - - // querystring params - var query = { - appId: APP_ID, - SecurityToken: this.securityToken, - filterOn: "true" - }; - - // some necessary duplicated info in the headers - var headers = { - MyQApplicationId: APP_ID, - SecurityToken: this.securityToken - }; - - // PUT request body - var body = { - AttributeName: "desireddoorstate", - AttributeValue: liftmasterState, - ApplicationId: APP_ID, - SecurityToken: this.securityToken, - MyQDeviceId: this.deviceId - }; - - // send the state request to liftmaster - request.put({ - url: "https://myqexternal.myqdevice.com/api/v4/DeviceAttribute/PutDeviceAttribute", - qs: query, - headers: headers, - body: body, - json: true - }, function(err, response, json) { - - if (!err && response.statusCode == 200) { - - if (json["ReturnCode"] == "0") - that.log("State was successfully set."); - else - that.log("Bad return code: " + json["ReturnCode"]); - that.log("Raw response " + JSON.stringify(json)); - } - else { - that.log("Error '"+err+"' setting door state: " + JSON.stringify(json)); - } - }); - }, - - 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: "LiftMaster", - 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.GARAGE_DOOR_OPENER_STYPE, - characteristics: [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "Garage Door Opener Control", - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - },{ - cType: types.CURRENT_DOOR_STATE_CTYPE, - onUpdate: function(value) { that.log("Update current state to " + value); }, - perms: ["pr","ev"], - format: "int", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "BlaBla", - designedMinValue: 0, - designedMaxValue: 4, - designedMinStep: 1, - designedMaxLength: 1 - },{ - cType: types.TARGET_DOORSTATE_CTYPE, - onUpdate: function(value) { that.setState(value); }, - perms: ["pr","pw","ev"], - format: "int", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "BlaBla", - designedMinValue: 0, - designedMaxValue: 1, - designedMinStep: 1, - designedMaxLength: 1 - },{ - cType: types.OBSTRUCTION_DETECTED_CTYPE, - onUpdate: function(value) { that.log("Obstruction detected: " + value); }, - perms: ["pr","ev"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "BlaBla" - }] - }]; - } -}; - -module.exports.accessory = LiftMasterAccessory; \ No newline at end of file diff --git a/accessories/Tesla.js b/accessories/Tesla.js deleted file mode 100644 index 8d96e57..0000000 --- a/accessories/Tesla.js +++ /dev/null @@ -1,119 +0,0 @@ -var types = require("hap-nodejs/accessories/types.js"); -var tesla = require("teslams"); - -function TeslaAccessory(log, config) { - this.log = log; - this.name = config["name"]; - this.username = config["username"]; - this.password = config["password"]; -} - -TeslaAccessory.prototype = { - - setPowerState: function(powerOn) { - var that = this; - - tesla.get_vid({email: this.username, password: this.password}, function(vehicle) { - - if (powerOn) { - tesla.auto_conditioning({id:vehicle, climate: 'start'}, function(response) { - if (response.result) - that.log("Started climate control."); - else - that.log("Error starting climate control: " + response.reason); - }); - } - else { - tesla.auto_conditioning({id:vehicle, climate: 'stop'}, function(response) { - if (response.result) - that.log("Stopped climate control."); - else - that.log("Error stopping climate control: " + response.reason); - }); - } - }) - }, - - 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: "Tesla", - 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.SWITCH_STYPE, - 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.setPowerState(value); }, - perms: ["pw","pr","ev"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Change the power state of the car", - designedMaxLength: 1 - }] - }]; - } -}; - -module.exports.accessory = TeslaAccessory; diff --git a/accessories/WeMo.js b/accessories/WeMo.js deleted file mode 100644 index 5cbc3f4..0000000 --- a/accessories/WeMo.js +++ /dev/null @@ -1,169 +0,0 @@ -var Service = require("hap-nodejs").Service; -var Characteristic = require("hap-nodejs").Characteristic; -var wemo = require('wemo'); - -module.exports = { - accessory: WeMoAccessory -} - -function WeMoAccessory(log, config) { - this.log = log; - this.name = config["name"]; - this.service = config["service"] || "Switch"; - this.wemoName = config["wemo_name"] || this.name; // fallback to "name" if you didn't specify an exact "wemo_name" - this.device = null; // instance of WeMo, for controlling the discovered device - this.log("Searching for WeMo device with exact name '" + this.wemoName + "'..."); - this.search(); -} - -WeMoAccessory.prototype.search = function() { - wemo.Search(this.wemoName, function(err, device) { - if (!err && device) { - this.log("Found '"+this.wemoName+"' device at " + device.ip); - this.device = new wemo(device.ip, device.port); - } - else { - this.log("Error finding device '" + this.wemoName + "': " + err); - this.log("Continuing search for WeMo device with exact name '" + this.wemoName + "'..."); - this.search(); - } - }.bind(this)); -} - -WeMoAccessory.prototype.getMotion = function(callback) { - - if (!this.device) { - this.log("No '%s' device found (yet?)", this.wemoName); - callback(new Error("Device not found"), false); - return; - } - - this.log("Getting motion state on the '%s'...", this.wemoName); - - this.device.getBinaryState(function(err, result) { - if (!err) { - var binaryState = parseInt(result); - var powerOn = binaryState > 0; - this.log("Motion state for the '%s' is %s", this.wemoName, binaryState); - callback(null, powerOn); - } - else { - this.log("Error getting motion state on the '%s': %s", this.wemoName, err.message); - callback(err); - } - }.bind(this)); -} - -WeMoAccessory.prototype.getPowerOn = function(callback) { - - if (!this.device) { - this.log("No '%s' device found (yet?)", this.wemoName); - callback(new Error("Device not found"), false); - return; - } - - this.log("Getting power state on the '%s'...", this.wemoName); - - this.device.getBinaryState(function(err, result) { - if (!err) { - var binaryState = parseInt(result); - var powerOn = binaryState > 0; - this.log("Power state for the '%s' is %s", this.wemoName, binaryState); - callback(null, powerOn); - } - else { - this.log("Error getting power state on the '%s': %s", this.wemoName, err.message); - callback(err); - } - }.bind(this)); -} - -WeMoAccessory.prototype.setPowerOn = function(powerOn, callback) { - - if (!this.device) { - this.log("No '%s' device found (yet?)", this.wemoName); - callback(new Error("Device not found")); - return; - } - - var binaryState = powerOn ? 1 : 0; // wemo langauge - this.log("Setting power state on the '%s' to %s", this.wemoName, binaryState); - - this.device.setBinaryState(binaryState, function(err, result) { - if (!err) { - this.log("Successfully set power state on the '%s' to %s", this.wemoName, binaryState); - callback(null); - } - else { - this.log("Error setting power state to %s on the '%s'", binaryState, this.wemoName); - callback(err); - } - }.bind(this)); -} - -WeMoAccessory.prototype.setTargetDoorState = function(targetDoorState, callback) { - - if (!this.device) { - this.log("No '%s' device found (yet?)", this.wemoName); - callback(new Error("Device not found")); - return; - } - - this.log("Activating WeMo switch '%s'", this.wemoName); - - this.device.setBinaryState(1, function(err, result) { - if (!err) { - this.log("Successfully activated WeMo switch '%s'", this.wemoName); - callback(null); - } - else { - this.log("Error activating WeMo switch '%s'", this.wemoName); - callback(err); - } - }.bind(this)); -} - -WeMoAccessory.prototype.getServices = function() { - - if (this.service == "Switch") { - var switchService = new Service.Switch(this.name); - - switchService - .getCharacteristic(Characteristic.On) - .on('get', this.getPowerOn.bind(this)) - .on('set', this.setPowerOn.bind(this)); - - return [switchService]; - } - else if (this.service == "GarageDoor") { - var garageDoorService = new Service.GarageDoorOpener("Garage Door Opener"); - - garageDoorService - .getCharacteristic(Characteristic.TargetDoorState) - .on('set', this.setTargetDoorState.bind(this)); - - return [garageDoorService]; - } - else if (this.service == "Light") { - var lightbulbService = new Service.Lightbulb(this.name); - - lightbulbService - .getCharacteristic(Characteristic.On) - .on('get', this.getPowerOn.bind(this)) - .on('set', this.setPowerOn.bind(this)); - - return [lightbulbService]; - } - else if (this.service == "MotionSensor") { - var motionSensorService = new Service.MotionSensor(this.name); - - motionSensorService - .getCharacteristic(Characteristic.MotionDetected) - .on('get', this.getMotion.bind(this)); - - return [motionSensorService]; - } - else { - throw new Error("Unknown service type '%s'", this.service); - } -} diff --git a/accessories/X10.js b/accessories/X10.js deleted file mode 100644 index 0cf781c..0000000 --- a/accessories/X10.js +++ /dev/null @@ -1,151 +0,0 @@ -var types = require("hap-nodejs/accessories/types.js"); -var request = require("request"); - -function X10(log, config) { - this.log = log; - this.ip_address = config["ip_address"]; - this.name = config["name"]; - this.deviceID = config["device_id"]; - this.protocol = config["protocol"]; - this.canDim = config["can_dim"]; -} - -X10.prototype = { - - setPowerState: function(powerOn) { - - var binaryState = powerOn ? "on" : "off"; - var that = this; - - this.log("Setting power state of " + this.deviceID + " to " + powerOn); - request.put({ - url: "http://"+this.ip_address+"/x10/"+this.deviceID+"/power/"+binaryState+"?protocol="+this.protocol, - }, function(err, response, body) { - - if (!err && response.statusCode == 200) { - that.log("State change complete."); - } - else { - that.log("Error '"+err+"' setting power state: " + body); - } - }); - }, - - setBrightnessLevel: function(value) { - - var that = this; - - this.log("Setting brightness level of " + this.deviceID + " to " + value); - request.put({ - url: "http://"+this.ip_address+"/x10/"+this.deviceID+"/brightness/?protocol="+this.protocol+"&value="+value, - }, function(err, response, body) { - - if (!err && response.statusCode == 200) { - that.log("State change complete."); - } - else { - that.log("Error '"+err+"' setting brightness level: " + body); - } - }); - }, - - getServices: function() { - var that = this; - var services = [{ - 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: "X10", - 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: false, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - },{ - cType: types.POWER_STATE_CTYPE, - onUpdate: function(value) { that.setPowerState(value); }, - perms: ["pw","pr","ev"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Change the power state of a Variable", - designedMaxLength: 1 - }] - }]; - if (that.canDim) { - services[1].characteristics.push({ - cType: types.BRIGHTNESS_CTYPE, - onUpdate: function(value) { that.setBrightnessLevel(value); }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Adjust Brightness of Light", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - }); - } - return services; - } -}; - -module.exports.accessory = X10; diff --git a/accessories/iControl.js b/accessories/iControl.js deleted file mode 100644 index a4299b5..0000000 --- a/accessories/iControl.js +++ /dev/null @@ -1,130 +0,0 @@ -var iControl = require('node-icontrol').iControl; -var Service = require("hap-nodejs").Service; -var Characteristic = require("hap-nodejs").Characteristic; - -module.exports = { - accessory: iControlAccessory -} - -/** - * Provides a Security System accessory for an iControl-based security system like Xfinity Home. - */ - -function iControlAccessory(log, config) { - this.log = log; - - this.iControl = new iControl({ - system: iControl.Systems[config.system], - email: config.email, - password: config.password, - pinCode: config.pin - }); - - this.iControl.on('change', this._handleChange.bind(this)); - this.iControl.on('error', this._handleError.bind(this)); - - this.log("Logging into iControl..."); - this.iControl.login(); - - this._securitySystem = new Service.SecuritySystem("Security System"); - - this._securitySystem - .getCharacteristic(Characteristic.SecuritySystemTargetState) - .on('get', this._getTargetState.bind(this)) - .on('set', this._setTargetState.bind(this)); - - this._securitySystem - .getCharacteristic(Characteristic.SecuritySystemCurrentState) - .on('get', this._getCurrentState.bind(this)); -} - -iControlAccessory.prototype._getTargetState = function(callback) { - this.iControl.getArmState(function(err, armState) { - if (err) return callback(err); - - var currentState = this._getHomeKitStateFromArmState(armState); - callback(null, currentState); - - }.bind(this)); -} - -iControlAccessory.prototype._getCurrentState = function(callback) { - this.iControl.getArmState(function(err, armState) { - if (err) return callback(err); - - var currentState = this._getHomeKitStateFromArmState(armState); - callback(null, currentState); - - }.bind(this)); -} - -iControlAccessory.prototype._setTargetState = function(targetState, callback, context) { - if (context == "internal") return callback(null); // we set this state ourself, no need to react to it - - var armState = this._getArmStateFromHomeKitState(targetState); - this.log("Setting target state to %s", armState); - - this.iControl.setArmState(armState, function(err) { - if (err) return callback(err); - - this.log("Successfully set target state to %s", armState); - - // also update current state - this._securitySystem - .getCharacteristic(Characteristic.SecuritySystemCurrentState) - .setValue(targetState); - - callback(null); // success! - - }.bind(this)); -} - -iControlAccessory.prototype._handleChange = function(armState) { - this.log("Arm state changed to %s", armState); - - var homeKitState = this._getHomeKitStateFromArmState(armState); - - this._securitySystem - .getCharacteristic(Characteristic.SecuritySystemCurrentState) - .setValue(homeKitState); - - this._securitySystem - .getCharacteristic(Characteristic.SecuritySystemTargetState) - .setValue(homeKitState, null, "internal"); // these characteristics happen to share underlying values -} - -iControlAccessory.prototype._handleError = function(err) { - this.log(err.message); -} - -iControlAccessory.prototype.getServices = function() { - return [this._securitySystem]; -} - -iControlAccessory.prototype._getHomeKitStateFromArmState = function(armState) { - switch (armState) { - case "disarmed": return Characteristic.SecuritySystemCurrentState.DISARMED; - case "away": return Characteristic.SecuritySystemCurrentState.AWAY_ARM; - case "night": return Characteristic.SecuritySystemCurrentState.NIGHT_ARM; - case "stay": return Characteristic.SecuritySystemCurrentState.STAY_ARM; - } -} - -iControlAccessory.prototype._getArmStateFromHomeKitState = function(homeKitState) { - switch (homeKitState) { - case Characteristic.SecuritySystemCurrentState.DISARMED: return "disarmed"; - case Characteristic.SecuritySystemCurrentState.AWAY_ARM: return "away"; - case Characteristic.SecuritySystemCurrentState.NIGHT_ARM: return "night"; - case Characteristic.SecuritySystemCurrentState.STAY_ARM: return "stay"; - } -} - - -/** - * TESTING - */ - -if (require.main === module) { - var config = JSON.parse(require('fs').readFileSync("config.json")).accessories[0]; - var accessory = new iControlAccessory(console.log, config); -} diff --git a/accessories/knxdevice.js b/accessories/knxdevice.js deleted file mode 100644 index 92bb88c..0000000 --- a/accessories/knxdevice.js +++ /dev/null @@ -1,1032 +0,0 @@ -/** - * This is a KNX universal accessory shim. - * This is NOT the version for dynamic installation - * -New 2015-09-16: Welcome iOS9.0 -new features include: -- services: -- Window -- WindowCovering -- ContactSensor -New 2015-09-18: -- Services Switch and Outlet -- Code cleanup -New 2015-09-19: -- GarageDoorOpener Service -- MotionSensor Service -New 2015-10-02: -- Check for valid group addresses -- new "R" flag allowed for Boolean addresses: 1/2/3R is the boolean not(1/2/3), i.e. 0 and 1 switched on read and write -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 knxd = require("eibd"); -var knxd_registerGA = require('../platforms/KNX.js').registerGA; -var knxd_startMonitor = require('../platforms/KNX.js').startMonitor; - -var milliTimeout = 300; // used to block responses while swiping - -var colorOn = "\x1b[30;47m"; -var colorOff = "\x1b[0m"; - -function KNXDevice(log, config) { - this.log = log; - // everything in one object, do not copy individually - this.config = config; - log("Accessory constructor called"); - if (config.name) { - this.name = config.name; - } - if (config.uuid_base) { - this.uuid_base = config.uuid_base; - } - if (config.knxd_ip){ - this.knxd_ip = config.knxd_ip; - } else { - throw new Error("KNX configuration fault: MISSING KNXD IP"); - } - if (config.knxd_port){ - this.knxd_port = config.knxd_port; - } else { - throw new Error("MISSING KNXD PORT"); - } - -} - - -//debugging helper only -//inspects an object and prints its properties (also inherited properties) -var iterate = function nextIteration(myObject, path){ - // this function iterates over all properties of an object and print them to the console - // when finding objects it goes one level deeper - var name; - if (!path){ - console.log("---iterating--------------------") - } - for (name in myObject) { - if (typeof myObject[name] !== 'function') { - if (typeof myObject[name] !== 'object' ) { - console.log((path || "") + name + ': ' + myObject[name]); - } else { - nextIteration(myObject[name], path ? path + name + "." : name + "."); - } - } else { - console.log((path || "") + name + ': (function)' ); - } - } - if (!path) { - console.log("================================"); - } -}; - - -module.exports = { - accessory: KNXDevice -}; - - -KNXDevice.prototype = { - - // all purpose / all types write function - knxwrite: function(callback, groupAddress, dpt, value) { - // this.log("DEBUG in knxwrite"); - var knxdConnection = new knxd.Connection(); - // this.log("DEBUG in knxwrite: created empty connection, trying to connect socket to "+this.knxd_ip+":"+this.knxd_port); - knxdConnection.socketRemote({ host: this.knxd_ip, port: this.knxd_port }, function() { - var dest = knxd.str2addr(groupAddress); - // this.log("DEBUG got dest="+dest); - knxdConnection.openTGroup(dest, 1, function(err) { - if (err) { - this.log("[ERROR] knxwrite:openTGroup: " + err); - callback(err); - } else { - // this.log("DEBUG opened TGroup "); - var msg = knxd.createMessage('write', dpt, parseFloat(value)); - knxdConnection.sendAPDU(msg, function(err) { - if (err) { - this.log("[ERROR] knxwrite:sendAPDU: " + err); - callback(err); - } else { - this.log("knx data sent: Value "+value+ " for GA "+groupAddress); - callback(); - } - }.bind(this)); - } - }.bind(this)); - }.bind(this)); - }, - // issues an all purpose read request on the knx bus - // DOES NOT WAIT for an answer. Please register the address with a callback using registerGA() function - knxread: function(groupAddress){ - // this.log("DEBUG in knxread"); - if (!groupAddress) { - return null; - } - this.log("[knxdevice:knxread] preparing knx request for "+groupAddress); - var knxdConnection = new knxd.Connection(); - // this.log("DEBUG in knxread: created empty connection, trying to connect socket to "+this.knxd_ip+":"+this.knxd_port); - knxdConnection.socketRemote({ host: this.knxd_ip, port: this.knxd_port }, function() { - var dest = knxd.str2addr(groupAddress); - // this.log("DEBUG got dest="+dest); - knxdConnection.openTGroup(dest, 1, function(err) { - if (err) { - this.log("[ERROR] knxread:openTGroup: " + err); - } else { - // this.log("DEBUG knxread: opened TGroup "); - var msg = knxd.createMessage('read', 'DPT1', 0); - knxdConnection.sendAPDU(msg, function(err) { - if (err) { - this.log("[ERROR] knxread:sendAPDU: " + err); - } else { - this.log("[knxdevice:knxread] knx request sent for "+groupAddress); - } - }.bind(this)); - } - }.bind(this)); - }.bind(this)); - }, - // issuing multiple read requests at once - knxreadarray: function (groupAddresses) { - if (groupAddresses.constructor.toString().indexOf("Array") > -1) { - // handle multiple addresses - for (var i = 0; i < groupAddresses.length; i++) { - if (groupAddresses[i]) { // do not bind empty addresses - this.knxread (groupAddresses[i].match(/(\d*\/\d*\/\d*)/)[0]); // clean address - } - } - } else { - // it's only one - this.knxread (groupAddresses.match(/(\d*\/\d*\/\d*)/)[0]); // regex for cleaning address - } - }, - -/** Registering routines - * - */ - // boolean: get 0 or 1 from the bus, write boolean - knxregister_bool: function(addresses, characteristic) { - this.log("knx registering BOOLEAN " + addresses); - knxd_registerGA(addresses, function(val, src, dest, type, reverse){ - this.log("[" +this.name + "]: Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type + " for " + characteristic.displayName); -// iterate(characteristic); - - characteristic.setValue(val ? (reverse ? 0:1) : (reverse ? 1:0), undefined, 'fromKNXBus'); - }.bind(this)); - }, -// knxregister_boolReverse: function(addresses, characteristic) { -// this.log("knx registering BOOLEAN REVERSE " + addresses); -// knxd_registerGA(addresses, function(val, src, dest, type, reverse){ -// this.log("[" +this.name + "]: Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type + " for " + characteristic.displayName); -//// iterate(characteristic); -// characteristic.setValue(val ? 0 : 1, undefined, 'fromKNXBus'); -// }.bind(this)); -// }, - // percentage: get 0..255 from the bus, write 0..100 to characteristic - knxregister_percent: function(addresses, characteristic) { - this.log("knx registering PERCENT " + addresses); - knxd_registerGA(addresses, function(val, src, dest, type, reverse){ - this.log("[" +this.name + "]: Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName); - if (type !== "DPT5") { - this.log("[ERROR] Received value cannot be a percentage value"); - } else { - characteristic.setValue(Math.round(( reverse ? (255-val):val)/255*100), undefined, 'fromKNXBus'); - } - }.bind(this)); - }, - // float - knxregister_float: function(addresses, characteristic) { - // update for props refactor https://github.com/KhaosT/HAP-NodeJS/commit/1d84d128d1513beedcafc24d2c07d98185563243#diff-cb84de3a1478a38b2cf8388d709f1c1cR50 - - var validValue = true; - var hk_value = 0.0; - this.log("["+ this.name +"]:[" + characteristic.displayName+ "]:knx registering FLOAT " + addresses); - knxd_registerGA(addresses, function(val, src, dest, type, reverse){ - this.log("["+ this.name +"]:[" + characteristic.displayName+ "]: Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName); - // make hk_value compliant to properties - if (characteristic.props.minStep) { - // quantize - hk_value = Math.round(val/characteristic.props.minStep)/(1/characteristic.props.minStep); - } else { - hk_value = val; - } - // range check - validValue = true; // assume validity at beginning - if (characteristic.props.minValue) { - validValue = validValue && (hk_value>=characteristic.props.minValue); - } - if (characteristic.props.maxValue) { - validValue = validValue && (hk_value<=characteristic.props.maxValue); - } - if (validValue) { - characteristic.setValue(hk_value, undefined, 'fromKNXBus'); // 1 decimal for HomeKit - } else { - this.log("["+ this.name +"]:[" + characteristic.displayName+ "]: Value %s out of bounds %s...%s ",hk_value, characteristic.props.minValue, characteristic.props.maxValue); - } - }.bind(this)); - }, - //integer - knxregister_int: function(addresses, characteristic) { - this.log("["+ this.name +"]:[" + characteristic.displayName+ "]:knx registering INT " + addresses); - knxd_registerGA(addresses, function(val, src, dest, type, reverse){ - this.log("["+ this.name +"]:[" + characteristic.displayName+ "]: Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName); - if (val>=(characteristic.props.minValue || 0) && val<=(characteristic.props.maxValue || 255)) { - characteristic.setValue(reverse ? (255-val):val, undefined, 'fromKNXBus'); - } else { - this.log("["+ this.name +"]:[" + characteristic.displayName+ "]: Value %s out of bounds %s...%s ",hk_value, (characteristic.props.minValue || 0), (characteristic.props.maxValue || 255)); - } - }.bind(this)); - }, - knxregister_HVAC: function(addresses, characteristic) { - this.log("["+ this.name +"]:[" + characteristic.displayName+ "]:knx registering HVAC " + addresses); - knxd_registerGA(addresses, function(val, src, dest, type){ - this.log("["+ this.name +"]:[" + characteristic.displayName+ "]:Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName); - var HAPvalue = 0; - switch (val){ - case 0: - HAPvalue = 1; - break; - case 1: - HAPvalue = 1; - break; - case 2: - HAPvalue = 1; - break; - case 3: - HAPvalue = 1; - break; - case 4: - HAPvalue = 0; - break; - default: - HAPvalue = 0; - } - characteristic.setValue(HAPvalue, undefined, 'fromKNXBus'); - }.bind(this)); - }, - /** KNX HVAC (heating, ventilation, and air conditioning) types do not really match to homekit types: -// 0 = Auto -// 1 = Comfort -// 2 = Standby -// 3 = Night -// 4 = Freezing/Heat Protection -// 5 – 255 = not allowed” - // The value property of TargetHeatingCoolingState must be one of the following: -// Characteristic.TargetHeatingCoolingState.OFF = 0; -// Characteristic.TargetHeatingCoolingState.HEAT = 1; -// Characteristic.TargetHeatingCoolingState.COOL = 2; -// Characteristic.TargetHeatingCoolingState.AUTO = 3; - AUTO (3) is not allowed as return type from devices! -*/ - // undefined, has to match! - knxregister: function(addresses, characteristic) { - this.log("["+ this.name +"]:[" + characteristic.displayName+ "]:knx registering " + addresses); - knxd_registerGA(addresses, function(val, src, dest, type){ - this.log("["+ this.name +"]:[" + characteristic.displayName+ "]:Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName); - characteristic.setValue(val, undefined, 'fromKNXBus'); - }.bind(this)); - }, - -/** set methods used for creating callbacks - * such as - * var Characteristic = myService.addCharacteristic(new Characteristic.Brightness()) - * .on('set', function(value, callback, context) { - * this.setPercentage(value, callback, context, this.config[index].Set) - * }.bind(this)); - * - */ - setBooleanState: function(value, callback, context, gaddress, reverseflag) { - if (context === 'fromKNXBus') { -// this.log(gaddress + " event ping pong, exit!"); - if (callback) { - callback(); - } - } else { - var numericValue = reverseflag ? 1:0; - if (value) { - numericValue = reverseflag ? 0:1; // need 0 or 1, not true or something - } - this.log("["+ this.name +"]:Setting "+gaddress+" " + reverseflag ? " (reverse)":""+ " Boolean to %s", numericValue); - this.knxwrite(callback, gaddress,'DPT1',numericValue); - } - - }, -// setBooleanReverseState: function(value, callback, context, gaddress) { -// if (context === 'fromKNXBus') { -//// this.log(gaddress + " event ping pong, exit!"); -// if (callback) { -// callback(); -// } -// } else { -// var numericValue = 0; -// if (!value) { -// numericValue = 1; // need 0 or 1, not true or something -// } -// this.log("["+ this.name +"]:Setting "+gaddress+" Boolean to %s", numericValue); -// this.knxwrite(callback, gaddress,'DPT1',numericValue); -// } -// -// }, - setPercentage: function(value, callback, context, gaddress, reverseflag) { - if (context === 'fromKNXBus') { -// this.log(gaddress + "event ping pong, exit!"); - if (callback) { - callback(); - } - } else { - var numericValue = 0; - value = ( value>=0 ? (value<=100 ? value:100):0 ); //ensure range 0..100 - if (reverseflag) { - numericValue = 255 - Math.round(255*value/100); // convert 0..100 to 255..0 for KNX bus - } else { - numericValue = Math.round(255*value/100); // convert 0..100 to 0..255 for KNX bus - } - this.log("["+ this.name +"]:Setting "+gaddress+" percentage to %s (%s)", value, numericValue); - this.knxwrite(callback, gaddress,'DPT5',numericValue); - } - }, - setInt: function(value, callback, context, gaddress) { - if (context === 'fromKNXBus') { -// this.log(gaddress + "event ping pong, exit!"); - if (callback) { - callback(); - } - } else { - var numericValue = 0; - if (value && value>=0 && value<=255) { - numericValue = value; // assure 0..255 for KNX bus - } - this.log("["+ this.name +"]:Setting "+gaddress+" int to %s (%s)", value, numericValue); - this.knxwrite(callback, gaddress,'DPT5',numericValue); - } - }, - setFloat: function(value, callback, context, gaddress) { - if (context === 'fromKNXBus') { -// this.log(gaddress + " event ping pong, exit!"); - if (callback) { - callback(); - } - } else { - var numericValue = 0; - if (value) { - numericValue = value; // homekit expects precision of 1 decimal - } - this.log("["+ this.name +"]:Setting "+gaddress+" Float to %s", numericValue); - this.knxwrite(callback, gaddress,'DPT9',numericValue); - } - }, - setHVACState: function(value, callback, context, gaddress) { - if (context === 'fromKNXBus') { -// this.log(gaddress + " event ping pong, exit!"); - if (callback) { - callback(); - } - } else { - var numericValue = 0; - switch (value){ - case 0: - KNXvalue = 4; - break; - case 1: - KNXvalue = 1; - break; - case 2: - KNXvalue = 1; - break; - case 3: - KNXvalue = 1; - break; - default: - KNXvalue = 1; - } - - this.log("["+ this.name +"]:Setting "+gaddress+" HVAC to %s", KNXvalue); - this.knxwrite(callback, gaddress,'DPT5',KNXvalue); - } - - }, -/** identify dummy - * - */ - identify: function(callback) { - this.log("["+ this.name +"]:Identify requested!"); - callback(); // success - }, -/** bindCharacteristic - * initializes callbacks for 'set' events (from HK) and for KNX bus reads (to HK) - */ - bindCharacteristic: function(myService, characteristicType, valueType, config, defaultValue) { - var myCharacteristic = myService.getCharacteristic(characteristicType); - var setGA = ""; - var setReverse = false; - if (myCharacteristic === undefined) { - throw new Error("unknown characteristics cannot be bound"); - } - if (defaultValue) { - myCharacteristic.setValue(defaultValue); - } - if (config.Set) { - // can write - // extract address and Reverse flag - setGA = config.Set.match(/\d*\/\d*\/\d*/); - if (setGA===null) { - this.log(colorOn + "["+ this.name +"]:["+myCharacteristic.displayName+"] Error in group adress: ["+ config.Set +"] "+colorOff); - throw new Error("EINVGROUPADRESS - Invalid group address given"); - } else { - setGA=setGA[0]; // first element of returned array is the group address - } - - setReverse = config.Set.match(/\d*\/\d*\/\d*(R)/) ? true:false; - - switch (valueType) { - case "Bool": - myCharacteristic.on('set', function(value, callback, context) { - this.setBooleanState(value, callback, context, setGA, setReverse); //NEW - }.bind(this)); - break; -// case "BoolReverse": -// this.log("["+ this.name +"]:["+myCharacteristic.displayName+"] \x1b[30;47m%s\x1b[0mWARNING in group adress: "+ config.Set +": Legacy BoolReverse used. Use " + config.Set +"R instead"); -// myCharacteristic.on('set', function(value, callback, context) { -// this.setBooleanReverseState(value, callback, context, config.Set); -// }.bind(this)); -// break; - case "Percent": - myCharacteristic.on('set', function(value, callback, context) { - this.setPercentage(value, callback, context, setGA, setReverse); - myCharacteristic.timeout = Date.now()+milliTimeout; - }.bind(this)); - break; - case "Float": - myCharacteristic.on('set', function(value, callback, context) { - this.setFloat(value, callback, context, config.Set); - }.bind(this)); - break; - case "Int": - myCharacteristic.on('set', function(value, callback, context) { - this.setInt(value, callback, context, config.Set); - }.bind(this)); - break; - case "HVAC": - myCharacteristic.on('set', function(value, callback, context) { - this.setHVACState(value, callback, context, config.Set); - }.bind(this)); - break; - default: { - this.log(colorOn + "[ERROR] unknown type passed: [" + valueType+"]"+ colorOff); - throw new Error("[ERROR] unknown type passed"); - } - } - } - if ([config.Set].concat(config.Listen || []).length>0) { - //this.log("Binding LISTEN"); - // can read - switch (valueType) { - case "Bool": - this.knxregister_bool([config.Set].concat(config.Listen || []), myCharacteristic); - break; -// case "BoolReverse": -// this.knxregister_boolReverse([config.Set].concat(config.Listen || []), myCharacteristic); -// break; - case "Percent": - this.knxregister_percent([config.Set].concat(config.Listen || []), myCharacteristic); - break; - case "Float": - this.knxregister_float([config.Set].concat(config.Listen || []), myCharacteristic); - break; - case "Int": - this.knxregister_int([config.Set].concat(config.Listen || []), myCharacteristic); - break; - case "HVAC": - this.knxregister_HVAC([config.Set].concat(config.Listen || []), myCharacteristic); - break; - default: - this.log(colorOn+ "[ERROR] unknown type passed: ["+valueType+"]"+colorOff); - throw new Error("[ERROR] unknown type passed"); - } - this.log("["+ this.name +"]:["+myCharacteristic.displayName+"]: Issuing read requests on the KNX bus..."); - this.knxreadarray([config.Set].concat(config.Listen || [])); - } - return myCharacteristic; // for chaining or whatsoever - }, -/** - * function getXXXXXXXService(config) - * returns a configured service object to the caller (accessory/device) - * - * @param config - * pass a configuration array parsed from config.json - * specifically for this service - * - */ - getContactSenserService: function(config) { -// Characteristic.ContactSensorState.CONTACT_DETECTED = 0; -// Characteristic.ContactSensorState.CONTACT_NOT_DETECTED = 1; - - // some sanity checks - if (config.type !== "ContactSensor") { - this.log("[ERROR] ContactSensor Service for non 'ContactSensor' service called"); - return undefined; - } - if (!config.name) { - this.log("[ERROR] ContactSensor Service without 'name' property called"); - return undefined; - } - - var myService = new Service.ContactSensor(config.name,config.name); - if (config.ContactSensorState) { - this.log("["+ this.name +"]:ContactSensor ContactSensorState characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.ContactSensorState, "Bool", config.ContactSensorState); - } else if (config.ContactSensorStateContact1) { - this.log(colorOn+ "[ERROR] outdated type passed: [ContactSensorStateContact1]"+colorOff); - throw new Error("[ERROR] outdated type passed"); - } - //optionals - if (config.StatusActive) { - this.log("["+ this.name +"]:ContactSensor StatusActive characteristic enabled"); - myService.addCharacteristic(Characteristic.StatusActive); - this.bindCharacteristic(myService, Characteristic.StatusActive, "Bool", config.StatusActive); - } - if (config.StatusFault) { - this.log("["+ this.name +"]:ContactSensor StatusFault characteristic enabled"); - myService.addCharacteristic(Characteristic.StatusFault); - this.bindCharacteristic(myService, Characteristic.StatusFault, "Bool", config.StatusFault); - } - if (config.StatusTampered) { - this.log("["+ this.name +"]:ContactSensor StatusTampered characteristic enabled"); - myService.addCharacteristic(Characteristic.StatusTampered); - this.bindCharacteristic(myService, Characteristic.StatusTampered, "Bool", config.StatusTampered); - } - if (config.StatusLowBattery) { - this.log("["+ this.name +"]:ContactSensor StatusLowBattery characteristic enabled"); - myService.addCharacteristic(Characteristic.StatusLowBattery); - this.bindCharacteristic(myService, Characteristic.StatusLowBattery, "Bool", config.StatusLowBattery); - } - return myService; - }, - getGarageDoorOpenerService: function(config) { -// // Required Characteristics -// this.addCharacteristic(Characteristic.CurrentDoorState); -// this.addCharacteristic(Characteristic.TargetDoorState); -// this.addCharacteristic(Characteristic.ObstructionDetected); -// Characteristic.CurrentDoorState.OPEN = 0; -// Characteristic.CurrentDoorState.CLOSED = 1; -// Characteristic.CurrentDoorState.OPENING = 2; -// Characteristic.CurrentDoorState.CLOSING = 3; -// Characteristic.CurrentDoorState.STOPPED = 4; -// // -// // Optional Characteristics -// this.addOptionalCharacteristic(Characteristic.LockCurrentState); -// this.addOptionalCharacteristic(Characteristic.LockTargetState); - // The value property of LockCurrentState must be one of the following: -// Characteristic.LockCurrentState.UNSECURED = 0; -// Characteristic.LockCurrentState.SECURED = 1; -// Characteristic.LockCurrentState.JAMMED = 2; -// Characteristic.LockCurrentState.UNKNOWN = 3; - - // some sanity checks - if (config.type !== "GarageDoorOpener") { - this.log("[ERROR] GarageDoorOpener Service for non 'GarageDoorOpener' service called"); - return undefined; - } - if (!config.name) { - this.log("[ERROR] GarageDoorOpener Service without 'name' property called"); - return undefined; - } - - var myService = new Service.GarageDoorOpener(config.name,config.name); - if (config.CurrentDoorState) { - this.log("["+ this.name +"]:GarageDoorOpener CurrentDoorState characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.CurrentDoorState, "Int", config.CurrentDoorState); - } - if (config.TargetDoorState) { - this.log("["+ this.name +"]:GarageDoorOpener TargetDoorState characteristic enabled"); - //myService.getCharacteristic(Characteristic.TargetDoorState).minimumValue=0; // - //myService.getCharacteristic(Characteristic.TargetDoorState).maximumValue=4; // - this.bindCharacteristic(myService, Characteristic.TargetDoorState, "Int", config.TargetDoorState); - } - if (config.ObstructionDetected) { - this.log("["+ this.name +"]:GarageDoorOpener ObstructionDetected characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.ObstructionDetected, "Bool", config.ObstructionDetected); - } - //optionals - if (config.LockCurrentState) { - this.log("["+ this.name +"]:GarageDoorOpener LockCurrentState characteristic enabled"); - myService.addCharacteristic(Characteristic.LockCurrentState); - this.bindCharacteristic(myService, Characteristic.LockCurrentState, "Int", config.LockCurrentState); - } - if (config.LockTargetState) { - this.log("["+ this.name +"]:GarageDoorOpener LockTargetState characteristic enabled"); - myService.addCharacteristic(Characteristic.LockTargetState); - this.bindCharacteristic(myService, Characteristic.LockTargetState, "Bool", config.LockTargetState); - } - return myService; - }, - getLightbulbService: function(config) { - // some sanity checks - //this.config = config; - - if (config.type !== "Lightbulb") { - this.log("[ERROR] Lightbulb Service for non 'Lightbulb' service called"); - return undefined; - } - if (!config.name) { - this.log("[ERROR] Lightbulb Service without 'name' property called"); - return undefined; - } - var myService = new Service.Lightbulb(config.name,config.name); - // On (and Off) - if (config.On) { - this.log("["+ this.name +"]:Lightbulb on/off characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.On, "Bool", config.On); - } // On characteristic - // Brightness if available - if (config.Brightness) { - this.log("["+ this.name +"]:Lightbulb Brightness characteristic enabled"); - myService.addCharacteristic(Characteristic.Brightness); // it's an optional - this.bindCharacteristic(myService, Characteristic.Brightness, "Percent", config.Brightness); - } - // Hue and Saturation could be added here if available in KNX lamps - //iterate(myService); - return myService; - }, - getLightSensorService: function(config) { - - // some sanity checks - if (config.type !== "LightSensor") { - this.log("[ERROR] LightSensor Service for non 'LightSensor' service called"); - return undefined; - } - if (!config.name) { - this.log("[ERROR] LightSensor Service without 'name' property called"); - return undefined; - } - var myService = new Service.LightSensor(config.name,config.name); - // CurrentTemperature) - if (config.CurrentAmbientLightLevel) { - this.log("["+ this.name +"]:LightSensor CurrentAmbientLightLevel characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.CurrentAmbientLightLevel, "Float", config.CurrentAmbientLightLevel); - } - return myService; - }, - getLockMechanismService: function(config) { - -/** //this.config = config; -// Characteristic.LockCurrentState.UNSECURED = 0; -// Characteristic.LockCurrentState.SECURED = 1; -*/ - // some sanity checks - if (config.type !== "LockMechanism") { - this.log("[ERROR] LockMechanism Service for non 'LockMechanism' service called"); - return undefined; - } - if (!config.name) { - this.log("[ERROR] LockMechanism Service without 'name' property called"); - return undefined; - } - - var myService = new Service.LockMechanism(config.name,config.name); - // LockCurrentState - if (config.LockCurrentState) { - // for normal contacts: Secured = 1 - this.log("["+ this.name +"]:LockMechanism LockCurrentState characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.LockCurrentState, "Bool", config.LockCurrentState); - } else if (config.LockCurrentStateSecured0) { - // for reverse contacts Secured = 0 - this.log(colorOn+ "[ERROR] outdated type passed: [LockCurrentStateSecured0]"+colorOff); - throw new Error("[ERROR] outdated type passed"); - } - // LockTargetState - if (config.LockTargetState) { - this.log("["+ this.name +"]:LockMechanism LockTargetState characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.LockTargetState, "Bool", config.LockTargetState); - } else if (config.LockTargetStateSecured0) { - this.log(colorOn+ "[ERROR] outdated type passed: [LockTargetStateSecured0]"+colorOff); - throw new Error("[ERROR] outdated type passed"); - } - - //iterate(myService); - return myService; - }, - getMotionSensorService: function(config) { -// Characteristic.ContactSensorState.CONTACT_DETECTED = 0; -// Characteristic.ContactSensorState.CONTACT_NOT_DETECTED = 1; - - // some sanity checks - if (config.type !== "MotionSensor") { - this.log("[ERROR] MotionSensor Service for non 'MotionSensor' service called"); - return undefined; - } - if (!config.name) { - this.log("[ERROR] MotionSensor Service without 'name' property called"); - return undefined; - } - - var myService = new Service.MotionSensor(config.name,config.name); - if (config.MotionDetected) { - this.log("["+ this.name +"]:MotionSensor MotionDetected characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.MotionDetected, "Bool", config.MotionDetected); - } - //optionals - if (config.StatusActive) { - this.log("["+ this.name +"]:MotionSensor StatusActive characteristic enabled"); - myService.addCharacteristic(Characteristic.StatusActive); - this.bindCharacteristic(myService, Characteristic.StatusActive, "Bool", config.StatusActive); - } - if (config.StatusFault) { - this.log("["+ this.name +"]:MotionSensor StatusFault characteristic enabled"); - myService.addCharacteristic(Characteristic.StatusFault); - this.bindCharacteristic(myService, Characteristic.StatusFault, "Bool", config.StatusFault); - } - if (config.StatusTampered) { - this.log("["+ this.name +"]:MotionSensor StatusTampered characteristic enabled"); - myService.addCharacteristic(Characteristic.StatusTampered); - this.bindCharacteristic(myService, Characteristic.StatusTampered, "Bool", config.StatusTampered); - } - if (config.StatusLowBattery) { - this.log("["+ this.name +"]:MotionSensor StatusLowBattery characteristic enabled"); - myService.addCharacteristic(Characteristic.StatusLowBattery); - this.bindCharacteristic(myService, Characteristic.StatusLowBattery, "Bool", config.StatusLowBattery); - } - return myService; - }, - getOutletService: function(config) { - /** - * this.addCharacteristic(Characteristic.On); - * this.addCharacteristic(Characteristic.OutletInUse); - */ - // some sanity checks - if (config.type !== "Outlet") { - this.log("[ERROR] Outlet Service for non 'Outlet' service called"); - return undefined; - } - if (!config.name) { - this.log("[ERROR] Outlet Service without 'name' property called"); - return undefined; - } - var myService = new Service.Outlet(config.name,config.name); - // On (and Off) - if (config.On) { - this.log("["+ this.name +"]:Outlet on/off characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.On, "Bool", config.On); - } // OutletInUse characteristic - if (config.OutletInUse) { - this.log("["+ this.name +"]:Outlet on/off characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.OutletInUse, "Bool", config.OutletInUse); - } - return myService; - }, - getSwitchService: function(config) { - // some sanity checks - if (config.type !== "Switch") { - this.log("[ERROR] Switch Service for non 'Switch' service called"); - return undefined; - } - if (!config.name) { - this.log("[ERROR] Switch Service without 'name' property called"); - return undefined; - } - var myService = new Service.Switch(config.name,config.name); - // On (and Off) - if (config.On) { - this.log("["+ this.name +"]:Switch on/off characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.On, "Bool", config.On); - } // On characteristic - - return myService; - }, - getThermostatService: function(config) { -/** - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.CurrentRelativeHumidity); - this.addOptionalCharacteristic(Characteristic.TargetRelativeHumidity); - this.addOptionalCharacteristic(Characteristic.CoolingThresholdTemperature); - this.addOptionalCharacteristic(Characteristic.HeatingThresholdTemperature); -*/ - - // some sanity checks - if (config.type !== "Thermostat") { - this.log("[ERROR] Thermostat Service for non 'Thermostat' service called"); - return undefined; - } - if (!config.name) { - this.log("[ERROR] Thermostat Service without 'name' property called"); - return undefined; - } - - var myService = new Service.Thermostat(config.name,config.name); - // CurrentTemperature) - // props update for https://github.com/KhaosT/HAP-NodeJS/commit/1d84d128d1513beedcafc24d2c07d98185563243#diff-cb84de3a1478a38b2cf8388d709f1c1cR108 - if (config.CurrentTemperature) { - this.log("["+ this.name +"]:Thermostat CurrentTemperature characteristic enabled"); - myService.getCharacteristic(Characteristic.CurrentTemperature).setProps({ - minValue: config.CurrentTemperature.minValue || -40, - maxValue: config.CurrentTemperature.maxValue || 60 - }); // °C by default - this.bindCharacteristic(myService, Characteristic.CurrentTemperature, "Float", config.CurrentTemperature); - } - // TargetTemperature if available - if (config.TargetTemperature) { - this.log("["+ this.name +"]:Thermostat TargetTemperature characteristic enabled"); - // default boundary too narrow for thermostats - // props update for https://github.com/KhaosT/HAP-NodeJS/commit/1d84d128d1513beedcafc24d2c07d98185563243#diff-cb84de3a1478a38b2cf8388d709f1c1cR108 - myService.getCharacteristic(Characteristic.TargetTemperature).setProps({ - minValue: config.TargetTemperature.minValue || 0, - maxValue: config.TargetTemperature.maxValue || 40 - }); - - this.bindCharacteristic(myService, Characteristic.TargetTemperature, "Float", config.TargetTemperature); - } - // HVAC - if (config.CurrentHeatingCoolingState) { - this.log("["+ this.name +"]:Thermostat CurrentHeatingCoolingState characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.CurrentHeatingCoolingState, "HVAC", config.CurrentHeatingCoolingState); - } - // HVAC - if (config.TargetHeatingCoolingState) { - this.log("["+ this.name +"]:Thermostat TargetHeatingCoolingState characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.TargetHeatingCoolingState, "HVAC", config.TargetHeatingCoolingState); - } - return myService; - }, - getTemperatureSensorService: function(config) { - - // some sanity checks - if (config.type !== "TemperatureSensor") { - this.log("[ERROR] TemperatureSensor Service for non 'TemperatureSensor' service called"); - return undefined; - } - if (!config.name) { - this.log("[ERROR] TemperatureSensor Service without 'name' property called"); - return undefined; - } - var myService = new Service.TemperatureSensor(config.name,config.name); - // CurrentTemperature) - // props update for https://github.com/KhaosT/HAP-NodeJS/commit/1d84d128d1513beedcafc24d2c07d98185563243#diff-cb84de3a1478a38b2cf8388d709f1c1cR108 - if (config.CurrentTemperature) { - this.log("["+ this.name +"]:TemperatureSensor CurrentTemperature characteristic enabled"); - myService.getCharacteristic(Characteristic.CurrentTemperature).setProps({ - minValue: config.CurrentTemperature.minValue || -40, - maxValue: config.CurrentTemperature.maxValue || 60 - }); // °C by default - this.bindCharacteristic(myService, Characteristic.CurrentTemperature, "Float", config.CurrentTemperature); - } - - return myService; - }, - getWindowService: function(config) { -/** - Optional Characteristics - this.addOptionalCharacteristic(Characteristic.HoldPosition); - this.addOptionalCharacteristic(Characteristic.ObstructionDetected); - this.addOptionalCharacteristic(Characteristic.Name); - - PositionState values: The KNX blind actuators I have return only MOVING=1 and STOPPED=0 - Characteristic.PositionState.DECREASING = 0; - Characteristic.PositionState.INCREASING = 1; - Characteristic.PositionState.STOPPED = 2; -*/ - - // some sanity checks - - - if (config.type !== "Window") { - this.log("[ERROR] Window Service for non 'Window' service called"); - return undefined; - } - if (!config.name) { - this.log("[ERROR] Window Service without 'name' property called"); - return undefined; - } - var myService = new Service.Window(config.name,config.name); - - if (config.CurrentPosition) { - this.log("["+ this.name +"]:Window CurrentPosition characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.CurrentPosition, "Percent", config.CurrentPosition); - } - if (config.TargetPosition) { - this.log("["+ this.name +"]:Window TargetPosition characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.TargetPosition, "Percent", config.TargetPosition); - } - if (config.PositionState) { - this.log("["+ this.name +"]:Window PositionState characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.PositionState, "Int", config.PositionState); - } - return myService; - }, - getWindowCoveringService: function(config) { - /** - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.HoldPosition); - this.addOptionalCharacteristic(Characteristic.TargetHorizontalTiltAngle); - this.addOptionalCharacteristic(Characteristic.TargetVerticalTiltAngle); - this.addOptionalCharacteristic(Characteristic.CurrentHorizontalTiltAngle); - this.addOptionalCharacteristic(Characteristic.CurrentVerticalTiltAngle); - this.addOptionalCharacteristic(Characteristic.ObstructionDetected); - */ - // some sanity checks - if (config.type !== "WindowCovering") { - this.log("[ERROR] WindowCovering Service for non 'WindowCovering' service called"); - return undefined; - } - if (!config.name) { - this.log("[ERROR] WindowCovering Service without 'name' property called"); - return undefined; - } - - var myService = new Service.WindowCovering(config.name,config.name); - if (config.CurrentPosition) { - this.log("["+ this.name +"]:WindowCovering CurrentPosition characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.CurrentPosition, "Percent", config.CurrentPosition); - } - if (config.TargetPosition) { - this.log("["+ this.name +"]:WindowCovering TargetPosition characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.TargetPosition, "Percent", config.TargetPosition); - } - if (config.PositionState) { - this.log("["+ this.name +"]:WindowCovering PositionState characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.PositionState, "Float", config.PositionState); - } - return myService; - }, - - - - -/* assemble the device ***************************************************************************************************/ - getServices: function() { - - // you can OPTIONALLY create an information service if you wish to override - // the default values for things like serial number, model, etc. - - var accessoryServices = []; - - var informationService = new Service.AccessoryInformation(); - - informationService - .setCharacteristic(Characteristic.Manufacturer, "Opensource Community") - .setCharacteristic(Characteristic.Model, "KNX Universal Device") - .setCharacteristic(Characteristic.SerialNumber, "Version 1.1.4"); - - accessoryServices.push(informationService); - - //iterate(this.config); - - if (!this.config.services){ - this.log("No services found in accessory?!") - } - var currServices = this.config.services; - this.log("Preparing Services: " + currServices.length) - // go through the config thing and look for services - for (var int = 0; int < currServices.length; int++) { - var configService = currServices[int]; - // services need to have type and name properties - if (!configService.type && !configService.name) { - this.log("[ERROR] must specify 'type' and 'name' properties for each service in config.json. KNX platform section fault "); - throw new Error("Must specify 'type' and 'name' properties for each service in config.json"); - } - this.log("Preparing Service: " + int + " of type "+configService.type) - switch (configService.type) { - case "ContactSensor": - accessoryServices.push(this.getContactSenserService(configService)); - break; - case "GarageDoorOpener": - accessoryServices.push(this.getGarageDoorOpenerService(configService)); - break; - case "Lightbulb": - accessoryServices.push(this.getLightbulbService(configService)); - break; - case "LightSensor": - accessoryServices.push(this.getLightSensorService(configService)); - break; - case "LockMechanism": - accessoryServices.push(this.getLockMechanismService(configService)); - break; - case "MotionSensor": - accessoryServices.push(this.getMotionSensorService(configService)); - break; - case "Switch": - accessoryServices.push(this.getSwitchService(configService)); - break; - case "TemperatureSensor": - accessoryServices.push(this.getTemperatureSensorService(configService)); - break; - case "Thermostat": - accessoryServices.push(this.getThermostatService(configService)); - break; - case "Window": - accessoryServices.push(this.getWindowService(configService)); - break; - case "WindowCovering": - accessoryServices.push(this.getWindowCoveringService(configService)); - break; - default: - this.log("[ERROR] unknown 'type' property of ["+configService.type+"] for service ["+ configService.name + "] in config.json. KNX platform section fault "); - throw new Error("[ERROR] unknown 'type' property of ["+configService.type+"] for service '"+ configService.name + "' in config.json. KNX platform section fault "); - } - } - // start listening for events on the bus (if not started yet - will prevent itself) - knxd_startMonitor({ host: this.knxd_ip, port: this.knxd_port }); - return accessoryServices; - } -}; diff --git a/accessories/mpdclient.js b/accessories/mpdclient.js deleted file mode 100644 index ac6bf5d..0000000 --- a/accessories/mpdclient.js +++ /dev/null @@ -1,89 +0,0 @@ -var Service = require("hap-nodejs").Service; -var Characteristic = require("hap-nodejs").Characteristic; -var request = require("request"); -var komponist = require('komponist') - -module.exports = { - accessory: MpdClient -} - -function MpdClient(log, config) { - this.log = log; - this.host = config["host"] || 'localhost'; - this.port = config["port"] || 6600; -} - -MpdClient.prototype = { - - setPowerState: function(powerOn, callback) { - - var log = this.log; - - komponist.createConnection(this.port, this.host, function(error, client) { - - if (error) { - return callback(error); - } - - if (powerOn) { - client.play(function(error) { - log("start playing"); - client.destroy(); - callback(error); - }); - } else { - client.stop(function(error) { - log("stop playing"); - client.destroy(); - callback(error); - }); - } - - }); - }, - - getPowerState: function(callback) { - - komponist.createConnection(this.port, this.host, function(error, client) { - - if (error) { - return callback(error); - } - - client.status(function(error, status) { - - client.destroy(); - - if (status['state'] == 'play') { - callback(error, 1); - } else { - callback(error, 0); - } - }); - - }); - }, - - identify: function(callback) { - this.log("Identify requested!"); - callback(); // success - }, - - getServices: function() { - - var informationService = new Service.AccessoryInformation(); - - informationService - .setCharacteristic(Characteristic.Manufacturer, "MPD") - .setCharacteristic(Characteristic.Model, "MPD Client") - .setCharacteristic(Characteristic.SerialNumber, "81536334"); - - var switchService = new Service.Switch(); - - switchService.getCharacteristic(Characteristic.On) - .on('get', this.getPowerState.bind(this)) - .on('set', this.setPowerState.bind(this)); - - return [informationService, switchService]; - } -}; diff --git a/app.js b/app.js deleted file mode 100644 index 20279fa..0000000 --- a/app.js +++ /dev/null @@ -1,223 +0,0 @@ -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; - -console.log("Starting HomeBridge server..."); - -console.log("_____________________________________________________________________"); -console.log("IMPORTANT: Homebridge is in the middle of some big changes."); -console.log(" Read more about it here:"); -console.log(" https://github.com/nfarina/homebridge/wiki/Migration-Guide"); -console.log("_____________________________________________________________________"); -console.log(""); - -// Look for the configuration file -var configPath = path.join(__dirname, "config.json"); - -// Complain and exit if it doesn't exist yet -if (!fs.existsSync(configPath)) { - console.log("Couldn't find a config.json file in the same directory as app.js. Look at config-sample.json for examples of how to format your config.js and add your home accessories."); - process.exit(1); -} - -// Initialize HAP-NodeJS -hap.init(); - -// Load up the configuration file -var config; -try { - config = JSON.parse(fs.readFileSync(configPath)); -} -catch (err) { - console.log("There was a problem reading your config.json file."); - console.log("Please try pasting your config.json file here to validate it: http://jsonlint.com"); - console.log(""); - throw err; -} - -// pull out our custom Bridge settings from config.json, if any -var bridgeConfig = config.bridge || {}; - -// Start by creating our Bridge which will host all loaded Accessories -var bridge = new Bridge(bridgeConfig.name || 'Homebridge', uuid.generate("HomeBridge")); - -// keep track of async calls we're waiting for callbacks on before we can start up -// this is hacky but this is all going away once we build proper plugin support -var asyncCalls = 0; -var asyncWait = false; - -function startup() { - asyncWait = true; - if (config.platforms) loadPlatforms(); - if (config.accessories) loadAccessories(); - asyncWait = false; - - // publish now unless we're waiting on anyone - if (asyncCalls == 0) - publish(); -} - -function loadAccessories() { - - // Instantiate all accessories in the config - console.log("Loading " + config.accessories.length + " accessories..."); - - for (var i=0; i=0.2.0" + }, + "dependencies": { + "request": "^2.65.0" + } +} diff --git a/lib/cli.js b/lib/cli.js new file mode 100644 index 0000000..594914f --- /dev/null +++ b/lib/cli.js @@ -0,0 +1,29 @@ +var program = require('commander'); +var hap = require("hap-nodejs"); +var version = require('./version'); +var Server = require('./server').Server; +var Plugin = require('./plugin').Plugin; +var User = require('./user').User; + +'use strict'; + +module.exports = function() { + + console.log("_____________________________________________________________________"); + console.log("IMPORTANT: Homebridge is in the middle of some big changes."); + console.log(" Read more about it here:"); + console.log(" https://github.com/nfarina/homebridge/wiki/Migration-Guide"); + console.log("_____________________________________________________________________"); + console.log(""); + + program + .version(version) + .option('-P, --plugin-path [path]', 'look for plugins installed at [path] as well as node_modules', function(p) { Plugin.addPluginPath(p); }) + .option('-D, --debug', 'turn on debug level logging', function() { logger.setDebugEnabled(true) }) + .parse(process.argv); + + // Initialize HAP-NodeJS with a custom persist directory + hap.init(User.persistPath()); + + new Server().run(); +} diff --git a/lib/logger.js b/lib/logger.js new file mode 100644 index 0000000..fecd081 --- /dev/null +++ b/lib/logger.js @@ -0,0 +1,64 @@ +var chalk = require('chalk'); + +'use strict'; + +module.exports = { + Logger: Logger, + setDebugEnabled: setDebugEnabled, + _system: new Logger() // system logger, for internal use only +} + +var DEBUG_ENABLED = false; + +// Turns on debug level logging +function setDebugEnabled(enabled) { + DEBUG_ENABLED = enabled; +} + +// global cache of logger instances by plugin name +var loggerCache = {}; + +/** + * Logger class + */ + +function Logger(pluginName) { + this.pluginName = pluginName; +} + +Logger.prototype.debug = function(msg) { + if (DEBUG_ENABLED) + this.log('debug', msg); +} + +Logger.prototype.info = function(msg) { + this.log('info', msg); +} + +Logger.prototype.warn = function(msg) { + this.log('warn', msg); +} + +Logger.prototype.error = function(msg) { + this.log('error', msg); +} + +Logger.prototype.log = function(level, msg) { + + if (level == 'debug') + msg = chalk.gray(msg); + else if (level == 'warn') + msg = chalk.yellow(msg); + else if (level == 'error') + msg = chalk.bold.red(msg); + + // prepend plugin name if applicable + if (this.pluginName) + msg = chalk.cyan("[" + this.pluginName + "]") + " " + msg; + + console.log(msg); +} + +Logger.forPlugin = function(pluginName) { + return loggerCache[pluginName] || (loggerCache[pluginName] = new Logger(pluginName)); +} diff --git a/lib/plugin.js b/lib/plugin.js new file mode 100644 index 0000000..41d6819 --- /dev/null +++ b/lib/plugin.js @@ -0,0 +1,176 @@ +var path = require('path'); +var fs = require('fs'); +var semver = require('semver'); +var User = require('./user').User; +var version = require('./version'); + +'use strict'; + +module.exports = { + Plugin: Plugin +} + +/** + * Homebridge Plugin. + * + * Allows for discovering and loading installed Homebridge plugins. + */ + +function Plugin(pluginPath) { + this.pluginPath = pluginPath; // like "/usr/local/lib/node_modules/plugin-lockitron" + + // these are exports pulled from the loaded plugin module + this.accessory = null; // single exposed accessory + this.platform = null; // single exposed platform + this.accessories = []; // array of exposed accessories + this.platforms = []; // array of exposed platforms +} + +Plugin.prototype.name = function() { + return path.basename(this.pluginPath); +} + +Plugin.prototype.load = function(options) { + options = options || {}; + + // does this plugin exist at all? + if (!fs.existsSync(this.pluginPath)) { + throw new Error("Plugin " + this.pluginPath + " was not found. Make sure the module '" + this.pluginPath + "' is installed."); + } + + // attempt to load package.json + var pjson = Plugin.loadPackageJSON(this.pluginPath); + + // pluck out the HomeBridge version requirement + if (!pjson.peerDepdendencies || !pjson.peerDepdendencies.homebridge) { + throw new Error("Plugin " + this.pluginPath + " does not contain the 'homebridge' package in 'peerDepdendencies'."); + } + + var versionRequired = pjson.peerDepdendencies.homebridge; + + // make sure the version is satisfied by the currently running version of HomeBridge + if (!semver.satisfies(version, versionRequired)) { + throw new Error("Plugin " + this.pluginPath + " requires a HomeBridge version of " + versionRequired + " which does not satisfy the current HomeBridge version of " + version + ". You may need to upgrade your installation of HomeBridge."); + } + + // figure out the main module - index.js unless otherwise specified + var main = pjson.main || "./index.js"; + + var mainPath = path.join(this.pluginPath, main); + + // try to require() it + var pluginModule = require(mainPath); + + // extract all exposed accessories and platforms + this.accessories = pluginModule.accessories || {}; + this.platforms = pluginModule.platforms || {}; +} + +Plugin.loadPackageJSON = function(pluginPath) { + // check for a package.json + var pjsonPath = path.join(pluginPath, "package.json"); + var pjson = null; + + if (!fs.existsSync(pjsonPath)) { + throw new Error("Plugin " + pluginPath + " does not contain a package.json."); + } + + try { + // attempt to parse package.json + pjson = JSON.parse(fs.readFileSync(pjsonPath)); + } + catch (err) { + throw new Error("Plugin " + pluginPath + " contains an invalid package.json. Error: " + err); + } + + // verify that it's tagged with the correct keyword + if (!pjson.keywords || pjson.keywords.indexOf("homebridge-plugin") == -1) { + throw new Error("Plugin " + pluginPath + " package.json does not contain the keyword 'homebridge-plugin'."); + } + + return pjson; +} + +Plugin.getDefaultPaths = function() { + var win32 = process.platform === 'win32'; + var paths = []; + + // add the paths used by require() + paths = paths.concat(require.main.paths); + + // THIS SECTION FROM: https://github.com/yeoman/environment/blob/master/lib/resolver.js + + // Adding global npm directories + // We tried using npm to get the global modules path, but it haven't work out + // because of bugs in the parseable implementation of `ls` command and mostly + // performance issues. So, we go with our best bet for now. + if (process.env.NODE_PATH) { + paths = process.env.NODE_PATH.split(path.delimiter) + .filter(function(p) { return !!p; }) // trim out empty values + .concat(paths); + } else { + // Default paths for each system + if (win32) { + paths.push(path.join(process.env.APPDATA, 'npm/node_modules')); + } else { + paths.push('/usr/local/lib/node_modules'); + paths.push('/usr/lib/node_modules'); + } + } + + return paths; +} + +// All search paths we will use to discover installed plugins +Plugin.paths = Plugin.getDefaultPaths(); + +Plugin.addPluginPath = function(pluginPath) { + Plugin.paths.unshift(path.resolve(process.cwd(), pluginPath)); +} + +// Gets all plugins installed on the local system +Plugin.installed = function() { + + var plugins = []; + var pluginsByName = {}; // don't add duplicate plugins + + // search for plugins among all known paths, in order + for (var index in Plugin.paths) { + var requirePath = Plugin.paths[index]; + + // just because this path is in require.main.paths doesn't mean it necessarily exists! + if (!fs.existsSync(requirePath)) + continue; + + var names = fs.readdirSync(requirePath); + + // read through each directory in this node_modules folder + for (var index2 in names) { + var name = names[index2]; + + // reconstruct full path + var pluginPath = path.join(requirePath, name); + + // we only care about directories + if (!fs.statSync(pluginPath).isDirectory()) continue; + + // does this module contain a package.json? + try { + // throws an Error if this isn't a homebridge plugin + Plugin.loadPackageJSON(pluginPath); + } + catch (err) { + // swallow error and skip this module + continue; + } + + // add it to the return list + if (!pluginsByName[name]) { + pluginsByName[name] = true; + plugins.push(new Plugin(pluginPath)); + } + } + } + + return plugins; +} diff --git a/lib/server.js b/lib/server.js new file mode 100644 index 0000000..c2cbd7b --- /dev/null +++ b/lib/server.js @@ -0,0 +1,297 @@ +var path = require('path'); +var fs = require('fs'); +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 Plugin = require('./plugin').Plugin; +var User = require('./user').User; + +'use strict'; + +module.exports = { + Server: Server +} + +function Server() { + this._accessories = {}; // this._accessories[name] = accessory constructor + this._platforms = {}; // this._platforms[name] = platform constructor + this._plugins = this._loadPlugins(this._accessories, this._platforms); // plugins[name] = plugin + this._config = this._loadConfig(); + this._bridge = this._createBridge(); +} + +Server.prototype.run = function() { + + // keep track of async calls we're waiting for callbacks on before we can start up + this._asyncCalls = 0; + this._asyncWait = true; + + if (this._config.platforms) this._loadPlatforms(); + if (this._config.accessories) this._loadAccessories(); + + this._asyncWait = false; + + // publish now unless we're waiting on anyone + if (this._asyncCalls == 0) + this._publish(); +} + +Server.prototype._publish = function() { + // pull out our custom Bridge settings from config.json, if any + var bridgeConfig = this._config.bridge || {}; + + this._printPin(bridgeConfig.pin); + this._bridge.publish({ + username: bridgeConfig.username || "CC:22:3D:E3:CE:30", + port: bridgeConfig.port || 51826, + pincode: bridgeConfig.pin || "031-45-154", + category: Accessory.Categories.OTHER + }); +} + +Server.prototype._loadPlugins = function(accessories, platforms) { + + var plugins = {}; + + // load and validate plugins - check for valid package.json, etc. + Plugin.installed().forEach(function(plugin) { + + // attempt to load it + try { + plugin.load(); + } + catch (err) { + console.error(err); + plugin.loadError = err; + } + + // add it to our dict for easy lookup later + plugins[plugin.name()] = plugin; + + console.log("Loaded plugin: " + plugin.name()); + + if (plugin.accessories) { + var sep = "" + var line = "Accessories: ["; + for (var name in plugin.accessories) { + if (accessories[name]) + throw new Error("Plugin " + plugin.name() + " wants to publish an accessory '" + name + "' which has already been published by another plugin!"); + + accessories[name] = plugin.accessories[name]; // copy to global dict + line += sep + name; sep = ","; + } + line += "]"; + if (sep) console.log(line); + } + + if (plugin.platforms) { + var sep = "" + var line = "Platforms: ["; + for (var name in plugin.platforms) { + if (plugin.platforms[name]) + throw new Error("Plugin " + plugin.name() + " wants to publish a platform '" + name + "' which has already been published by another plugin!"); + + platforms[name] = plugin.platforms[name]; // copy to global dict + line += sep + name; sep = ","; + } + line += "]"; + if (sep) console.log(line); + } + + console.log("---"); + + }.bind(this)); + + return plugins; +} + +Server.prototype._loadConfig = function() { + + // Look for the configuration file + var configPath = User.configPath(); + + // Complain and exit if it doesn't exist yet + if (!fs.existsSync(configPath)) { + console.log("Couldn't find a config.json file in the same directory as app.js. Look at config-sample.json for examples of how to format your config.js and add your home accessories."); + process.exit(1); + } + + // Load up the configuration file + var config; + try { + config = JSON.parse(fs.readFileSync(configPath)); + } + catch (err) { + console.log("There was a problem reading your config.json file."); + console.log("Please try pasting your config.json file here to validate it: http://jsonlint.com"); + console.log(""); + throw err; + } + + var accessoryCount = (config.accessories && config.accessories.length) || 0; + var platformCount = (config.platforms && config.platforms.length) || 0; + console.log("Loaded config.json with %s accessories and %s platforms.", accessoryCount, platformCount); + + console.log("---"); + + return config; +} + +Server.prototype._createBridge = function() { + // pull out our custom Bridge settings from config.json, if any + var bridgeConfig = this._config.bridge || {}; + + // Create our Bridge which will host all loaded Accessories + return new Bridge(bridgeConfig.name || 'Homebridge', uuid.generate("HomeBridge")); +} + +Server.prototype._loadAccessories = function() { + + // Instantiate all accessories in the config + console.log("Loading " + this._config.accessories.length + " accessories..."); + + for (var i=0; i= 0.12.0" + }, + "preferGlobal": true, "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": "^0.0.2", - "harmonyhubjs-client": "^1.1.4", - "harmonyhubjs-discover": "git+https://github.com/swissmanu/harmonyhubjs-discover.git", - "komponist": "0.1.0", - "lifx": "git+https://github.com/magicmonkey/lifxjs.git", - "lifx-api": "^1.0.1", - "mdns": "^2.2.4", - "node-hue-api": "^1.0.5", - "node-icontrol": "^0.1.5", - "node-milight-promise": "0.0.x", - "node-persist": "0.0.x", - "q": "1.4.x", - "request": "2.49.x", - "sonos": "0.8.x", - "telldus-live": "^0.2.1", - "teslams": "1.0.1", - "tough-cookie": "^2.0.0", - "unofficial-nest-api": "git+https://github.com/hachidorii/unofficial_nodejs_nest.git#d8d48edc952b049ff6320ef99afa7b2f04cdee98", - "wemo": "0.2.x", - "wink-js": "0.0.5", - "xml2js": "0.4.x", - "xmldoc": "0.1.x", - "yamaha-nodejs": "0.4.x" + "commander": "2.8.1", + "hap-nodejs": "0.0.2", + "semver": "5.0.3" } } diff --git a/platforms/Domoticz.js b/platforms/Domoticz.js deleted file mode 100644 index 948167d..0000000 --- a/platforms/Domoticz.js +++ /dev/null @@ -1,378 +0,0 @@ -// Domoticz Platform Shim for HomeBridge -// Written by Joep Verhaeg (http://www.joepverhaeg.nl) -// -// Revisions: -// -// 12 June 2015 [GizMoCuz] -// - Added support for RGB lights -// - Added support for Scenes -// - Sorting device names -// -// 22 July 2015 [lukeredpath] -// - Added SSL and basic auth support -// -// 26 August 2015 [EddyK69] -// - Added parameter in config.json: 'loadscenes' for enabling/disabling loading scenes -// - Fixed issue with dimmer-range; was 0-100, should be 0-16 -// -// 27 August 2015 [EddyK69] -// - Fixed issue that 'on/off'-type lights showed as dimmers in HomeKit. Checking now on SwitchType instead of HaveDimmer -// - Fixed issue that 'on-off'-type lights would not react on Siri 'Switch on/off light'; On/Off types are now handled as Lights instead of Switches -// (Cannot determine if 'on/off'-type device is a Light or a Switch :( ) -// -// 14 September 2015 [lukeredpath] -// - Fixed incorrect dimmer range for LightwaveRF lights (0-32 required, MaxDimLevel should be honored) -// -// -// Domoticz JSON API required -// https://www.domoticz.com/wiki/Domoticz_API/JSON_URL's#Lights_and_switches -// -// Remember to add platform to config.json. Example: -// "platforms": [ -// { -// "platform": "Domoticz", -// "name": "Domoticz", -// "server": "127.0.0.1", -// "port": "8080", -// "roomid": 123, (0=no roomplan) -// "loadscenes": 1 (0=disable scenes) -// } -// ], -// -// If your server uses HTTPS, you can specify "ssl": true in your config. If -// your server uses a self-signed certificate, you'll need to run the following -// before starting the server or you will get an error: -// -// export NODE_TLS_REJECT_UNAUTHORIZED=0 -// -// For basic auth support, specify the "user" and "password" in your config. -// -// 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 request = require("request"); - -function DomoticzPlatform(log, config){ - this.log = log; - this.user = config["user"]; - this.password = config["password"]; - this.server = config["server"]; - this.port = config["port"]; - this.protocol = config["ssl"] ? "https" : "http"; - this.roomid = 0; - if (typeof config["roomid"] != 'undefined') { - this.roomid = config["roomid"]; - } - this.loadscenes = 1; - if (typeof config["loadscenes"] != 'undefined') { - this.loadscenes = config["loadscenes"]; - } -} - -function sortByKey(array, key) { - return array.sort(function(a, b) { - var x = a[key]; var y = b[key]; - return ((x < y) ? -1 : ((x > y) ? 1 : 0)); - }); -} - -DomoticzPlatform.prototype = { - urlForQuery: function(query) { - var serverString = this.server; - if (this.user && this.password) { - serverString = this.user + ":" + this.password + "@" + serverString; - } - return this.protocol + "://" + serverString + ":" + this.port + "/json.htm?" + query; - }, - - accessories: function(callback) { - this.log("Fetching Domoticz lights and switches..."); - var that = this; - var foundAccessories = []; - - // mechanism to ensure callback is only executed once all requests complete - var asyncCalls = 0; - function callbackLater() { if (--asyncCalls == 0) callback(foundAccessories); } - - if (this.roomid == 0) { - //Get Lights - asyncCalls++; - request.get({ - url: this.urlForQuery("type=devices&filter=light&used=true&order=Name"), - json: true - }, function(err, response, json) { - if (!err && response.statusCode == 200) { - if (json['result'] != undefined) { - var sArray=sortByKey(json['result'],"Name"); - sArray.map(function(s) { - var havedimmer = (s.SwitchType == 'Dimmer') - accessory = new DomoticzAccessory(that.log, that, false, s.idx, s.Name, havedimmer, s.MaxDimLevel, (s.SubType=="RGB")||(s.SubType=="RGBW")); - foundAccessories.push(accessory); - }) - } - callbackLater(); - } else { - that.log("There was a problem connecting to Domoticz. (" + err + ")"); - } - }); - } - else { - //Get all devices specified in the room - asyncCalls++; - request.get({ - url: this.urlForQuery("type=devices&plan=" + this.roomid), - json: true - }, function(err, response, json) { - if (!err && response.statusCode == 200) { - if (json['result'] != undefined) { - var sArray=sortByKey(json['result'],"Name"); - sArray.map(function(s) { - //only accept switches for now - if (typeof s.SwitchType != 'undefined') { - var havedimmer = (s.SwitchType == 'Dimmer') - accessory = new DomoticzAccessory(that.log, that, false, s.idx, s.Name, havedimmer, s.MaxDimLevel, (s.SubType=="RGB")||(s.SubType=="RGBW")); - foundAccessories.push(accessory); - } - }) - } - callbackLater(); - } else { - that.log("There was a problem connecting to Domoticz."); - } - }); - } - //Get Scenes - if (this.loadscenes == 1) { - asyncCalls++; - request.get({ - url: this.urlForQuery("type=scenes"), - json: true - }, function(err, response, json) { - if (!err && response.statusCode == 200) { - if (json['result'] != undefined) { - var sArray=sortByKey(json['result'],"Name"); - sArray.map(function(s) { - accessory = new DomoticzAccessory(that.log, that, true, s.idx, s.Name, false, 0, false); - foundAccessories.push(accessory); - }) - } - callbackLater(); - } else { - that.log("There was a problem connecting to Domoticz."); - } - }); - } - } -} - -function DomoticzAccessory(log, platform, IsScene, idx, name, HaveDimmer, MaxDimLevel, HaveRGB) { - // device info - this.IsScene = IsScene; - this.idx = idx; - this.name = name; - this.HaveDimmer = HaveDimmer; - this.MaxDimLevel = MaxDimLevel; - this.HaveRGB = HaveRGB; - this.log = log; - this.platform = platform; -} - -DomoticzAccessory.prototype = { - command: function(c,value) { - this.log(this.name + " sending command " + c + " with value " + value); - if (this.IsScene == false) { - //Lights - if (c == "On" || c == "Off") { - url = this.platform.urlForQuery("type=command¶m=switchlight&idx=" + this.idx + "&switchcmd=" + c + "&level=0"); - } - else if (c == "setHue") { - url = this.platform.urlForQuery("type=command¶m=setcolbrightnessvalue&idx=" + this.idx + "&hue=" + value + "&brightness=100" + "&iswhite=false"); - } - else if (c == "setLevel") { - value = this.dimmerLevelForValue(value) - url = this.platform.urlForQuery("type=command¶m=switchlight&idx=" + this.idx + "&switchcmd=Set%20Level&level=" + value); - } - else if (value != undefined) { - this.log(this.name + " Unhandled Light command! cmd=" + c + ", value=" + value); - } - } - else { - //Scenes - if (c == "On" || c == "Off") { - url = this.platform.urlForQuery("type=command¶m=switchscene&idx=" + this.idx + "&switchcmd=" + c); - } - else if (value != undefined) { - this.log(this.name + " Unhandled Scene command! cmd=" + c + ", value=" + value); - } - } - - var that = this; - request.put({ url: url }, function(err, response) { - if (err) { - that.log("There was a problem sending command " + c + " to" + that.name); - that.log(url); - } else { - that.log(that.name + " sent command " + c + " (value: " + value + ")"); - } - }) - }, - - // translates the HomeKit dim level as a percentage to whatever scale the device requires - dimmerLevelForValue: function(value) { - if (this.MaxDimLevel == 100) { - return value; - } - return Math.round((value / 100.0) * this.MaxDimLevel) - }, - - 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: "Domoticz", - 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 - } - ] - }, - - controlCharacteristics: function(that) { - cTypes = [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: true, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - }] - - if (this.idx != undefined) { - cTypes.push({ - cType: types.POWER_STATE_CTYPE, - onUpdate: function(value) { - if (value == 0) { - that.command("Off") - } else { - that.command("On") - } - }, - perms: ["pw","pr","ev"], - format: "bool", - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Change the power state", - designedMaxLength: 1 - }) - } - - if (this.HaveDimmer == true) { - cTypes.push({ - cType: types.BRIGHTNESS_CTYPE, - onUpdate: function(value) { that.command("setLevel", value); }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Adjust Brightness of Light", - designedMinValue: 0, - designedMaxValue: this.MaxDimLevel, - designedMinStep: 1, - unit: "%" - }) - } - if (this.HaveRGB == true) { - cTypes.push({ - cType: types.HUE_CTYPE, - onUpdate: function(value) { that.command("setHue", value); }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Adjust Hue of Light", - designedMinValue: 0, - designedMaxValue: 360, - designedMinStep: 1, - unit: "arcdegrees" - }) - } - - return cTypes - }, - - sType: function() { - //if (this.HaveDimmer == true) { - return types.LIGHTBULB_STYPE - //} else { - // return types.SWITCH_STYPE - //} - }, - - getServices: function() { - var that = this; - var services = [{ - sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: this.informationCharacteristics(), - }, - { - sType: this.sType(), - characteristics: this.controlCharacteristics(that) - }]; - this.log("Loaded services for " + this.name) - return services; - } -}; - -module.exports.accessory = DomoticzAccessory; -module.exports.platform = DomoticzPlatform; diff --git a/platforms/FHEM.js b/platforms/FHEM.js deleted file mode 100644 index b892a0a..0000000 --- a/platforms/FHEM.js +++ /dev/null @@ -1,1570 +0,0 @@ -// FHEM Platform Shim for HomeBridge -// -// Remember to add platform to config.json. Example: -// "platforms": [ -// { -// 'platform': "FHEM", -// 'name': "FHEM", -// 'server': "127.0.0.1", -// 'port': 8083, -// 'ssl': true, -// 'auth': {'user': "fhem", 'pass': "fhempassword"}, -// 'filter': "room=xyz" -// } -// ], -// -// 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 types = require('hap-nodejs/accessories/types.js'); - -var util = require('util'); - - -// subscriptions to fhem longpoll evens -var FHEM_subscriptions = {}; -function -FHEM_subscribe(characteristic, inform_id, accessory) { - FHEM_subscriptions[inform_id] = { 'characteristic': characteristic, 'accessory': accessory }; -} - -// cached readings from longpoll & query -var FHEM_cached = {}; -//var FHEM_internal = {}; -function -FHEM_update(inform_id, value, no_update) { - var subscription = FHEM_subscriptions[inform_id]; - if( subscription != undefined ) { - if( value == undefined - || FHEM_cached[inform_id] === value ) - return; - - FHEM_cached[inform_id] = value; - //FHEM_cached[inform_id] = { 'value': value, 'timestamp': Date.now() }; - console.log(" caching: " + inform_id + ": " + value + " as " + typeof(value) ); - - if( !no_update ) - subscription.characteristic.setValue(value, undefined, 'fromFhem'); - } -} - - -var FHEM_lastEventTime; -var FHEM_longpoll_running = false; -//FIXME: add filter -function FHEM_startLongpoll(connection) { - if( FHEM_longpoll_running ) - return; - FHEM_longpoll_running = true; - - var filter = ".*"; - var since = "null"; - if( FHEM_lastEventTime ) - since = FHEM_lastEventTime/1000; - var query = "/fhem.pl?XHR=1"+ - "&inform=type=status;filter="+filter+";since="+since+";fmt=JSON"+ - "×tamp="+Date.now() - - var url = encodeURI( connection.base_url + query ); - console.log( 'starting longpoll: ' + url ); - - var FHEM_longpollOffset = 0; - var input = ""; - connection.request.get( { url: url } ).on( 'data', function(data) { -//console.log( 'data: '+ data ); - if( !data ) - return; - - input += data; - var lastEventTime = Date.now(); - for(;;) { - var nOff = input.indexOf('\n', FHEM_longpollOffset); - if(nOff < 0) - break; - var l = input.substr(FHEM_longpollOffset, nOff-FHEM_longpollOffset); - FHEM_longpollOffset = nOff+1; -//console.log( "Rcvd: "+(l.length>132 ? l.substring(0,132)+"...("+l.length+")":l) ); - if(!l.length) - continue; - - var d; - if( l.substr(0,1) == '[' ) - d = JSON.parse(l); - else - d = l.split("<<", 3); - - //console.log(d); - - if(d.length != 3) - continue; - if(d[0].match(/-ts$/)) - continue; - -//console.log( "Rcvd: "+(l.length>132 ? l.substring(0,132)+"...("+l.length+")":l) ); - - var subscription = FHEM_subscriptions[d[0]]; - if( subscription != undefined ) { -//console.log( "Rcvd: "+(l.length>132 ? l.substring(0,132)+"...("+l.length+")":l) ); - FHEM_lastEventTime = lastEventTime; - var accessory = subscription.accessory; - - var value = d[1]; - if( value.match( /^set-/ ) ) - continue; - - var match = d[0].match(/([^-]*)-(.*)/); - var device = match[1]; - var reading = match[2]; - if( reading == undefined ) - continue; - - if( reading == 'state') { - if( accessory.mappings.window ) { - var level = 50; - if( match = value.match(/^(\d+)/ ) ) - level = parseInt( match[1] ); - else if( value == 'locked' ) - level = 0; - - FHEM_update( accessory.mappings.window.informId, level ); - continue; - - } else if( accessory.mappings.lock ) { - var lock = Characteristic.LockCurrentState.UNSECURED; - if( value.match( /^locked/ ) ) - lock = Characteristic.LockCurrentState.SECURED; - - if( value.match( /uncertain/ ) ) - level = Characteristic.LockCurrentState.UNKNOWN; - - FHEM_update( accessory.mappings.lock.informId, lock ); - continue; - - } else if( match = value.match(/dim(\d+)%/ ) ) { - var pct = parseInt( match[1] ); - - FHEM_update( device+'-pct', pct ); - } - - } else if(accessory.mappings.rgb && reading == accessory.mappings.rgb.reading) { - var hsv = FHEM_rgb2hsv(value); - var hue = parseInt( hsv[0] * 360 ); - var sat = parseInt( hsv[1] * 100 ); - var bri = parseInt( hsv[2] * 100 ); - - //FHEM_update( device+'-'+reading, value, false ); - FHEM_update( device+'-hue', hue ); - FHEM_update( device+'-sat', sat ); - FHEM_update( device+'-bri', bri ); - continue; - } - - value = accessory.reading2homekit(reading, value); - FHEM_update( device+'-'+reading, value ); - - } else { - } - - } - - input = input.substr(FHEM_longpollOffset); - FHEM_longpollOffset = 0; - - } ).on( 'end', function() { - console.log( "longpoll ended" ); - - FHEM_longpoll_running = false; - setTimeout( function(){FHEM_startLongpoll(connection)}, 2000 ); - - } ).on( 'error', function(err) { - console.log( "longpoll error: " + err ); - - FHEM_longpoll_running = false; - setTimeout( function(){FHEM_startLongpoll(connection)}, 5000 ); - - } ); -} - - -function FHEMPlatform(log, config) { - this.log = log; - this.server = config['server']; - this.port = config['port']; - this.filter = config['filter']; - - var base_url; - if( config['ssl'] ) - base_url = 'https://'; - else - base_url = 'http://'; - base_url += this.server + ':' + this.port; - - var request = require('request'); - var auth = config['auth']; - if( auth ) { - if( auth.sendImmediately == undefined ) - auth.sendImmediately = false; - - request = request.defaults( { 'auth': auth, 'rejectUnauthorized': false } ); - } - - this.connection = { 'base_url': base_url, 'request': request }; - - FHEM_startLongpoll( this.connection ); -} - -function -FHEM_sortByKey(array, key) { - return array.sort( function(a, b) { - var x = a[key]; var y = b[key]; - return ((x < y) ? -1 : ((x > y) ? 1 : 0)); - }); -} - -function -FHEM_rgb2hex(r,g,b) { - if( g == undefined ) - return Number(0x1000000 + r[0]*0x10000 + r[1]*0x100 + r[2]).toString(16).substring(1); - - return Number(0x1000000 + r*0x10000 + g*0x100 + b).toString(16).substring(1); -} - -function -FHEM_hsv2rgb(h,s,v) { - var r = 0.0; - var g = 0.0; - var b = 0.0; - - if( s == 0 ) { - r = v; - g = v; - b = v; - - } else { - var i = Math.floor( h * 6.0 ); - var f = ( h * 6.0 ) - i; - var p = v * ( 1.0 - s ); - var q = v * ( 1.0 - s * f ); - var t = v * ( 1.0 - s * ( 1.0 - f ) ); - i = i % 6; - - if( i == 0 ) { - r = v; - g = t; - b = p; - } else if( i == 1 ) { - r = q; - g = v; - b = p; - } else if( i == 2 ) { - r = p; - g = v; - b = t; - } else if( i == 3 ) { - r = p; - g = q; - b = v; - } else if( i == 4 ) { - r = t; - g = p; - b = v; - } else if( i == 5 ) { - r = v; - g = p; - b = q; - } - } - - return FHEM_rgb2hex( Math.round(r*255),Math.round(g*255),Math.round(b*255) ); -} - -function -FHEM_rgb2hsv(r,g,b){ - if( r == undefined ) - return; - - if( g == undefined ) { - var str = r; - r = parseInt( str.substr(0,2), 16 ); - g = parseInt( str.substr(2,2), 16 ); - b = parseInt( str.substr(4,2), 16 ); - } - - var M = Math.max( r, g, b ); - var m = Math.min( r, g, b ); - var c = M - m; - - var h, s, v; - if( c == 0 ) { - h = 0; - } else if( M == r ) { - h = ( 60 * ( ( g - b ) / c ) % 360 ) / 360; - } else if( M == g ) { - h = ( 60 * ( ( b - r ) / c ) + 120 ) / 360; - } else if( M == b ) { - h = ( 60 * ( ( r - g ) / c ) + 240 ) / 360; - } - - if( M == 0 ) { - s = 0; - } else { - s = c / M; - } - - v = M/255; - - return [h,s,v]; -} - - -FHEMPlatform.prototype = { - accessories: function(callback) { - this.log("Fetching FHEM switchable devices..."); - - var foundAccessories = []; - - // mechanism to ensure callback is only executed once all requests complete - var asyncCalls = 0; - function callbackLater() { if (--asyncCalls == 0) callback(foundAccessories); } - - var cmd = 'jsonlist2'; - if( this.filter ) - cmd += " " + this.filter; - var url = encodeURI( this.connection.base_url + "/fhem?cmd=" + cmd + "&XHR=1"); - this.log( 'fetching: ' + url ); - - - asyncCalls++; - - var that = this; - this.connection.request.get( { url: url, json: true, gzip: true }, - function(err, response, json) { - if( !err && response.statusCode == 200 ) { - that.log( 'got: ' + json['totalResultsReturned'] + ' results' ); -//that.log("got json: " + util.inspect(json) ); - if( json['totalResultsReturned'] ) { - var sArray=FHEM_sortByKey(json['Results'],"Name"); - sArray.map(function(s) { - - var accessory; - if( s.Attributes.disable == 1 ) { - that.log( s.Internals.NAME + ' is disabled'); - - } else if( s.Internals.TYPE == 'structure' ) { - that.log( s.Internals.NAME + ' is a structure'); - - } else if( s.Attributes.genericDisplayType - || s.Attributes.genericDeviceType ) { - accessory = new FHEMAccessory(that.log, that.connection, s); - - } else if( s.PossibleSets.match(/[\^ ]on\b/) - && s.PossibleSets.match(/[\^ ]off\b/) ) { - accessory = new FHEMAccessory(that.log, that.connection, s); - - } else if( s.PossibleSets.match(/[\^ ]Volume\b/) ) { //FIXME: use sets [Pp]lay/[Pp]ause/[Ss]top - that.log( s.Internals.NAME + ' has volume'); - accessory = new FHEMAccessory(that.log, that.connection, s); - - } else if( s.Attributes.subType == 'thermostat' - || s.Attributes.subType == 'blindActuator' - || s.Attributes.subType == 'threeStateSensor' ) { - accessory = new FHEMAccessory(that.log, that.connection, s); - - } else if( s.Attributes.model == 'HM-SEC-WIN' ) { - accessory = new FHEMAccessory(that.log, that.connection, s); - - } else if( s.Attributes.model == 'HM-SEC-KEY' ) { - accessory = new FHEMAccessory(that.log, that.connection, s); - - } else if( s.Internals.TYPE == 'PRESENCE' ) { - accessory = new FHEMAccessory(that.log, that.connection, s); - - } else if( s.Readings.temperature ) { - accessory = new FHEMAccessory(that.log, that.connection, s); - - } else if( s.Readings.humidity ) { - accessory = new FHEMAccessory(that.log, that.connection, s); - - } else if( s.Readings.voc ) { - accessory = new FHEMAccessory(that.log, that.connection, s); - - } else { - that.log( 'ignoring ' + s.Internals.NAME ); - - } - - if( accessory && Object.getOwnPropertyNames(accessory).length ) - foundAccessories.push(accessory); - - }); - } - - //callback(foundAccessories); - callbackLater(); - - } else { - that.log("There was a problem connecting to FHEM (1)."); - if( response ) - that.log( " " + response.statusCode + ": " + response.statusMessage ); - - } - - }); - } -} - -function -FHEMAccessory(log, connection, s) { -//log( 'sets: ' + s.PossibleSets ); -//log("got json: " + util.inspect(s) ); -//log("got json: " + util.inspect(s.Internals) ); - - if( !(this instanceof FHEMAccessory) ) - return new FHEMAccessory(log, connection, s); - - if( s.Attributes.disable == 1 ) { - that.log( s.Internals.NAME + ' is disabled'); - return null; - - } else if( s.Internals.TYPE == 'structure' ) { - that.log( s.Internals.NAME + ' is a structure'); - return null; - - } - - - this.mappings = {}; - - var match; - if( match = s.PossibleSets.match(/[\^ ]pct\b/) ) { - this.mappings.pct = { reading: 'pct', cmd: 'pct' }; - } else if( match = s.PossibleSets.match(/[\^ ]dim\d+%/) ) { - s.hasDim = true; - s.pctMax = 100; - } - if( match = s.PossibleSets.match(/[\^ ]hue[^\b\s]*(,(\d+)?)+\b/) ) { - s.isLight = true; - var max = 360; - if( match[2] != undefined ) - max = match[2]; - this.mappings.hue = { reading: 'hue', cmd: 'hue', min: 0, max: max }; - } - if( match = s.PossibleSets.match(/[\^ ]sat[^\b\s]*(,(\d+)?)+\b/) ) { - s.isLight = true; - var max = 100; - if( match[2] != undefined ) - max = match[2]; - this.mappings.sat = { reading: 'sat', cmd: 'sat', min: 0, max: max }; - } - - if( s.PossibleSets.match(/[\^ ]rgb\b/) ) { - s.isLight = true; - this.mappings.rgb = { reading: 'rgb', cmd: 'rgb' }; - if( s.Internals.TYPE == 'SWAP_0000002200000003' ) - this.mappings.rgb = { reading: '0B-RGBlevel', cmd: 'rgb' }; - } else if( s.PossibleSets.match(/[\^ ]RGB\b/) ) { - s.isLight = true; - this.mappings.rgb = { reading: 'RGB', cmd: 'RGB' }; - } - - if( s.Readings['measured-temp'] ) - this.mappings.temperature = { reading: 'measured-temp' }; - else if( s.Readings.temperature ) - this.mappings.temperature = { reading: 'temperature' }; - - if( s.Readings.volume ) - this.mappings.volume = { reading: 'volume', cmd: 'volume' }; - else if( s.Readings.Volume ) { - this.mappings.volume = { reading: 'Volume', cmd: 'Volume', nocache: true }; - if( s.Attributes.generateVolumeEvent == 1 ) - delete this.mappings.volume.nocache; - } - - if( s.Readings.humidity ) - this.mappings.humidity = { reading: 'humidity' }; - - if( s.Readings.voc ) - this.mappings.airquality = { reading: 'voc' }; - - if( s.Readings.motor ) - this.mappings.motor = { reading: 'motor' }; - - if( s.Readings.direction ) - this.mappings.direction = { reading: 'direction' }; - - - var genericType = s.Attributes.genericDeviceType; - if( !genericType ) - genericType = s.Attributes.genericDisplayType; - - if( genericType == 'switch' ) - s.isSwitch = true; - - else if( genericType == 'garage' ) - this.mappings.garage = { cmdOpen: 'on', cmdClose: 'off' }; - - else if( genericType == 'light' ) - s.isLight = true; - - else if( genericType == 'blind' - || s.Attributes.subType == 'blindActuator' ) { - delete this.mappings.pct; - this.mappings.blind = { reading: 'pct', cmd: 'pct' }; - - } else if( genericType == 'window' - || s.Attributes.model == 'HM-SEC-WIN' ) { - this.mappings.window = { reading: 'level', cmd: 'level' }; - - } else if( genericType == 'lock' - || s.Attributes.model == 'HM-SEC-KEY' ) { - this.mappings.lock = { reading: 'lock' }; - - } else if( genericType == 'thermostat' - || s.Attributes.subType == 'thermostat' ) { - s.isThermostat = true; - - } else if( s.Internals.TYPE == 'CUL_FHTTK' ) { - this.mappings.contact = { reading: 'Window' }; - - } else if( s.Attributes.subType == 'threeStateSensor' ) { - this.mappings.contact = { reading: 'contact' }; - - } else if( s.Internals.TYPE == 'PRESENCE' ) - this.mappings.occupancy = { reading: 'state' }; - - else if( s.Attributes.model == 'fs20di' ) - s.isLight = true; - - if( s.PossibleSets.match(/[\^ ]desired-temp\b/) ) - this.mappings.thermostat = { reading: 'desired-temp', cmd: 'desired-temp' }; - else if( s.PossibleSets.match(/[\^ ]desiredTemperature\b/) ) - this.mappings.thermostat = { reading: 'desiredTemperature', cmd: 'desiredTemperature' }; - else if( s.isThermostat ) { - s.isThermostat = false; - delete this.mappings.thermostat; - log( s.Internals.NAME + ' is NOT a thermostat. set for target temperature missing' ); - } - - if( s.Internals.TYPE == 'SONOSPLAYER' ) //FIXME: use sets [Pp]lay/[Pp]ause/[Ss]top - this.mappings.onOff = { reading: 'transportState', cmdOn: 'play', cmdOff: 'pause' }; - else if( s.PossibleSets.match(/[\^ ]on\b/) - && s.PossibleSets.match(/[\^ ]off\b/) ) { - this.mappings.onOff = { reading: 'state', cmdOn: 'on', cmdOff: 'off' }; - if( !s.Readings.state ) - delete this.mappings.onOff.reading; - } - - var event_map = s.Attributes.eventMap; - if( event_map ) { - var parts = event_map.split( ' ' ); - for( var p = 0; p < parts.length; p++ ) { - var map = parts[p].split( ':' ); - if( map[1] == 'on' - || map[1] == 'off' ) { - if( !this.event_map ) - this.event_map = {} - this.event_map[map[0]] = map[1]; - } - } - } - - if( this.mappings.door ) - log( s.Internals.NAME + ' is door' ); - else if( this.mappings.garage ) - log( s.Internals.NAME + ' is garage' ); - else if( this.mappings.lock ) - log( s.Internals.NAME + ' is lock ['+ this.mappings.lock.reading +']' ); - else if( this.mappings.window ) - log( s.Internals.NAME + ' is window' ); - else if( this.mappings.blind ) - log( s.Internals.NAME + ' is blind ['+ this.mappings.blind.reading +']' ); - else if( this.mappings.thermostat ) - log( s.Internals.NAME + ' is thermostat ['+ this.mappings.thermostat.reading +']' ); - else if( this.mappings.contact ) - log( s.Internals.NAME + ' is contact sensor [' + this.mappings.contact.reading +']' ); - else if( this.mappings.occupancy ) - log( s.Internals.NAME + ' is occupancy sensor' ); - else if( this.mappings.rgb ) - log( s.Internals.NAME + ' has RGB [' + this.mappings.rgb.reading +']'); - else if( this.mappings.pct ) - log( s.Internals.NAME + ' is dimable ['+ this.mappings.pct.reading +']' ); - else if( s.hasDim ) - log( s.Internals.NAME + ' is dimable [0-'+ s.pctMax +']' ); - else if( s.isLight ) - log( s.Internals.NAME + ' is light' ); - else if( this.mappings.onOff || s.isSwitch ) - log( s.Internals.NAME + ' is switchable' ); - else if( !this.mappings ) - return {}; - - - if( this.mappings.onOff ) - log( s.Internals.NAME + ' has onOff [' + this.mappings.onOff.reading + ';' + this.mappings.onOff.cmdOn +',' + this.mappings.onOff.cmdOff + ']' ); - if( this.mappings.hue ) - log( s.Internals.NAME + ' has hue [0-' + this.mappings.hue.max +']' ); - if( this.mappings.sat ) - log( s.Internals.NAME + ' has sat [0-' + this.mappings.sat.max +']' ); - if( this.mappings.temperature ) - log( s.Internals.NAME + ' has temperature ['+ this.mappings.temperature.reading +']' ); - if( this.mappings.humidity ) - log( s.Internals.NAME + ' has humidity ['+ this.mappings.humidity.reading +']' ); - if( this.mappings.airquality ) - log( s.Internals.NAME + ' has voc ['+ this.mappings.airquality.reading +']' ); - if( this.mappings.motor ) - log( s.Internals.NAME + ' has motor' ); - if( this.mappings.direction ) - log( s.Internals.NAME + ' has direction' ); - -//log( util.inspect(s) ); - - // device info - this.name = s.Internals.NAME; - this.alias = s.Attributes.alias ? s.Attributes.alias : s.Internals.NAME; - this.device = s.Internals.NAME; - this.type = s.Internals.TYPE; - this.model = s.Readings.model ? s.Readings.model.Value - : (s.Attributes.model ? s.Attributes.model - : ( s.Internals.model ? s.Internals.model : '' ) ); - this.PossibleSets = s.PossibleSets; - - if( this.type == 'CUL_HM' ) { - this.serial = s.Internals.DEF; - if( s.Attributes.serialNr ) - this.serial = s.Attributes.serialNr; - else if( s.Readings['D-serialNr'] && s.Readings['D-serialNr'].Value ) - this.serial = s.Readings['D-serialNr'].Value; - } else if( this.type == 'CUL_WS' ) - this.serial = s.Internals.DEF; - else if( this.type == 'FS20' ) - this.serial = s.Internals.DEF; - else if( this.type == 'IT' ) - this.serial = s.Internals.DEF; - else if( this.type == 'HUEDevice' ) - this.serial = s.Internals.uniqueid; - else if( this.type == 'SONOSPLAYER' ) - this.serial = s.Internals.UDN; - - this.hasDim = s.hasDim; - this.pctMax = s.pctMax; - - this.isLight = s.isLight; - this.isSwitch = s.isSwitch; - -//log( util.inspect(s.Readings) ); - - if( this.mappings.blind || this.mappings.door || this.mappings.garage || this.mappings.window || this.mappings.thermostat ) - delete this.mappings.onOff; - - var that = this; - Object.keys(this.mappings).forEach(function(key) { - var reading = that.mappings[key].reading; - if( s.Readings[reading] && s.Readings[reading].Value ) { - var value = s.Readings[reading].Value; - value = that.reading2homekit(reading, value); - - if( value != undefined ) { - var inform_id = that.device +'-'+ reading; - that.mappings[key].informId = inform_id; - - if( !that.mappings[key].nocache ) - FHEM_cached[inform_id] = value; - } - } - } ); - - this.log = log; - this.connection = connection; -} - -FHEM_dim_values = [ 'dim06%', 'dim12%', 'dim18%', 'dim25%', 'dim31%', 'dim37%', 'dim43%', 'dim50%', 'dim56%', 'dim62%', 'dim68%', 'dim75%', 'dim81%', 'dim87%', 'dim93%' ]; - -FHEMAccessory.prototype = { - reading2homekit: function(reading,value) { - if( value == undefined ) - return undefined; - - if( reading == 'hue' ) { - value = Math.round(value * 360 / this.mappings.hue ? this.mappings.hue.max : 360); - - } else if( reading == 'sat' ) { - value = Math.round(value * 100 / this.mappings.sat ? this.mappings.sat.max : 100); - - } else if( reading == 'pct' ) { - value = parseInt( value ); - - } else if(reading == 'motor') { - if( value.match(/^up/)) - value = Characteristic.PositionState.INCREASING; - else if( value.match(/^down/)) - value = Characteristic.PositionState.DECREASING; - else - value = Characteristic.PositionState.STOPPED; - - } else if(reading == 'direction') { - if( value.match(/^opening/)) - value = PositionState.INCREASING; - else if( value.match(/^closing/)) - value = Characteristic.PositionState.DECREASING; - else - value = Characteristic.PositionState.STOPPED; - - } else if( reading == 'transportState' ) { - if( value == 'PLAYING' ) - value = 1; - else - value = 0; - - } else if( reading == 'volume' - || reading == 'Volume' ) { - value = parseInt( value ); - - } else if( reading == 'contact' ) { - if( value.match( /^closed/ ) ) - value = Characteristic.ContactSensorState.CONTACT_DETECTED; - else - value = Characteristic.ContactSensorState.CONTACT_NOT_DETECTED; - - } else if( reading == 'Window' ) { - if( value.match( /^Closed/ ) ) - value = Characteristic.ContactSensorState.CONTACT_DETECTED; - else - value = Characteristic.ContactSensorState.CONTACT_NOT_DETECTED; - - } else if( reading == 'lock' ) { - if( value.match( /uncertain/ ) ) - value = Characteristic.LockCurrentState.UNKNOWN; - else if( value.match( /^locked/ ) ) - value = Characteristic.LockCurrentState.SECURED; - else - value = Characteristic.LockCurrentState.UNSECURED; - - } else if( reading == 'temperature' - || reading == 'measured-temp' - || reading == 'desired-temp' - || reading == 'desiredTemperature' ) { - value = parseFloat( value ); - - } else if( reading == 'humidity' ) { - value = parseInt( value ); - - } else if( reading == 'voc' ) { - value = parseInt( value ); - if( value > 1500 ) - Characteristic.AirQuality.POOR; - else if( value > 1000 ) - Characteristic.AirQuality.INFERIOR; - else if( value > 800 ) - Characteristic.AirQuality.FAIR; - else if( value > 600 ) - Characteristic.AirQuality.GOOD; - else if( value > 0 ) - Characteristic.AirQuality.EXCELLENT; - else - Characteristic.AirQuality.UNKNOWN; - - } else if( reading == 'state' ) { - if( value.match(/^set-/ ) ) - return undefined; - - if( this.event_map != undefined ) { - var mapped = this.event_map[value]; - if( mapped != undefined ) - value = mapped; - } - - if( value == 'off' ) - value = 0; - else if( value == 'present' ) - value = Characteristic.OccupancyDetected.OCCUPANCY_DETECTED; - else if( value == 'absent' ) - value = Characteristic.OccupancyDetected.OCCUPANCY_NOT_DETECTED; - else if( value == '000000' ) - value = 0; - else if( value.match( /^[A-D]0$/ ) ) //FIXME: is handled by event_map now - value = 0; - else - value = 1; - - } - - return(value); - }, - - delayed: function(c,value,delay) { - var timer = this.delayed[c]; - if( timer ) { - //this.log(this.name + " removing old command " + c); - clearTimeout( timer ); - } - - this.log(this.name + " delaying command " + c + " with value " + value); - var that = this; - this.delayed[c] = setTimeout( function(){clearTimeout(that.delayed[c]);that.command(c,value)}, delay?delay:1000 ); - }, - - command: function(c,value) { - this.log(this.name + " sending command " + c + " with value " + value); - if( c == 'identify' ) { - if( this.type == 'HUEDevice' ) - cmd = "set " + this.device + "alert select"; - else - cmd = "set " + this.device + " toggle; sleep 1; set "+ this.device + " toggle"; - - } else if( c == 'set' ) { - cmd = "set " + this.device + " " + value; - - } else if( c == 'volume' ) { - cmd = "set " + this.device + " volume " + value; - - } else if( c == 'pct' ) { - cmd = "set " + this.device + " pct " + value; - - } else if( c == 'dim' ) { - //if( value < 3 ) - // cmd = "set " + this.device + " off"; - //else - if( value > 97 ) - cmd = "set " + this.device + " on"; - else - cmd = "set " + this.device + " " + FHEM_dim_values[Math.round(value/6.25)]; - - } else if( c == 'H-rgb' || c == 'S-rgb' || c == 'B-rgb' ) { - var h = FHEM_cached[this.device + '-hue' ] / 360; - var s = FHEM_cached[this.device + '-sat' ] / 100; - var v = FHEM_cached[this.device + '-bri' ] / 100; - //this.log( this.name + ' cached : [' + h + ',' + s + ',' + v + ']' ); - if( h == undefined ) h = 0.0; - if( s == undefined ) s = 1.0; - if( v == undefined ) v = 1.0; - //this.log( this.name + ' old : [' + h + ',' + s + ',' + v + ']' ); - - if( c == 'H-rgb' ) { - FHEM_update(this.device + '-hue', value, false ); - h = value / 360; - } else if( c == 'S-rgb' ) { - FHEM_update(this.device + '-sat', value, false ); - s = value / 100; - } else if( c == 'B-rgb' ) { - FHEM_update(this.device + '-bri', value, false ); - v = value / 100; - } - //this.log( this.name + ' new : [' + h + ',' + s + ',' + v + ']' ); - - value = FHEM_hsv2rgb( h, s, v ); - //this.log( this.name + ' rgb : [' + value + ']' ); - cmd = "set " + this.device + " " + this.mappings.rgb.cmd + " " + value; - - } else if( c == 'hue' ) { - value = Math.round(value * this.mappings.hue.max / 360); - cmd = "set " + this.device + " hue " + value; - - } else if( c == 'sat' ) { - value = value / 100 * this.mappings.sat.max; - cmd = "set " + this.device + " sat " + value; - - } else if( c == 'targetTemperature' ) { - cmd = "set " + this.device + " " + this.mappings.thermostat.cmd + " " + value; - - } else if( c == 'targetPosition' ) { - if( this.mappings.window ) { - if( value == 0 ) - value = 'lock'; - - cmd = "set " + this.device + " " + this.mappings.window.cmd + " " + value; - - } else if( this.mappings.blind ) - cmd = "set " + this.device + " " + this.mappings.blind.cmd + " " + value; - - else - this.log(this.name + " Unhandled command! cmd=" + c + ", value=" + value); - - } else { - this.log(this.name + " Unhandled command! cmd=" + c + ", value=" + value); - return; - - } - - this.execute(cmd); - }, - - execute: function(cmd,callback) { - var url = encodeURI( this.connection.base_url + "/fhem?cmd=" + cmd + "&XHR=1"); - this.log( ' executing: ' + url ); - - var that = this; - this.connection.request.get( { url: url, gzip: true }, - function(err, response, result) { - if( !err && response.statusCode == 200 ) { - if( callback ) - callback( result ); - - } else { - that.log("There was a problem connecting to FHEM ("+ url +")."); - if( response ) - that.log( " " + response.statusCode + ": " + response.statusMessage ); - - } - - } ).on( 'error', function(err) { - that.log("There was a problem connecting to FHEM ("+ url +"):"+ err); - - } ); - }, - - query: function(reading, callback) { - if( reading == undefined ) { - if( callback != undefined ) - callback( 1 ); - return; - } - - this.log("query: " + this.name + "-" + reading); - - var result = FHEM_cached[this.device + '-' + reading]; - if( result != undefined ) { - this.log(" cached: " + result); - if( callback != undefined ) - callback( undefined, result ); - return result; - - } else - this.log(" not cached" ); - - var query_reading = reading; - if( reading == 'hue' && !this.mappings.hue && this.mappings.rgb ) { - query_reading = this.mappings.rgb.reading; - - } else if( reading == 'sat' && !this.mappings.sat && this.mappings.rgb ) { - query_reading = this.mappings.rgb.reading; - - } else if( reading == 'bri' && !this.mappings.pct && this.mappings.rgb ) { - query_reading = this.mappings.rgb.reading; - - } else if( reading == 'pct' && !this.mappings.pct && this.hasDim ) { - query_reading = 'state'; - - } else if( reading == 'level' && this.mappings.window ) { - query_reading = 'state'; - - } else if( reading == 'lock' && this.mappings.lock ) { - query_reading = 'state'; - - } - - var cmd = '{ReadingsVal("'+this.device+'","'+query_reading+'","")}'; - - var that = this; - this.execute( cmd, - function(result) { - value = result.replace(/[\r\n]/g, ""); - that.log(" value: " + value); - - if( value == undefined ) - return value; - - if( reading != query_reading ) { - if( reading == 'pct' - && query_reading == 'state') { - - if( match = value.match(/dim(\d+)%/ ) ) - value = parseInt( match[1] ); - else if( value == 'off' ) - value = 0; - else - value = 100; - - } else if( reading == 'level' - && query_reading == 'state') { - - if( match = value.match(/^(\d+)/ ) ) - value = parseInt( match[1] ); - else if( value == 'locked' ) - value = 0; - else - value = 50; - - } else if( reading == 'lock' - && query_reading == 'state') { - - if( value.match( /uncertain/ ) ) - value = Characteristic.LockCurrentState.UNKNOWN; - else if( value.match( /^locked/ ) ) - value = Characteristic.LockCurrentState.SECURED; - else - value = Characteristic.LockCurrentState.UNSECURED; - - } else if(reading == 'hue' && query_reading == that.mappings.rgb) { - //FHEM_update( that.device+'-'+query_reading, value ); - - value = parseInt( FHEM_rgb2hsv(value)[0] * 360 ); - - } else if(reading == 'sat' && query_reading == that.mappings.rgb) { - //FHEM_update( that.device+'-'+query_reading, value ); - - value = parseInt( FHEM_rgb2hsv(value)[1] * 100 ); - - } else if(reading == 'bri' && query_reading == that.mappings.rgb) { - //FHEM_update( that.device+'-'+query_reading, value ); - - value = parseInt( FHEM_rgb2hsv(value)[2] * 100 ); - - } - } else { - value = that.reading2homekit(reading, value); - } - - that.log(" mapped: " + value); - FHEM_update( that.device + '-' + reading, value, true ); - - if( callback != undefined ) { - if( value == undefined ) - callback(1); - else - callback(undefined, value); - } - - return value ; - - } ); - }, - - createDeviceService: function() { - var name = this.alias + 'xxx'; - - if( this.isSwitch ) { - this.log(" switch service for " + this.name) - return new Service.Switch(name); - } else if( this.mappings.garage ) { - this.log(" garage door opener service for " + this.name) - return new Service.GarageDoorOpener(name); - } else if( this.mappings.window ) { - this.log(" window service for " + this.name) - return new Service.Window(name); - } else if( this.mappings.blind ) { - this.log(" window covering service for " + this.name) - return new Service.WindowCovering(name); - } else if( this.mappings.thermostat ) { - this.log(" thermostat service for " + this.name) - return new Service.Thermostat(name); - } else if( this.mappings.contact ) { - this.log(" contact sensor service for " + this.name) - return new Service.ContactSensor(name); - } else if( this.mappings.occupancy ) { - this.log(" occupancy sensor service for " + this.name) - return new Service.OccupancySensor(name); - } else if( this.isLight || this.mappings.pct || this.mappings.hue || this.mappings.rgb ) { - this.log(" lightbulb service for " + this.name) - return new Service.Lightbulb(name); - } else if( this.mappings.temperature ) { - this.log(" temperature sensor service for " + this.name) - return new Service.TemperatureSensor(name); - } else if( this.mappings.humidity ) { - this.log(" humidity sensor service for " + this.name) - return new Service.HumiditySensor(name); - } else if( this.mappings.airquality ) { - this.log(" humidity sensor service for " + this.name) - return new Service.AirQualitySensor(name); - } - - this.log(" switch service for " + this.name) - return new Service.Switch(name); - }, - - identify: function(callback) { - this.log('['+this.name+'] identify requested!'); - if( match = this.PossibleSets.match(/[\^ ]toggle\b/) ) { - this.command( 'identify' ); - } - callback(); - }, - - getServices: function() { - this.log("creating services for " + this.name) - - this.log(" information service for " + this.name) - var informationService = new Service.AccessoryInformation(); - - informationService - .setCharacteristic(Characteristic.Manufacturer, "FHEM:"+this.type) - .setCharacteristic(Characteristic.Model, "FHEM:"+this.model ? this.model : '') - .setCharacteristic(Characteristic.SerialNumber, this.serial ? this.serial : ''); - - var controlService = this.createDeviceService(); - - var that = this; - if( this.mappings.onOff ) { - this.log(" power characteristic for " + this.name) - - var characteristic = controlService.getCharacteristic(Characteristic.On); - - FHEM_subscribe(characteristic, that.mappings.onOff.informId, that); - if( FHEM_cached[that.mappings.onOff.informId] != undefined ) - characteristic.value = FHEM_cached[that.mappings.onOff.informId]; - - characteristic - .on('set', function(value, callback, context) { - if( context !== 'fromFhem' ) - that.command( 'set', value == 0 ? that.mappings.onOff.cmdOff : that.mappings.onOff.cmdOn ); - callback(); - }.bind(this) ) - .on('get', function(callback) { - that.query(that.mappings.onOff.reading, callback); - }.bind(this) ); - } - - if( this.mappings.pct ) { - this.log(" brightness characteristic for " + this.name) - - var characteristic = controlService.addCharacteristic(Characteristic.Brightness); - - FHEM_subscribe(characteristic, that.mappings.pct.informId, that); - if( FHEM_cached[that.mappings.pct.informId] != undefined ) - characteristic.value = FHEM_cached[that.mappings.pct.informId]; - - characteristic - .on('set', function(value, callback, context) { - if( context !== 'fromFhem' ) - that.command('pct', value); - callback(); - }.bind(this) ) - .on('get', function(callback) { - that.query(that.mappings.pct.reading, callback); - }.bind(this) ); - - } else if( this.hasDim ) { - this.log(" fake brightness characteristic for " + this.name) - - var characteristic = controlService.addCharacteristic(Characteristic.Brightness); - - FHEM_subscribe(characteristic, that.name+'-pct', that); - characteristic.value = 0; - characteristic.maximumValue = this.pctMax; - - characteristic - .on('set', function(value, callback, context) { - if( context !== 'fromFhem' ) - that.delayed('dim', value); - callback(); - }.bind(this) ) - .on('get', function(callback) { - that.query('pct', callback); - }.bind(this) ); - - } - - if( that.mappings.hue ) { - this.log(" hue characteristic for " + this.name) - - var characteristic = controlService.addCharacteristic(Characteristic.Hue); - - FHEM_subscribe(characteristic, that.mappings.hue.informId, that); - if( FHEM_cached[that.mappings.hue.informId] != undefined ) - characteristic.value = FHEM_cached[that.mappings.hue.informId]; - - characteristic - .on('set', function(value, callback, context) { - if( context !== 'fromFhem' ) - that.command('hue', value); - callback(); - }.bind(this) ) - .on('get', function(callback) { - that.query(that.mappings.hue.reading, callback); - }.bind(this) ); - - } else if( this.mappings.rgb ) { - this.log(" fake hue characteristic for " + this.name) - - var characteristic = controlService.addCharacteristic(Characteristic.Hue); - - FHEM_subscribe(characteristic, that.name+'-hue', that); - FHEM_subscribe(characteristic, that.mappings.rgb.informId, that); - characteristic.value = 0; - - characteristic - .on('set', function(value, callback, context) { - if( context !== 'fromFhem' ) - that.command('H-rgb', value); - callback(); - }.bind(this) ) - .on('get', function(callback) { - that.query('hue', callback); - }.bind(this) ); - - if( !this.mappings.sat ) { - this.log(" fake saturation characteristic for " + this.name) - - var characteristic = controlService.addCharacteristic(Characteristic.Saturation); - - FHEM_subscribe(characteristic, that.name+'-sat', that); - characteristic.value = 100; - - characteristic - .on('set', function(value, callback, context) { - if( context !== 'fromFhem' ) - that.command('S-rgb', value); - callback(); - }.bind(this) ) - .on('get', function(callback) { - that.query('sat', callback); - }.bind(this) ); - } - - if( !this.mappings.pct ) { - this.log(" fake brightness characteristic for " + this.name) - - var characteristic = controlService.addCharacteristic(Characteristic.Brightness); - - FHEM_subscribe(characteristic, that.name+'-bri', that); - characteristic.value = 0; - - characteristic - .on('set', function(value, callback, context) { - if( context !== 'fromFhem' ) - that.command('B-rgb', value); - callback(); - }.bind(this) ) - .on('get', function(callback) { - that.query('bri', callback); - }.bind(this) ); - } - - } - - if( this.mappings.sat ) { - this.log(" saturation characteristic for " + this.name) - - var characteristic = controlService.addCharacteristic(Characteristic.Saturation); - - FHEM_subscribe(characteristic, that.mappings.sat.informId, that); - if( FHEM_cached[that.mappings.sat.informId] != undefined ) - characteristic.value = FHEM_cached[that.mappings.sat.informId]; - - characteristic - .on('set', function(value, callback, context) { - if( context !== 'fromFhem' ) - that.command('sat', value); - callback(); - }.bind(this) ) - .on('get', function(callback) { - that.query(that.mappings.sat.reading, callback); - }.bind(this) ); - } - - if( this.mappings.volume ) { - this.log(" custom volume characteristic for " + this.name) - - var characteristic = new Characteristic('Volume', '00000027-0000-1000-8000-0026BB765291'); // FIXME!!! - controlService.addCharacteristic(characteristic); - - if( !that.mappings.volume.nocache ) { - FHEM_subscribe(characteristic, that.mappings.volume.informId, that); - characteristic.value = FHEM_cached[that.mappings.volume.informId]; - } else { - characteristic.value = 10; - } - - characteristic.format = 'uint8'; - characteristic.unit = 'percentage'; - characteristic.maximumValue = 100; - characteristic.minimumValue = 0; - characteristic.stepValue = 1; - characteristic.readable = true; - characteristic.writable = true; - characteristic.supportsEventNotification = true; - - characteristic - .on('set', function(value, callback, context) { - if( context !== 'fromFhem' ) - that.delayed('volume', value); - callback(); - }.bind(this) ) - .on('get', function(callback) { - that.query(that.mappings.volume.reading, callback); - }.bind(this) ); - - } - - if( this.mappings.blind ) { - this.log(" current position characteristic for " + this.name) - - var characteristic = controlService.getCharacteristic(Characteristic.CurrentPosition); - - FHEM_subscribe(characteristic, that.mappings.blind.informId, that); - characteristic.value = FHEM_cached[that.mappings.blind.informId]; - - characteristic - .on('get', function(callback) { - that.query(that.mappings.blind.reading, callback); - }.bind(this) ); - - - this.log(" target position characteristic for " + this.name) - - var characteristic = controlService.getCharacteristic(Characteristic.TargetPosition); - - characteristic.value = FHEM_cached[that.mappings.blind.informId]; - - characteristic - .on('set', function(value, callback, context) { - if( context !== 'fromFhem' ) - that.delayed('targetPosition', value, 1500); - callback(); - }.bind(this) ) - .on('get', function(callback) { - that.query(that.mappings.blind.reading, callback); - }.bind(this) ); - - - this.log(" position state characteristic for " + this.name) - - var characteristic = controlService.getCharacteristic(Characteristic.PositionState); - - if( that.mappings.motor ) - FHEM_subscribe(characteristic, that.mappings.motor.informId, that); - characteristic.value = that.mappings.motor?FHEM_cached[that.mappings.motor.informId]:Characteristic.PositionState.STOPPED; - - characteristic - .on('get', function(callback) { - if( that.mappings.motor ) - that.query(that.mappings.motor.reading, callback); - }.bind(this) ); - - } - - if( this.mappings.window ) { - this.log(" current position characteristic for " + this.name) - - var characteristic = controlService.getCharacteristic(Characteristic.CurrentPosition); - - FHEM_subscribe(characteristic, that.name+'-state', that); - FHEM_subscribe(characteristic, that.mappings.window.informId, that); - characteristic.value = FHEM_cached[that.mappings.window.informId]; - - characteristic - .on('get', function(callback) { - that.query(that.mappings.window.reading, callback); - }.bind(this) ); - - - this.log(" target position characteristic for " + this.name) - - var characteristic = controlService.getCharacteristic(Characteristic.TargetPosition); - - characteristic.value = FHEM_cached[that.mappings.window.informId]; - - characteristic - .on('set', function(value, callback, context) { - if( context !== 'fromFhem' ) - that.delayed('targetPosition', value, 1500); - callback(); - }.bind(this) ) - .on('get', function(callback) { - that.query(that.mappings.window.reading, callback); - }.bind(this) ); - - - this.log(" position state characteristic for " + this.name) - - var characteristic = controlService.getCharacteristic(Characteristic.PositionState); - - if( that.mappings.direction ) - FHEM_subscribe(characteristic, that.mappings.direction.informId, that); - characteristic.value = that.mappings.direction?FHEM_cached[that.mappings.direction.informId]:Characteristic.PositionState.STOPPED; - - characteristic - .on('get', function(callback) { - if( that.mappings.direction ) - that.query(that.mappings.direction.reading, callback); - }.bind(this) ); - - } - - if( this.mappings.garage ) { - this.log(" current door state characteristic for " + this.name) - - var characteristic = controlService.getCharacteristic(Characteristic.CurrentDoorState); - - characteristic.value = Characteristic.CurrentDoorState.STOPPED; - - characteristic - .on('get', function(callback) { - callback(undefined, Characteristic.CurrentDoorState.STOPPED); - }.bind(this) ); - - - this.log(" target door state characteristic for " + this.name) - - var characteristic = controlService.getCharacteristic(Characteristic.TargetDoorState); - - characteristic.value = 1; - - characteristic - .on('set', function(value, callback, context) { - if( context !== 'fromFhem' ) - that.command( 'set', value == 0 ? that.mappings.garage.cmdOpen : that.mappings.garage.cmdClose ); - callback(); - }.bind(this) ) - .on('get', function(callback) { - callback(undefined,0); - }.bind(this) ); - - - this.log(" obstruction detected characteristic for " + this.name) - - var characteristic = controlService.getCharacteristic(Characteristic.ObstructionDetected); - - //FHEM_subscribe(characteristic, that.mappings.direction.informId, that); - characteristic.value = 0; - - characteristic - .on('get', function(callback) { - callback(undefined,1); - }.bind(this) ); - - } - - if( this.mappings.temperature ) { - this.log(" temperature characteristic for " + this.name) - - var characteristic = controlService.getCharacteristic(Characteristic.CurrentTemperature) - || controlService.addCharacteristic(Characteristic.CurrentTemperature); - - FHEM_subscribe(characteristic, that.mappings.temperature.informId, that); - characteristic.value = FHEM_cached[that.mappings.temperature.informId]; - - characteristic - .on('get', function(callback) { - that.query(that.mappings.temperature.reading, callback); - }.bind(this) ); - - } - - if( this.mappings.humidity ) { - this.log(" humidity characteristic for " + this.name) - - var characteristic = controlService.getCharacteristic(Characteristic.CurrentRelativeHumidity) - || controlService.addCharacteristic(Characteristic.CurrentRelativeHumidity); - - FHEM_subscribe(characteristic, that.mappings.humidity.informId, that); - characteristic.value = FHEM_cached[that.mappings.humidity.informId]; - - characteristic - .on('get', function(callback) { - that.query(that.mappings.humidity.reading, callback); - }.bind(this) ); - - } - - if( this.mappings.airquality ) { - this.log(" air quality characteristic for " + this.name) - - var characteristic = controlService.getCharacteristic(Characteristic.AirQuality) - || controlService.addCharacteristic(Characteristic.AirQuality); - - FHEM_subscribe(characteristic, that.mappings.airquality.informId, that); - characteristic.value = FHEM_cached[that.mappings.airquality.informId]; - - characteristic - .on('get', function(callback) { - that.query(that.mappings.airquality.reading, callback); - }.bind(this) ); - } - - - //FIXME: parse range and set designedMinValue & designedMaxValue & designedMinStep - if( this.mappings.thermostat ) { - this.log(" target temperature characteristic for " + this.name) - - var characteristic = controlService.getCharacteristic(Characteristic.TargetTemperature); - - FHEM_subscribe(characteristic, that.mappings.thermostat.informId, that); - characteristic.value = FHEM_cached[that.mappings.thermostat.informId]; - - characteristic - .on('set', function(value, callback, context) { - if( context !== 'fromFhem' ) - that.delayed('targetTemperature', value, 1500); - callback(); - }.bind(this) ) - .on('get', function(callback) { - that.query(that.mappings.thermostat.reading, callback); - }.bind(this) ); - - } - - if( this.mappings.contact ) { - this.log(" contact sensor characteristic for " + this.name) - - var characteristic = controlService.getCharacteristic(Characteristic.ContactSensorState); - - FHEM_subscribe(characteristic, that.mappings.contact.informId, that); - characteristic.value = FHEM_cached[that.mappings.contact.informId]; - - characteristic - .on('get', function(callback) { - that.query(that.mappings.contact.reading, callback); - }.bind(this) ); - - } - - if( this.mappings.occupancy ) { - this.log(" occupancy detected characteristic for " + this.name) - - var characteristic = controlService.getCharacteristic(Characteristic.OccupancyDetected); - - FHEM_subscribe(characteristic, that.mappings.occupancy.informId, that); - characteristic.value = FHEM_cached[that.mappings.occupancy.informId]; - - characteristic - .on('get', function(callback) { - that.query(that.mappings.occupancy.reading, callback); - }.bind(this) ); - - } - - return [informationService, controlService]; - } - -}; - -//module.exports.accessory = FHEMAccessory; -module.exports.platform = FHEMPlatform; - - - -//http server for debugging -var http = require('http'); - -const FHEMdebug_PORT=8081; - -function FHEMdebug_handleRequest(request, response){ - //console.log( request ); - - if( request.url == "/cached" ) { - response.write( "home

" ); - if( FHEM_lastEventTime ) - response.write( "FHEM_lastEventTime: "+ new Date(FHEM_lastEventTime) +"

" ); - response.end( "cached: " + util.inspect(FHEM_cached).replace(/\n/g, '
') ); - - } else if( request.url == "/subscriptions" ) { - response.write( "home

" ); - response.end( "subscriptions: " + util.inspect(FHEM_subscriptions, {depth: 4}).replace(/\n/g, '
') ); - - } else - response.end( "cached
subscriptions" ); -} - -var FHEMdebug_server = http.createServer( FHEMdebug_handleRequest ); - -FHEMdebug_server.on('error', function (e) { - console.log("Server error: " + e); -}); - -//Lets start our server -FHEMdebug_server.listen(FHEMdebug_PORT, function(){ - console.log("Server listening on: http://:%s", FHEMdebug_PORT); -}); - diff --git a/platforms/FibaroHC2.js b/platforms/FibaroHC2.js deleted file mode 100644 index 57c880c..0000000 --- a/platforms/FibaroHC2.js +++ /dev/null @@ -1,253 +0,0 @@ -// Fibaro Home Center 2 Platform Shim for HomeBridge -// -// Remember to add platform to config.json. Example: -// "platforms": [ -// { -// "platform": "FibaroHC2", -// "name": "FibaroHC2", -// "host": "PUT IP ADDRESS OF YOUR HC2 HERE", -// "username": "PUT USERNAME OF YOUR HC2 HERE", -// "password": "PUT PASSWORD OF YOUR HC2 HERE" -// } -// ], -// -// 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 request = require("request"); - -function FibaroHC2Platform(log, config){ - this.log = log; - this.host = config["host"]; - this.username = config["username"]; - this.password = config["password"]; - this.auth = "Basic " + new Buffer(this.username + ":" + this.password).toString("base64"); - this.url = "http://"+this.host+"/api/devices"; - - startPollingUpdate( this ); -} - -FibaroHC2Platform.prototype = { - accessories: function(callback) { - this.log("Fetching Fibaro Home Center devices..."); - - var that = this; - var foundAccessories = []; - - request.get({ - url: this.url, - headers : { - "Authorization" : this.auth - }, - json: true - }, function(err, response, json) { - if (!err && response.statusCode == 200) { - if (json != undefined) { - json.map(function(s) { - that.log("Found: " + s.type); - if (s.visible == true) { - var accessory = null; - if (s.type == "com.fibaro.multilevelSwitch") - accessory = new FibaroAccessory(new Service.Lightbulb(s.name), [Characteristic.On, Characteristic.Brightness]); - else if (s.type == "com.fibaro.FGRM222" || s.type == "com.fibaro.FGR221") - accessory = new FibaroAccessory(new Service.WindowCovering(s.name), [Characteristic.CurrentPosition, Characteristic.TargetPosition, Characteristic.PositionState]); - else if (s.type == "com.fibaro.binarySwitch" || s.type == "com.fibaro.developer.bxs.virtualBinarySwitch") - accessory = new FibaroAccessory(new Service.Switch(s.name), [Characteristic.On]); - else if (s.type == "com.fibaro.FGMS001" || s.type == "com.fibaro.motionSensor") - accessory = new FibaroAccessory(new Service.MotionSensor(s.name), [Characteristic.MotionDetected]); - else if (s.type == "com.fibaro.temperatureSensor") - accessory = new FibaroAccessory(new Service.TemperatureSensor(s.name), [Characteristic.CurrentTemperature]); - else if (s.type == "com.fibaro.doorSensor") - accessory = new FibaroAccessory(new Service.ContactSensor(s.name), [Characteristic.ContactSensorState]); - else if (s.type == "com.fibaro.lightSensor") - accessory = new FibaroAccessory(new Service.LightSensor(s.name), [Characteristic.CurrentAmbientLightLevel]); - else if (s.type == "com.fibaro.FGWP101") - accessory = new FibaroAccessory(new Service.Outlet(s.name), [Characteristic.On, Characteristic.OutletInUse]); - if (accessory != null) { - accessory.getServices = function() { - return that.getServices(accessory); - }; - accessory.platform = that; - accessory.remoteAccessory = s; - accessory.id = s.id; - accessory.name = s.name; - accessory.model = s.type; - accessory.manufacturer = "Fibaro"; - accessory.serialNumber = ""; - foundAccessories.push(accessory); - } - } - }) - } - callback(foundAccessories); - } else { - that.log("There was a problem connecting with FibaroHC2."); - } - }); - - }, - command: function(c,value, that) { - var url = "http://"+this.host+"/api/devices/"+that.id+"/action/"+c; - var body = value != undefined ? JSON.stringify({ - "args": [ value ] - }) : null; - var method = "post"; - request({ - url: url, - body: body, - method: method, - headers: { - "Authorization" : this.auth - }, - }, function(err, response) { - if (err) { - that.platform.log("There was a problem sending command " + c + " to" + that.name); - that.platform.log(url); - } else { - that.platform.log(that.name + " sent command " + c); - that.platform.log(url); - } - }); - }, - getAccessoryValue: function(callback, returnBoolean, homebridgeAccessory, powerValue) { - var url = "http://"+homebridgeAccessory.platform.host+"/api/devices/"+homebridgeAccessory.id+"/properties/"; - if (powerValue) - url = url + "power"; - else - url = url + "value"; - - request.get({ - headers : { - "Authorization" : homebridgeAccessory.platform.auth - }, - json: true, - url: url - }, function(err, response, json) { - homebridgeAccessory.platform.log(url); - if (!err && response.statusCode == 200) { - if (powerValue) { - callback(undefined, parseFloat(json.value) > 1.0 ? true : false); - } else if (returnBoolean) - callback(undefined, json.value == 0 ? 0 : 1); - else - callback(undefined, json.value); - } else { - homebridgeAccessory.platform.log("There was a problem getting value from" + homebridgeAccessory.id); - } - }) - }, - getInformationService: function(homebridgeAccessory) { - var informationService = new Service.AccessoryInformation(); - informationService - .setCharacteristic(Characteristic.Name, homebridgeAccessory.name) - .setCharacteristic(Characteristic.Manufacturer, homebridgeAccessory.manufacturer) - .setCharacteristic(Characteristic.Model, homebridgeAccessory.model) - .setCharacteristic(Characteristic.SerialNumber, homebridgeAccessory.serialNumber); - return informationService; - }, - bindCharacteristicEvents: function(characteristic, homebridgeAccessory) { - var onOff = characteristic.props.format == "bool" ? true : false; - var readOnly = true; - for (var i = 0; i < characteristic.props.perms.length; i++) - if (characteristic.props.perms[i] == "pw") - readOnly = false; - var powerValue = (characteristic.UUID == "00000026-0000-1000-8000-0026BB765291") ? true : false; - subscribeUpdate(characteristic, homebridgeAccessory, onOff); - if (!readOnly) { - characteristic - .on('set', function(value, callback, context) { - if( context !== 'fromFibaro' ) { - if (onOff) - homebridgeAccessory.platform.command(value == 0 ? "turnOff": "turnOn", null, homebridgeAccessory); - else - homebridgeAccessory.platform.command("setValue", value, homebridgeAccessory); - } - callback(); - }.bind(this) ); - } - characteristic - .on('get', function(callback) { - homebridgeAccessory.platform.getAccessoryValue(callback, onOff, homebridgeAccessory, powerValue); - }.bind(this) ); - }, - getServices: function(homebridgeAccessory) { - var informationService = homebridgeAccessory.platform.getInformationService(homebridgeAccessory); - for (var i=0; i < homebridgeAccessory.characteristics.length; i++) { - var characteristic = homebridgeAccessory.controlService.getCharacteristic(homebridgeAccessory.characteristics[i]); - if (characteristic == undefined) - characteristic = homebridgeAccessory.controlService.addCharacteristic(homebridgeAccessory.characteristics[i]); - homebridgeAccessory.platform.bindCharacteristicEvents(characteristic, homebridgeAccessory); - } - - return [informationService, homebridgeAccessory.controlService]; - } -} - -function FibaroAccessory(controlService, characteristics) { - this.controlService = controlService; - this.characteristics = characteristics; -} - -var lastPoll=0; -var pollingUpdateRunning = false; - -function startPollingUpdate( platform ) -{ - if( pollingUpdateRunning ) - return; - pollingUpdateRunning = true; - - var updateUrl = "http://"+platform.host+"/api/refreshStates?last="+lastPoll; - - request.get({ - url: updateUrl, - headers : { - "Authorization" : platform.auth - }, - json: true - }, function(err, response, json) { - if (!err && response.statusCode == 200) { - if (json != undefined) { - lastPoll = json.last; - if (json.changes != undefined) { - json.changes.map(function(s) { - if (s.value != undefined) { - - var value=parseInt(s.value); - if (isNaN(value)) - value=(s.value === "true"); - for (i=0;i 1.0 ? true : false, undefined, 'fromFibaro'); - } else if ((subscription.onOff && typeof(value) == "boolean") || !subscription.onOff) - subscription.characteristic.setValue(value, undefined, 'fromFibaro'); - else - subscription.characteristic.setValue(value == 0 ? false : true, undefined, 'fromFibaro'); - } - } - } - }) - } - } - } else { - platform.log("There was a problem connecting with FibaroHC2."); - } - pollingUpdateRunning = false; - setTimeout( function(){startPollingUpdate(platform)}, 2000 ); - }); - -} - -var updateSubscriptions = []; -function subscribeUpdate(characteristic, accessory, onOff) -{ -// TODO: optimized management of updateSubscription data structure (no array with sequential access) - updateSubscriptions.push({ 'id': accessory.id, 'characteristic': characteristic, 'accessory': accessory, 'onOff': onOff }); -} - -module.exports.platform = FibaroHC2Platform; diff --git a/platforms/HomeAssistant.js b/platforms/HomeAssistant.js deleted file mode 100644 index 1bfdc6c..0000000 --- a/platforms/HomeAssistant.js +++ /dev/null @@ -1,542 +0,0 @@ -// Home Assistant -// -// Current Support: lights -// -// This is a shim to publish lights maintained by Home Assistant. -// Home Assistant is an open-source home automation platform. -// URL: http://home-assistant.io -// GitHub: https://github.com/balloob/home-assistant -// -// HA accessories supported: Lights, Switches, Media Players, Scenes. -// -// Optional Devices - Edit the supported_types key in the config to pick which -// of the 4 types you would like to expose to HomeKit from -// Home Assistant. light, switch, media_player, scene. -// -// -// Scene Support -// -// You can optionally import your Home Assistant scenes. These will appear to -// HomeKit as switches. You can simply say "turn on party time". In some cases -// scenes names are already rerved in HomeKit...like "Good Morning" and -// "Good Night". You will be able to just say "Good Morning" or "Good Night" to -// have these triggered. -// -// You might want to play with the wording to figure out what ends up working well -// for your scene names. It's also important to not populate any actual HomeKit -// scenes with the same names, as Siri will pick these instead of your Home -// Assistant scenes. -// -// -// -// Media Player Support -// -// Media players on your Home Assistant will be added to your HomeKit as a light. -// While this seems like a hack at first, it's actually quite useful. You can -// turn them on, off, and set their volume (as a function of brightness). -// -// There are some rules to know about how on/off treats your media player. If -// your media player supports play/pause, then turning them on and off via -// HomeKit will play and pause them. If they do not support play/pause but then -// support on/off they will be turned on and then off. -// -// HomeKit does not have a characteristic of Volume or a Speaker type. So we are -// using the light device type here. So to turn your speaker up and down, you -// will need to use the same language you use to set the brighness of a light. -// You can play around with language to see what fits best. -// -// -// -// Examples -// -// Dim the Kitchen Speaker to 40% - sets volume to 40% -// Dim the the Kitchen Speaker 10% - lowers the volume by 10% -// Set the brightness of the Kitchen Speaker to 40% -// -// Remember to add platform to config.json. Example: -// "platforms": [ -// { -// "platform": "HomeAssistant", -// "name": "HomeAssistant", -// "host": "http://192.168.1.50:8123", -// "password": "xxx", -// "supported_types": ["light", "switch", "media_player", "scene"] -// } -// ] -// -// 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 url = require('url') -var request = require("request"); - -var communicationError = new Error('Can not communicate with Home Assistant.') - -function HomeAssistantPlatform(log, config){ - - // auth info - this.host = config["host"]; - this.password = config["password"]; - this.supportedTypes = config["supported_types"]; - - this.log = log; -} - -HomeAssistantPlatform.prototype = { - _request: function(method, path, options, callback) { - var self = this - var requestURL = this.host + '/api' + path - options = options || {} - options.query = options.query || {} - - var reqOpts = { - url: url.parse(requestURL), - method: method || 'GET', - qs: options.query, - body: JSON.stringify(options.body), - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'x-ha-access': this.password - } - } - - request(reqOpts, function onResponse(error, response, body) { - if (error) { - callback(error, response) - return - } - - if (response.statusCode === 401) { - callback(new Error('You are not authenticated'), response) - return - } - - json = JSON.parse(body) - callback(error, response, json) - }) - - }, - fetchState: function(entity_id, callback){ - this._request('GET', '/states/' + entity_id, {}, function(error, response, data){ - if (error) { - callback(null) - }else{ - callback(data) - } - }) - }, - callService: function(domain, service, service_data, callback){ - var options = {} - options.body = service_data - - this._request('POST', '/services/' + domain + '/' + service, options, function(error, response, data){ - if (error) { - callback(null) - }else{ - callback(data) - } - }) - }, - accessories: function(callback) { - this.log("Fetching HomeAssistant devices."); - - var that = this; - var foundAccessories = []; - - this._request('GET', '/states', {}, function(error, response, data){ - - for (var i = 0; i < data.length; i++) { - entity = data[i] - entity_type = entity.entity_id.split('.')[0] - - // ignore devices that are not in the list of supported types - if (that.supportedTypes.indexOf(entity_type) == -1) { - continue; - } - - // ignore hidden devices - if (entity.attributes && entity.attributes.hidden) { - continue; - } - - var accessory = null - - if (entity_type == 'light') { - accessory = new HomeAssistantLight(that.log, entity, that) - }else if (entity_type == 'switch'){ - console.log(JSON.stringify(entity)) - console.log(""); - console.log(""); - accessory = new HomeAssistantSwitch(that.log, entity, that) - }else if (entity_type == 'scene'){ - accessory = new HomeAssistantSwitch(that.log, entity, that, 'scene') - }else if (entity_type == 'media_player' && entity.attributes && entity.attributes.supported_media_commands){ - accessory = new HomeAssistantMediaPlayer(that.log, entity, that) - } - - if (accessory) { - foundAccessories.push(accessory) - } - } - - callback(foundAccessories) - }) - - } -} - -function HomeAssistantLight(log, data, client) { - // device info - this.domain = "light" - this.data = data - this.entity_id = data.entity_id - if (data.attributes && data.attributes.friendly_name) { - this.name = data.attributes.friendly_name - }else{ - this.name = data.entity_id.split('.').pop().replace(/_/g, ' ') - } - - this.client = client - this.log = log; -} - -HomeAssistantLight.prototype = { - getPowerState: function(callback){ - this.log("fetching power state for: " + this.name); - - this.client.fetchState(this.entity_id, function(data){ - if (data) { - powerState = data.state == 'on' - callback(null, powerState) - }else{ - callback(communicationError) - } - }.bind(this)) - }, - getBrightness: function(callback){ - this.log("fetching brightness for: " + this.name); - - this.client.fetchState(this.entity_id, function(data){ - if (data && data.attributes) { - brightness = ((data.attributes.brightness || 0) / 255)*100 - callback(null, brightness) - }else{ - callback(communicationError) - } - }.bind(this)) - }, - setPowerState: function(powerOn, callback) { - var that = this; - var service_data = {} - service_data.entity_id = this.entity_id - - if (powerOn) { - this.log("Setting power state on the '"+this.name+"' to on"); - - this.client.callService(this.domain, 'turn_on', service_data, function(data){ - if (data) { - that.log("Successfully set power state on the '"+that.name+"' to on"); - callback() - }else{ - callback(communicationError) - } - }.bind(this)) - }else{ - this.log("Setting power state on the '"+this.name+"' to off"); - - this.client.callService(this.domain, 'turn_off', service_data, function(data){ - if (data) { - that.log("Successfully set power state on the '"+that.name+"' to off"); - callback() - }else{ - callback(communicationError) - } - }.bind(this)) - } - }, - setBrightness: function(level, callback) { - var that = this; - var service_data = {} - service_data.entity_id = this.entity_id - - service_data.brightness = 255*(level/100.0) - - this.log("Setting brightness on the '"+this.name+"' to " + level); - - this.client.callService(this.domain, 'turn_on', service_data, function(data){ - if (data) { - that.log("Successfully set brightness on the '"+that.name+"' to " + level); - callback() - }else{ - callback(communicationError) - } - }.bind(this)) - }, - getServices: function() { - var lightbulbService = new Service.Lightbulb(); - var informationService = new Service.AccessoryInformation(); - - informationService - .setCharacteristic(Characteristic.Manufacturer, "Home Assistant") - .setCharacteristic(Characteristic.Model, "Light") - .setCharacteristic(Characteristic.SerialNumber, "xxx"); - - lightbulbService - .getCharacteristic(Characteristic.On) - .on('get', this.getPowerState.bind(this)) - .on('set', this.setPowerState.bind(this)); - - lightbulbService - .addCharacteristic(Characteristic.Brightness) - .on('get', this.getBrightness.bind(this)) - .on('set', this.setBrightness.bind(this)); - - return [informationService, lightbulbService]; - } - -} - -function HomeAssistantMediaPlayer(log, data, client) { - var SUPPORT_PAUSE = 1 - var SUPPORT_SEEK = 2 - var SUPPORT_VOLUME_SET = 4 - var SUPPORT_VOLUME_MUTE = 8 - var SUPPORT_PREVIOUS_TRACK = 16 - var SUPPORT_NEXT_TRACK = 32 - var SUPPORT_YOUTUBE = 64 - var SUPPORT_TURN_ON = 128 - var SUPPORT_TURN_OFF = 256 - - // device info - this.domain = "media_player" - this.data = data - this.entity_id = data.entity_id - this.supportsVolume = false - this.supportedMediaCommands = data.attributes.supported_media_commands - - if (data.attributes && data.attributes.friendly_name) { - this.name = data.attributes.friendly_name - }else{ - this.name = data.entity_id.split('.').pop().replace(/_/g, ' ') - } - - if ((this.supportedMediaCommands | SUPPORT_PAUSE) == this.supportedMediaCommands) { - this.onState = "playing" - this.offState = "paused" - this.onService = "media_play" - this.offService = "media_pause" - }else if ((this.supportedMediaCommands | SUPPORT_TURN_ON) == this.supportedMediaCommands && (this.supportedMediaCommands | SUPPORT_TURN_OFF) == this.supportedMediaCommands) { - this.onState = "on" - this.offState = "off" - this.onService = "turn_on" - this.offService = "turn_off" - } - - if ((this.supportedMediaCommands | SUPPORT_VOLUME_SET) == this.supportedMediaCommands) { - this.supportsVolume = true - } - - this.client = client - this.log = log; -} - -HomeAssistantMediaPlayer.prototype = { - getPowerState: function(callback){ - this.log("fetching power state for: " + this.name); - - this.client.fetchState(this.entity_id, function(data){ - if (data) { - powerState = data.state == this.onState - callback(null, powerState) - }else{ - callback(communicationError) - } - }.bind(this)) - }, - getVolume: function(callback){ - this.log("fetching volume for: " + this.name); - that = this - this.client.fetchState(this.entity_id, function(data){ - if (data && data.attributes) { - that.log(JSON.stringify(data.attributes)) - level = data.attributes.volume_level ? data.attributes.volume_level*100 : 0 - callback(null, level) - }else{ - callback(communicationError) - } - }.bind(this)) - }, - setPowerState: function(powerOn, callback) { - var that = this; - var service_data = {} - service_data.entity_id = this.entity_id - - if (powerOn) { - this.log("Setting power state on the '"+this.name+"' to on"); - - this.client.callService(this.domain, this.onService, service_data, function(data){ - if (data) { - that.log("Successfully set power state on the '"+that.name+"' to on"); - callback() - }else{ - callback(communicationError) - } - }.bind(this)) - }else{ - this.log("Setting power state on the '"+this.name+"' to off"); - - this.client.callService(this.domain, this.offService, service_data, function(data){ - if (data) { - that.log("Successfully set power state on the '"+that.name+"' to off"); - callback() - }else{ - callback(communicationError) - } - }.bind(this)) - } - }, - setVolume: function(level, callback) { - var that = this; - var service_data = {} - service_data.entity_id = this.entity_id - - service_data.volume_level = level/100.0 - - this.log("Setting volume on the '"+this.name+"' to " + level); - - this.client.callService(this.domain, 'volume_set', service_data, function(data){ - if (data) { - that.log("Successfully set volume on the '"+that.name+"' to " + level); - callback() - }else{ - callback(communicationError) - } - }.bind(this)) - }, - getServices: function() { - var lightbulbService = new Service.Lightbulb(); - var informationService = new Service.AccessoryInformation(); - - informationService - .setCharacteristic(Characteristic.Manufacturer, "Home Assistant") - .setCharacteristic(Characteristic.Model, "Media Player") - .setCharacteristic(Characteristic.SerialNumber, "xxx"); - - lightbulbService - .getCharacteristic(Characteristic.On) - .on('get', this.getPowerState.bind(this)) - .on('set', this.setPowerState.bind(this)); - - - if (this.supportsVolume) { - lightbulbService - .addCharacteristic(Characteristic.Brightness) - .on('get', this.getVolume.bind(this)) - .on('set', this.setVolume.bind(this)); - } - - return [informationService, lightbulbService]; - } - -} - - -function HomeAssistantSwitch(log, data, client, type) { - // device info - this.domain = type || "switch" - this.data = data - this.entity_id = data.entity_id - if (data.attributes && data.attributes.friendly_name) { - this.name = data.attributes.friendly_name - }else{ - this.name = data.entity_id.split('.').pop().replace(/_/g, ' ') - } - - this.client = client - this.log = log; -} - -HomeAssistantSwitch.prototype = { - getPowerState: function(callback){ - this.log("fetching power state for: " + this.name); - - this.client.fetchState(this.entity_id, function(data){ - if (data) { - powerState = data.state == 'on' - callback(null, powerState) - }else{ - callback(communicationError) - } - }.bind(this)) - }, - setPowerState: function(powerOn, callback) { - var that = this; - var service_data = {} - service_data.entity_id = this.entity_id - - if (powerOn) { - this.log("Setting power state on the '"+this.name+"' to on"); - - this.client.callService(this.domain, 'turn_on', service_data, function(data){ - if (data) { - that.log("Successfully set power state on the '"+that.name+"' to on"); - callback() - }else{ - callback(communicationError) - } - }.bind(this)) - }else{ - this.log("Setting power state on the '"+this.name+"' to off"); - - this.client.callService(this.domain, 'turn_off', service_data, function(data){ - if (data) { - that.log("Successfully set power state on the '"+that.name+"' to off"); - callback() - }else{ - callback(communicationError) - } - }.bind(this)) - } - }, - getServices: function() { - var switchService = new Service.Switch(); - var informationService = new Service.AccessoryInformation(); - var model; - - switch (this.domain) { - case "scene": - model = "Scene" - break; - default: - model = "Switch" - } - - informationService - .setCharacteristic(Characteristic.Manufacturer, "Home Assistant") - .setCharacteristic(Characteristic.Model, model) - .setCharacteristic(Characteristic.SerialNumber, "xxx"); - - if (this.domain == 'switch') { - switchService - .getCharacteristic(Characteristic.On) - .on('get', this.getPowerState.bind(this)) - .on('set', this.setPowerState.bind(this)); - - }else{ - switchService - .getCharacteristic(Characteristic.On) - .on('set', this.setPowerState.bind(this)); - } - - return [informationService, switchService]; - } - -} - -module.exports.accessory = HomeAssistantLight; -module.exports.accessory = HomeAssistantMediaPlayer; -module.exports.accessory = HomeAssistantSwitch; -module.exports.platform = HomeAssistantPlatform; diff --git a/platforms/HomeSeer.js b/platforms/HomeSeer.js deleted file mode 100644 index 7c899f8..0000000 --- a/platforms/HomeSeer.js +++ /dev/null @@ -1,1066 +0,0 @@ -'use strict'; - -// -// HomeSeer Platform Shim for HomeBridge by Jean-Michel Joudrier - (stipus at stipus dot com) -// V0.1 - 2015/10/07 -// - Initial version -// V0.2 - 2015/10/10 -// - Occupancy sensor fix -// V0.3 - 2015/10/11 -// - Added TemperatureUnit=F|C option to temperature sensors -// - Added negative temperature support to temperature sensors -// V0.4 - 2015/10/12 -// - Added thermostat support -// V0.5 - 2015/10/12 -// - Added Humidity sensor support -// V0.6 - 2015/10/12 -// - Added Battery support -// - Added low battery support for all sensors -// - Added HomeSeer event support (using HomeKit switches...) -// V0.7 - 2015/10/13 -// - You can add multiple HomeKit devices for the same HomeSeer device reference -// - Added CarbonMonoxide sensor -// - Added CarbonDioxide sensor -// - Added onValues option to all binary sensors -// V0.8 - 2015/10/14 -// - Added uuid_base parameter to all accessories -// V0.9 - 2015/10/16 -// - Smoke sensor battery fix -// - Added offEventGroup && offEventName to events (turn on launches one HS event. turn off can launch another HS event) -// - Added GarageDoorOpener support -// - Added Lock support -// -// -// Remember to add platform to config.json. -// -// You can get HomeSeer Device References by clicking a HomeSeer device name, then -// choosing the Advanced Tab. -// -// Example: -// "platforms": [ -// { -// "platform": "HomeSeer", // Required -// "name": "HomeSeer", // Required -// "host": "http://192.168.3.4:81", // Required - If you did setup HomeSeer authentication, use "http://user:password@ip_address:port" -// -// "events":[ // Optional - List of Events - Currently they are imported into HomeKit as switches -// { -// "eventGroup":"My Group", // Required - The HomeSeer event group -// "eventName":"My On Event", // Required - The HomeSeer event name -// "offEventGroup":"My Group", // Optional - The HomeSeer event group for turn-off -// "offEventName":"My Off Event", // Optional - The HomeSeer event name for turn-off -// "name":"Test", // Optional - HomeSeer event name is the default -// "uuid_base":"SomeUniqueId" // Optional - HomeKit identifier will be derived from this parameter instead of the name -// } -// ], -// -// "accessories":[ // Required - List of Accessories -// { -// "ref":8, // Required - HomeSeer Device Reference (To get it, select the HS Device - then Advanced Tab) -// "type":"Lightbulb", // Optional - Lightbulb is the default -// "name":"My Light", // Optional - HomeSeer device name is the default -// "offValue":"0", // Optional - 0 is the default -// "onValue":"100", // Optional - 100 is the default -// "can_dim":true, // Optional - true is the default - false for a non dimmable lightbulb -// "uuid_base":"SomeUniqueId2" // Optional - HomeKit identifier will be derived from this parameter instead of the name. You SHOULD add this parameter to all accessories ! -// }, -// { -// "ref":9 // This is a dimmable Lightbulb by default -// }, -// { -// "ref":58, // This is a controllable outlet -// "type":"Outlet" -// }, -// { -// "ref":111, // Required - HomeSeer Device Reference for your sensor -// "type":"TemperatureSensor", // Required for a temperature sensor -// "temperatureUnit":"F", // Optional - C is the default -// "name":"Bedroom temp", // Optional - HomeSeer device name is the default -// "batteryRef":112, // Optional - HomeSeer device reference for the sensor battery level -// "batteryThreshold":15 // Optional - If sensor battery level is below this value, the HomeKit LowBattery characteristic is set to 1. Default is 10 -// }, -// { -// "ref":34, // Required - HomeSeer Device Reference for your sensor -// "type":"SmokeSensor", // Required for a smoke sensor -// "name":"Kichen smoke detector", // Optional - HomeSeer device name is the default -// "batteryRef":35, // Optional - HomeSeer device reference for the sensor battery level -// "batteryThreshold":15, // Optional - If sensor battery level is below this value, the HomeKit LowBattery characteristic is set to 1. Default is 10 -// "onValues":[1,1.255] // Optional - List of all HomeSeer values triggering a "ON" sensor state - Default is any value different than 0 -// }, -// { -// "ref":34, // Required - HomeSeer Device Reference for your sensor (Here it's the same device as the SmokeSensor above) -// "type":"CarbonMonoxideSensor", // Required for a carbon monoxide sensor -// "name":"Kichen CO detector", // Optional - HomeSeer device name is the default -// "batteryRef":35, // Optional - HomeSeer device reference for the sensor battery level -// "batteryThreshold":15, // Optional - If sensor battery level is below this value, the HomeKit LowBattery characteristic is set to 1. Default is 10 -// "onValues":[2,2.255] // Optional - List of all HomeSeer values triggering a "ON" sensor state - Default is any value different than 0 -// }, -// { -// "ref":113, // Required - HomeSeer Device Reference of the Current Temperature Device -// "type":"Thermostat", // Required for a Thermostat -// "name":"Température Salon", // Optional - HomeSeer device name is the default -// "temperatureUnit":"C", // Optional - F for Fahrenheit, C for Celsius, C is the default -// "setPointRef":167, // Required - HomeSeer device reference for your thermostat Set Point. -// "setPointReadOnly":true, // Optional - Set to false if your SetPoint is read/write. true is the default -// "stateRef":166, // Required - HomeSeer device reference for your thermostat current state -// "stateOffValues":[0,4,5], // Required - List of the HomeSeer device values for a HomeKit state=OFF -// "stateHeatValues":[1], // Required - List of the HomeSeer device values for a HomeKit state=HEAT -// "stateCoolValues":[2], // Required - List of the HomeSeer device values for a HomeKit state=COOL -// "stateAutoValues":[3], // Required - List of the HomeSeer device values for a HomeKit state=AUTO -// "controlRef":168, // Required - HomeSeer device reference for your thermostat mode control (It can be the same as stateRef for some thermostats) -// "controlOffValue":0, // Required - HomeSeer device control value for OFF -// "controlHeatValue":1, // Required - HomeSeer device control value for HEAT -// "controlCoolValue":2, // Required - HomeSeer device control value for COOL -// "controlAutoValue":3, // Required - HomeSeer device control value for AUTO -// "coolingThresholdRef":169, // Optional - Not-implemented-yet - HomeSeer device reference for your thermostat cooling threshold -// "heatingThresholdRef":170 // Optional - Not-implemented-yet - HomeSeer device reference for your thermostat heating threshold -// }, -// { -// "ref":200, // Required - HomeSeer Device Reference of a garage door opener -// "type":"GarageDoorOpener", // Required for a Garage Door Opener -// "name":"Garage Door", // Optional - HomeSeer device name is the default -// "stateRef":201, // Required - HomeSeer device reference for your garage door opener current state (can be the same as ref) -// "stateOpenValues":[0], // Required - List of the HomeSeer device values for a HomeKit state=OPEN -// "stateClosedValues":[1], // Required - List of the HomeSeer device values for a HomeKit state=CLOSED -// "stateOpeningValues":[2], // Optional - List of the HomeSeer device values for a HomeKit state=OPENING -// "stateClosingValues":[3], // Optional - List of the HomeSeer device values for a HomeKit state=CLOSING -// "stateStoppedValues":[4], // Optional - List of the HomeSeer device values for a HomeKit state=STOPPED -// "controlRef":201, // Required - HomeSeer device reference for your garage door opener control (can be the same as ref and stateRef) -// "controlOpenValue":0, // Required - HomeSeer device control value for OPEN -// "controlCloseValue":1, // Required - HomeSeer device control value for CLOSE -// "obstructionRef":201, // Optional - HomeSeer device reference for your garage door opener obstruction state (can be the same as ref) -// "obstructionValues":[5], // Optional - List of the HomeSeer device values for a HomeKit obstruction state=OBSTRUCTION -// "lockRef":202, // Optional - HomeSeer device reference for your garage door lock (can be the same as ref) -// "lockUnsecuredValues":[0], // Optional - List of the HomeSeer device values for a HomeKit lock state=UNSECURED -// "lockSecuredValues":[1], // Optional - List of the HomeSeer device values for a HomeKit lock state=SECURED -// "lockJammedValues":[2], // Optional - List of the HomeSeer device values for a HomeKit lock state=JAMMED -// "unlockValue":0, // Optional - HomeSeer device control value to unlock the garage door opener -// "lockValue":1 // Optional - HomeSeer device control value to lock the garage door opener -// }, -// { -// "ref":210, // Required - HomeSeer Device Reference of a Lock -// "type":"Lock", // Required for a Lock -// "name":"Main Door Lock", // Optional - HomeSeer device name is the default -// "lockUnsecuredValues":[0], // Required - List of the HomeSeer device values for a HomeKit lock state=UNSECURED -// "lockSecuredValues":[1], // Required - List of the HomeSeer device values for a HomeKit lock state=SECURED -// "lockJammedValues":[2], // Optional - List of the HomeSeer device values for a HomeKit lock state=JAMMED -// "unlockValue":0, // Required - HomeSeer device control value to unlock -// "lockValue":1 // Required - HomeSeer device control value to lock -// }, -// { -// "ref":115, // Required - HomeSeer Device Reference for a device holding battery level (0-100) -// "type":"Battery", // Required for a Battery -// "name":"Roomba battery", // Optional - HomeSeer device name is the default -// "batteryThreshold":15 // Optional - If the level is below this value, the HomeKit LowBattery characteristic is set to 1. Default is 10 -// } -// ] -// } -// ], -// -// -// SUPORTED TYPES: -// - Lightbulb (can_dim, onValue, offValue options) -// - Fan (onValue, offValue options) -// - Switch (onValue, offValue options) -// - Outlet (onValue, offValue options) -// - Thermostat (temperatureUnit, setPoint, state, control options) -// - TemperatureSensor (temperatureUnit=C|F) -// - HumiditySensor (HomeSeer device value in % - batteryRef, batteryThreshold options) -// - LightSensor (HomeSeer device value in Lux - batteryRef, batteryThreshold options) -// - ContactSensor (onValues, batteryRef, batteryThreshold options) -// - MotionSensor (onValues, batteryRef, batteryThreshold options) -// - LeakSensor (onValues, batteryRef, batteryThreshold options) -// - OccupancySensor (onValues, batteryRef, batteryThreshold options) -// - SmokeSensor (onValues, batteryRef, batteryThreshold options) -// - CarbonMonoxideSensor (onValues, batteryRef, batteryThreshold options) -// - CarbonDioxideSensor (onValues, batteryRef, batteryThreshold options) -// - Battery (batteryThreshold option) -// - GarageDoorOpener (state, control, obstruction, lock options) -// - Lock (unsecured, secured, jammed options) -// - Door - - -var Service = require("hap-nodejs").Service; -var Characteristic = require("hap-nodejs").Characteristic; -var request = require("request"); - - -function httpRequest(url, method, callback) { - request({ - url: url, - method: method - }, - function (error, response, body) { - callback(error, response, body) - }) -} - - - -function HomeSeerPlatform(log, config){ - this.log = log; - this.config = config; -} - -HomeSeerPlatform.prototype = { - accessories: function(callback) { - var that = this; - var foundAccessories = []; - - if( this.config.events ) { - this.log("Creating HomeSeer events."); - for( var i=0; i b.name) - (a.name < b.name); - })); - }); - } -} - -function ISYAccessory(log, host, port, user, pass, name, address, uom) { - this.log = log; - this.host = host; - this.port = port; - this.user = user; - this.pass = pass; - this.name = name; - this.address = address; - this.uom = uom; -} - -ISYAccessory.prototype = { - query: function() { - var path = util.format("/rest/status/%s", encodeURI(this.address)); - var url = ISYURL(this.user, this.pass, this.host, this.port, path); - - var options = { url: url, method: 'GET' }; - request(options, function(error, response, body) { - if (error) - { - console.trace("Requesting Device Status."); - that.log(error); - return error; - } - - parser.parseString(body, function(err, result) { - var value = result.properties.property[0].$.value; - return value; - }); - - }); - }, - - command: function(c, value) { - this.log(this.name + " sending command " + c + " with value " + value); - - switch (c) - { - case 'On': - path = "/rest/nodes/" + this.address + "/cmd/DFON"; - break; - case 'Off': - path = "/rest/nodes/" + this.address + "/cmd/DFOF"; - break; - case 'Low': - path = "/rest/nodes/" + this.address + "/cmd/DON/85"; - break; - case 'Medium': - path = "/rest/nodes/" + this.address + "/cmd/DON/128"; - break; - case 'High': - path = "/rest/nodes/" + this.address + "/cmd/DON/255"; - break; - case 'setLevel': - if (value > 0) - { - path = "/rest/nodes/" + this.address + "/cmd/DON/" + Math.floor(255 * (value / 100)); - } - break; - default: - this.log("Unimplemented command sent to " + this.name + " Command " + c); - break; - } - - if (path) - { - var url = ISYURL(this.user, this.pass, this.host, this.port, path); - var options = { - url: url, - method: 'GET' - }; - - var that = this; - request(options, function(error, response, body) { - if (error) - { - console.trace("Sending Command."); - that.log(error); - return error; - } - that.log("Sent command " + path + " to " + that.name); - }); - } - }, - - 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: "SmartHome", - 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: this.address, - 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 - } - ] - }, - - controlCharacteristics: function(that) { - cTypes = [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: true, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - }] - - if (this.uom == "%/on/off") { - cTypes.push({ - cType: types.POWER_STATE_CTYPE, - perms: ["pw","pr","ev"], - format: "bool", - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Change the power state", - designedMaxLength: 1, - onUpdate: function(value) { - if (value == 0) { - that.command("Off") - } else { - that.command("On") - } - }, - onRead: function() { - return this.query(); - } - }); - cTypes.push({ - cType: types.BRIGHTNESS_CTYPE, - perms: ["pw","pr","ev"], - format: "int", - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Adjust Brightness of Light", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%", - onUpdate: function(value) { - that.command("setLevel", value); - }, - onRead: function() { - var val = this.query(); - that.log("Query: " + val); - return val; - } - }); - } - else if (this.uom == "off/low/med/high") - { - cTypes.push({ - cType: types.POWER_STATE_CTYPE, - perms: ["pw","pr","ev"], - format: "bool", - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Change the power state", - designedMaxLength: 1, - onUpdate: function(value) { - if (value == 0) { - that.command("Off") - } else { - that.command("On") - } - }, - onRead: function() { - return this.query(); - } - }); - cTypes.push({ - cType: types.ROTATION_SPEED_CTYPE, - perms: ["pw","pr","ev"], - format: "bool", - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Change the speed of the fan", - designedMaxLength: 1, - onUpdate: function(value) { - if (value == 0) { - that.command("Off"); - } else if (value > 0 && value < 40) { - that.command("Low"); - } else if (value > 40 && value < 75) { - that.command("Medium"); - } else { - that.command("High"); - } - }, - onRead: function() { - return this.query(); - } - }); - } - else if (this.uom == "on/off") - { - cTypes.push({ - cType: types.POWER_STATE_CTYPE, - perms: ["pw","pr","ev"], - format: "bool", - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Change the power state", - designedMaxLength: 1, - onUpdate: function(value) { - if (value == 0) { - that.command("Off") - } else { - that.command("On") - } - }, - onRead: function() { - return this.query(); - } - }); - } - - return cTypes; - }, - - sType: function() { - if (this.uom == "%/on/off") { - return types.LIGHTBULB_STYPE; - } else if (this.uom == "on/off") { - return types.SWITCH_STYPE; - } else if (this.uom == "off/low/med/high") { - return types.FAN_STYPE; - } - - return types.SWITCH_STYPE; - }, - - getServices: function() { - var that = this; - var services = [{ - sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: this.informationCharacteristics(), - }, - { - sType: this.sType(), - characteristics: this.controlCharacteristics(that) - }]; - - //that.log("Loaded services for " + that.name); - return services; - } -}; - -module.exports.accessory = ISYAccessory; -module.exports.platform = ISYPlatform; diff --git a/platforms/Indigo.js b/platforms/Indigo.js deleted file mode 100644 index 4886f29..0000000 --- a/platforms/Indigo.js +++ /dev/null @@ -1,552 +0,0 @@ -// 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; diff --git a/platforms/KNX-sample-config.json b/platforms/KNX-sample-config.json deleted file mode 100644 index eda2fe3..0000000 --- a/platforms/KNX-sample-config.json +++ /dev/null @@ -1,156 +0,0 @@ -{ - "bridge": { - "name": "Homebridge", - "username": "CC:22:3D:E3:CE:30", - "port": 51826, - "pin": "031-45-154" - }, - "description": "This is an example configuration file for KNX platform shim", - "hint": "Always paste into jsonlint.com validation page before starting your homebridge, saves a lot of frustration", - "hint2": "Replace all group addresses by current addresses of your installation, these are arbitrary examples!", - "hint3": "For valid services and their characteristics have a look at the KNX.md file in folder platforms!", - "platforms": [ - { - "platform": "KNX", - "name": "KNX", - "knxd_ip": "192.168.178.205", - "knxd_port": 6720, - "accessories": [ - { - "accessory_type": "knxdevice", - "description": "Only generic type knxdevice is supported, all previous knx types have been merged into that.", - "name": "Living Room North Lamp", - "services": [ - { - "type": "Lightbulb", - "description": "iOS8 Lightbulb type, supports On (Switch) and Brightness", - "name": "Living Room North Lamp", - "On": { - "Set": "1/1/6", - "Listen": [ - "1/1/63" - ] - }, - "Brightness": { - "Set": "1/1/62", - "Listen": [ - "1/1/64" - ] - } - } - ], - "services-description": "Services is an array, you CAN have multiple service types in one accessory, though it is not fully supported in many iOS HK apps, such as EVE and myTouchHome" - }, - { - "accessory_type": "knxdevice", - "name": "Office Temperature", - "description": "iOS8.4.1 TemperatureSensor type, supports CurrentTemperature", - "services": [ - { - "type": "TemperatureSensor", - "name": "Raumtemperatur", - "CurrentTemperature": { - "Listen": "3/3/44" - } - } - ] - }, - { - "accessory_type": "knxdevice", - "name": "Office Window Lock", - "services": [ - { - "type": "LockMechanism", - "description": "iOS8 Lock mechanism, Supports LockCurrentState, LockTargetState, append R to the addresses if LOCKED is 1", - "name": "Office Window Lock", - "LockCurrentState": { - "Listen": "5/3/15R" - }, - "LockTargetState": { - "Listen": "5/3/16R" - } - } - ] - }, - { - "accessory_type": "knxdevice", - "description": "sample device with multiple services. Multiple services of different types are widely supported", - "name": "Office", - "services": [ - { - "type": "Lightbulb", - "name": "Office Lamp", - "On": { - "Set": "1/3/5" - } - }, - { - "type": "Thermostat", - "description": "iOS8 Thermostat type, supports CurrentTemperature, TargetTemperature, CurrentHeatingCoolingState ", - "name": "Raumtemperatur", - "CurrentTemperature": { - "Listen": "3/3/44" - }, - "TargetTemperature": { - "Set": "3/3/94" - }, - "CurrentHeatingCoolingState": { - "Listen": "3/3/64" - } - }, - { - "type": "WindowCovering", - "description": "iOS9 Window covering (blinds etc) type, still WIP", - "name": "Blinds", - "TargetPosition": { - "Set": "1/2/3", - "Listen": "1/2/4" - }, - "CurrentPosition": { - "Set": "1/3/1", - "Listen": "1/3/2" - }, - "PositionState": { - "Listen": "2/7/1" - } - } - ] - }, - { - "accessory_type": "knxdevice", - "description": "sample contact sensor device", - "name": "Office Contact", - "services": [ - { - "type": "ContactSensor", - "name": "Office Door", - "ContactSensorState": { - "Listen": "5/3/5" - } - } - ] - }, - { - "accessory_type": "knxdevice", - "description": "sample garage door opener", - "name": "Office Garage", - "services": [ - { - "type": "GarageDoorOpener", - "name": "Office Garage Opener", - "CurrentDoorState": { - "Listen": "5/4/5" - }, - "TargetDoorState": { - "Listen": "5/4/6" - } - } - ] - } - ] - } - ], - "accessories": [ - - ] -} \ No newline at end of file diff --git a/platforms/KNX.js b/platforms/KNX.js deleted file mode 100644 index 0ca4309..0000000 --- a/platforms/KNX.js +++ /dev/null @@ -1,206 +0,0 @@ -/** Sample platform outline - * based on Sonos platform - */ -'use strict'; -var types = require("hap-nodejs/accessories/types.js"); -//var hardware = require('myHardwareSupport'); //require any additional hardware packages -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) { - this.log("Fetching KNX devices."); - var that = this; - - - // iterate through all devices the platform my offer - // for each device, create an accessory - - // read accessories from file !!!!! - var foundAccessories = this.config.accessories; - - - //create array of accessories - var myAccessories = []; - - for (var int = 0; int < foundAccessories.length; int++) { - this.log("parsing acc " + int + " of " + foundAccessories.length); - // instantiate and push to array - switch (foundAccessories[int].accessory_type) { - case "knxdevice": - this.log("push new universal device "+foundAccessories[int].name); - // push knxd connection setting to each device from platform - foundAccessories[int].knxd_ip = this.config.knxd_ip; - foundAccessories[int].knxd_port = this.config.knxd_port; - var accConstructor = require('./../accessories/knxdevice.js'); - var acc = new accConstructor.accessory(this.log,foundAccessories[int]); - this.log("created "+acc.name+" universal accessory"); - myAccessories.push(acc); - break; - default: - // do something else - this.log("unkown accessory type found") - } - - }; - // if done, return the array to callback function - this.log("returning "+myAccessories.length+" accessories"); - callback(myAccessories); - } -}; - - -/** - * The buscallbacks module is to expose a simple function to listen on the bus and register callbacks for value changes - * of registered addresses. - * - * Usage: - * You can start the monitoring process at any time - startMonitor({host: name-ip, port: port-num }); - - * You can add addresses to the subscriptions using - -registerGA(groupAddress, callback) - - * groupAddress has to be an groupAddress in common knx notation string '1/2/3' - * the callback has to be a - * var f = function(value) { handle value update;} - * so you can do a - * registerGA('1/2/3', function(value){ - * console.log('1/2/3 got a hit with '+value); - * }); - * but of course it is meant to be used programmatically, not literally, otherwise it has no advantage - * - * You can also use arrays of addresses if your callback is supposed to listen to many addresses: - -registerGA(groupAddresses[], callback) - - * as in - * registerGA(['1/2/3','1/0/0'], function(value){ - * console.log('1/2/3 or 1/0/0 got a hit with '+value); - * }); - * if you are having central addresses like "all lights off" or additional response objects - * - * - * callbacks can have a signature of - * function(value, src, dest, type) but do not have to support these parameters (order matters) - * src = physical address such as '1.1.20' - * dest = groupAddress hit (you subscribed to that address, remember?), as '1/2/3' - * type = Data point type, as 'DPT1' - * - * - */ - - - -//array of registered addresses and their callbacks -var subscriptions = []; -//check variable to avoid running two listeners -var running; - -function groupsocketlisten(opts, callback) { - var conn = knxd.Connection(); - conn.socketRemote(opts, function() { - conn.openGroupSocket(0, callback); - }); -} - - -var registerSingleGA = function registerSingleGA (groupAddress, callback, reverse) { - subscriptions.push({address: groupAddress, callback: callback, reverse:reverse }); -} - -/* - * public busMonitor.startMonitor() - * starts listening for telegrams on KNX bus - * - */ -var startMonitor = function startMonitor(opts) { // using { host: name-ip, port: port-num } options object - if (!running) { - running = true; - } else { - console.log("<< knxd socket listener already running >>"); - return null; - } - console.log(">>> knxd groupsocketlisten starting <<<"); - groupsocketlisten(opts, function(parser) { - //console.log("knxfunctions.read: in callback parser"); - parser.on('write', function(src, dest, type, val){ - // search the registered group addresses - //console.log('recv: Write from '+src+' to '+dest+': '+val+' ['+type+'], listeners:' + subscriptions.length); - for (var i = 0; i < subscriptions.length; i++) { - // iterate through all registered addresses - if (subscriptions[i].address === dest) { - // found one, notify - console.log('HIT: Write from '+src+' to '+dest+': '+val+' ['+type+']'); - subscriptions[i].callback(val, src, dest, type, subscriptions[i].reverse); - } - } - }); - - parser.on('response', function(src, dest, type, val) { - // search the registered group addresses -// console.log('recv: resp from '+src+' to '+dest+': '+val+' ['+type+']'); - for (var i = 0; i < subscriptions.length; i++) { - // iterate through all registered addresses - if (subscriptions[i].address === dest) { - // found one, notify -// console.log('HIT: Response from '+src+' to '+dest+': '+val+' ['+type+']'); - subscriptions[i].callback(val, src, dest, type, subscriptions[i].reverse); - } - } - - }); - - //dont care about reads here -// parser.on('read', function(src, dest) { -// console.log('Read from '+src+' to '+dest); -// }); - //console.log("knxfunctions.read: in callback parser at end"); - }); // groupsocketlisten parser -}; //startMonitor - - -/* - * public registerGA(groupAdresses[], callback(value)) - * parameters - * callback: function(value, src, dest, type) called when a value is sent on the bus - * groupAddresses: (Array of) string(s) for group addresses - * - * - * - */ -var registerGA = function (groupAddresses, callback) { - // check if the groupAddresses is an array - if (groupAddresses.constructor.toString().indexOf("Array") > -1) { - // handle multiple addresses - for (var i = 0; i < groupAddresses.length; i++) { - if (groupAddresses[i] && groupAddresses[i].match(/(\d*\/\d*\/\d*)/)) { // do not bind empty addresses or invalid addresses - // clean the addresses - registerSingleGA (groupAddresses[i].match(/(\d*\/\d*\/\d*)/)[0], callback,groupAddresses[i].match(/\d*\/\d*\/\d*(R)/) ? true:false ); - } - } - } 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); - } - } -// console.log("listeners now: " + subscriptions.length); -}; - - - -module.exports.platform = KNXPlatform; -module.exports.registerGA = registerGA; -module.exports.startMonitor = startMonitor; \ No newline at end of file diff --git a/platforms/KNX.md b/platforms/KNX.md deleted file mode 100644 index ffb7301..0000000 --- a/platforms/KNX.md +++ /dev/null @@ -1,213 +0,0 @@ -# Syntax of the config.json -In the platforms section, you can insert a KNX type platform. -You need to configure all devices directly in the config.json. -````json - "platforms": [ - { - "platform": "KNX", - "name": "KNX", - "knxd_ip": "192.168.178.205", - "knxd_port": 6720, - "accessories": [ - { - "accessory_type": "knxdevice", - "name": "Living Room North Lamp", - "services": [ - { - "type": "Lightbulb", - "description": "iOS8 Lightbulb type, supports On (Switch) and Brightness", - "name": "Living Room North Lamp", - "On": { - "Set": "1/1/6", - "Listen": ["1/1/63"] - }, - "Brightness": { - "Set": "1/1/62", - "Listen": ["1/1/64"] - } - } - ] - } - ] - } -```` -In the accessories section (the array within the brackets [ ]) you can insert as many objects as you like in the following form -````json - { - "accessory_type": "knxdevice", - "name": "Here goes your display name, this will be shown in HomeKit apps", - "services": [ - { - } - ] - } -```` -You have to add services in the following syntax: -````json - { - "type": "SERVICENAME", - "description": "This is just for you to remember things", - "name": "beer tap thermostat", - "CHARACTERISTIC1": { - "Set": "1/1/6", - "Listen": [ - "1/1/63" - ] - }, - "CHARACTERISTIC2": { - "Set": "1/1/62", - "Listen": [ - "1/1/64" - ] - } - } -```` -`CHARACTERISTICx` are properties that are dependent on the service type, so they are listed below. - -Two kinds of addresses are supported: `"Set":"1/2/3"` is a writable group address, to which changes are sent if the service supports changing values. Changes on the bus are listened to, too. -`"Listen":["1/2/3","1/2/4","1/2/5"]` is an array of addresses that are listened to additionally. To these addresses never values get written, but the on startup the service will issue *KNX read requests* to ALL addresses listed in `Set:` and in `Listen:` - - -For two characteristics there are additional minValue and maxValue attributes. These are CurrentTemperature and TargetTemperature, and are used in TemperatureSensor and Thermostat. - -So the charcteristic section may look like: - - ````json - { - "type": "Thermostat", - "description": "Sample thermostat", - "name": "We need a name for each service, though it usually shows only if multiple services are present in one accessory", - "CurrentTemperature": { - "Set": "1/1/6", - "Listen": [ - "1/1/63" - ], - "minValue": -18, - "maxValue": 30 - }, - "TargetTemperature": { - "Set": "1/1/62", - "Listen": [ - "1/1/64" - ], - "minValue": -4, - "maxValue": 12 - } - } -```` - - -## reversal of values for characteristics -In general, all DPT1 types can be reversed. If you need a 1 for "contact" of a contact senser, you can append an "R" to the group address. -Likewise, all percentages of DPT5 can be reversed, if you need a 100% (=255) for window closed, append an "R" to the group address. Do not forget the listening addresses! - ````json - { - "type": "ContactSensor", - "description": "Sample ContactSensor with 1 as contact (0 is Apple's default)", - "name": "WindowContact1", - "ContactSensorState": { - "Listen": [ - "1/1/100R" - ] - } - } -```` -# Supported Services and their characteristics -## ContactSensor -- ContactSensorState: DPT 1.002, 0 as contact -- ~~ContactSensorStateContact1: DPT 1.002, 1 as contact~~ - -- StatusActive: DPT 1.011, 1 as true -- StatusFault: DPT 1.011, 1 as true -- StatusTampered: DPT 1.011, 1 as true -- StatusLowBattery: DPT 1.011, 1 as true - -## GarageDoorOpener -- CurrentDoorState: DPT5 integer value in range 0..4 - // Characteristic.CurrentDoorState.OPEN = 0; - // Characteristic.CurrentDoorState.CLOSED = 1; - // Characteristic.CurrentDoorState.OPENING = 2; - // Characteristic.CurrentDoorState.CLOSING = 3; - // Characteristic.CurrentDoorState.STOPPED = 4; - -- TargetDoorState: DPT5 integer value in range 0..1 - // Characteristic.TargetDoorState.OPEN = 0; - // Characteristic.TargetDoorState.CLOSED = 1; - -- ObstructionDetected: DPT1, 1 as true - -- LockCurrentState: DPT5 integer value in range 0..3 - // Characteristic.LockCurrentState.UNSECURED = 0; - // Characteristic.LockCurrentState.SECURED = 1; - // Characteristic.LockCurrentState.JAMMED = 2; - // Characteristic.LockCurrentState.UNKNOWN = 3; - -- LockTargetState: DPT5 integer value in range 0..1 - // Characteristic.LockTargetState.UNSECURED = 0; - // Characteristic.LockTargetState.SECURED = 1; - - - -## Lightbulb - - On: DPT 1.001, 1 as on, 0 as off - - Brightness: DPT5.001 percentage, 100% (=255) the brightest - -## LightSensor -- CurrentAmbientLightLevel: DPT 9.004, 0 to 100000 Lux - -## LockMechanism (This is poorly mapped!) -- LockCurrentState: DPT 1, 1 as secured -- ~~LockCurrentStateSecured0: DPT 1, 0 as secured~~ -- LockTargetState: DPT 1, 1 as secured -- ~~LockTargetStateSecured0: DPT 1, 0 as secured~~ - -*ToDo here: correction of mappings, HomeKit reqires lock states UNSECURED=0, SECURED=1, JAMMED = 2, UNKNOWN=3* - -## MotionSensor -- MotionDetected: DPT 1.002, 1 as motion detected - -- StatusActive: DPT 1.011, 1 as true -- StatusFault: DPT 1.011, 1 as true -- StatusTampered: DPT 1.011, 1 as true -- StatusLowBattery: DPT 1.011, 1 as true - -## Outlet - - On: DPT 1.001, 1 as on, 0 as off - - OutletInUse: DPT 1.011, 1 as on, 0 as off - -## Switch - - On: DPT 1.001, 1 as on, 0 as off - -## TemperatureSensor -- CurrentTemperature: DPT9.001 in °C [listen only] - -## Thermostat -- CurrentTemperature: DPT9.001 in °C [listen only], -40 to 80°C if not overriden as shown above -- TargetTemperature: DPT9.001, values 0..40°C only, all others are ignored -- CurrentHeatingCoolingState: DPT20.102 HVAC, because of the incompatible mapping only off and heating (=auto) are shown, [listen only] -- TargetHeatingCoolingState: DPT20.102 HVAC, as above - -## Window -- CurrentPosition: DPT5.001 percentage -- TargetPosition: DPT5.001 percentage -- PositionState: DPT5.005 value [listen only: 0 Increasing, 1 Decreasing, 2 Stopped] - -## WindowCovering -- CurrentPosition: DPT5 percentage -- TargetPosition: DPT5 percentage -- PositionState: DPT5 value [listen only: 0 Closing, 1 Opening, 2 Stopped] - -### not yet supported -- HoldPosition -- TargetHorizontalTiltAngle -- TargetVerticalTiltAngle -- CurrentHorizontalTiltAngle -- CurrentVerticalTiltAngle -- ObstructionDetected - - - - -# DISCLAIMER -**This is work in progress!** - diff --git a/platforms/LIFx.js b/platforms/LIFx.js deleted file mode 100644 index 89de156..0000000 --- a/platforms/LIFx.js +++ /dev/null @@ -1,302 +0,0 @@ -'use strict'; - -// LiFX Platform Shim for HomeBridge -// -// Remember to add platform to config.json. Example: -// "platforms": [ -// { -// "platform": "LIFx", // required -// "name": "LIFx", // required -// "access_token": "access token", // required -// "use_lan": "true" // optional set to "true" (gets and sets over the lan) or "get" (gets only over the lan) -// } -// ], -// -// 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 lifxRemoteObj = require('lifx-api'); -var lifx_remote; - -var lifxLanObj; -var lifx_lan; -var use_lan; - -function LIFxPlatform(log, config){ - // auth info - this.access_token = config["access_token"]; - - lifx_remote = new lifxRemoteObj(this.access_token); - - // use remote or lan api ? - use_lan = config["use_lan"] || false; - - if (use_lan != false) { - lifxLanObj = require('lifx'); - lifx_lan = lifxLanObj.init(); - } - - this.log = log; -} - -LIFxPlatform.prototype = { - accessories: function(callback) { - this.log("Fetching LIFx devices."); - - var that = this; - var foundAccessories = []; - - lifx_remote.listLights("all", function(body) { - var bulbs = JSON.parse(body); - - for(var i = 0; i < bulbs.length; i ++) { - var accessory = new LIFxBulbAccessory(that.log, bulbs[i]); - foundAccessories.push(accessory); - } - callback(foundAccessories) - }); - } -} - -function LIFxBulbAccessory(log, bulb) { - // device info - this.name = bulb.label; - this.model = bulb.product_name; - this.deviceId = bulb.id; - this.serial = bulb.uuid; - this.capabilities = bulb.capabilities; - this.log = log; -} - -LIFxBulbAccessory.prototype = { - getLan: function(type, callback){ - var that = this; - - if (!lifx_lan.bulbs[this.deviceId]) { - callback(new Error("Device not found"), false); - return; - } - - lifx_lan.requestStatus(); - lifx_lan.on('bulbstate', function(bulb) { - if (callback == null) { - return; - } - - if (bulb.addr.toString('hex') == that.deviceId) { - switch(type) { - case "power": - callback(null, bulb.state.power > 0); - break; - case "brightness": - callback(null, Math.round(bulb.state.brightness * 100 / 65535)); - break; - case "hue": - callback(null, Math.round(bulb.state.hue * 360 / 65535)); - break; - case "saturation": - callback(null, Math.round(bulb.state.saturation * 100 / 65535)); - break; - } - - callback = null - } - }); - }, - getRemote: function(type, callback){ - var that = this; - - lifx_remote.listLights("id:"+ that.deviceId, function(body) { - var bulb = JSON.parse(body); - - if (bulb.connected != true) { - callback(new Error("Device not found"), false); - return; - } - - switch(type) { - case "power": - callback(null, bulb.power == "on" ? 1 : 0); - break; - case "brightness": - callback(null, Math.round(bulb.brightness * 100)); - break; - case "hue": - callback(null, bulb.color.hue); - break; - case "saturation": - callback(null, Math.round(bulb.color.saturation * 100)); - break; - } - }); - }, - identify: function(callback) { - lifx_remote.breatheEffect("id:"+ this.deviceId, 'green', null, 1, 3, false, true, 0.5, function (body) { - callback(); - }); - }, - setLanColor: function(type, value, callback){ - var bulb = lifx_lan.bulbs[this.deviceId]; - - if (!bulb) { - callback(new Error("Device not found"), false); - return; - } - - var state = { - hue: bulb.state.hue, - saturation: bulb.state.saturation, - brightness: bulb.state.brightness, - kelvin: bulb.state.kelvin - }; - - var scale = type == "hue" ? 360 : 100; - - state[type] = Math.round(value * 65535 / scale) & 0xffff; - lifx_lan.lightsColour(state.hue, state.saturation, state.brightness, state.kelvin, 0, bulb); - - callback(null); - }, - setLanPower: function(state, callback){ - var bulb = lifx_lan.bulbs[this.deviceId]; - - if (!bulb) { - callback(new Error("Device not found"), false); - return; - } - - if (state) { - lifx_lan.lightsOn(bulb); - } - else { - lifx_lan.lightsOff(bulb); - } - - callback(null); - }, - setRemoteColor: function(type, value, callback){ - var color; - - switch(type) { - case "brightness": - color = "brightness:" + (value / 100); - break; - case "hue": - color = "hue:" + value; - break; - case "saturation": - color = "saturation:" + (value / 100); - break; - } - - lifx_remote.setColor("id:"+ this.deviceId, color, 0, null, function (body) { - callback(); - }); - }, - setRemotePower: function(state, callback){ - var that = this; - - lifx_remote.setPower("id:"+ that.deviceId, (state == 1 ? "on" : "off"), 0, function (body) { - callback(); - }); - }, - getServices: function() { - var that = this; - var services = [] - var service = new Service.Lightbulb(this.name); - - switch(use_lan) { - case true: - case "true": - // gets and sets over the lan api - service - .getCharacteristic(Characteristic.On) - .on('get', function(callback) { that.getLan("power", callback);}) - .on('set', function(value, callback) {that.setLanPower(value, callback);}); - - service - .addCharacteristic(Characteristic.Brightness) - .on('get', function(callback) { that.getLan("brightness", callback);}) - .on('set', function(value, callback) { that.setLanColor("brightness", value, callback);}); - - if (this.capabilities.has_color == true) { - service - .addCharacteristic(Characteristic.Hue) - .on('get', function(callback) { that.getLan("hue", callback);}) - .on('set', function(value, callback) { that.setLanColor("hue", value, callback);}); - - service - .addCharacteristic(Characteristic.Saturation) - .on('get', function(callback) { that.getLan("saturation", callback);}) - .on('set', function(value, callback) { that.setLanColor("saturation", value, callback);}); - } - break; - case "get": - // gets over the lan api, sets over the remote api - service - .getCharacteristic(Characteristic.On) - .on('get', function(callback) { that.getLan("power", callback);}) - .on('set', function(value, callback) {that.setRemotePower(value, callback);}); - - service - .addCharacteristic(Characteristic.Brightness) - .on('get', function(callback) { that.getLan("brightness", callback);}) - .on('set', function(value, callback) { that.setRemoteColor("brightness", value, callback);}); - - if (this.capabilities.has_color == true) { - service - .addCharacteristic(Characteristic.Hue) - .on('get', function(callback) { that.getLan("hue", callback);}) - .on('set', function(value, callback) { that.setRemoteColor("hue", value, callback);}); - - service - .addCharacteristic(Characteristic.Saturation) - .on('get', function(callback) { that.getLan("saturation", callback);}) - .on('set', function(value, callback) { that.setRemoteColor("saturation", value, callback);}); - } - break; - default: - // gets and sets over the remote api - service - .getCharacteristic(Characteristic.On) - .on('get', function(callback) { that.getRemote("power", callback);}) - .on('set', function(value, callback) {that.setRemotePower(value, callback);}); - - service - .addCharacteristic(Characteristic.Brightness) - .on('get', function(callback) { that.getRemote("brightness", callback);}) - .on('set', function(value, callback) { that.setRemoteColor("brightness", value, callback);}); - - if (this.capabilities.has_color == true) { - service - .addCharacteristic(Characteristic.Hue) - .on('get', function(callback) { that.getRemote("hue", callback);}) - .on('set', function(value, callback) { that.setRemoteColor("hue", value, callback);}); - - service - .addCharacteristic(Characteristic.Saturation) - .on('get', function(callback) { that.getRemote("saturation", callback);}) - .on('set', function(value, callback) { that.setRemoteColor("saturation", value, callback);}); - } - } - - services.push(service); - - service = new Service.AccessoryInformation(); - - service - .setCharacteristic(Characteristic.Manufacturer, "LIFX") - .setCharacteristic(Characteristic.Model, this.model) - .setCharacteristic(Characteristic.SerialNumber, this.serial); - - services.push(service); - - return services; - } -} - -module.exports.accessory = LIFxBulbAccessory; -module.exports.platform = LIFxPlatform; diff --git a/platforms/LogitechHarmony.js b/platforms/LogitechHarmony.js deleted file mode 100644 index 0521a65..0000000 --- a/platforms/LogitechHarmony.js +++ /dev/null @@ -1,305 +0,0 @@ -'use strict'; - -// Logitech Harmony Remote Platform Shim for HomeBridge -// Based on the Domoticz Platform Shim for HomeBridge by Joep Verhaeg (http://www.joepverhaeg.nl) -// Wriiten by John Wells (https://github.com/madmod) -// -// Remember to add platform to config.json. Example: -// "platforms": [ -// { -// "platform": "LogitechHarmony", -// "name": "Logitech Harmony" -// } -// ], -// -// 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 harmonyDiscover = require('harmonyhubjs-discover'); -var harmony = require('harmonyhubjs-client'); - -var _harmonyHubPort = 61991; - - -function sortByKey (array, key) { - return array.sort(function(a, b) { - var x = a[key]; var y = b[key]; - return ((x < y) ? -1 : ((x > y) ? 1 : 0)); - }); -}; - - -function LogitechHarmonyPlatform (log, config) { - this.log = log; - this.ip_address = config['ip_address']; -}; - - -LogitechHarmonyPlatform.prototype = { - - // Find one Harmony remote hub (only support one for now) - locateHub: function (callback) { - var self = this; - - // Connect to a Harmony hub - var createClient = function (ipAddress) { - self.log("Connecting to Logitech Harmony remote hub..."); - - harmony(ipAddress) - .then(function (client) { - self.log("Connected to Logitech Harmony remote hub"); - - callback(null, client); - }); - }; - - // Use the ip address in configuration if available - if (this.ip_address) { - console.log("Using Logitech Harmony hub ip address from configuration"); - - return createClient(this.ip_address) - } - - this.log("Searching for Logitech Harmony remote hubs..."); - - // Discover the harmony hub with bonjour - var discover = new harmonyDiscover(_harmonyHubPort); - - // TODO: Support update event with some way to add accessories - // TODO: Have some kind of timeout with an error message. Right now this searches forever until it finds one hub. - discover.on('online', function (hubInfo) { - self.log("Found Logitech Harmony remote hub: " + hubInfo.ip); - - // Stop looking for hubs once we find the first one - // TODO: Support multiple hubs - discover.stop(); - - createClient(hubInfo.ip); - }); - - // Start looking for hubs - discover.start(); - }, - - accessories: function (callback) { - 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); - }); - - // 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..."); - - hub.getActivities() - .then(function (activities) { - self.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'); - } - }, - - setPowerState: function (state, callback) { - var self = this; - - if (this.isActivity) { - this.log('Set activity ' + this.name + ' power state to ' + state); - - // Activity id -1 is turn off all devices - var id = state ? this.id : -1; - - this.hub.startActivity(id) - .then(function () { - self.log('Finished setting activity ' + self.name + ' power state to ' + state); - callback(); - }) - .catch(function (err) { - self.log('Failed setting activity ' + self.name + ' power state to ' + state + ' with error ' + err); - callback(err); - }); - } else { - // TODO: Support setting device power - this.log('TODO: Support setting device power'); - callback(); - } - }, - - getServices: function () { - var self = this; - - return [ - { - sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: [ - { - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: self.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of the accessory", - designedMaxLength: 255 - }, - { - cType: types.MANUFACTURER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "Logitech", - supportEvents: false, - supportBonjour: false, - manfDescription: "Manufacturer", - designedMaxLength: 255 - }, - { - cType: types.MODEL_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "Harmony", - supportEvents: false, - supportBonjour: false, - manfDescription: "Model", - designedMaxLength: 255 - }, - { - cType: types.SERIAL_NUMBER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - // TODO: Add hub unique id to this for people with multiple hubs so that it is really a guid. - initialValue: self.id, - supportEvents: false, - supportBonjour: false, - manfDescription: "SN", - designedMaxLength: 255 - }, - { - cType: types.IDENTIFY_CTYPE, - onUpdate: null, - perms: ["pw"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Identify Accessory", - designedMaxLength: 1 - } - ] - }, - { - sType: types.SWITCH_STYPE, - characteristics: [ - { - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: self.name, - supportEvents: true, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - }, - { - cType: types.POWER_STATE_CTYPE, - onUpdate: function (value) { - self.setPowerState(value) - }, - onRead: self.getPowerState, - perms: ["pw","pr","ev"], - format: "bool", - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Change the power state", - designedMaxLength: 1 - } - ] - } - ]; - } - -}; - -module.exports.accessory = LogitechHarmonyAccessory; -module.exports.platform = LogitechHarmonyPlatform; - diff --git a/platforms/MiLight.js b/platforms/MiLight.js deleted file mode 100644 index 0325352..0000000 --- a/platforms/MiLight.js +++ /dev/null @@ -1,242 +0,0 @@ -/* - -MiLight platform shim for Homebridge -Written by Sam Edwards (https://samedwards.ca/) - -Uses the node-milight-promise library (https://github.com/mwittig/node-milight-promise) which features some code from -applamp.nl (http://www.applamp.nl/service/applamp-api/) and uses other details from (http://www.limitlessled.com/dev/) - -Configure in config.json as follows: - -"platforms": [ - { - "platform":"MiLight", - "name":"MiLight", - "ip_address": "255.255.255.255", - "port": 8899, - "type": "rgbw", - "delay": 30, - "repeat": 3, - "zones":["Kitchen Lamp","Bedroom Lamp","Living Room Lamp","Hallway Lamp"] - } -] - -Where the parameters are: - *platform (required): This must be "MiLight", and refers to the name of the accessory as exported from this file - *name (optional): The display name used for logging output by Homebridge. Best to set to "MiLight" - *ip_address (optional): The IP address of the WiFi Bridge. Default to the broadcast address of 255.255.255.255 if not specified - *port (optional): Port of the WiFi bridge. Defaults to 8899 if not specified - *type (optional): One of either "rgbw", "rgb", or "white", depending on the type of bulb being controlled. This applies to all zones. Defaults to rgbw. - *delay (optional): Delay between commands sent over UDP. Default 30ms. May cause delays when sending a lot of commands. Try decreasing to improve. - *repeat (optional): Number of times to repeat the UDP command for better reliability. Default 3 - *zones (required): An array of the names of the zones, in order, 1-4. Use null if a zone is skipped. RGB lamps can only have a single zone. - -Tips and Tricks: - *Setting the brightness of an rgbw or a white bulb will set it to "night mode", which is dimmer than the lowest brightness setting - *White and rgb bulbs don't support absolute brightness setting, so we just send a brightness up/brightness down command depending - if we got a percentage above/below 50% respectively - *The only exception to the above is that white bulbs support a "maximum brightness" command, so we send that when we get 100% - *Implemented warmer/cooler for white lamps in a similar way to brightnes, except this time above/below 180 degrees on the colour wheel - *I welcome feedback on a better way to work the brightness/hue for white and rgb bulbs - -Troubleshooting: -The node-milight-promise library provides additional debugging output when the MILIGHT_DEBUG environmental variable is set - -TODO: - *Possibly build in some sort of state logging and persistance so that we can answswer HomeKit status queries to the best of our ability - -*/ - -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; - -module.exports = { - accessory: MiLightAccessory, - platform: MiLightPlatform -} - -function MiLightPlatform(log, config) { - this.log = log; - - this.config = config; -} - -MiLightPlatform.prototype = { - accessories: function(callback) { - var zones = []; - - // Various error checking - if (this.config.zones) { - var zoneLength = this.config.zones.length; - } else { - this.log("ERROR: Could not read zones from configuration."); - return; - } - - if (!this.config["type"]) { - this.log("INFO: Type not specified, defaulting to rgbw"); - this.config["type"] = "rgbw"; - } - - if (zoneLength == 0) { - this.log("ERROR: No zones found in configuration."); - return; - } else if (this.config["type"] == "rgb" && zoneLength > 1) { - this.log("WARNING: RGB lamps only have a single zone. Only the first defined zone will be used."); - zoneLength = 1; - } else if (zoneLength > 4) { - this.log("WARNING: Only a maximum of 4 zones are supported per bridge. Only recognizing the first 4 zones."); - zoneLength = 4; - } - - // Create lamp accessories for all of the defined zones - for (var i=0; i < zoneLength; i++) { - if (!!this.config.zones[i]) { - this.config["name"] = this.config.zones[i]; - this.config["zone"] = i+1; - lamp = new MiLightAccessory(this.log, this.config); - zones.push(lamp); - } - } - if (zones.length > 0) { - callback(zones); - } else { - this.log("ERROR: Unable to find any valid zones"); - return; - } - } -} - -function MiLightAccessory(log, config) { - this.log = log; - - // config info - this.ip_address = config["ip_address"]; - this.port = config["port"]; - this.name = config["name"]; - this.zone = config["zone"]; - this.type = config["type"]; - this.delay = config["delay"]; - this.repeat = config["repeat"]; - - this.light = new Milight({ - ip: this.ip_address, - port: this.port, - delayBetweenCommands: this.delay, - commandRepeat: this.repeat - }); - -} -MiLightAccessory.prototype = { - - setPowerState: function(powerOn, callback) { - if (powerOn) { - this.log("["+this.name+"] Setting power state to on"); - this.light.sendCommands(commands[this.type].on(this.zone)); - } else { - this.log("["+this.name+"] Setting power state to off"); - this.light.sendCommands(commands[this.type].off(this.zone)); - } - callback(); - }, - - setBrightness: function(level, callback) { - if (level == 0) { - // If brightness is set to 0, turn off the lamp - this.log("["+this.name+"] Setting brightness to 0 (off)"); - this.light.sendCommands(commands[this.type].off(this.zone)); - } else if (level <= 2 && (this.type == "rgbw" || this.type == "white")) { - // If setting brightness to 2 or lower, instead set night mode for lamps that support it - this.log("["+this.name+"] Setting night mode", level); - - this.light.sendCommands(commands[this.type].off(this.zone)); - // Ensure we're pausing for 100ms between these commands as per the spec - this.light.pause(100); - this.light.sendCommands(commands[this.type].nightMode(this.zone)); - - } else { - this.log("["+this.name+"] Setting brightness to %s", level); - - // Send on command to ensure we're addressing the right bulb - this.light.sendCommands(commands[this.type].on(this.zone)); - - // If this is an rgbw lamp, set the absolute brightness specified - if (this.type == "rgbw") { - this.light.sendCommands(commands.rgbw.brightness(level)); - } else { - // If this is an rgb or a white lamp, they only support brightness up and down. - // Set brightness up when value is >50 and down otherwise. Not sure how well this works real-world. - if (level >= 50) { - if (this.type == "white" && level == 100) { - // But the white lamps do have a "maximum brightness" command - this.light.sendCommands(commands.white.maxBright(this.zone)); - } else { - this.light.sendCommands(commands[this.type].brightUp()); - } - } else { - this.light.sendCommands(commands[this.type].brightDown()); - } - } - } - callback(); - }, - - setHue: function(value, callback) { - this.log("["+this.name+"] Setting hue to %s", value); - - var hue = Array(value, 0, 0); - - // Send on command to ensure we're addressing the right bulb - this.light.sendCommands(commands[this.type].on(this.zone)); - - if (this.type == "rgbw") { - if (value == 0) { - this.light.sendCommands(commands.rgbw.whiteMode(this.zone)); - } else { - this.light.sendCommands(commands.rgbw.hue(commands.rgbw.hsvToMilightColor(hue))); - } - } else if (this.type == "rgb") { - this.light.sendCommands(commands.rgb.hue(commands.rgbw.hsvToMilightColor(hue))); - } else if (this.type == "white") { - // Again, white lamps don't support setting an absolue colour temp, so trying to do warmer/cooler step at a time based on colour - if (value >= 180) { - this.light.sendCommands(commands.white.cooler()); - } else { - this.light.sendCommands(commands.white.warmer()); - } - } - callback(); - }, - - identify: function(callback) { - this.log("["+this.name+"] Identify requested!"); - callback(); // success - }, - - getServices: function() { - var informationService = new Service.AccessoryInformation(); - - informationService - .setCharacteristic(Characteristic.Manufacturer, "MiLight") - .setCharacteristic(Characteristic.Model, this.type) - .setCharacteristic(Characteristic.SerialNumber, "MILIGHT12345"); - - var lightbulbService = new Service.Lightbulb(); - - lightbulbService - .getCharacteristic(Characteristic.On) - .on('set', this.setPowerState.bind(this)); - - lightbulbService - .addCharacteristic(new Characteristic.Brightness()) - .on('set', this.setBrightness.bind(this)); - - lightbulbService - .addCharacteristic(new Characteristic.Hue()) - .on('set', this.setHue.bind(this)); - - return [informationService, lightbulbService]; - } -}; diff --git a/platforms/Nest.js b/platforms/Nest.js deleted file mode 100644 index 9d225c0..0000000 --- a/platforms/Nest.js +++ /dev/null @@ -1,397 +0,0 @@ -var types = require("hap-nodejs/accessories/types.js"); -var nest = require('unofficial-nest-api'); - -function NestPlatform(log, config){ - - // auth info - this.username = config["username"]; - this.password = config["password"]; - - this.log = log; -} - -NestPlatform.prototype = { - accessories: function(callback) { - this.log("Fetching Nest devices."); - - var that = this; - var foundAccessories = []; - - nest.login(this.username, this.password, function (err, data) { - if (err) { - that.log("There was a problem authenticating with Nest."); - } - else { - nest.fetchStatus(function (data) { - for (var deviceId in data.device) { - if (data.device.hasOwnProperty(deviceId)) { - var device = data.device[deviceId]; - // 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); - foundAccessories.push(accessory); - } - } - } - callback(foundAccessories) - }); - } - }); - } -} - -function NestThermostatAccessory(log, name, device, deviceId) { - // device info - if (name) { - this.name = name; - } else { - this.name = "Nest"; - } - this.model = device.model_version; - this.serial = device.serial_number; - this.deviceId = deviceId; - this.log = log; -} - -NestThermostatAccessory.prototype = { - getCurrentHeatingCooling: function(callback){ - - 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; - } - that.log("Current heating for " + this.name + "is: " + currentHeatingCooling); - callback(currentHeatingCooling); - }); - - - }, - - getTargetHeatingCoooling: function(callback){ - - var that = this; - - this.log("Checking target heating cooling for: " + this.name); - nest.fetchStatus(function (data) { - var device = data.device[that.deviceId]; - - var targetHeatingCooling = 0; - switch(device.target_temperature_type) { - case "off": - targetHeatingCooling = 0; - break; - case "heat": - targetHeatingCooling = 1; - break; - case "cool": - targetHeatingCooling = 2; - break; - case "range": - targetHeatingCooling = 3; - break; - default: - targetHeatingCooling = 0; - } - that.log("Current target heating for " + this.name + " is: " + targetHeatingCooling); - callback(targetHeatingCooling); - }); - }, - - getCurrentTemperature: function(callback){ - - var that = this; - - nest.fetchStatus(function (data) { - var device = data.shared[that.deviceId]; - that.log("Current temperature for " + this.name + " is: " + device.current_temperature); - callback(device.current_temperature); - }); - - - }, - - getTargetTemperature: function(callback){ - - var that = this; - - nest.fetchStatus(function (data) { - var device = data.shared[that.deviceId]; - that.log("Target temperature for " + this.name + " is: " + device.target_temperature); - callback(device.target_temperature); - }); - - - }, - - getTemperatureUnits: function(callback){ - - var that = this; - - nest.fetchStatus(function (data) { - var device = data.device[that.deviceId]; - var temperatureUnits = 0; - switch(device.temperature_scale) { - case "F": - that.log("Tempature unit for " + this.name + " is: " + "Fahrenheit"); - temperatureUnits = 1; - break; - case "C": - that.log("Tempature unit for " + this.name + " is: " + "Celsius"); - temperatureUnits = 0; - break; - default: - temperatureUnits = 0; - } - - callback(temperatureUnits); - }); - - - }, - - getCurrentRelativeHumidity: function(callback){ - - var that = this; - - nest.fetchStatus(function (data) { - var device = data.device[that.deviceId]; - that.log("Humidity for " + this.name + " is: " + device.current_humidity); - callback(device.current_humidity); - }) - - - }, - - setTargetHeatingCooling: function(targetHeatingCooling){ - - var that = this; - - var targetTemperatureType = 'off'; - switch(targetHeatingCooling) { - case 0: - targetTemperatureType = 'off'; - break; - case 1: - targetTemperatureType = 'heat'; - break; - case 2: - targetTemperatureType = 'cool'; - break; - case 3: - targetTemperatureType = 'range'; - break; - default: - targetTemperatureType = 'off'; - } - - this.log("Setting target heating cooling for " + this.name + " to: " + targetTemperatureType); - nest.setTargetTemperatureType(this.deviceId, targetTemperatureType); - - - }, - - setTargetTemperature: function(targetTemperature){ - - var that = this; - - this.log("Setting target temperature for " + this.name + " to: " + targetTemperature); - nest.setTemperature(this.deviceId, targetTemperature); - - - }, - - getServices: function() { - var that = this; - return [{ - sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of the accessory", - designedMaxLength: 255 - },{ - cType: types.MANUFACTURER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "Nest", - supportEvents: false, - supportBonjour: false, - manfDescription: "Manufacturer", - designedMaxLength: 255 - },{ - cType: types.MODEL_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.model, - supportEvents: false, - supportBonjour: false, - manfDescription: "Model", - designedMaxLength: 255 - },{ - cType: types.SERIAL_NUMBER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.serial, - supportEvents: false, - supportBonjour: false, - manfDescription: "SN", - designedMaxLength: 255 - },{ - cType: types.IDENTIFY_CTYPE, - onUpdate: null, - perms: ["pw"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Identify Accessory", - designedMaxLength: 1 - }] - },{ - sType: types.THERMOSTAT_STYPE, - characteristics: [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of thermostat", - designedMaxLength: 255 - },{ - cType: types.CURRENTHEATINGCOOLING_CTYPE, - onUpdate: null, - onRead: function(callback) { - that.getCurrentHeatingCooling(function(currentHeatingCooling){ - callback(currentHeatingCooling); - }); - }, - perms: ["pr","ev"], - format: "int", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Current Mode", - designedMaxLength: 1, - designedMinValue: 0, - designedMaxValue: 2, - designedMinStep: 1, - },{ - cType: types.TARGETHEATINGCOOLING_CTYPE, - onUpdate: function(value) { - that.setTargetHeatingCooling(value); - }, - onRead: function(callback) { - that.getTargetHeatingCoooling(function(targetHeatingCooling){ - callback(targetHeatingCooling); - }); - }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Target Mode", - designedMinValue: 0, - designedMaxValue: 3, - designedMinStep: 1, - },{ - cType: types.CURRENT_TEMPERATURE_CTYPE, - onUpdate: null, - onRead: function(callback) { - that.getCurrentTemperature(function(currentTemperature){ - callback(currentTemperature); - }); - }, - perms: ["pr","ev"], - format: "int", - initialValue: 20, - supportEvents: false, - supportBonjour: false, - manfDescription: "Current Temperature", - unit: "celsius" - },{ - cType: types.TARGET_TEMPERATURE_CTYPE, - onUpdate: function(value) { - that.setTargetTemperature(value); - }, - onRead: function(callback) { - that.getTargetTemperature(function(targetTemperature){ - callback(targetTemperature); - }); - }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: 20, - supportEvents: false, - supportBonjour: false, - manfDescription: "Target Temperature", - designedMinValue: 16, - designedMaxValue: 38, - designedMinStep: 1, - unit: "celsius" - },{ - cType: types.TEMPERATURE_UNITS_CTYPE, - onUpdate: null, - onRead: function(callback) { - that.getTemperatureUnits(function(temperatureUnits){ - callback(temperatureUnits); - }); - }, - perms: ["pr","ev"], - format: "int", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Unit", - },{ - cType: types.CURRENT_RELATIVE_HUMIDITY_CTYPE, - onUpdate: null, - onRead: function(callback) { - that.getCurrentRelativeHumidity(function(currentRelativeHumidity){ - callback(currentRelativeHumidity); - }); - }, - perms: ["pr","ev"], - format: "int", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Humidity", - }] - }]; - } -} - -module.exports.accessory = NestThermostatAccessory; -module.exports.platform = NestPlatform; diff --git a/platforms/PhilipsHue.js b/platforms/PhilipsHue.js deleted file mode 100644 index 40dcc33..0000000 --- a/platforms/PhilipsHue.js +++ /dev/null @@ -1,375 +0,0 @@ -// Philips Hue Platform Shim for HomeBridge -// -// Remember to add platform to config.json. Example: -// "platforms": [ -// { -// "platform": "PhilipsHue", -// "name": "Philips Hue", -// "ip_address": "127.0.0.1", -// "username": "252deadbeef0bf3f34c7ecb810e832f" -// } -// ], -// -// If you do not know the IP address of your Hue Bridge, simply leave it blank and your Bridge -// will be discovered automatically. -// -// If you do not have a "username" for your Hue API already, simply leave the field blank and -// you will be prompted to press the link button on your Hue Bridge before running HomeBridge. -// A username will be created for you and printed out, then the server will exit so you may -// enter it in your config.json. -// -// -// 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. -// - -/* jslint node: true */ -/* globals require: false */ -/* globals config: false */ - -"use strict"; - -var hue = require("node-hue-api"), - HueApi = hue.HueApi, - lightState = hue.lightState; - -var types = require("hap-nodejs/accessories/types.js"); - -function PhilipsHuePlatform(log, config) { - this.log = log; - this.ip_address = config["ip_address"]; - this.username = config["username"]; -} - -function PhilipsHueAccessory(log, device, api) { - this.id = device.id; - this.name = device.name; - this.model = device.modelid; - this.device = device; - this.api = api; - this.log = log; -} - -// Get the ip address of the first available bridge with meethue.com or a network scan. -var locateBridge = function (callback) { - var that = this; - - // Report the results of the scan to the user - var getIp = function (err, bridges) { - if (!bridges || bridges.length === 0) { - that.log("No Philips Hue bridges found."); - callback(err || new Error("No bridges found")); - return; - } - - if (bridges.length > 1) { - that.log("Warning: Multiple Philips Hue bridges detected. The first bridge will be used automatically. To use a different bridge, set the `ip_address` manually in the configuration."); - } - - that.log( - "Philips Hue bridges found:\n" + - (bridges.map(function (bridge) { - // Bridge name is only returned from meethue.com so use id instead if it isn't there - return "\t" + bridge.ipaddress + ' - ' + (bridge.name || bridge.id); - })).join("\n") - ); - - callback(null, bridges[0].ipaddress); - }; - - // Try to discover the bridge ip using meethue.com - that.log("Attempting to discover Philips Hue bridge with meethue.com..."); - hue.nupnpSearch(function (locateError, bridges) { - if (locateError) { - that.log("Philips Hue bridge discovery with meethue.com failed. Register your bridge with the meethue.com for more reliable discovery."); - - that.log("Attempting to discover Philips Hue bridge with network scan..."); - - // Timeout after one minute - hue.upnpSearch(60000) - .then(function (bridges) { - that.log("Scan complete"); - getIp(null, bridges); - }) - .fail(function (scanError) { - that.log("Philips Hue bridge discovery with network scan failed. Check your network connection or set ip_address manually in configuration."); - getIp(new Error("Scan failed: " + scanError.message)); - }).done(); - } else { - getIp(null, bridges); - } - }); -}; - -PhilipsHuePlatform.prototype = { - - accessories: function(callback) { - this.log("Fetching Philips Hue lights..."); - var that = this; - var getLights = function () { - var api = new HueApi(that.ip_address, that.username); - - // Connect to the API - // Get a dump of all lights, so as not to hit rate limiting for installations with larger amounts of bulbs - - api.fullState(function(err, response) { - if (err) throw err; - - var foundAccessories = []; - for (var deviceId in response.lights) { - var device = response.lights[deviceId]; - device.id = deviceId; - var accessory = new PhilipsHueAccessory(that.log, device, api); - foundAccessories.push(accessory); - } - callback(foundAccessories); - - }); - }; - - // Create a new user if needed - function checkUsername() { - if (!that.username) { - var api = new HueApi(that.ip_address); - api.createUser(that.ip_address, null, null, function(err, user) { - - // try and help explain this particular error - if (err && err.message == "link button not pressed") - throw "Please press the link button on your Philips Hue bridge, then start the HomeBridge server within 30 seconds."; - - if (err) throw err; - - throw "Created a new username " + JSON.stringify(user) + " for your Philips Hue. Please add it to your config.json then start the HomeBridge server again: "; - }); - } - else { - getLights(); - } - } - - // Discover the bridge if needed - if (!this.ip_address) { - locateBridge.call(this, function (err, ip_address) { - if (err) throw err; - - // TODO: Find a way to persist this - that.ip_address = ip_address; - that.log("Save the Philips Hue bridge ip address "+ ip_address +" to your config to skip discovery."); - checkUsername(); - }); - } else { - checkUsername(); - } - } -}; - -PhilipsHueAccessory.prototype = { - // Convert 0-65535 to 0-360 - hueToArcDegrees: function(value) { - value = value/65535; - value = value*100; - value = Math.round(value); - return value; - }, - // Convert 0-360 to 0-65535 - arcDegreesToHue: function(value) { - value = value/360; - value = value*65535; - value = Math.round(value); - return value; - }, - // Convert 0-255 to 0-100 - bitsToPercentage: function(value) { - value = value/255; - value = value*100; - value = Math.round(value); - return value; - }, - // Create and set a light state - executeChange: function(api, device, characteristic, value) { - var that = this; - var state = lightState.create(); - switch(characteristic.toLowerCase()) { - case 'identify': - state.alert('select'); - break; - case 'power': - if (value) { - state.on(); - } - else { - state.off(); - } - break; - case 'hue': - state.hue(this.arcDegreesToHue(value)); - break; - case 'brightness': - state.brightness(value); - break; - case 'saturation': - state.saturation(value); - break; - } - api.setLightState(device.id, state, function(err, lights) { - if (!err) { - that.log(device.name + ", characteristic: " + characteristic + ", value: " + value + "."); - } - else { - if (err.code == "ECONNRESET") { - setTimeout(function() { - that.executeChange(api, device, characteristic, value); - }, 300); - } else { - that.log(err); - } - } - }); - }, - // 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: "%" - } - ]; - // 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: "%" - }); - } - 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; - } -}; - -module.exports.platform = PhilipsHuePlatform; diff --git a/platforms/SmartThings.js b/platforms/SmartThings.js deleted file mode 100644 index 470ee55..0000000 --- a/platforms/SmartThings.js +++ /dev/null @@ -1,242 +0,0 @@ -// SmartThings JSON API SmartApp required -// https://github.com/jnewland/SmartThings/blob/master/JSON.groovy -// -var types = require("hap-nodejs/accessories/types.js"); -var request = require("request"); - -function SmartThingsPlatform(log, config){ - this.log = log; - this.app_id = config["app_id"]; - this.access_token = config["access_token"]; -} - -SmartThingsPlatform.prototype = { - accessories: function(callback) { - this.log("Fetching SmartThings devices..."); - - var that = this; - var foundAccessories = []; - - request.get({ - url: "https://graph.api.smartthings.com/api/smartapps/installations/"+this.app_id+"/devices?access_token="+this.access_token, - json: true - }, function(err, response, json) { - if (!err && response.statusCode == 200) { - if (json['switches'] != undefined) { - json['switches'].map(function(s) { - accessory = new SmartThingsAccessory(that.log, s.name, s.commands); - foundAccessories.push(accessory); - }) - } - if (json['hues'] != undefined) { - json['hues'].map(function(s) { - accessory = new SmartThingsAccessory(that.log, s.name, s.commands); - foundAccessories.push(accessory); - }) - } - callback(foundAccessories); - } else { - that.log("There was a problem authenticating with SmartThings."); - } - }); - - } -} - -function SmartThingsAccessory(log, name, commands) { - // device info - this.name = name; - this.commands = commands; - this.log = log; -} - -SmartThingsAccessory.prototype = { - - command: function(c,value) { - this.log(this.name + " sending command " + c); - var url = this.commands[c]; - if (value != undefined) { - url = this.commands[c] + "&value="+value - } - - var that = this; - request.put({ - url: url - }, function(err, response) { - if (err) { - that.log("There was a problem sending command " + c + " to" + that.name); - that.log(url); - } else { - that.log(that.name + " sent command " + c); - } - }) - }, - - 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: "SmartThings", - 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 - } - ] - }, - - controlCharacteristics: function(that) { - cTypes = [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: true, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - }] - - if (this.commands['on'] != undefined) { - cTypes.push({ - cType: types.POWER_STATE_CTYPE, - onUpdate: function(value) { - if (value == 0) { - that.command("off") - } else { - that.command("on") - } - }, - perms: ["pw","pr","ev"], - format: "bool", - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Change the power state", - designedMaxLength: 1 - }) - } - - if (this.commands['on'] != undefined) { - cTypes.push({ - cType: types.BRIGHTNESS_CTYPE, - onUpdate: function(value) { that.command("setLevel", value); }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Adjust Brightness of Light", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - }) - } - - if (this.commands['setHue'] != undefined) { - cTypes.push({ - cType: types.HUE_CTYPE, - onUpdate: function(value) { that.command("setHue", value); }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Adjust Hue of Light", - designedMinValue: 0, - designedMaxValue: 360, - designedMinStep: 1, - unit: "arcdegrees" - }) - } - - if (this.commands['setSaturation'] != undefined) { - cTypes.push({ - cType: types.SATURATION_CTYPE, - onUpdate: function(value) { that.command("setSaturation", value); }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Adjust Brightness of Light", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - }) - } - - return cTypes - }, - - sType: function() { - if (this.commands['setLevel'] != undefined) { - return types.LIGHTBULB_STYPE - } else { - return types.SWITCH_STYPE - } - }, - - getServices: function() { - var that = this; - var services = [{ - sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: this.informationCharacteristics(), - }, - { - sType: this.sType(), - characteristics: this.controlCharacteristics(that) - }]; - this.log("Loaded services for " + this.name) - return services; - } -}; - -module.exports.accessory = SmartThingsAccessory; -module.exports.platform = SmartThingsPlatform; diff --git a/platforms/Sonos.js b/platforms/Sonos.js deleted file mode 100644 index 812a803..0000000 --- a/platforms/Sonos.js +++ /dev/null @@ -1,188 +0,0 @@ -var types = require("hap-nodejs/accessories/types.js"); -var sonos = require('sonos'); - -function SonosPlatform(log, config){ - this.log = log; - this.config = config; - this.name = config["name"]; - this.playVolume = config["play_volume"]; - // timeout for device discovery - this.discoveryTimeout = (config.deviceDiscoveryTimeout || 10)*1000; // assume 10sec as a default -} - -SonosPlatform.prototype = { - accessories: function(callback) { - this.log("Fetching Sonos devices."); - var that = this; - - // track found devices so we don't add duplicates - var roomNamesFound = {}; - - // collector array for the devices from callbacks - var devicesFound = []; - // tell the sonos callbacks if timeout already occured - var timeout = false; - - // the timeout event will push the accessories back - setTimeout(function(){ - timeout=true; - callback(devicesFound); - }, this.discoveryTimeout); - - - sonos.search(function (device) { - that.log("Found device at " + device.host); - - device.deviceDescription(function (err, description) { - if (description["zoneType"] != '11' && description["zoneType"] != '8') { // 8 is the Sonos SUB - var roomName = description["roomName"]; - - if (!roomNamesFound[roomName]) { - roomNamesFound[roomName] = true; - that.log("Found playable device - " + roomName); - if (timeout) { - that.log("Ignored: Discovered after timeout (Set deviceDiscoveryTimeout parameter in Sonos section of config.json)"); - } - // device is an instance of sonos.Sonos - var accessory = new SonosAccessory(that.log, that.config, device, description); - // add it to the collector array - devicesFound.push(accessory); - } - else { - that.log("Ignoring playable device with duplicate room name - " + roomName); - } - } - }); - }); - } -}; - -function SonosAccessory(log, config, device, description) { - this.log = log; - this.config = config; - this.device = device; - this.description = description; - - this.name = this.description["roomName"] + " " + this.config["name"]; - this.serviceName = this.description["roomName"] + " Speakers"; - this.playVolume = this.config["play_volume"]; -} - -SonosAccessory.prototype = { - - setPlaying: function(playing) { - - if (!this.device) { - this.log("No device found (yet?)"); - return; - } - - var that = this; - - if (playing) { - this.device.play(function(err, success) { - that.log("Playback attempt with success: " + success); - }); - - if (this.playVolume) { - this.device.setVolume(this.playVolume, function(err, success) { - if (!err) { - that.log("Set volume to " + that.playVolume); - } - else { - that.log("Problem setting volume: " + err); - } - }); - } - } - else { - this.device.stop(function(err, success) { - that.log("Stop attempt with success: " + success); - }); - } - }, - - 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: "Sonos", - 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.SWITCH_STYPE, - characteristics: [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.serviceName, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - },{ - cType: types.POWER_STATE_CTYPE, - onUpdate: function(value) { that.setPlaying(value); }, - perms: ["pw","pr","ev"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Change the playback state of the sonos", - designedMaxLength: 1 - }] - }]; - } -}; - -module.exports.accessory = SonosAccessory; -module.exports.platform = SonosPlatform; diff --git a/platforms/Telldus.js b/platforms/Telldus.js deleted file mode 100644 index c192a31..0000000 --- a/platforms/Telldus.js +++ /dev/null @@ -1,265 +0,0 @@ -var types = require("hap-nodejs/accessories/types.js"); -var telldus = require('telldus'); - -function TelldusPlatform(log, config) { - var that = this; - that.log = log; -} - -TelldusPlatform.prototype = { - - accessories: function(callback) { - var that = this; - - that.log("Fetching devices..."); - - var devices = telldus.getDevicesSync(); - - that.log("Found " + devices.length + " devices..."); - - var foundAccessories = []; - - // Clean non device - for (var i = 0; i < devices.length; i++) { - if (devices[i].type != 'DEVICE') { - devices.splice(i, 1); - } - } - - for (var i = 0; i < devices.length; i++) { - if (devices[i].type === 'DEVICE') { - TelldusAccessory.create(that.log, devices[i], function(err, accessory) { - if (!!err) that.log("Couldn't load device info"); - foundAccessories.push(accessory); - if (foundAccessories.length >= devices.length) { - callback(foundAccessories); - } - }); - } - } - } -}; - -var TelldusAccessory = function TelldusAccessory(log, device) { - - this.log = log; - - var m = device.model.split(':'); - - this.dimTimeout = false; - - // Set accessory info - this.device = device; - this.id = device.id; - this.name = device.name; - this.manufacturer = "Telldus"; // NOTE: Change this later - this.model = device.model; - this.status = device.status; - switch (device.status.name) { - case 'OFF': - this.state = 0; - this.stateValue = 0; - break; - case 'ON': - this.state = 2; - this.stateValue = 1; - break; - case 'DIM': - this.state = 16; - this.stateValue = device.status.level; - break; - } -}; - -TelldusAccessory.create = function (log, device, callback) { - - callback(null, new TelldusAccessory(log, device)); - -}; - -TelldusAccessory.prototype = { - - dimmerValue: function() { - - if (this.state === 1) { - return 100; - } - - if (this.state === 16 && this.stateValue != "unde") { - return parseInt(this.stateValue * 100 / 255); - } - - return 0; - }, - - informationCharacteristics: function() { - var that = this; - - informationCharacteristics = [ - { - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: that.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of the accessory", - designedMaxLength: 255 - },{ - cType: types.MANUFACTURER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: that.manufacturer, - 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: "A1S2NASF88EW", - supportEvents: false, - supportBonjour: false, - manfDescription: "SN", - designedMaxLength: 255 - },{ - cType: types.IDENTIFY_CTYPE, - onUpdate: function () { - telldus.turnOff(that.id, function(err){ - if (!!err) that.log("Error: " + err.message); - telldus.turnOn(that.id, function(err){ - if (!!err) that.log("Error: " + err.message); - telldus.turnOff(that.id, function(err){ - if (!!err) that.log("Error: " + err.message); - telldus.turnOn(that.id, function(err){ - if (!!err) that.log("Error: " + err.message); - }); - }); - }); - }); - }, - perms: ["pw"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Identify Accessory", - designedMaxLength: 1 - } - ]; - return informationCharacteristics; - }, - - controlCharacteristics: function() { - var that = this; - - cTypes = [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: that.name, - supportEvents: true, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - }] - - cTypes.push({ - cType: types.POWER_STATE_CTYPE, - onUpdate: function(value) { - if (value) { - telldus.turnOn(that.id, function(err){ - if (!!err) { - that.log("Error: " + err.message) - } else { - that.log(that.name + " - Updated power state: ON"); - } - }); - } else { - telldus.turnOff(that.id, function(err){ - if (!!err) { - that.log("Error: " + err.message) - } else { - that.log(that.name + " - Updated power state: OFF"); - } - }); - } - }, - perms: ["pw","pr","ev"], - format: "bool", - initialValue: (that.state != 2 && (that.state === 16 && that.stateValue != 0)) ? 1 : 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Change the power state", - designedMaxLength: 1 - }) - - if (that.model === "selflearning-dimmer") { - cTypes.push({ - cType: types.BRIGHTNESS_CTYPE, - onUpdate: function (value) { - if (that.dimTimeout) { - clearTimeout(that.dimTimeout); - } - - that.dimTimeout = setTimeout(function(){ - telldus.dim(that.id, (255 * (value / 100)), function(err, result){ - if (!!err) { - that.log("Error: " + err.message); - } else { - that.log(that.name + " - Updated brightness: " + value); - } - }); - that.dimTimeout = false; - }, 250); - }, - perms: ["pw", "pr", "ev"], - format: "int", - initialValue: that.dimmerValue(), - supportEvents: true, - supportBonjour: false, - manfDescription: "Adjust Brightness of Light", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - }) - } - - return cTypes - }, - - getServices: function() { - - var services = [ - { - sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: this.informationCharacteristics() - }, - { - sType: types.LIGHTBULB_STYPE, - characteristics: this.controlCharacteristics() - } - ]; - - return services; - } -}; - -module.exports.platform = TelldusPlatform; -module.exports.accessory = TelldusAccessory; diff --git a/platforms/TelldusLive.js b/platforms/TelldusLive.js deleted file mode 100644 index 0e861b5..0000000 --- a/platforms/TelldusLive.js +++ /dev/null @@ -1,265 +0,0 @@ -var types = require("hap-nodejs/accessories/types.js"); -var TellduAPI = require("telldus-live"); - -function TelldusLivePlatform(log, config) { - var that = this; - that.log = log; - - that.isLoggedIn = false; - - // Login to Telldus Live! - that.cloud = new TellduAPI.TelldusAPI({publicKey: config["public_key"], privateKey: config["private_key"]}) - .login(config["token"], config["token_secret"], function(err, user) { - if (!!err) that.log("Login error: " + err.message); - that.log("User logged in: " + user.firstname + " " + user.lastname + ", " + user.email); - that.isLoggedIn = true; - } - ); -} - -TelldusLivePlatform.prototype = { - - accessories: function(callback) { - var that = this; - - that.log("Fetching devices..."); - - that.cloud.getDevices(function(err, devices) { - - if (!!err) return that.log('getDevices: ' + err.message); - - var foundAccessories = []; - - // Clean non device - for (var i = 0; i < devices.length; i++) { - if (devices[i].type != 'device') { - devices.splice(i, 1); - } - } - - for (var i = 0; i < devices.length; i++) { - if (devices[i].type === 'device') { - TelldusLiveAccessory.create(that.log, devices[i], that.cloud, function(err, accessory) { - if (!!err) that.log("Couldn't load device info"); - foundAccessories.push(accessory); - if (foundAccessories.length >= devices.length) { - callback(foundAccessories); - } - }); - } - } - - }); - } -}; - -var TelldusLiveAccessory = function TelldusLiveAccessory(log, cloud, device) { - - this.log = log; - this.cloud = cloud; - - var m = device.model.split(':'); - - // Set accessory info - this.device = device; - this.id = device.id; - this.name = device.name; - this.manufacturer = m[1]; - this.model = m[0]; - this.state = device.state; - this.stateValue = device.stateValue; - this.status = device.status; -}; - -TelldusLiveAccessory.create = function (log, device, cloud, callback) { - - cloud.getDeviceInfo(device, function(err, device) { - - if (!!err) that.log("Couldn't load device info"); - - callback(err, new TelldusLiveAccessory(log, cloud, device)); - }); -}; - -TelldusLiveAccessory.prototype = { - - dimmerValue: function() { - - if (this.state === 1) { - return 100; - } - - if (this.state === 16 && this.stateValue != "unde") { - return parseInt(this.stateValue * 100 / 255); - } - - return 0; - }, - - informationCharacteristics: function() { - var that = this; - - informationCharacteristics = [ - { - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: that.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of the accessory", - designedMaxLength: 255 - },{ - cType: types.MANUFACTURER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: that.manufacturer, - 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: "A1S2NASF88EW", - supportEvents: false, - supportBonjour: false, - manfDescription: "SN", - designedMaxLength: 255 - },{ - cType: types.IDENTIFY_CTYPE, - onUpdate: function () { - that.cloud.onOffDevice(that.device, true, function(err, result) { - if (!!err) that.log("Error: " + err.message); - that.cloud.onOffDevice(that.device, false, function(err, result) { - if (!!err) that.log("Error: " + err.message); - that.cloud.onOffDevice(that.device, true, function(err, result) { - if (!!err) that.log("Error: " + err.message); - that.cloud.onOffDevice(that.device, false, function(err, result) { - if (!!err) that.log("Error: " + err.message); - that.cloud.onOffDevice(that.device, true, function(err, result) { - if (!!err) that.log("Error: " + err.message); - }) - }) - }) - }) - }) - }, - perms: ["pw"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Identify Accessory", - designedMaxLength: 1 - } - ]; - return informationCharacteristics; - }, - - controlCharacteristics: function() { - var that = this; - - cTypes = [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: that.name, - supportEvents: true, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - }] - - cTypes.push({ - cType: types.POWER_STATE_CTYPE, - onUpdate: function(value) { - if (value == 1) { - that.cloud.onOffDevice(that.device, value, function(err, result) { - if (!!err) { - that.log("Error: " + err.message) - } else { - that.log(that.name + " - Updated power state: " + (value === true ? 'ON' : 'OFF')); - } - }); - } else { - that.cloud.onOffDevice(that.device, value, function(err, result) { - if (!!err) { - that.log("Error: " + err.message) - } else { - that.log(that.name + " - Updated power state: " + (value === true ? 'ON' : 'OFF')); - } - }); - } - }, - perms: ["pw","pr","ev"], - format: "bool", - initialValue: (that.state != 2 && (that.state === 16 && that.stateValue != "0")) ? 1 : 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Change the power state", - designedMaxLength: 1 - }) - - if (that.model === "selflearning-dimmer") { - cTypes.push({ - cType: types.BRIGHTNESS_CTYPE, - onUpdate: function (value) { - that.cloud.dimDevice(that.device, (255 * (value / 100)), function (err, result) { - if (!!err) { - that.log("Error: " + err.message); - } else { - that.log(that.name + " - Updated brightness: " + value); - } - }); - }, - perms: ["pw", "pr", "ev"], - format: "int", - initialValue: that.dimmerValue(), - supportEvents: true, - supportBonjour: false, - manfDescription: "Adjust Brightness of Light", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - }) - } - - return cTypes - }, - - getServices: function() { - - var services = [ - { - sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: this.informationCharacteristics() - }, - { - sType: types.LIGHTBULB_STYPE, - characteristics: this.controlCharacteristics() - } - ]; - - return services; - } -}; - -module.exports.platform = TelldusLivePlatform; -module.exports.accessory = TelldusLiveAccessory; \ No newline at end of file diff --git a/platforms/Wink.js b/platforms/Wink.js deleted file mode 100644 index d9de07f..0000000 --- a/platforms/Wink.js +++ /dev/null @@ -1,252 +0,0 @@ -var types = require("hap-nodejs/accessories/types.js"); -var wink = require('wink-js'); - -var model = { - light_bulbs: require('wink-js/lib/model/light') -}; - - -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; -} - -WinkPlatform.prototype = { - accessories: function(callback) { - this.log("Fetching Wink devices."); - - var that = this; - var foundAccessories = []; - - wink.init({ - "client_id": this.client_id, - "client_secret": this.client_secret, - "username": this.username, - "password": this.password - }, function(auth_return) { - if ( auth_return === undefined ) { - that.log("There was a problem authenticating with Wink."); - } else { - // success - wink.user().devices('light_bulbs', function(devices) { - for (var i=0; i= this.expectedDevices) - timeoutFunction(); // We're done, call the timeout function now. - }.bind(this) - ); - }.bind(this); - - // process manually specified devices... - for(var key in this.manualAddresses){ - if(!this.manualAddresses.hasOwnProperty(key)) continue; - setupFromService({ - name: key, - host: this.manualAddresses[key], - port: 80 - }); - } - - browser.on('serviceUp', setupFromService); - browser.start(); - - // The callback can only be called once...so we'll have to find as many as we can - // in a fixed time and then call them in. - var timeoutFunction = function(){ - if(accessories.length >= that.expectedDevices){ - clearTimeout(timer); - } else { - timeElapsed += checkCyclePeriod; - if(timeElapsed > that.discoveryTimeout * 1000){ - that.log("Waited " + that.discoveryTimeout + " seconds, stopping discovery."); - } else { - timer = setTimeout(timeoutFunction, checkCyclePeriod); - return; - } - } - browser.stop(); - browser.removeAllListeners('serviceUp'); - that.log("Discovery finished, found " + accessories.length + " Yamaha AVR devices."); - callback(accessories); - }; - timer = setTimeout(timeoutFunction, checkCyclePeriod); - } -}; - -function YamahaAVRAccessory(log, config, name, yamaha, sysConfig) { - this.log = log; - this.config = config; - this.yamaha = yamaha; - this.sysConfig = sysConfig; - - this.nameSuffix = config["name_suffix"] || " Speakers"; - this.name = name; - this.serviceName = name + this.nameSuffix; - this.setMainInputTo = config["setMainInputTo"]; - this.playVolume = this.config["play_volume"]; - this.minVolume = config["min_volume"] || -50.0; - this.maxVolume = config["max_volume"] || -20.0; - this.gapVolume = this.maxVolume - this.minVolume; -} - -YamahaAVRAccessory.prototype = { - - setPlaying: function(playing) { - var that = this; - var yamaha = this.yamaha; - - if (playing) { - - return yamaha.powerOn().then(function(){ - if (that.playVolume) return yamaha.setVolumeTo(that.playVolume*10); - else return Q(); - }).then(function(){ - if (that.setMainInputTo) return yamaha.setMainInputTo(that.setMainInputTo); - else return Q(); - }).then(function(){ - if (that.setMainInputTo == "AirPlay") return yamaha.SendXMLToReceiver( - 'Play' - ); - else return Q(); - }); - } - else { - return yamaha.powerOff(); - } - }, - - getServices: function() { - var that = this; - var informationService = new Service.AccessoryInformation(); - var yamaha = this.yamaha; - - informationService - .setCharacteristic(Characteristic.Name, this.name) - .setCharacteristic(Characteristic.Manufacturer, "Yamaha") - .setCharacteristic(Characteristic.Model, this.sysConfig.YAMAHA_AV.System[0].Config[0].Model_Name[0]) - .setCharacteristic(Characteristic.SerialNumber, this.sysConfig.YAMAHA_AV.System[0].Config[0].System_ID[0]); - - var switchService = new Service.Switch("Power State"); - switchService.getCharacteristic(Characteristic.On) - .on('get', function(callback, context){ - yamaha.isOn().then(function(result){ - callback(false, result); - }.bind(this)); - }.bind(this)) - .on('set', function(powerOn, callback){ - this.setPlaying(powerOn).then(function(){ - callback(false, powerOn); - }, function(error){ - callback(error, !powerOn); //TODO: Actually determine and send real new status. - }); - }.bind(this)); - - var audioDeviceService = new YamahaAVRPlatform.AudioDeviceService("Audio Functions"); - audioDeviceService.getCharacteristic(YamahaAVRPlatform.AudioVolume) - .on('get', function(callback, context){ - yamaha.getBasicInfo().done(function(basicInfo){ - var v = basicInfo.getVolume()/10.0; - var p = 100 * ((v - that.minVolume) / that.gapVolume); - p = p < 0 ? 0 : p > 100 ? 100 : Math.round(p); - debug("Got volume percent of " + p + "%"); - callback(false, p); - }); - }) - .on('set', function(p, callback){ - var v = ((p / 100) * that.gapVolume) + that.minVolume; - v = Math.round(v*10.0); - debug("Setting volume to " + v); - yamaha.setVolumeTo(v).then(function(){ - callback(false, p); - }); - }) - .getValue(null, null); // force an asynchronous get - - - return [informationService, switchService, audioDeviceService]; - - } -}; - -module.exports.accessory = YamahaAVRAccessory; -module.exports.platform = YamahaAVRPlatform; diff --git a/platforms/ZWayServer.js b/platforms/ZWayServer.js deleted file mode 100644 index f456cff..0000000 --- a/platforms/ZWayServer.js +++ /dev/null @@ -1,935 +0,0 @@ -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 request = require("request"); -var tough = require('tough-cookie'); -var Q = require("q"); - -function ZWayServerPlatform(log, config){ - this.log = log; - this.url = config["url"]; - this.login = config["login"]; - this.password = config["password"]; - this.opt_in = config["opt_in"]; - this.name_overrides = config["name_overrides"]; - this.batteryLow = config["battery_low_level"] || 15; - this.pollInterval = config["poll_interval"] || 2; - this.splitServices= config["split_services"] || false; - this.lastUpdate = 0; - this.cxVDevMap = {}; - this.vDevStore = {}; - this.sessionId = ""; - this.jar = request.jar(new tough.CookieJar()); -} - -ZWayServerPlatform.getVDevTypeKey = function(vdev){ - return vdev.deviceType + (vdev.metrics && vdev.metrics.probeTitle ? "." + vdev.metrics.probeTitle : "") -} - -ZWayServerPlatform.prototype = { - - zwayRequest: function(opts){ - var that = this; - var deferred = Q.defer(); - - opts.jar = true;//this.jar; - opts.json = true; - opts.headers = { - "Cookie": "ZWAYSession=" + this.sessionId - }; - - request(opts, function(error, response, body){ - if(response.statusCode == 401){ - debug("Authenticating..."); - request({ - method: "POST", - url: that.url + 'ZAutomation/api/v1/login', - body: { //JSON.stringify({ - "form": true, - "login": that.login, - "password": that.password, - "keepme": false, - "default_ui": 1 - }, - headers: { - "Accept": "application/json", - "Content-Type": "application/json" - }, - json: true, - jar: true//that.jar - }, function(error, response, body){ - if(response.statusCode == 200){ - that.sessionId = body.data.sid; - opts.headers["Cookie"] = "ZWAYSession=" + that.sessionId; - debug("Authenticated. Resubmitting original request..."); - request(opts, function(error, response, body){ - if(response.statusCode == 200){ - deferred.resolve(body); - } else { - deferred.reject(response); - } - }); - } else { - deferred.reject(response); - } - }); - } else if(response.statusCode == 200) { - deferred.resolve(body); - } else { - deferred.reject(response); - } - }); - return deferred.promise; - } - , - getTagValue: function(vdev, tagStem){ - if(!(vdev.tags && vdev.tags.length > 0)) return false; - var tagStem = "Homebridge." + tagStem; - if(vdev.tags.indexOf(tagStem) >= 0) return true; - var tags = vdev.tags, l = tags.length, tag; - for(var i = 0; i < l; i++){ - tag = tags[i]; - if(tag.indexOf(tagStem + ":") === 0){ - return tag.substr(tagStem.length + 1); - } - } - return false; - } - , - accessories: function(callback) { - debug("Fetching Z-Way devices..."); - - //TODO: Unify this with getVDevServices, so there's only one place with mapping between service and vDev type. - //Note: Order matters! - var primaryDeviceClasses = [ - "thermostat", - "switchMultilevel", - "switchBinary", - "sensorBinary.Door/Window", - "sensorMultilevel.Temperature" - ]; - - var that = this; - var foundAccessories = []; - - this.zwayRequest({ - method: "GET", - url: this.url + 'ZAutomation/api/v1/devices' - }).then(function(result){ - this.lastUpdate = result.data.updateTime; - - var devices = result.data.devices; - var groupedDevices = {}; - for(var i = 0; i < devices.length; i++){ - var vdev = devices[i]; - if(this.getTagValue("Skip")) { debug("Tag says skip!"); continue; } - if(this.opt_in && !this.getTagValue(vdev, "Include")) continue; - - var gdid = this.getTagValue(vdev, "Accessory.Id"); - if(!gdid){ - gdid = vdev.id.replace(/^(.*?)_zway_(\d+-\d+)-\d.*/, '$1_$2'); - } - - var gd = groupedDevices[gdid] || (groupedDevices[gdid] = { devices: [], types: {}, extras: {}, primary: undefined, cxmap: {} }); - - gd.devices.push(vdev); - var vdevIndex = gd.devices.length - 1; - - var tk = ZWayServerPlatform.getVDevTypeKey(vdev); - - // If this is explicitly set as primary, set it now... - if(this.getTagValue(vdev, "IsPrimary")){ - // everybody out of the way! Can't be in "extras" if you're the primary... - if(gd.types[tk] !== undefined){ - gd.extras[tk] = gd.extras[tk] || []; - gd.extras[tk].push(gd.types[tk]); - delete gd.types[tk]; // clear the way for this one to be set here below... - } - gd.primary = vdevIndex; - //gd.types[tk] = gd.primary; - } - - if(gd.types[tk] === undefined){ - gd.types[tk] = vdevIndex; - } else { - gd.extras[tk] = gd.extras[tk] || []; - gd.extras[tk].push(vdevIndex); - } - if(tk !== vdev.deviceType) gd.types[vdev.deviceType] = vdevIndex; // also include the deviceType only as a possibility - - // Create a map entry when Homebridge.Characteristic.Type is set... - var ctype = this.getTagValue(vdev, "Characteristic.Type"); - if(ctype && Characteristic[ctype]){ - var cx = new Characteristic[ctype](); - gd.cxmap[cx.UUID] = vdevIndex; - } - } - - for(var gdid in groupedDevices) { - if(!groupedDevices.hasOwnProperty(gdid)) continue; - - // Debug/log... - debug('Got grouped device ' + gdid + ' consiting of devices:'); - var gd = groupedDevices[gdid]; - for(var j = 0; j < gd.devices.length; j++){ - debug(gd.devices[j].id + " - " + gd.devices[j].deviceType + (gd.devices[j].metrics && gd.devices[j].metrics.probeTitle ? "." + gd.devices[j].metrics.probeTitle : "")); - } - - var accessory = null; - if(gd.primary !== undefined){ - var pd = gd.devices[gd.primary]; - var name = pd.metrics && pd.metrics.title ? pd.metrics.title : pd.id; - accessory = new ZWayServerAccessory(name, gd, that); - } - else for(var ti = 0; ti < primaryDeviceClasses.length; ti++){ - if(gd.types[primaryDeviceClasses[ti]] !== undefined){ - gd.primary = gd.types[primaryDeviceClasses[ti]]; - var pd = gd.devices[gd.primary]; - var name = pd.metrics && pd.metrics.title ? pd.metrics.title : pd.id; - //debug("Using primary device with type " + primaryDeviceClasses[ti] + ", " + name + " (" + pd.id + ") as primary."); - accessory = new ZWayServerAccessory(name, gd, that); - break; - } - } - - if(!accessory) - debug("WARN: Didn't find suitable device class!"); - else - foundAccessories.push(accessory); - - } - callback(foundAccessories); - - // Start the polling process... - this.pollingTimer = setTimeout(this.pollUpdate.bind(this), this.pollInterval*1000); - - }.bind(this)); - - } - , - - pollUpdate: function(){ - //debug("Polling for updates since " + this.lastUpdate + "..."); - return this.zwayRequest({ - method: "GET", - url: this.url + 'ZAutomation/api/v1/devices', - qs: {since: this.lastUpdate} - }).then(function(result){ - this.lastUpdate = result.data.updateTime; - if(result.data && result.data.devices && result.data.devices.length){ - var updates = result.data.devices; - debug("Got " + updates.length + " updates."); - for(var i = 0; i < updates.length; i++){ - var upd = updates[i]; - if(this.cxVDevMap[upd.id]){ - var vdev = this.vDevStore[upd.id]; - vdev.metrics.level = upd.metrics.level; - if(upd.metrics.color){ - vdev.metrics.r = upd.metrics.r; - vdev.metrics.g = upd.metrics.g; - vdev.metrics.b = upd.metrics.b; - } - vdev.updateTime = upd.updateTime; - var cxs = this.cxVDevMap[upd.id]; - for(var j = 0; j < cxs.length; j++){ - var cx = cxs[j]; - if(typeof cx.zway_getValueFromVDev !== "function") continue; - var oldValue = cx.value; - var newValue = cx.zway_getValueFromVDev(vdev); - if(oldValue !== newValue){ - cx.value = newValue; - cx.emit('change', { oldValue:oldValue, newValue:cx.value, context:null }); - debug("Updated characteristic " + cx.displayName + " on " + vdev.metrics.title); - } - } - } - } - } - - // setup next poll... - this.pollingTimer = setTimeout(this.pollUpdate.bind(this), this.pollInterval*1000); - }.bind(this)); - } - -} - -function ZWayServerAccessory(name, devDesc, platform) { - // device info - this.name = name; - this.devDesc = devDesc; - this.platform = platform; - this.log = platform.log; -} - - -ZWayServerAccessory.prototype = { - - getVDev: function(vdev){ - return this.platform.zwayRequest({ - method: "GET", - url: this.platform.url + 'ZAutomation/api/v1/devices/' + vdev.id - })//.then(function()); - } - , - command: function(vdev, command, value) { - return this.platform.zwayRequest({ - method: "GET", - url: this.platform.url + 'ZAutomation/api/v1/devices/' + vdev.id + '/command/' + command, - qs: (value === undefined ? undefined : value) - }); - }, - - rgb2hsv: function(obj) { - // RGB: 0-255; H: 0-360, S,V: 0-100 - var r = obj.r/255, g = obj.g/255, b = obj.b/255; - var max, min, d, h, s, v; - - min = Math.min(r, Math.min(g, b)); - max = Math.max(r, Math.max(g, b)); - - if (min === max) { - // shade of gray - return {h: 0, s: 0, v: r * 100}; - } - - var d = (r === min) ? g - b : ((b === min) ? r - g : b - r); - h = (r === min) ? 3 : ((b === min) ? 1 : 5); - h = 60 * (h - d/(max - min)); - s = (max - min) / max; - v = max; - return {"h": h, "s": s * 100, "v": v * 100}; - } - , - hsv2rgb: function(obj) { - // H: 0-360; S,V: 0-100; RGB: 0-255 - var r, g, b; - var sfrac = obj.s / 100; - var vfrac = obj.v / 100; - - if(sfrac === 0){ - var vbyte = Math.round(vfrac*255); - return { r: vbyte, g: vbyte, b: vbyte }; - } - - var hdb60 = (obj.h % 360) / 60; - var sector = Math.floor(hdb60); - var fpart = hdb60 - sector; - var c = vfrac * (1 - sfrac); - var x1 = vfrac * (1 - sfrac * fpart); - var x2 = vfrac * (1 - sfrac * (1 - fpart)); - switch(sector){ - case 0: - r = vfrac; g = x2; b = c; break; - case 1: - r = x1; g = vfrac; b = c; break; - case 2: - r = c; g = vfrac; b = x2; break; - case 3: - r = c; g = x1; b = vfrac; break; - case 4: - r = x2; g = c; b = vfrac; break; - case 5: - default: - r = vfrac; g = c; b = x1; break; - } - - return { "r": Math.round(255 * r), "g": Math.round(255 * g), "b": Math.round(255 * b) }; - } - , - getVDevServices: function(vdev){ - var typeKey = ZWayServerPlatform.getVDevTypeKey(vdev); - var services = [], service; - switch (typeKey) { - case "thermostat": - services.push(new Service.Thermostat(vdev.metrics.title, vdev.id)); - break; - case "switchBinary": - services.push(new Service.Switch(vdev.metrics.title, vdev.id)); - break; - case "switchRGBW": - case "switchMultilevel": - if(this.platform.getTagValue(vdev, "Service.Type") === "Switch"){ - services.push(new Service.Switch(vdev.metrics.title, vdev.id)); - } else { - services.push(new Service.Lightbulb(vdev.metrics.title, vdev.id)); - } - break; - case "sensorBinary.Door/Window": - services.push(new Service.GarageDoorOpener(vdev.metrics.title, vdev.id)); - break; - case "sensorMultilevel.Temperature": - services.push(new Service.TemperatureSensor(vdev.metrics.title, vdev.id)); - break; - case "battery.Battery": - services.push(new Service.BatteryService(vdev.metrics.title, vdev.id)); - break; - case "sensorMultilevel.Luminiscence": - services.push(new Service.LightSensor(vdev.metrics.title, vdev.id)); - break; - case "sensorBinary": - var stype = this.platform.getTagValue(vdev, "Service.Type"); - if(stype === "MotionSensor"){ - services.push(new Service.MotionSensor(vdev.metrics.title, vdev.id)); - } - } - - var validServices =[]; - for(var i = 0; i < services.length; i++){ - if(this.configureService(services[i], vdev)) - validServices.push(services[i]); - } - - return validServices; - } - , - uuidToTypeKeyMap: null - , - extraCharacteristicsMap: { - "battery.Battery": [Characteristic.BatteryLevel, Characteristic.StatusLowBattery], - "sensorMultilevel.Temperature": [Characteristic.CurrentTemperature, Characteristic.TemperatureDisplayUnits], - "sensorMultilevel.Luminiscence": [Characteristic.CurrentAmbientLightLevel] - } - , - getVDevForCharacteristic: function(cx, vdevPreferred){ - - // If we know which vdev should be used for this Characteristic, we're done! - if(this.devDesc.cxmap[cx.UUID] !== undefined){ - return this.devDesc.devices[this.devDesc.cxmap[cx.UUID]]; - } - - var map = this.uuidToTypeKeyMap; - if(!map){ - this.uuidToTypeKeyMap = map = {}; - map[(new Characteristic.On).UUID] = ["switchBinary","switchMultilevel"]; - map[(new Characteristic.Brightness).UUID] = ["switchMultilevel"]; - map[(new Characteristic.Hue).UUID] = ["switchRGBW"]; - map[(new Characteristic.Saturation).UUID] = ["switchRGBW"]; - map[(new Characteristic.CurrentTemperature).UUID] = ["sensorMultilevel.Temperature","thermostat"]; - map[(new Characteristic.TargetTemperature).UUID] = ["thermostat"]; - map[(new Characteristic.TemperatureDisplayUnits).UUID] = ["sensorMultilevel.Temperature","thermostat"]; //TODO: Always a fixed result - map[(new Characteristic.CurrentHeatingCoolingState).UUID] = ["thermostat"]; //TODO: Always a fixed result - map[(new Characteristic.TargetHeatingCoolingState).UUID] = ["thermostat"]; //TODO: Always a fixed result - map[(new Characteristic.CurrentDoorState).UUID] = ["sensorBinary.Door/Window","sensorBinary"]; - map[(new Characteristic.TargetDoorState).UUID] = ["sensorBinary.Door/Window","sensorBinary"]; //TODO: Always a fixed result - map[(new Characteristic.ObstructionDetected).UUID] = ["sensorBinary.Door/Window","sensorBinary"]; //TODO: Always a fixed result - map[(new Characteristic.BatteryLevel).UUID] = ["battery.Battery"]; - map[(new Characteristic.StatusLowBattery).UUID] = ["battery.Battery"]; - map[(new Characteristic.ChargingState).UUID] = ["battery.Battery"]; //TODO: Always a fixed result - map[(new Characteristic.CurrentAmbientLightLevel).UUID] = ["sensorMultilevel.Luminiscence"]; - } - - if(cx instanceof Characteristic.Name) return vdevPreferred; - - // Special case!: If cx is a CurrentTemperature, ignore the preferred device...we want the sensor if available! - if(cx instanceof Characteristic.CurrentTemperature) vdevPreferred = null; - // - - var typekeys = map[cx.UUID]; - if(typekeys === undefined) return null; - - if(vdevPreferred && typekeys.indexOf(ZWayServerPlatform.getVDevTypeKey(vdevPreferred)) >= 0){ - return vdevPreferred; - } - - var candidates = this.devDesc.devices; - for(var i = 0; i < typekeys.length; i++){ - for(var j = 0; j < candidates.length; j++){ - if(ZWayServerPlatform.getVDevTypeKey(candidates[j]) === typekeys[i]) return candidates[j]; - } - } - - return null; - } - , - configureCharacteristic: function(cx, vdev, service){ - var accessory = this; - - // Add this combination to the maps... - if(!this.platform.cxVDevMap[vdev.id]) this.platform.cxVDevMap[vdev.id] = []; - this.platform.cxVDevMap[vdev.id].push(cx); - if(!this.platform.vDevStore[vdev.id]) this.platform.vDevStore[vdev.id] = vdev; - - if(cx instanceof Characteristic.Name){ - cx.zway_getValueFromVDev = function(vdev){ - return vdev.metrics.title; - }; - cx.value = cx.zway_getValueFromVDev(vdev); - cx.on('get', function(callback, context){ - debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); - callback(false, accessory.name); - }); - return cx; - } - - // We don't want to override "Name"'s name...so we just move this below that block. - var descOverride = this.platform.getTagValue(vdev, "Characteristic.Description"); - if(descOverride){ - cx.displayName = descOverride; - } - - if(cx instanceof Characteristic.On){ - cx.zway_getValueFromVDev = function(vdev){ - var val = false; - if(vdev.metrics.level === "on"){ - val = true; - } else if(vdev.metrics.level <= 5) { - val = false; - } else if (vdev.metrics.level > 5) { - val = true; - } - return val; - }; - 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('set', function(powerOn, callback){ - this.command(vdev, powerOn ? "on" : "off").then(function(result){ - callback(); - }); - }.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.Brightness){ - cx.zway_getValueFromVDev = function(vdev){ - return vdev.metrics.level; - }; - 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('set', function(level, callback){ - this.command(vdev, "exact", {level: parseInt(level, 10)}).then(function(result){ - callback(); - }); - }.bind(this)); - return cx; - } - - if(cx instanceof Characteristic.Hue){ - cx.zway_getValueFromVDev = function(vdev){ - debug("Derived value " + accessory.rgb2hsv(vdev.metrics.color).h + " for hue."); - return accessory.rgb2hsv(vdev.metrics.color).h; - }; - 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('set', function(hue, callback){ - var scx = service.getCharacteristic(Characteristic.Saturation); - var vcx = service.getCharacteristic(Characteristic.Brightness); - if(!scx || !vcx){ - debug("Hue without Saturation and Brightness is not supported! Cannot set value!") - callback(true, cx.value); - } - var rgb = this.hsv2rgb({ h: hue, s: scx.value, v: vcx.value }); - this.command(vdev, "exact", { red: rgb.r, green: rgb.g, blue: rgb.b }).then(function(result){ - callback(); - }); - }.bind(this)); - - return cx; - } - - if(cx instanceof Characteristic.Saturation){ - cx.zway_getValueFromVDev = function(vdev){ - debug("Derived value " + accessory.rgb2hsv(vdev.metrics.color).s + " for saturation."); - return accessory.rgb2hsv(vdev.metrics.color).s; - }; - 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('set', function(saturation, callback){ - var hcx = service.getCharacteristic(Characteristic.Hue); - var vcx = service.getCharacteristic(Characteristic.Brightness); - if(!hcx || !vcx){ - debug("Saturation without Hue and Brightness is not supported! Cannot set value!") - callback(true, cx.value); - } - var rgb = this.hsv2rgb({ h: hcx.value, s: saturation, v: vcx.value }); - this.command(vdev, "exact", { red: rgb.r, green: rgb.g, blue: rgb.b }).then(function(result){ - callback(); - }); - }.bind(this)); - - return cx; - } - - if(cx instanceof Characteristic.CurrentTemperature){ - cx.zway_getValueFromVDev = function(vdev){ - return vdev.metrics.level; - }; - 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.setProps({ - minValue: vdev.metrics && vdev.metrics.min !== undefined ? vdev.metrics.min : -40, - maxValue: vdev.metrics && vdev.metrics.max !== undefined ? vdev.metrics.max : 999 - }); - return cx; - } - - if(cx instanceof Characteristic.TargetTemperature){ - cx.zway_getValueFromVDev = function(vdev){ - return vdev.metrics.level; - }; - 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('set', function(level, callback){ - this.command(vdev, "exact", {level: parseInt(level, 10)}).then(function(result){ - //debug("Got value: " + result.data.metrics.level + ", for " + vdev.metrics.title + "."); - callback(); - }); - }.bind(this)); - cx.setProps({ - minValue: vdev.metrics && vdev.metrics.min !== undefined ? vdev.metrics.min : 5, - maxValue: vdev.metrics && vdev.metrics.max !== undefined ? vdev.metrics.max : 40 - }); - return cx; - } - - if(cx instanceof Characteristic.TemperatureDisplayUnits){ - //TODO: Always in °C for now. - cx.zway_getValueFromVDev = function(vdev){ - return Characteristic.TemperatureDisplayUnits.CELSIUS; - }; - cx.value = cx.zway_getValueFromVDev(vdev); - cx.on('get', function(callback, context){ - debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); - callback(false, Characteristic.TemperatureDisplayUnits.CELSIUS); - }); - cx.setProps({ - perms: [Characteristic.Perms.READ] - }); - return cx; - } - - if(cx instanceof Characteristic.CurrentHeatingCoolingState){ - //TODO: Always HEAT for now, we don't have an example to work with that supports another function. - cx.zway_getValueFromVDev = function(vdev){ - return Characteristic.CurrentHeatingCoolingState.HEAT; - }; - cx.value = cx.zway_getValueFromVDev(vdev); - cx.on('get', function(callback, context){ - debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); - callback(false, Characteristic.CurrentHeatingCoolingState.HEAT); - }); - return cx; - } - - if(cx instanceof Characteristic.TargetHeatingCoolingState){ - //TODO: Always HEAT for now, we don't have an example to work with that supports another function. - cx.zway_getValueFromVDev = function(vdev){ - return Characteristic.TargetHeatingCoolingState.HEAT; - }; - cx.value = cx.zway_getValueFromVDev(vdev); - cx.on('get', function(callback, context){ - debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); - callback(false, Characteristic.TargetHeatingCoolingState.HEAT); - }); - // Hmm... apparently if this is not setable, we can't add a thermostat change to a scene. So, make it writable but a no-op. - cx.on('set', function(newValue, callback){ - debug("WARN: Set of TargetHeatingCoolingState not yet implemented, resetting to HEAT!") - callback(undefined, Characteristic.TargetHeatingCoolingState.HEAT); - }.bind(this)); - return cx; - } - - if(cx instanceof Characteristic.CurrentDoorState){ - cx.zway_getValueFromVDev = function(vdev){ - return vdev.metrics.level === "off" ? Characteristic.CurrentDoorState.CLOSED : Characteristic.CurrentDoorState.OPEN; - }; - 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); - }); - } - - if(cx instanceof Characteristic.TargetDoorState){ - //TODO: We only support this for Door sensors now, so it's a fixed value. - cx.zway_getValueFromVDev = function(vdev){ - return Characteristic.TargetDoorState.CLOSED; - }; - cx.value = cx.zway_getValueFromVDev(vdev); - cx.on('get', function(callback, context){ - debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); - callback(false, Characteristic.TargetDoorState.CLOSED); - }); - cx.setProps({ - perms: [Characteristic.Perms.READ] - }); - } - - if(cx instanceof Characteristic.ObstructionDetected){ - //TODO: We only support this for Door sensors now, so it's a fixed value. - cx.zway_getValueFromVDev = function(vdev){ - return false; - }; - cx.value = cx.zway_getValueFromVDev(vdev); - cx.on('get', function(callback, context){ - debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); - callback(false, false); - }); - } - - if(cx instanceof Characteristic.BatteryLevel){ - cx.zway_getValueFromVDev = function(vdev){ - return vdev.metrics.level; - }; - 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)); - } - - if(cx instanceof Characteristic.StatusLowBattery){ - cx.zway_getValueFromVDev = function(vdev){ - return vdev.metrics.level <= accessory.platform.batteryLow ? Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; - }; - 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)); - } - - if(cx instanceof Characteristic.ChargingState){ - //TODO: No known chargeable devices(?), so always return false. - cx.zway_getValueFromVDev = function(vdev){ - return Characteristic.ChargingState.NOT_CHARGING; - }; - cx.value = cx.zway_getValueFromVDev(vdev); - cx.on('get', function(callback, context){ - debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); - callback(false, Characteristic.ChargingState.NOT_CHARGING); - }); - } - - if(cx instanceof Characteristic.CurrentAmbientLightLevel){ - cx.zway_getValueFromVDev = function(vdev){ - if(vdev.metrics.scaleTitle === "%"){ - // Completely unscientific guess, based on test-fit data and Wikipedia real-world lux values. - // This will probably change! - var lux = 0.0005 * (vdev.metrics.level^3.6); - // Bounds checking now done upstream! - //if(lux < cx.minimumValue) return cx.minimumValue; if(lux > cx.maximumValue) return cx.maximumValue; - return lux; - } else { - return vdev.metrics.level; - } - }; - 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.MotionDetected){ - cx.zway_getValueFromVDev = function(vdev){ - return vdev.metrics.level === "off" ? false : true; - }; - 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.StatusTampered){ - cx.zway_getValueFromVDev = function(vdev){ - return vdev.metrics.level === "off" ? Characteristic.StatusTampered.NOT_TAMPERED : Characteristic.StatusTampered.TAMPERED; - }; - 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; - } - - } - , - configureService: function(service, vdev){ - var success = true; - for(var i = 0; i < service.characteristics.length; i++){ - var cx = service.characteristics[i]; - var vdev = this.getVDevForCharacteristic(cx, vdev); - if(!vdev){ - success = false; - debug("ERROR! Failed to configure required characteristic \"" + service.characteristics[i].displayName + "\"!"); - } - cx = this.configureCharacteristic(cx, vdev, service); - } - for(var i = 0; i < service.optionalCharacteristics.length; i++){ - var cx = service.optionalCharacteristics[i]; - var vdev = this.getVDevForCharacteristic(cx, vdev); - if(!vdev) continue; - - //NOTE: Questionable logic, but if the vdev has already been used for the same - // characteristic type elsewhere, lets not duplicate it just for the sake of an - // optional characteristic. This eliminates the problem with RGB+W+W bulbs - // having the HSV controls shown again, but might have unintended consequences... - var othercx, othercxs = this.platform.cxVDevMap[vdev.id]; - if(othercxs) for(var j = 0; j < othercxs.length; j++) if(othercxs[j].UUID === cx.UUID) othercx = othercxs[j]; - if(othercx) - continue; - - cx = this.configureCharacteristic(cx, vdev, service); - try { - if(cx) service.addCharacteristic(cx); - } - catch (ex) { - debug('Adding Characteristic "' + cx.displayName + '" failed with message "' + ex.message + '". This may be expected.'); - } - } - return success; - } - , - getServices: function() { - var that = this; - - var vdevPrimary = this.devDesc.devices[this.devDesc.primary]; - var accId = this.platform.getTagValue(vdevPrimary, "Accessory.Id"); - if(!accId){ - accId = "VDev-" + vdevPrimary.h; //FIXME: Is this valid? - } - - var informationService = new Service.AccessoryInformation(); - - informationService - .setCharacteristic(Characteristic.Name, this.name) - .setCharacteristic(Characteristic.Manufacturer, "Z-Wave.me") - .setCharacteristic(Characteristic.Model, "Virtual Device (VDev version 1)") - .setCharacteristic(Characteristic.SerialNumber, accId); - - var services = [informationService]; - - services = services.concat(this.getVDevServices(vdevPrimary)); - - // Any extra switchMultilevels? Could be a RGBW+W bulb, add them as additional services... - if(this.devDesc.extras["switchMultilevel"]) for(var i = 0; i < this.devDesc.extras["switchMultilevel"].length; i++){ - var xvdev = this.devDesc.devices[this.devDesc.extras["switchMultilevel"][i]]; - var xservice = this.getVDevServices(xvdev); - services = services.concat(xservice); - } - - if(this.platform.splitServices){ - if(this.devDesc.types["battery.Battery"]){ - services = services.concat(this.getVDevServices(this.devDesc.devices[this.devDesc.types["battery.Battery"]])); - } - - // Odds and ends...if there are sensors that haven't been used, add services for them... - - var tempSensor = this.devDesc.types["sensorMultilevel.Temperature"] !== undefined ? this.devDesc.devices[this.devDesc.types["sensorMultilevel.Temperature"]] : false; - if(tempSensor && !this.platform.cxVDevMap[tempSensor.id]){ - services = services.concat(this.getVDevServices(tempSensor)); - } - - var lightSensor = this.devDesc.types["sensorMultilevel.Luminiscence"] !== undefined ? this.devDesc.devices[this.devDesc.types["sensorMultilevel.Luminiscence"]] : false; - if(lightSensor && !this.platform.cxVDevMap[lightSensor.id]){ - services = services.concat(this.getVDevServices(lightSensor)); - } - } else { - // Everything outside the primary service gets added as optional characteristics... - var service = services[1]; - var existingCxUUIDs = {}; - for(var i = 0; i < service.characteristics.length; i++) existingCxUUIDs[service.characteristics[i].UUID] = true; - - for(var i = 0; i < this.devDesc.devices.length; i++){ - var vdev = this.devDesc.devices[i]; - if(this.platform.cxVDevMap[vdev.id]) continue; // Don't double-use anything - var extraCxClasses = this.extraCharacteristicsMap[ZWayServerPlatform.getVDevTypeKey(vdev)]; - var extraCxs = []; - if(!extraCxClasses || extraCxClasses.length === 0) continue; - for(var j = 0; j < extraCxClasses.length; j++){ - var cx = new extraCxClasses[j](); - if(existingCxUUIDs[cx.UUID]) continue; // Don't have two of the same Characteristic type in one service! - var vdev2 = this.getVDevForCharacteristic(cx, vdev); // Just in case...will probably return vdev. - if(!vdev2){ - // Uh oh... one of the extraCxClasses can't be configured! Abort all extras for this vdev! - extraCxs = []; // to wipe out any already setup cxs. - break; - } - this.configureCharacteristic(cx, vdev2, service); - extraCxs.push(cx); - } - for(var j = 0; j < extraCxs.length; j++) - service.addCharacteristic(extraCxs[j]); - } - } - - debug("Loaded services for " + this.name); - return services; - } -}; - -module.exports.accessory = ZWayServerAccessory; -module.exports.platform = ZWayServerPlatform;