diff --git a/README.md b/README.md index d75b54e..4fae78b 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Since Siri supports devices added through HomeKit, this means that with Homebrid * _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)) + * _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), [LimitlessLED/MiLight/Easybulb](http://www.limitlessled.com/)) * _Siri, set the movie scene._ ([Logitech Harmony](http://myharmony.com/)) 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. diff --git a/accessories/HomeMatic.js b/accessories/HomeMatic.js old mode 100755 new mode 100644 diff --git a/accessories/MiLight.js b/accessories/MiLight.js index 409fcb5..9ab18f6 100644 --- a/accessories/MiLight.js +++ b/accessories/MiLight.js @@ -1,181 +1,113 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +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: MiLight +} + function MiLight(log, config) { - this.log = log; - 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.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"]; } var light = new Milight({ - ip: this.ip_address, - port: this.port, - delayBetweenCommands: this.delay, - commandRepeat: this.repeat + ip: this.ip_address, + port: this.port, + delayBetweenCommands: this.delay, + commandRepeat: this.repeat }); MiLight.prototype = { - setPowerState: function(powerOn) { + setPowerState: function(powerOn, callback) { + if (powerOn) { + light.sendCommands(commands[this.type].on(this.zone)); + this.log("Setting power state to on"); + } + else { + light.sendCommands(commands[this.type].off(this.zone)); + this.log("Setting power state to off"); + } + callback(); + }, - var binaryState = powerOn ? "on" : "off"; - var that = this; + setBrightness: function(level, callback) { + this.log("Setting brightness to %s", level); - if (binaryState === "on") { - this.log("Setting power state of zone " + this.zone + " to " + powerOn); - light.sendCommands(commands[this.type].on(this.zone)); - } else { - this.log("Setting power state of zone " + this.zone + " to " + powerOn); - light.sendCommands(commands[this.type].off(this.zone)); - } + // If this is an rgbw lamp, set the absolute brightness specified + if (this.type == "rgbw") { + 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) { + light.sendCommands(commands[this.type].brightUp()); + } else { + light.sendCommands(commands[this.type].brightDown()); + } + } + callback(); + }, - }, + setHue: function(value, callback) { + this.log("Setting hue to %s", value); - setBrightnessLevel: function(value) { + if (this.type == "rgbw") { + if (value == 0) { + light.sendCommands(commands.rgbw.whiteMode(this.zone)); + } else { + light.sendCommands(commands.rgbw.hue(commands.rgbw.hsvToMilightColor(Array(value, 0, 0)))); + } + } else if (this.type == "rgb") { + light.sendCommands(commands.rgb.hue(commands.rgbw.hsvToMilightColor(Array(value, 0, 0)))); + } 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) { + light.sendCommands(commands.white.warmer()); + } else { + light.sendCommands(commands.white.cooler()); + } + } - var that = this; + }, + + identify: function(callback) { + this.log("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)); - this.log("Setting brightness level of zone " + this.zone + " to " + value); - - light.sendCommands(commands[this.type].brightness(value)); - }, - - setHue: function(value) { - - var that = this; - - this.log("Setting hue of zone " + this.zone + " to " + value); - - if (value == "0") { - light.sendCommands(commands.rgbw.whiteMode(this.zone)); - } else { - light.sendCommands(commands.rgbw.hue(commands.rgbw.hsvToMilightColor(Array(value, 0, 0)))); - } - }, - - - 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: "MiLight", - supportEvents: false, - supportBonjour: false, - manfDescription: "Manufacturer", - designedMaxLength: 255 - }, { - cType: types.MODEL_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.type, - supportEvents: false, - supportBonjour: false, - manfDescription: "Model", - designedMaxLength: 255 - }, { - cType: types.SERIAL_NUMBER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "MILIGHT1234", - 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: "Turn on the light", - designedMaxLength: 1 - }, { - cType: types.BRIGHTNESS_CTYPE, - onUpdate: function(value) { - that.setBrightnessLevel(value); - }, - perms: ["pw", "pr", "ev"], - format: "bool", - initialValue: 100, - supportEvents: false, - supportBonjour: false, - manfDescription: "Adjust brightness of light", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - }] - }]; - if (that.type == "rgbw" || that.type == "rgb") { - services[1].characteristics.push({ - cType: types.HUE_CTYPE, - onUpdate: function(value) { - that.setHue(value); - }, - perms: ["pw", "pr", "ev"], - format: "int", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Adjust Hue of Light", - designedMinValue: 0, - designedMaxValue: 360, - designedMinStep: 1, - unit: "arcdegrees" - }); - } - return services; - } + lightbulbService + .addCharacteristic(new Characteristic.Hue()) + .on('set', this.setHue.bind(this)); + + return [informationService, lightbulbService]; + } }; - -module.exports.accessory = MiLight; diff --git a/config-sample.json b/config-sample.json index adfe40c..c9ba829 100644 --- a/config-sample.json +++ b/config-sample.json @@ -41,7 +41,9 @@ "platform": "Domoticz", "name": "Domoticz", "server": "127.0.0.1", - "port": "8005" + "port": "8080", + "roomid": 0, + "loadscenes": 1 }, { "platform": "PhilipsHue", @@ -153,12 +155,12 @@ { "accessory":"MiLight", "name": "Lamp", - "ip_address": "255.255.255.255", // IP Address of the WiFi Bridge, or 255.255.255.255 to broadcast to all - "port": 8899, // Default port 8899 (50000 for v1 or v2 bridge) - "zone": 1, // Zone to address commands to (not used for rgb only bulbs) - "type": "rgbw", // Bulb type (rgbw, rgb, white) - "delay": 35, // Delay between commands sent to the WiFi bridge (default 35) - "repeat": 3 // Number of times each command is repeated for reliability (default 3) + "ip_address": "255.255.255.255", + "port": 8899, + "zone": 1, + "type": "rgbw", + "delay": 35, + "repeat": 3 }, { "accessory": "Tesla", diff --git a/package.json b/package.json index 8984751..ca43ef5 100644 --- a/package.json +++ b/package.json @@ -15,12 +15,12 @@ "carwingsjs": "0.0.x", "color": "0.10.x", "elkington": "kevinohara80/elkington", - "hap-nodejs": "git+https://github.com/KhaosT/HAP-NodeJS#46ba0597eb339983a14d98c53764a58a5516fcd2", + "hap-nodejs": "git+https://github.com/KhaosT/HAP-NodeJS#fff863d7a387636fc612cf27cb859e82d9ee3294", "harmonyhubjs-client": "^1.1.4", "harmonyhubjs-discover": "git+https://github.com/swissmanu/harmonyhubjs-discover.git", "mdns": "^2.2.4", "node-hue-api": "^1.0.5", - "node-milight-promise": "0.0.2", + "node-milight-promise": "0.0.x", "node-persist": "0.0.x", "q": "1.4.x", "tough-cookie": "^2.0.0", diff --git a/platforms/Domoticz.js b/platforms/Domoticz.js index d80e6d6..de5b466 100644 --- a/platforms/Domoticz.js +++ b/platforms/Domoticz.js @@ -8,6 +8,15 @@ // - Added support for Scenes // - Sorting device names // +// 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 :( ) +// // Domoticz JSON API required // https://www.domoticz.com/wiki/Domoticz_API/JSON_URL's#Lights_and_switches // @@ -18,7 +27,8 @@ // "name": "Domoticz", // "server": "127.0.0.1", // "port": "8080", -// "roomid": 123 (0=no roomplan) +// "roomid": 123, (0=no roomplan) +// "loadscenes": 1 (0=disable scenes) // } // ], // @@ -47,6 +57,10 @@ function DomoticzPlatform(log, config){ 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) { @@ -85,7 +99,8 @@ DomoticzPlatform.prototype = { if (json['result'] != undefined) { var sArray=sortByKey(json['result'],"Name"); sArray.map(function(s) { - accessory = new DomoticzAccessory(that.log, that, false, s.idx, s.Name, s.HaveDimmer, s.MaxDimLevel, (s.SubType=="RGB")||(s.SubType=="RGBW")); + 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); }) } @@ -108,7 +123,8 @@ DomoticzPlatform.prototype = { sArray.map(function(s) { //only accept switches for now if (typeof s.SwitchType != 'undefined') { - accessory = new DomoticzAccessory(that.log, that, false, s.idx, s.Name, s.HaveDimmer, s.MaxDimLevel, (s.SubType=="RGB")||(s.SubType=="RGBW")); + 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); } }) @@ -120,24 +136,26 @@ DomoticzPlatform.prototype = { }); } //Get Scenes - 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); - }) + 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."); } - callbackLater(); - } else { - that.log("There was a problem connecting to Domoticz."); - } - }); + }); + } } } @@ -165,6 +183,9 @@ DomoticzAccessory.prototype = { url = this.platform.urlForQuery("type=command¶m=setcolbrightnessvalue&idx=" + this.idx + "&hue=" + value + "&brightness=100" + "&iswhite=false"); } else if (c == "setLevel") { + //Range should be 0-16 instead of 0-100 + //See http://www.domoticz.com/wiki/Domoticz_API/JSON_URL%27s#Set_a_dimmable_light_to_a_certain_level + value = Math.round((value / 100) * 16) url = this.platform.urlForQuery("type=command¶m=switchlight&idx=" + this.idx + "&switchcmd=Set%20Level&level=" + value); } else if (value != undefined) { @@ -318,11 +339,11 @@ DomoticzAccessory.prototype = { }, sType: function() { - if (this.HaveDimmer == true) { + //if (this.HaveDimmer == true) { return types.LIGHTBULB_STYPE - } else { - return types.SWITCH_STYPE - } + //} else { + // return types.SWITCH_STYPE + //} }, getServices: function() { diff --git a/platforms/PhilipsHue.js b/platforms/PhilipsHue.js index 0988b06..9bceaf0 100644 --- a/platforms/PhilipsHue.js +++ b/platforms/PhilipsHue.js @@ -216,7 +216,13 @@ PhilipsHueAccessory.prototype = { that.log(device.name + ", characteristic: " + characteristic + ", value: " + value + "."); } else { - that.log(err); + if (err.code == "ECONNRESET") { + setTimeout(function() { + that.executeChange(api, device, characteristic, value); + }, 300); + } else { + that.log(err); + } } }); },