From 681cd7d9eb88cb82dcb1ee223225a61f10db1b41 Mon Sep 17 00:00:00 2001 From: madmod Date: Sat, 11 Jul 2015 19:18:14 -0600 Subject: [PATCH 1/3] Add Logitech Harmony platform --- platforms/LogitechHarmony.js | 270 +++++++++++++++++++++++++++++++++++ 1 file changed, 270 insertions(+) create mode 100644 platforms/LogitechHarmony.js diff --git a/platforms/LogitechHarmony.js b/platforms/LogitechHarmony.js new file mode 100644 index 0000000..494c3ed --- /dev/null +++ b/platforms/LogitechHarmony.js @@ -0,0 +1,270 @@ +'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("../lib/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 that = this; + + // Connect to a Harmony hub + var createClient = function (ipAddress) { + that.log("Connecting to Logitech Harmony remote hub..."); + + harmony(ipAddress) + .then(function (client) { + that.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) { + that.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 that = this; + var foundAccessories = []; + + // Get the first hub + this.locateHub(function (err, hub) { + if (err) throw err; + + that.log("Fetching Logitech Harmony devices and activites..."); + + //getDevices(hub); + getActivities(hub); + }); + + // Get Harmony Devices + var getDevices = function(hub) { + that.log("Fetching Logitech Harmony devices..."); + + hub.getDevices() + .then(function (devices) { + that.log("Found devices: ", devices); + + var sArray = sortByKey(json['result'],"Name"); + + sArray.map(function(s) { + accessory = new DomoticzAccessory(that.log, that.server, that.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) { + that.log("Fetching Logitech Harmony activities..."); + + hub.getActivities() + .then(function (activities) { + that.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(that.log, hub, s, true); + 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; +}; + + +LogitechHarmonyAccessory.prototype = { + + command: function (command, value) { + this.log(this.name + " sending command " + command + " with value " + value); + if (this.isActivity) { + if (command === "On") { + this.hub.startActivity(this.id) + } else { + this.hub.turnOff(); + } + } else { + // TODO: Support device specific commands + } + }, + + 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: "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", + 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: true, + supportBonjour: false, + manfDescription: "Name of service", + designedMaxLength: 255 + }, + { + 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 + } + ] + } + ]; + } +}; + +module.exports.accessory = LogitechHarmonyAccessory; +module.exports.platform = LogitechHarmonyPlatform; + From 6cb90084247b58fa827fd256c94984847dcdd7f4 Mon Sep 17 00:00:00 2001 From: madmod Date: Sat, 11 Jul 2015 22:11:08 -0600 Subject: [PATCH 2/3] Add get and set power state for activity --- config-sample.json | 4 ++ package.json | 20 ++++--- platforms/LogitechHarmony.js | 107 +++++++++++++++++++++++------------ 3 files changed, 86 insertions(+), 45 deletions(-) diff --git a/config-sample.json b/config-sample.json index 201c8f1..e03a94b 100644 --- a/config-sample.json +++ b/config-sample.json @@ -42,6 +42,10 @@ "port": "8000", "username": "username", "password": "password" + }, + { + "accessory": "LogitechHarmony", + "name": "Living Room Harmony Hub" } ], diff --git a/package.json b/package.json index da1e577..561cdab 100644 --- a/package.json +++ b/package.json @@ -11,19 +11,21 @@ }, "license": "ISC", "dependencies": { - "hap-nodejs": "git+https://github.com/khaost/HAP-NodeJS#2a1bc8d99a2009317ab5da93faebea34c89f197c", "ad2usb": "git+https://github.com/alistairg/node-ad2usb.git#local", - "request": "2.49.x", - "node-persist": "0.0.x", - "xmldoc": "0.1.x", - "node-hue-api": "^1.0.5", - "xml2js": "0.4.x", "carwingsjs": "0.0.x", + "elkington": "kevinohara80/elkington", + "hap-nodejs": "git+https://github.com/khaost/HAP-NodeJS#2a1bc8d99a2009317ab5da93faebea34c89f197c", + "harmonyhubjs-client": "^1.1.4", + "harmonyhubjs-discover": "git+https://github.com/swissmanu/harmonyhubjs-discover.git", + "node-hue-api": "^1.0.5", + "node-milight-promise": "0.0.2", + "node-persist": "0.0.x", + "request": "2.49.x", "sonos": "0.8.x", + "telldus-live": "0.2.x", "wemo": "0.2.x", "wink-js": "0.0.5", - "elkington": "kevinohara80/elkington", - "node-milight-promise": "0.0.2", - "telldus-live" : "0.2.x" + "xml2js": "0.4.x", + "xmldoc": "0.1.x" } } diff --git a/platforms/LogitechHarmony.js b/platforms/LogitechHarmony.js index 494c3ed..a5c5e22 100644 --- a/platforms/LogitechHarmony.js +++ b/platforms/LogitechHarmony.js @@ -17,7 +17,7 @@ // -var types = require("../lib/HAP-NodeJS/accessories/types.js"); +var types = require('HAP-NodeJS/accessories/types.js'); var harmonyDiscover = require('harmonyhubjs-discover'); var harmony = require('harmonyhubjs-client'); @@ -41,18 +41,17 @@ function LogitechHarmonyPlatform (log, config) { LogitechHarmonyPlatform.prototype = { - // Find one harmony remote hub (only support one for now) + // Find one Harmony remote hub (only support one for now) locateHub: function (callback) { - - var that = this; + var self = this; // Connect to a Harmony hub var createClient = function (ipAddress) { - that.log("Connecting to Logitech Harmony remote hub..."); + self.log("Connecting to Logitech Harmony remote hub..."); harmony(ipAddress) .then(function (client) { - that.log("Connected to Logitech Harmony remote hub"); + self.log("Connected to Logitech Harmony remote hub"); callback(null, client); }); @@ -73,7 +72,7 @@ LogitechHarmonyPlatform.prototype = { // TODO: Support update event with some way to add accessories // TODO: Have some kind of timeout with an error message. Right now this searches forever until it finds one hub. discover.on('online', function (hubInfo) { - that.log("Found Logitech Harmony remote hub: " + hubInfo.ip); + self.log("Found Logitech Harmony remote hub: " + hubInfo.ip); // Stop looking for hubs once we find the first one // TODO: Support multiple hubs @@ -87,50 +86,53 @@ LogitechHarmonyPlatform.prototype = { }, accessories: function (callback) { - var that = this; + var self = this; var foundAccessories = []; // Get the first hub this.locateHub(function (err, hub) { if (err) throw err; - that.log("Fetching Logitech Harmony devices and activites..."); + self.log("Fetching Logitech Harmony devices and activites..."); //getDevices(hub); getActivities(hub); }); // Get Harmony Devices + /* var getDevices = function(hub) { - that.log("Fetching Logitech Harmony devices..."); + self.log("Fetching Logitech Harmony devices..."); hub.getDevices() .then(function (devices) { - that.log("Found devices: ", devices); + self.log("Found devices: ", devices); var sArray = sortByKey(json['result'],"Name"); sArray.map(function(s) { - accessory = new DomoticzAccessory(that.log, that.server, that.port, false, s.idx, s.Name, s.HaveDimmer, s.MaxDimLevel, (s.SubType=="RGB")||(s.SubType=="RGBW")); + 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) { - that.log("Fetching Logitech Harmony activities..."); + self.log("Fetching Logitech Harmony activities..."); hub.getActivities() .then(function (activities) { - that.log("Found activities: \n" + activities.map(function (a) { return "\t" + a.label; }).join("\n")); + 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(that.log, hub, s, true); + var accessory = new LogitechHarmonyAccessory(self.log, hub, s, true); + // TODO: Update the initial power state foundAccessories.push(accessory); }); @@ -150,26 +152,56 @@ function LogitechHarmonyAccessory (log, hub, details, isActivity) { this.id = details.id; this.name = details.label; this.isActivity = isActivity; + this.isActivityActive = false; }; LogitechHarmonyAccessory.prototype = { - command: function (command, value) { - this.log(this.name + " sending command " + command + " with value " + value); + // 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) { - if (command === "On") { - this.hub.startActivity(this.id) - } else { - this.hub.turnOff(); - } + 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 device specific commands + // 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 that = this; + var self = this; return [ { @@ -180,12 +212,13 @@ LogitechHarmonyAccessory.prototype = { onUpdate: null, perms: ["pr"], format: "string", - initialValue: this.name, + initialValue: self.name, supportEvents: false, supportBonjour: false, manfDescription: "Name of the accessory", designedMaxLength: 255 - },{ + }, + { cType: types.MANUFACTURER_CTYPE, onUpdate: null, perms: ["pr"], @@ -195,7 +228,8 @@ LogitechHarmonyAccessory.prototype = { supportBonjour: false, manfDescription: "Manufacturer", designedMaxLength: 255 - },{ + }, + { cType: types.MODEL_CTYPE, onUpdate: null, perms: ["pr"], @@ -205,17 +239,20 @@ LogitechHarmonyAccessory.prototype = { supportBonjour: false, manfDescription: "Model", designedMaxLength: 255 - },{ + }, + { cType: types.SERIAL_NUMBER_CTYPE, onUpdate: null, perms: ["pr"], format: "string", - initialValue: "A1S2NASF88EW", + // 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"], @@ -236,7 +273,7 @@ LogitechHarmonyAccessory.prototype = { onUpdate: null, perms: ["pr"], format: "string", - initialValue: this.name, + initialValue: self.name, supportEvents: true, supportBonjour: false, manfDescription: "Name of service", @@ -245,12 +282,9 @@ LogitechHarmonyAccessory.prototype = { { cType: types.POWER_STATE_CTYPE, onUpdate: function (value) { - if (value == 0) { - that.command("Off") - } else { - that.command("On") - } + self.setPowerState(value) }, + onRead: self.getPowerState, perms: ["pw","pr","ev"], format: "bool", initialValue: 0, @@ -263,6 +297,7 @@ LogitechHarmonyAccessory.prototype = { } ]; } + }; module.exports.accessory = LogitechHarmonyAccessory; From 1cd697fb8bd698de29d6f3fed0208221d47f32a9 Mon Sep 17 00:00:00 2001 From: madmod Date: Mon, 13 Jul 2015 13:16:29 -0600 Subject: [PATCH 3/3] Updated README.md with Logitech Harmony --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 55f4e85..1e35d10 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Since Siri supports devices added through HomeKit, this means that with HomeBrid * _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, 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.