From a3c0df1c7c6612dcafb7433ec063b9b3b11f50b3 Mon Sep 17 00:00:00 2001 From: Nick Farina Date: Sun, 18 Oct 2015 16:34:21 -0700 Subject: [PATCH 1/9] 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; From 27b39cbfa0c94770998339d86ce0d0204a4d80c8 Mon Sep 17 00:00:00 2001 From: Nick Farina Date: Sun, 18 Oct 2015 22:20:06 -0700 Subject: [PATCH 2/9] New Plugin API - Homebridge calls single exported initializer function and passes an API object - No more require() for HAP classes (doesn't play well with plugin structure) --- .gitignore | 4 + example-plugins/homebridge-lockitron/index.js | 12 +-- lib/api.js | 55 +++++++++++ lib/cli.js | 13 +-- lib/logger.js | 56 +++++++---- lib/plugin.js | 23 ++--- lib/server.js | 93 +++++++------------ package.json | 1 + 8 files changed, 155 insertions(+), 102 deletions(-) create mode 100644 lib/api.js diff --git a/.gitignore b/.gitignore index 2f5c281..137b758 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,7 @@ node_modules/ npm-debug.log .node-version + +# Ignore any extra plugins in the example directory that aren't in Git already +# (this is a sandbox for the user) +example-plugins \ No newline at end of file diff --git a/example-plugins/homebridge-lockitron/index.js b/example-plugins/homebridge-lockitron/index.js index 1c04f63..1cb7db5 100644 --- a/example-plugins/homebridge-lockitron/index.js +++ b/example-plugins/homebridge-lockitron/index.js @@ -1,11 +1,11 @@ -var Service = require("hap-nodejs").Service; -var Characteristic = require("hap-nodejs").Characteristic; var request = require("request"); +var Service, Characteristic; -module.exports = { - accessories: { - Lockitron: LockitronAccessory - } +module.exports = function(homebridge) { + Service = homebridge.hap.Service; + Characteristic = homebridge.hap.Characteristic; + + homebridge.registerAccessory("Lockitron", LockitronAccessory); } function LockitronAccessory(log, config) { diff --git a/lib/api.js b/lib/api.js new file mode 100644 index 0000000..3bb96ec --- /dev/null +++ b/lib/api.js @@ -0,0 +1,55 @@ +var hap = require("hap-nodejs"); +var hapLegacyTypes = require("hap-nodejs/accessories/types.js"); +var log = require("./logger")._system; + +// The official homebridge API is the object we feed the plugin's exported initializer function. + +module.exports = { + API: API +} + +function API() { + this._accessories = {}; // this._accessories[name] = accessory constructor + this._platforms = {}; // this._platforms[name] = platform constructor + + // expose HAP-NodeJS in its entirely for plugins to use instead of making Plugins + // require() it as a dependency - it's a heavy dependency so we don't want it in + // every single plugin. + this.hap = hap; + + // we also need to "bolt on" the legacy "types" constants for older accessories/platforms + // still using the "object literal" style JSON. + this.hapLegacyTypes = hapLegacyTypes; +} + +API.prototype.accessory = function(name) { + if (!this._accessories[name]) + throw new Error("The requested accessory '" + name + "' was not registered by any plugin."); + + return this._accessories[name]; +} + +API.prototype.registerAccessory = function(name, constructor) { + if (this._accessories[name]) + throw new Error("Attempting to register an accessory '" + name + "' which has already been registered!"); + + log.info("Registering accessory '%s'", name); + + this._accessories[name] = constructor; +} + +API.prototype.platform = function(name) { + if (!this._platforms[name]) + throw new Error("The requested platform '" + name + "' was not registered by any plugin."); + + return this._platforms[name]; +} + +API.prototype.registerPlatform = function(name, constructor) { + if (this._platforms[name]) + throw new Error("Attempting to register a platform '" + name + "' which has already been registered!"); + + log.info("Registering platform '%s'", name); + + this._platforms[name] = constructor; +} \ No newline at end of file diff --git a/lib/cli.js b/lib/cli.js index 594914f..f921a48 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -4,17 +4,18 @@ var version = require('./version'); var Server = require('./server').Server; var Plugin = require('./plugin').Plugin; var User = require('./user').User; +var log = require("./logger")._system; '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(""); + log.warn("_____________________________________________________________________"); + log.warn("IMPORTANT: Homebridge is in the middle of some big changes."); + log.warn(" Read more about it here:"); + log.warn(" https://github.com/nfarina/homebridge/wiki/Migration-Guide"); + log.warn("_____________________________________________________________________"); + log.warn(""); program .version(version) diff --git a/lib/logger.js b/lib/logger.js index fecd081..9e8c4ca 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -1,4 +1,5 @@ var chalk = require('chalk'); +var util = require('util'); 'use strict'; @@ -22,43 +23,66 @@ var loggerCache = {}; * Logger class */ -function Logger(pluginName) { - this.pluginName = pluginName; +function Logger(prefix) { + this.prefix = prefix; } Logger.prototype.debug = function(msg) { if (DEBUG_ENABLED) - this.log('debug', msg); + this.log.apply(this, ['debug'].concat(Array.prototype.slice.call(arguments))); } Logger.prototype.info = function(msg) { - this.log('info', msg); + this.log.apply(this, ['info'].concat(Array.prototype.slice.call(arguments))); } Logger.prototype.warn = function(msg) { - this.log('warn', msg); + this.log.apply(this, ['warn'].concat(Array.prototype.slice.call(arguments))); } Logger.prototype.error = function(msg) { - this.log('error', msg); + this.log.apply(this, ['error'].concat(Array.prototype.slice.call(arguments))); } Logger.prototype.log = function(level, msg) { - if (level == 'debug') + msg = util.format.apply(util, Array.prototype.slice.call(arguments, 1)); + func = console.log; + + if (level == 'debug') { msg = chalk.gray(msg); - else if (level == 'warn') + } + else if (level == 'warn') { msg = chalk.yellow(msg); - else if (level == 'error') + func = console.error; + } + else if (level == 'error') { msg = chalk.bold.red(msg); + func = console.error; + } - // prepend plugin name if applicable - if (this.pluginName) - msg = chalk.cyan("[" + this.pluginName + "]") + " " + msg; - - console.log(msg); + // prepend prefix if applicable + if (this.prefix) + msg = chalk.cyan("[" + this.prefix + "]") + " " + msg; + + func(msg); } -Logger.forPlugin = function(pluginName) { - return loggerCache[pluginName] || (loggerCache[pluginName] = new Logger(pluginName)); +Logger.withPrefix = function(prefix) { + + if (!loggerCache[prefix]) { + // create a class-like logger thing that acts as a function as well + // as an instance of Logger. + var logger = new Logger(prefix); + var log = logger.info.bind(logger); + log.debug = logger.debug; + log.info = logger.info; + log.warn = logger.warn; + log.error = logger.error; + log.log = logger.log; + log.prefix = logger.prefix; + loggerCache[prefix] = log; + } + + return loggerCache[prefix]; } diff --git a/lib/plugin.js b/lib/plugin.js index 41d6819..c0f7a05 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -18,12 +18,7 @@ module.exports = { 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 + this.initializer; // exported function from the plugin that initializes it } Plugin.prototype.name = function() { @@ -58,12 +53,8 @@ Plugin.prototype.load = function(options) { 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 || {}; + // try to require() it and grab the exported initialization hook + this.initializer = require(mainPath); } Plugin.loadPackageJSON = function(pluginPath) { @@ -141,8 +132,12 @@ Plugin.installed = function() { // 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); + + var names = fs.readdirSync(requirePath); + + // does this path point inside a single plugin and not a directory containing plugins? + if (fs.existsSync(path.join(requirePath, "package.json"))) + names = [""]; // read through each directory in this node_modules folder for (var index2 in names) { diff --git a/lib/server.js b/lib/server.js index c2cbd7b..36e7cb7 100644 --- a/lib/server.js +++ b/lib/server.js @@ -7,6 +7,9 @@ var Service = require("hap-nodejs").Service; var Characteristic = require("hap-nodejs").Characteristic; var Plugin = require('./plugin').Plugin; var User = require('./user').User; +var API = require('./api').API; +var log = require("./logger")._system; +var Logger = require('./logger').Logger; 'use strict'; @@ -15,9 +18,8 @@ module.exports = { } 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._api = new API(); // object we feed to Plugins + this._plugins = this._loadPlugins(); // plugins[name] = Plugin instance this._config = this._loadConfig(); this._bridge = this._createBridge(); } @@ -49,6 +51,8 @@ Server.prototype._publish = function() { pincode: bridgeConfig.pin || "031-45-154", category: Accessory.Categories.OTHER }); + + log.info("Homebridge is running on port %s.", bridgeConfig.port || 51826); } Server.prototype._loadPlugins = function(accessories, platforms) { @@ -63,44 +67,22 @@ Server.prototype._loadPlugins = function(accessories, platforms) { plugin.load(); } catch (err) { - console.error(err); + log.error("====================") + log.error("ERROR LOADING PLUGIN " + plugin.name() + ":") + log.error(err); + log.error("====================") 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); - } + log.info("Loaded plugin: " + plugin.name()); - 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); - } + // call the plugin's initializer and pass it our API instance + plugin.initializer(this._api); - console.log("---"); + log.info("---"); }.bind(this)); @@ -114,7 +96,7 @@ Server.prototype._loadConfig = function() { // 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."); + log.error("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); } @@ -124,17 +106,17 @@ Server.prototype._loadConfig = function() { 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(""); + log.error("There was a problem reading your config.json file."); + log.error("Please try pasting your config.json file here to validate it: http://jsonlint.com"); + log.error(""); 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); + log.info("Loaded config.json with %s accessories and %s platforms.", accessoryCount, platformCount); - console.log("---"); + log.info("---"); return config; } @@ -150,7 +132,7 @@ Server.prototype._createBridge = function() { Server.prototype._loadAccessories = function() { // Instantiate all accessories in the config - console.log("Loading " + this._config.accessories.length + " accessories..."); + log.info("Loading " + this._config.accessories.length + " accessories..."); for (var i=0; i Date: Tue, 20 Oct 2015 09:31:57 -0700 Subject: [PATCH 3/9] Fix stray comma --- example-plugins/homebridge-lockitron/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example-plugins/homebridge-lockitron/index.js b/example-plugins/homebridge-lockitron/index.js index 1cb7db5..e4653b7 100644 --- a/example-plugins/homebridge-lockitron/index.js +++ b/example-plugins/homebridge-lockitron/index.js @@ -75,7 +75,7 @@ LockitronAccessory.prototype.setState = function(state, callback) { callback(err || new Error("Error setting lock state.")); } }.bind(this)); -}, +} LockitronAccessory.prototype.getServices = function() { return [this.service]; From d443286dcb84ac76dba9b727a60c42e48a4e3e87 Mon Sep 17 00:00:00 2001 From: Nick Farina Date: Tue, 20 Oct 2015 11:36:06 -0700 Subject: [PATCH 4/9] Fix platform logger --- lib/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/server.js b/lib/server.js index 36e7cb7..c406ff7 100644 --- a/lib/server.js +++ b/lib/server.js @@ -176,7 +176,7 @@ Server.prototype._loadPlatforms = function() { throw new Error("Your config.json is requesting the platform '" + platformType + "' which has not been published by any installed plugins."); // Create a custom logging function that prepends the platform name for debugging - var platformLogger = Logger.withPrefix(accessoryName); + var platformLogger = Logger.withPrefix(platformName); platformLogger("Initializing %s platform...", platformType); From 1779ddd75463f0a6b731fcb07fd24eda46bf27de Mon Sep 17 00:00:00 2001 From: Nick Farina Date: Tue, 20 Oct 2015 12:00:27 -0700 Subject: [PATCH 5/9] Fix "once" import --- lib/server.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/server.js b/lib/server.js index c406ff7..5cc0ee0 100644 --- a/lib/server.js +++ b/lib/server.js @@ -5,6 +5,7 @@ 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 once = require("hap-nodejs/lib/util/once").once; var Plugin = require('./plugin').Plugin; var User = require('./user').User; var API = require('./api').API; From 15cc98217bc7e4ba105f3cbf2b7f93ca74c33fe5 Mon Sep 17 00:00:00 2001 From: Nick Farina Date: Mon, 2 Nov 2015 12:04:18 -0800 Subject: [PATCH 6/9] Move bugged peerDependencies -> engines --- example-plugins/homebridge-lockitron/package.json | 3 ++- lib/plugin.js | 6 +++--- package.json | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/example-plugins/homebridge-lockitron/package.json b/example-plugins/homebridge-lockitron/package.json index 7c36b11..ef37247 100644 --- a/example-plugins/homebridge-lockitron/package.json +++ b/example-plugins/homebridge-lockitron/package.json @@ -6,7 +6,8 @@ "keywords": [ "homebridge-plugin" ], - "peerDepdendencies": { + "engines": { + "node": ">=0.12.0", "homebridge": ">=0.2.0" }, "dependencies": { diff --git a/lib/plugin.js b/lib/plugin.js index c0f7a05..1a05726 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -37,11 +37,11 @@ Plugin.prototype.load = function(options) { 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'."); + if (!pjson.engines || !pjson.engines.homebridge) { + throw new Error("Plugin " + this.pluginPath + " does not contain the 'homebridge' package in 'engines'."); } - var versionRequired = pjson.peerDepdendencies.homebridge; + var versionRequired = pjson.engines.homebridge; // make sure the version is satisfied by the currently running version of HomeBridge if (!semver.satisfies(version, versionRequired)) { diff --git a/package.json b/package.json index da6dfed..e4011dd 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "homebridge": "bin/homebridge" }, "engines": { - "node": ">= 0.12.0" + "node": ">=0.12.0" }, "preferGlobal": true, "dependencies": { From 52d3a9a96e032a9d60ad1fc0c890c53c1acdf144 Mon Sep 17 00:00:00 2001 From: Nick Farina Date: Mon, 2 Nov 2015 15:30:29 -0800 Subject: [PATCH 7/9] First pass at README update. --- README.md | 126 +++++++----------- .../homebridge-lockitron/package.json | 2 +- lib/cli.js | 7 - lib/plugin.js | 16 ++- lib/server.js | 24 +++- 5 files changed, 80 insertions(+), 95 deletions(-) diff --git a/README.md b/README.md index ab3b98a..793078a 100644 --- a/README.md +++ b/README.md @@ -1,95 +1,67 @@ # Homebridge -Homebridge is a lightweight NodeJS server you can run on your home network that emulates the iOS HomeKit API. It includes a set of "shims" (found in the [accessories](accessories/) and [platforms](platforms/) folders) that provide a basic bridge from HomeKit to various 3rd-party APIs provided by manufacturers of "smart home" devices. +Homebridge is a lightweight NodeJS server you can run on your home network that emulates the iOS HomeKit API. It supports Plugins, which are community-contributed modules that provide a basic bridge from HomeKit to various 3rd-party APIs provided by manufacturers of "smart home" devices. -Since Siri supports devices added through HomeKit, this means that with Homebridge you can ask Siri to control devices that don't have any support for HomeKit at all. For instance, using the included shims, you can say things like: +Since Siri supports devices added through HomeKit, this means that with Homebridge you can ask Siri to control devices that don't have any support for HomeKit at all. For instance, using just some of the available plugins, you can say: - * _Siri, unlock the front door._ ([Lockitron](https://lockitron.com)) - * _Siri, open the garage door._ ([LiftMaster MyQ](https://www.myliftmaster.com)) - * _Siri, turn on the Leaf._ ([Carwings](http://www.nissanusa.com/innovations/carwings.article.html)) - * _Siri, turn off the Speakers._ ([Sonos](http://www.sonos.com)) - * _Siri, turn on the Dehumidifier._ ([WeMo](http://www.belkin.com/us/Products/home-automation/c/wemo-home-automation/)) - * _Siri, turn on Away Mode._ ([Xfinity Home](http://www.comcast.com/home-security.html)) - * _Siri, turn on the living room lights._ ([Wink](http://www.wink.com), [SmartThings](http://www.smartthings.com), [X10](http://github.com/edc1591/rest-mochad), [Philips Hue](http://meethue.com), [Home Assistant](http://home-assistant.io) [LimitlessLED/MiLight/Easybulb](http://www.limitlessled.com/), [LIFx](http://www.lifx.com/)) - * _Siri, set the movie scene._ ([Logitech Harmony](http://myharmony.com/)) + * _Siri, unlock the front door._ + * _Siri, open the garage door._ + * _Siri, turn on the coffee maker._ + * _Siri, turn on the living room lights._ + * _Siri, good morning!_ -If you would like to support any other devices, please write a shim and create a pull request and I'd be happy to add it to this official list. +You can explore all available plugins at the NPM website by [searching for the keyword `homebridge-plugin`](https://www.npmjs.com/browse/keyword/homebridge-plugin). -# Shim types -There are 2 types of shims supported in Homebridge. +# Installation -* Accessory - Individual device -* Platform - A full bridge to another system +**Note:** If you're running on Linux, you'll need to make sure you have the `libavahi-compat-libdnssd-dev` package installed. If you're running on a Raspberry Pi, you should have a look at the [Wiki](/nfarina/homebridge/wiki/Running-HomeBridge-on-a-Raspberry-Pi). -## Accessories +Homebridge is published through [NPM](https://www.npmjs.com/package/homebridge) and should be installed "globally" by typing: -Accessories are individual devices you would like to bridge to HomeKit. You set them up by declaring them individually in your `config.json` file. Generally, you specify them by `name` or `id` and which system they use. + npm install -g homebridge -## Platforms +You may have to execute commands with `sudo` depending on your system. Now you should be able to run Homebridge: -Platforms bridge entire systems to HomeKit. Platforms can be things like Wink or SmartThings or Vera. By adding a platform to your `config.json`, Homebridge will automatically detect all of your devices for you. + $ `homebridge` + No plugins found. See the README for information on installing plugins. -All you have to do is add the right config options so Homebridge can authenticate and communicate with your other system, and voila, your devices will be available to HomeKit via Homebridge. +Homebridge will complain if you don't have any Plugins installed, since it will essentially be useless, although you can still "pair" with it. See the next section "Installing Plugins" for more info. -# Why? +Once you've installed a Plugin or two, you can run Homebridge again: -Technically, the device manufacturers should be the ones implementing the HomeKit API. And I'm sure they will - eventually. When they do, these shims will be obsolete, and I hope that happens soon. In the meantime, this server is a fun way to get a taste of the future, for those who just can't bear to wait until "real" HomeKit devices are on the market. - -# Credit - -Homebridge itself is basically just a set of shims and a README. The actual HomeKit API work was done by [KhaosT](http://twitter.com/khaost) in his [HAP-NodeJS](https://github.com/KhaosT/HAP-NodeJS) project. Additionally, many of the shims benefit from amazing NodeJS projects out there like `sonos` and `wemo` that implement all the interesting functionality. - -# Before you Begin - -I would call this project a "novelty" in its current form, and is for **intrepid hackers only**. To make any of this work, you'll need: - - * An app on your iOS device that can manage your HomeKit database. - * An always-running server (like a Raspberry Pi) on which you can install NodeJS. - * Knowledge of Git submodules and npm. - -You'll also need some patience, as Siri can be very strict about sentence structure, and occasionally she will forget about HomeKit altogether. But it's not surprising that HomeKit isn't rock solid, since almost no one can actually use it today besides developers who are creating hardware accessories for it. There are, to my knowledge, exactly zero licensed HomeKit devices on the market right now, so Apple can easily get away with this all being a work in progress. - -# Getting Started - -OK, if you're still excited enough about ordering Siri to make your coffee (which, who wouldn't be!) then here's how to set things up. - -**Note:** If you're running on Linux, you'll need to make sure you have the `libavahi-compat-libdnssd-dev` package installed. - -First, clone this repo: - - $ git clone https://github.com/nfarina/homebridge.git - $ cd homebridge - $ npm install - -**Note**: You'll need to have NodeJS version 0.12.x or better installed for required submodule `HAP-NodeJS` to load. - - -Now you should be able to run the homebridge server: - - $ cd homebridge - $ npm run start - Starting Homebridge server... + $ `homebridge` Couldn't find a config.json file [snip] -The server won't do anything until you've created a `config.json` file containing your home devices (or _accessories_ in HomeKit parlance) or platforms you wish to make available to iOS. You can start by copying and modifying the included `config-sample.json` file which includes declarations for all supported accessories and platforms. +However, Homebridge won't do anything until you've created a `config.json` file containing your accessories and/or platforms. You can start by copying and modifying the included `config-sample.json` file which includes declarations for some example accessories and platforms. Each Plugin will have its own expected configuration; the documentation for Plugins should give you some real-world examples for that plugin. -Once you've added your devices and/or platforms, you should be able to run the server again and see them initialize: +**NOTE**: Your `config.json` file MUST live in your home directory inside `.homebridge`. The full error message will contain the exact path where your config is expected to be found. - $ npm run start - Starting Homebridge server... - Loading 6 accessories... - [Speakers] Initializing 'Sonos' accessory... - [Coffee Maker] Initializing 'WeMo' accessory... - [Speakers] Initializing 'Sonos' accessory... - [Coffee Maker] Initializing 'WeMo' accessory... - [Wink] Initializing Wink platform... - [Wink] Fetching Wink devices. - [Wink] Initializing device with name Living Room Lamp... +Once you've added your config file, you should be able to run the server again: + + $ homebridge + Loaded plugin: homebridge-lockitron + Registering accessory 'Lockitron' + --- + Loaded config.json with 1 accessories and 0 platforms. + --- + Loading 0 platforms... + Loading 1 accessories... + [Back Door] Initializing Lockitron accessory... Your server is now ready to receive commands from iOS. -# Adding your devices to iOS +# Installing Plugins + +Plugins are NodeJS modules published through NPM and tagged with the keyword `homebridge-plugin`. They must have a name with the prefix `homebridge-`, like **homebridge-mysmartlock**. + +Plugins can publish Accessories and/or Platforms. Accessories are individual devices, like a smart switch or a garage door. Platforms act like a single device but can expose a set of devices, like a house full of smart lightbulbs. + +You install Plugins the same way you installed Homebridge - as a global NPM module. For example: + + npm install -g homebridge-lockitron + +# Adding Homebridge to iOS HomeKit is actually not an app; it's a "database" similar to HealthKit and PassKit. But where HealthKit has the companion _Health_ app and PassKit has _Passbook_, Apple has supplied no app for managing your HomeKit database (at least [not yet](http://9to5mac.com/2015/05/20/apples-planned-ios-9-home-app-uses-virtual-rooms-to-manage-homekit-accessories/)). However, the HomeKit API is open for developers to write their own apps for adding devices to HomeKit. @@ -99,11 +71,9 @@ There are also some free apps that work OK. Try [Insteon+](https://itunes.apple. If you are a member of the iOS developer program, I highly recommend Apple's [HomeKit Catalog](https://developer.apple.com/library/ios/samplecode/HomeKitCatalog/Introduction/Intro.html) app, as it is reliable and comprehensive and free (and open source). -## Adding HomeKit Accessories +Once you've gotten a HomeKit app running on your iOS device, it should "discover" the single accessory "Homebridge", assuming that you're still running the Homebridge server and you're on the same Wifi network. Adding this accessory will automatically add all accessories and platforms defined in `config.json`. -Once you've gotten a HomeKit app running on your iOS device, you can use it to add your Homebridge devices. The app should "discover" the single accessory "Homebridge", assuming that you're still running the Homebridge server and you're on the same Wifi network. Adding this accessory will automatically add all accessories and platforms defined in `config.json`. - -When you attempt to add Homebridge, it will ask for a "PIN code". The default code is `031-45-154` (but this can be changed, see `config-sample.json`). This process will create some files in the `persist` directory of the Homebridge server, which stores the pairing relationship. +When you attempt to add Homebridge, it will ask for a "PIN code". The default code is `031-45-154` (but this can be changed, see `config-sample.json`). # Interacting with your Devices @@ -111,10 +81,10 @@ Once your device has been added to HomeKit, you should be able to tell Siri to c One final thing to remember is that Siri will almost always prefer its default phrase handling over HomeKit devices. For instance, if you name your Sonos device "Radio" and try saying "Siri, turn on the Radio" then Siri will probably start playing an iTunes Radio station on your phone. Even if you name it "Sonos" and say "Siri, turn on the Sonos", Siri will probably just launch the Sonos app instead. This is why, for instance, the suggested `name` for the Sonos shim in `config-samples.json` is "Speakers". -# Final Notes +# Why? -HomeKit is definitely amazing when it works. Speaking to Siri is often much quicker and easier than launching whatever app your device manufacturer provides. +Technically, the device manufacturers should be the ones implementing the HomeKit API. And I'm sure they will - eventually. When they do, these shims will be obsolete, and I hope that happens soon. In the meantime, this server is a fun way to get a taste of the future, for those who just can't bear to wait until "real" HomeKit devices are on the market. -I welcome any suggestions or pull requests, but keep in mind that it's likely not possible to support all the things you might want to do with a device through HomeKit. For instance, you might want to hack the Sonos shim to play the specific kind of music you want and that's great, but it might not be appropriate to merge those specific changes into this repository. The shims here should be mostly simple "canonical examples" and easily hackable by others. +# Credit -Good luck! +The original HomeKit API work was done by [KhaosT](http://twitter.com/khaost) in his [HAP-NodeJS](https://github.com/KhaosT/HAP-NodeJS) project. diff --git a/example-plugins/homebridge-lockitron/package.json b/example-plugins/homebridge-lockitron/package.json index ef37247..f33cd09 100644 --- a/example-plugins/homebridge-lockitron/package.json +++ b/example-plugins/homebridge-lockitron/package.json @@ -1,5 +1,5 @@ { - "name": "plugin-lockitron", + "name": "homebridge-lockitron", "version": "0.0.1", "description": "Lockitron plugin for homebridge: https://github.com/nfarina/homebridge", "license": "ISC", diff --git a/lib/cli.js b/lib/cli.js index f921a48..cbdeb06 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -10,13 +10,6 @@ var log = require("./logger")._system; module.exports = function() { - log.warn("_____________________________________________________________________"); - log.warn("IMPORTANT: Homebridge is in the middle of some big changes."); - log.warn(" Read more about it here:"); - log.warn(" https://github.com/nfarina/homebridge/wiki/Migration-Guide"); - log.warn("_____________________________________________________________________"); - log.warn(""); - program .version(version) .option('-P, --plugin-path [path]', 'look for plugins installed at [path] as well as node_modules', function(p) { Plugin.addPluginPath(p); }) diff --git a/lib/plugin.js b/lib/plugin.js index 1a05726..262523c 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -17,7 +17,7 @@ module.exports = { */ function Plugin(pluginPath) { - this.pluginPath = pluginPath; // like "/usr/local/lib/node_modules/plugin-lockitron" + this.pluginPath = pluginPath; // like "/usr/local/lib/node_modules/homebridge-lockitron" this.initializer; // exported function from the plugin that initializes it } @@ -35,7 +35,19 @@ Plugin.prototype.load = function(options) { // attempt to load package.json var pjson = Plugin.loadPackageJSON(this.pluginPath); - + + // make sure the name is prefixed with 'homebridge-' + if (!pjson.name || pjson.name.indexOf('homebridge-') != 0) { + throw new Error("Plugin " + this.pluginPath + " does not have a package name that begins with 'homebridge-'."); + } + + // very temporary fix for first wave of plugins + if (pjson.peerDepdendencies && (!pjson.engines || !pjson.engines.homebridge)) { + var engines = pjson.engines || {} + engines.homebridge = pjson.peerDepdendencies.homebridge; + pjson.engines = engines; + } + // pluck out the HomeBridge version requirement if (!pjson.engines || !pjson.engines.homebridge) { throw new Error("Plugin " + this.pluginPath + " does not contain the 'homebridge' package in 'engines'."); diff --git a/lib/server.js b/lib/server.js index 5cc0ee0..a9838d7 100644 --- a/lib/server.js +++ b/lib/server.js @@ -59,6 +59,7 @@ Server.prototype._publish = function() { Server.prototype._loadPlugins = function(accessories, platforms) { var plugins = {}; + var foundOnePlugin = false; // load and validate plugins - check for valid package.json, etc. Plugin.installed().forEach(function(plugin) { @@ -75,17 +76,26 @@ Server.prototype._loadPlugins = function(accessories, platforms) { plugin.loadError = err; } - // add it to our dict for easy lookup later - plugins[plugin.name()] = plugin; + if (!plugin.loadError) { + + // add it to our dict for easy lookup later + plugins[plugin.name()] = plugin; - log.info("Loaded plugin: " + plugin.name()); + log.info("Loaded plugin: " + plugin.name()); - // call the plugin's initializer and pass it our API instance - plugin.initializer(this._api); + // call the plugin's initializer and pass it our API instance + plugin.initializer(this._api); - log.info("---"); + log.info("---"); + foundOnePlugin = true; + } }.bind(this)); + + // Complain if you don't have any plugins. + if (!foundOnePlugin) { + log.warn("No plugins found. See the README for information on installing plugins.") + } return plugins; } @@ -97,7 +107,7 @@ Server.prototype._loadConfig = function() { // Complain and exit if it doesn't exist yet if (!fs.existsSync(configPath)) { - log.error("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."); + log.error("Couldn't find a config.json file at '"+configPath+"'. Look at config-sample.json for examples of how to format your config.js and add your home accessories."); process.exit(1); } From abc6ccfae7dd6393042af3c0dd8f1d5461eae074 Mon Sep 17 00:00:00 2001 From: Nick Farina Date: Tue, 3 Nov 2015 15:02:52 -0800 Subject: [PATCH 8/9] Require plugin name during registration --- README.md | 8 ++ example-plugins/homebridge-lockitron/index.js | 2 +- lib/api.js | 86 +++++++++++++++---- 3 files changed, 77 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 793078a..5203244 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,8 @@ +# IMPORTANT + +Homebridge has recently spun off its included accessories into a new module [homebridge-legacy-plugins](https://github.com/nfarina/homebridge-legacy-plugins). Please do not open any issues related to specific devices in this repository; go there instead. + # Homebridge Homebridge is a lightweight NodeJS server you can run on your home network that emulates the iOS HomeKit API. It supports Plugins, which are community-contributed modules that provide a basic bridge from HomeKit to various 3rd-party APIs provided by manufacturers of "smart home" devices. @@ -61,6 +65,10 @@ You install Plugins the same way you installed Homebridge - as a global NPM modu npm install -g homebridge-lockitron +You can explore all available plugins at the NPM website by [searching for the keyword `homebridge-plugin`](https://www.npmjs.com/browse/keyword/homebridge-plugin). + +**IMPORTANT**: Many of the plugins that Homebridge used to include with its default installation have been moved to the single plugin [homebridge-legacy-plugins](https://www.npmjs.com/package/homebridge-legacy-plugins). + # Adding Homebridge to iOS HomeKit is actually not an app; it's a "database" similar to HealthKit and PassKit. But where HealthKit has the companion _Health_ app and PassKit has _Passbook_, Apple has supplied no app for managing your HomeKit database (at least [not yet](http://9to5mac.com/2015/05/20/apples-planned-ios-9-home-app-uses-virtual-rooms-to-manage-homekit-accessories/)). However, the HomeKit API is open for developers to write their own apps for adding devices to HomeKit. diff --git a/example-plugins/homebridge-lockitron/index.js b/example-plugins/homebridge-lockitron/index.js index e4653b7..1d5bfd0 100644 --- a/example-plugins/homebridge-lockitron/index.js +++ b/example-plugins/homebridge-lockitron/index.js @@ -5,7 +5,7 @@ module.exports = function(homebridge) { Service = homebridge.hap.Service; Characteristic = homebridge.hap.Characteristic; - homebridge.registerAccessory("Lockitron", LockitronAccessory); + homebridge.registerAccessory("homebridge-lockitron", "Lockitron", LockitronAccessory); } function LockitronAccessory(log, config) { diff --git a/lib/api.js b/lib/api.js index 3bb96ec..f760917 100644 --- a/lib/api.js +++ b/lib/api.js @@ -9,8 +9,8 @@ module.exports = { } function API() { - this._accessories = {}; // this._accessories[name] = accessory constructor - this._platforms = {}; // this._platforms[name] = platform constructor + this._accessories = {}; // this._accessories[pluginName.accessoryName] = accessory constructor + this._platforms = {}; // this._platforms[pluginName.platformName] = platform constructor // expose HAP-NodeJS in its entirely for plugins to use instead of making Plugins // require() it as a dependency - it's a heavy dependency so we don't want it in @@ -23,33 +23,83 @@ function API() { } API.prototype.accessory = function(name) { - if (!this._accessories[name]) - throw new Error("The requested accessory '" + name + "' was not registered by any plugin."); - return this._accessories[name]; + // if you passed the "short form" name like "Lockitron" instead of "homebridge-lockitron.Lockitron", + // see if it matches exactly one accessory. + if (name.indexOf('.') == -1) { + var found = []; + for (var fullName in this._accessories) { + if (fullName.split(".")[1] == name) + found.push(fullName); + } + + if (found.length == 1) { + return this._accessories[found[0]]; + } + else if (found.length > 1) { + throw new Error("The requested accessory '" + name + "' has been registered multiple times. Please be more specific by writing one of: " + found.join(", ")); + } + else { + throw new Error("The requested accessory '" + name + "' was not registered by any plugin."); + } + } + else { + + if (!this._accessories[name]) + throw new Error("The requested accessory '" + name + "' was not registered by any plugin."); + + return this._accessories[name]; + } } -API.prototype.registerAccessory = function(name, constructor) { - if (this._accessories[name]) - throw new Error("Attempting to register an accessory '" + name + "' which has already been registered!"); +API.prototype.registerAccessory = function(pluginName, accessoryName, constructor) { + var fullName = pluginName + "." + accessoryName; + + if (this._accessories[fullName]) + throw new Error("Attempting to register an accessory '" + fullName + "' which has already been registered!"); - log.info("Registering accessory '%s'", name); + log.info("Registering accessory '%s'", fullName); - this._accessories[name] = constructor; + this._accessories[fullName] = constructor; } API.prototype.platform = function(name) { - if (!this._platforms[name]) - throw new Error("The requested platform '" + name + "' was not registered by any plugin."); - return this._platforms[name]; + // if you passed the "short form" name like "Lockitron" instead of "homebridge-lockitron.Lockitron", + // see if it matches exactly one platform. + if (name.indexOf('.') == -1) { + var found = []; + for (var fullName in this._platforms) { + if (fullName.split(".")[1] == name) + found.push(fullName); + } + + if (found.length == 1) { + return this._platforms[found[0]]; + } + else if (found.length > 1) { + throw new Error("The requested platform '" + name + "' has been registered multiple times. Please be more specific by writing one of: " + found.join(", ")); + } + else { + throw new Error("The requested platform '" + name + "' was not registered by any plugin."); + } + } + else { + + if (!this._platforms[name]) + throw new Error("The requested platform '" + name + "' was not registered by any plugin."); + + return this._platforms[name]; + } } -API.prototype.registerPlatform = function(name, constructor) { - if (this._platforms[name]) - throw new Error("Attempting to register a platform '" + name + "' which has already been registered!"); +API.prototype.registerPlatform = function(pluginName, platformName, constructor) { + var fullName = pluginName + "." + platformName; + + if (this._platforms[fullName]) + throw new Error("Attempting to register a platform '" + fullName + "' which has already been registered!"); - log.info("Registering platform '%s'", name); + log.info("Registering platform '%s'", fullName); - this._platforms[name] = constructor; + this._platforms[fullName] = constructor; } \ No newline at end of file From 46c4b6f58913140c70ef7c35c5264458471db9e4 Mon Sep 17 00:00:00 2001 From: Nick Farina Date: Tue, 3 Nov 2015 15:09:56 -0800 Subject: [PATCH 9/9] Trim down config-sample.json --- config-sample.json | 252 ++------------------------------------------- 1 file changed, 7 insertions(+), 245 deletions(-) diff --git a/config-sample.json b/config-sample.json index 05b14ca..6fb53f2 100644 --- a/config-sample.json +++ b/config-sample.json @@ -6,257 +6,19 @@ "pin": "031-45-154" }, - "description": "This is an example configuration file with all supported devices. You can use this as a template for creating your own configuration file containing devices you actually own.", - - "platforms": [ - { - "platform" : "Nest", - "name" : "Nest", - "username" : "username", - "password" : "password" - }, - { - "platform" : "TelldusLive", - "name" : "Telldus Live!", - "public_key" : "telldus public key", - "private_key" : "telldus private key", - "token" : "telldus token", - "token_secret" : "telldus token secret" - }, - { - "platform" : "Telldus", - "name" : "Telldus" - }, - { - "platform": "Wink", - "name": "Wink", - "client_id": "YOUR_WINK_API_CLIENT_ID", - "client_secret": "YOUR_WINK_API_CLIENT_SECRET", - "username": "your@email.com", - "password": "WINK_PASSWORD" - }, - { - "platform": "SmartThings", - "name": "SmartThings", - "app_id": "JSON SmartApp Id", - "access_token": "JSON SmartApp AccessToken" - }, - { - "platform": "Domoticz", - "name": "Domoticz", - "server": "127.0.0.1", - "port": "8080", - "roomid": 0, - "loadscenes": 1 - }, - { - "platform": "PhilipsHue", - "name": "Phillips Hue", - "username": "" - }, - { - "platform": "ISY", - "name": "ISY", - "host": "192.168.1.20", - "port": "8000", - "username": "username", - "password": "password" - }, - { - "platform": "LogitechHarmony", - "name": "Living Room Harmony Hub" - }, - { - "platform": "Sonos", - "name": "Sonos", - "play_volume": 25 - }, - { - "platform": "YamahaAVR", - "play_volume": -35, - "setMainInputTo": "AirPlay" - }, - { - "platform": "ZWayServer", - "url": "http://192.168.1.10:8083/", - "login": "zwayusername", - "password": "zwayuserpassword", - "poll_interval": 2, - "split_services": false - }, - { - "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"] - }, - { - "platform": "HomeAssistant", - "name": "HomeAssistant", - "host": "http://192.168.1.10:8123", - "password": "XXXXX", - "supported_types": ["light", "switch", "media_player", "scene"] - }, - { - "platform": "LIFx", - "name": "LIFx", - "access_token": "XXXXXXXX generate at https://cloud.lifx.com/settings" - } - ], + "description": "This is an example configuration file with one fake accessory and one fake platform. You can use this as a template for creating your own configuration file containing devices you actually own.", "accessories": [ { "accessory": "WeMo", "name": "Coffee Maker", - "description": "This shim supports Belkin WeMo devices on the same network as this server. You can create duplicate entries for this device and change the 'name' attribute to reflect what device is plugged into the WeMo, for instance 'Air Conditioner' or 'Coffee Maker'. This name will be used by Siri. Make sure to update the 'wemo_name' attribute with the EXACT name of the device in the WeMo app itself. This can be the same value as 'name' but it doesn't have to be.", - "wemo_name": "CoffeeMaker" - }, + } + ], + + "platforms": [ { - "accessory": "LiftMaster", - "name": "Garage Door", - "description": "This shim supports LiftMaster garage door openers that are already internet-connected to the 'MyQ' service.", - // "requiredDeviceId", "", - "username": "your-liftmaster-username", - "password" : "your-liftmaster-password" - }, - { - "accessory": "Lockitron", - "name": "Front Door", - "description": "This shim supports Lockitron locks. It uses the Lockitron cloud API, so the Lockitron must be 'awake' for locking and unlocking to actually happen. You can wake up Lockitron after issuing an lock/unlock command by knocking on the door.", - "lock_id": "your-lock-id", - "api_token" : "your-lockitron-api-access-token" - }, - { - "accessory": "Carwings", - "name": "Leaf", - "description": "This shim supports controlling climate control on Nissan cars with Carwings. Note that Carwings is super slow and it may take up to 5 minutes for your command to be processed by the Carwings system.", - "username": "your-carwings-username", - "password" : "your-carwings-password" - }, - { - "accessory": "iControl", - "name": "Xfinity Home", - "description": "This shim supports iControl-based security systems like Xfinity Home.", - "system": "XFINITY_HOME", - "email": "your-comcast-email", - "password": "your-comcast-password", - "pin": "your-security-system-pin-code" - }, - { - "accessory": "HomeMatic", - "name": "Light", - "description": "Control HomeMatic devices (The XMP-API addon for the CCU is required)", - "ccu_id": "The XMP-API id of your HomeMatic device", - "ccu_ip": "The IP-Adress of your HomeMatic CCU device" - }, - { - "accessory": "HomeMaticWindow", - "name": "Contact", - "description": "Control HomeMatic devices (The XMP-API addon for the CCU is required)", - "ccu_id": "The XMP-API id of your HomeMatic device (type HM-Sec-RHS)", - "ccu_ip": "The IP-Adress of your HomeMatic CCU device" - }, - { - "accessory": "HomeMaticThermo", - "name": "Contact", - "description": "Control HomeMatic devices (The XMP-API addon for the CCU is required)", - "ccu_id_TargetTemp": "The XMP-API id of your HomeMatic device (type HM-CC-RT-DN )", - "ccu_id_CurrentTemp": "The XMP-API id of your HomeMatic device (type HM-CC-RT-DN )", - "ccu_id_ControlMode": "The XMP-API id of your HomeMatic device (type HM-CC-RT-DN )", - "ccu_id_ManuMode": "The XMP-API id of your HomeMatic device (type HM-CC-RT-DN )", - "ccu_id_AutoMode": "The XMP-API id of your HomeMatic device (type HM-CC-RT-DN )", - "ccu_ip": "The IP-Adress of your HomeMatic CCU device" - }, - { - "accessory": "X10", - "name": "Lamp", - "ip_address": "localhost:3000", - "device_id": "E1", - "protocol": "pl", - "can_dim": true - }, - { - "accessory": "Http", - "name": "Kitchen Lamp", - "on_url": "https://192.168.1.22:3030/devices/23222/on", - "off_url": "https://192.168.1.22:3030/devices/23222/off", - "brightness_url": "https://192.168.1.22:3030/devices/23222/brightness/%b", - "http_method": "POST" - }, - { - "accessory": "HttpHygrometer", - "name": "Kitchen", - "url": "http://host/URL", - "http_method": "GET" - }, - { - "accessory": "HttpThermometer", - "name": "Garage", - "url": "http://home/URL", - "http_method": "GET" - }, - { - "accessory": "ELKM1", - "name": "Security System", - "description": "Allows basic control of Elk M1 security system. You can use 1 of 3 arm modes: Away, Stay, Night. If you need to access all 3, create 3 accessories with different names.", - "zone": "1", - "host": "192.168.1.10", - "port": "2101", - "pin": "1234", - "arm": "Away" - }, - { - "accessory": "AD2USB", - "name": "Alarm", - "description": "Arm, disarm, and status monitoring of the default partition for Honeywell/Ademco alarm systems. Requires network configured AD2USB interface", - "host": "192.168.1.200", // IP address of the SER2SOCK service - "port" : 4999, // Port the SER2SOCK process is running on - "pin": "1234" // PIN used for arming / disarming - }, - { - "accessory": "Tesla", - "name": "Tesla", - "description": "This shim supports controlling climate control on the Tesla Model S.", - "username": "tesla_email", - "password" : "tesla_password" - }, - { - "accessory": "Hyperion", - "name": "TV Backlight", - "description": "Control the Hyperion TV backlight server. https://github.com/tvdzwan/hyperion", - "host": "localhost", - "port": "19444" - }, - { - "accessory": "mpdclient", - "name" : "mpd", - "host" : "localhost", - "port" : 6600, - "description": "Allows some control of an MPD server" - }, - { - "accessory": "FileSensor", - "name": "File Time Motion Sensor", - "path": "/tmp/CameraDump/", - "window_seconds": 5, - "sensor_type": "m", - "inverse": false - }, - { - "accessory": "GenericRS232Device", - "name": "Projector", - "description": "Make sure you set a 'Siri-Name' for your iOS-Device (example: 'Home Cinema') otherwise it might not work.", - "id": "TYDYMU044UVNP", - "baudrate": 9600, - "device": "/dev/tty.usbserial", - "manufacturer": "Acer", - "model_name": "H6510BD", - "on_command": "* 0 IR 001\r", - "off_command": "* 0 IR 002\r" + "platform" : "PhilipsHue", + "name" : "Hue", } ] }