From 26dc3be0f8a51b3d39e0dad77cf155ebf683fdbf Mon Sep 17 00:00:00 2001 From: Nick Farina Date: Sun, 30 Nov 2014 23:07:32 -0800 Subject: [PATCH] Add LiftMaster accessory shim Refactoring --- README.md | 2 +- accessories/Carwings.js | 156 +++++++++++----------- accessories/LiftMaster.js | 262 +++++++++++++++++++++++++++++++++++++ accessories/Lockitron.js | 252 ++++++++++++++++++----------------- accessories/Sonos.js | 156 +++++++++++----------- accessories/WeMo.js | 165 +++++++++++------------ accessories/XfinityHome.js | 244 +++++++++++++++++----------------- app.js | 64 ++++----- config-sample.json | 19 ++- 9 files changed, 789 insertions(+), 531 deletions(-) create mode 100644 accessories/LiftMaster.js diff --git a/README.md b/README.md index bb9b8bf..3e4b787 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ At this point, you should be able to tell Siri to control your devices. However, Also, remember that HomeKit is not very robust yet, and it is common for it to fail intermittently ("Sorry, I wasn't able to control your devices" kind of thing) then start working again for no reason. Also I've noticed that it will get cranky and stop working altogether sometimes. The usual voodoo applies here: reboot your device, or run the BetterHomeKit app and poke around, etc. -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 `siri_name` for the Sonos shim in `config-samples.json` is "Speakers". +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 diff --git a/accessories/Carwings.js b/accessories/Carwings.js index 2c0e015..c027eda 100644 --- a/accessories/Carwings.js +++ b/accessories/Carwings.js @@ -3,7 +3,7 @@ var carwings = require("carwingsjs"); function CarwingsAccessory(log, config) { this.log = log; - this.siriName = config["siri_name"]; + this.name = config["name"]; this.username = config["username"]; this.password = config["password"]; } @@ -41,87 +41,85 @@ CarwingsAccessory.prototype = { }); }, - accessoryData: function() { + getServices: function() { var that = this; - return { - services: [{ - sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.siriName, - 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 - }] + 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 },{ - sType: types.SWITCH_STYPE, - characteristics: [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.siriName, - 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 - }] + 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 + }] + }]; } }; diff --git a/accessories/LiftMaster.js b/accessories/LiftMaster.js new file mode 100644 index 0000000..2b20448 --- /dev/null +++ b/accessories/LiftMaster.js @@ -0,0 +1,262 @@ +var types = require("../lib/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"]; +} + +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"]; + + // look through the array of devices for an opener + for (var i=0; i