{"action":"created","issue":{"url":"https://api.github.com/repos/nfarina/homebridge/issues/50","labels_url":"https://api.github.com/repos/nfarina/homebridge/issues/50/labels{/name}","comments_url":"https://api.github.com/repos/nfarina/homebridge/issues/50/comments","events_url":"https://api.github.com/repos/nfarina/homebridge/issues/50/events","html_url":"https://github.com/nfarina/homebridge/pull/50","id":92251803,"number":50,"title":"Ability to specify a specific MyQDeviceId for the Liftmaster shim","user":{"login":"alistairg","id":272786,"avatar_url":"https://avatars.githubusercontent.com/u/272786?v=3","gravatar_id":"","url":"https://api.github.com/users/alistairg","html_url":"https://github.com/alistairg","followers_url":"https://api.github.com/users/alistairg/followers","following_url":"https://api.github.com/users/alistairg/following{/other_user}","gists_url":"https://api.github.com/users/alistairg/gists{/gist_id}","starred_url":"https://api.github.com/users/alistairg/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/alistairg/subscriptions","organizations_url":"https://api.github.com/users/alistairg/orgs","repos_url":"https://api.github.com/users/alistairg/repos","events_url":"https://api.github.com/users/alistairg/events{/privacy}","received_events_url":"https://api.github.com/users/alistairg/received_events","type":"User","site_admin":false},"labels":[],"state":"closed","locked":false,"assignee":null,"milestone":null,"comments":14,"created_at":"2015-07-01T02:44:35Z","updated_at":"2015-09-02T17:32:36Z","closed_at":"2015-07-01T22:32:05Z","pull_request":{"url":"https://api.github.com/repos/nfarina/homebridge/pulls/50","html_url":"https://github.com/nfarina/homebridge/pull/50","diff_url":"https://github.com/nfarina/homebridge/pull/50.diff","patch_url":"https://github.com/nfarina/homebridge/pull/50.patch"},"body":"The existing Liftmaster shim works well if you have a single door opener on an account. If, however, you have multiple doors - it'll operate the first door found on the account.\r\n\r\nBy (optionally) specifying the device ID in config.json, you can restrict the control to a specific door on the target account."},"comment":{"url":"https://api.github.com/repos/nfarina/homebridge/issues/comments/137181000","html_url":"https://github.com/nfarina/homebridge/pull/50#issuecomment-137181000","issue_url":"https://api.github.com/repos/nfarina/homebridge/issues/50","id":137181000,"user":{"login":"shawc","id":2029999,"avatar_url":"https://avatars.githubusercontent.com/u/2029999?v=3","gravatar_id":"","url":"https://api.github.com/users/shawc","html_url":"https://github.com/shawc","followers_url":"https://api.github.com/users/shawc/followers","following_url":"https://api.github.com/users/shawc/following{/other_user}","gists_url":"https://api.github.com/users/shawc/gists{/gist_id}","starred_url":"https://api.github.com/users/shawc/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/shawc/subscriptions","organizations_url":"https://api.github.com/users/shawc/orgs","repos_url":"https://api.github.com/users/shawc/repos","events_url":"https://api.github.com/users/shawc/events{/privacy}","received_events_url":"https://api.github.com/users/shawc/received_events","type":"User","site_admin":false},"created_at":"2015-09-02T17:32:36Z","updated_at":"2015-09-02T17:32:36Z","body":"here is my liftmaster.js file:\r\nvar types = require(\"HAP-NodeJS/accessories/types.js\");\r\nvar request = require(\"request\");\r\n\r\n// This seems to be the \"id\" of the official LiftMaster iOS app\r\nvar APP_ID = \"JVM/G9Nwih5BwKgNCjLxiFUQxQijAebyyg8QUHr7JOrP+tuPb8iHfRHKwTmDzHOu\"\r\n//var APP_ID = \"Vj8pQggXLhLy0WHahglCD4N1nAkkXQtGYpq2HrHD7H1nvmbT55KqtN6RSF4ILB\"\r\n\r\nfunction LiftMasterAccessory(log, config) {\r\n  this.log = log;\r\n  this.name = config[\"Garage Door\"];\r\n  this.username = config[\"i deleted this for the post\"];\r\n  this.password = config[\"i deleted this for the post\"];\r\n  this.requiredDeviceId = config[\"i deleted this for the post\"];\r\n}\r\n\r\nLiftMasterAccessory.prototype = {\r\n\r\n  setState: function(state) {\r\n    this.targetState = state;\r\n    this.login();\r\n  },\r\n\r\n  login: function() {\r\n    var that = this;\r\n\r\n    // reset our logged-in state hint until we're logged in\r\n    this.deviceId = null;\r\n\r\n    // querystring params\r\n    var query = {\r\n      appId: APP_ID,\r\n      username: this.username,\r\n      password: this.password,\r\n      culture: \"en\"\r\n    };\r\n\r\n    // login to liftmaster\r\n    request.get({\r\n      url: \"https://myqexternal.myqdevice.com/api/user/validatewithculture\",\r\n      qs: query\r\n    }, function(err, response, body) {\r\n\r\n      if (!err && response.statusCode == 200) {\r\n\r\n        // parse and interpret the response\r\n        var json = JSON.parse(body);\r\n        that.userId = json[\"UserId\"];\r\n        that.securityToken = json[\"SecurityToken\"];\r\n        that.log(\"Logged in with user ID \" + that.userId);\r\n        that.getDevice();\r\n      }\r\n      else {\r\n        that.log(\"Error '\"+err+\"' logging in: \" + body);\r\n      }\r\n    });\r\n  },\r\n\r\n  // find your garage door ID\r\n  getDevice: function() {\r\n    var that = this;\r\n\r\n    // querystring params\r\n    var query = {\r\n      appId: APP_ID,\r\n      SecurityToken: this.securityToken,\r\n      filterOn: \"true\"\r\n    };\r\n\r\n    // some necessary duplicated info in the headers\r\n    var headers = {\r\n      MyQApplicationId: APP_ID,\r\n      SecurityToken: this.securityToken\r\n    };\r\n\r\n    // request details of all your devices\r\n    request.get({\r\n      url: \"https://myqexternal.myqdevice.com/api/v4/userdevicedetails/get\",\r\n      qs: query,\r\n      headers: headers\r\n    }, function(err, response, body) {\r\n\r\n      if (!err && response.statusCode == 200) {\r\n\r\n        // parse and interpret the response\r\n        var json = JSON.parse(body);\r\n        var devices = json[\"Devices\"];\r\n        var foundDoors = [];\r\n\r\n        // look through the array of devices for an opener\r\n        for (var i=0; i<devices.length; i++) {\r\n          var device = devices[i];\r\n\r\n          if (device[\"MyQDeviceTypeName\"] == \"GarageDoorOpener\") {\r\n\r\n            // If we haven't explicity specified a door ID, we'll loop to make sure we don't have multiple openers, which is confusing\r\n            if (!that.requiredDeviceId) {\r\n              var thisDeviceId = device.MyQDeviceId;\r\n              var thisDoorName = \"Unknown\";\r\n              for (var j = 0; j < device.Attributes.length; j ++) {\r\n                var thisAttributeSet = device.Attributes[j];\r\n                if (thisAttributeSet.AttributeDisplayName == \"desc\") {\r\n                  thisDoorName = thisAttributeSet.Value;\r\n                  break;\r\n                }\r\n              }\r\n              foundDoors.push(thisDeviceId + \" - \" + thisDoorName);\r\n              that.deviceId = thisDeviceId;\r\n            }\r\n\r\n            // We specified a door ID, sanity check to make sure it's the one we expected\r\n            else if (that.requiredDeviceId == device.MyQDeviceId) {\r\n              that.deviceId = device.MyQDeviceId;\r\n              break;\r\n            }\r\n\r\n          }\r\n\r\n        }\r\n\r\n        // If we have multiple found doors, refuse to proceed\r\n        if (foundDoors.length > 1) {\r\n          that.log(\"WARNING: You have multiple doors on your MyQ account.\");\r\n          that.log(\"WARNING: Specify the ID of the door you want to control using the 'requiredDeviceId' property in your config.json file.\");\r\n          that.log(\"WARNING: You can have multiple liftmaster accessories to cover your multiple doors\");\r\n\r\n          for (var j = 0; j < foundDoors.length; j++) {\r\n            that.log(\"Found Door: \" + foundDoors[j]);\r\n          }\r\n\r\n          throw \"FATAL: Please specify which specific door this Liftmaster accessory should control - you have multiples on your account\";\r\n\r\n        }\r\n\r\n        // Did we get a device ID?\r\n        if (that.deviceId) {\r\n          that.log(\"Found an opener with ID \" + that.deviceId +\". Ready to send command...\");\r\n          that.setTargetState();\r\n        }\r\n        else\r\n        {\r\n          that.log(\"Error: Couldn't find a door device, or the ID you specified isn't associated with your account\");\r\n        }\r\n      }\r\n      else {\r\n        that.log(\"Error '\"+err+\"' getting devices: \" + body);\r\n      }\r\n    });\r\n  },\r\n\r\n  setTargetState: function() {\r\n\r\n    var that = this;\r\n    var liftmasterState = (this.targetState + \"\") == \"1\" ? \"0\" : \"1\";\r\n\r\n    // querystring params\r\n    var query = {\r\n      appId: APP_ID,\r\n      SecurityToken: this.securityToken,\r\n      filterOn: \"true\"\r\n    };\r\n\r\n    // some necessary duplicated info in the headers\r\n    var headers = {\r\n      MyQApplicationId: APP_ID,\r\n      SecurityToken: this.securityToken\r\n    };\r\n\r\n    // PUT request body\r\n    var body = {\r\n      AttributeName: \"desireddoorstate\",\r\n      AttributeValue: liftmasterState,\r\n      ApplicationId: APP_ID,\r\n      SecurityToken: this.securityToken,\r\n      MyQDeviceId: this.deviceId\r\n    };\r\n\r\n    // send the state request to liftmaster\r\n    request.put({\r\n      url: \"https://myqexternal.myqdevice.com/api/v4/DeviceAttribute/PutDeviceAttribute\",\r\n      qs: query,\r\n      headers: headers,\r\n      body: body,\r\n      json: true\r\n    }, function(err, response, json) {\r\n\r\n      if (!err && response.statusCode == 200) {\r\n\r\n        if (json[\"ReturnCode\"] == \"0\")\r\n          that.log(\"State was successfully set.\");\r\n        else\r\n          that.log(\"Bad return code: \" + json[\"ReturnCode\"]);\r\n          that.log(\"Raw response \" + JSON.stringify(json));\r\n      }\r\n      else {\r\n        that.log(\"Error '\"+err+\"' setting door state: \" + JSON.stringify(json));\r\n      }\r\n    });\r\n  },\r\n\r\n  getServices: function() {\r\n    var that = this;\r\n    return [{\r\n      sType: types.ACCESSORY_INFORMATION_STYPE,\r\n      characteristics: [{\r\n        cType: types.NAME_CTYPE,\r\n        onUpdate: null,\r\n        perms: [\"pr\"],\r\n        format: \"string\",\r\n        initialValue: this.name,\r\n        supportEvents: false,\r\n        supportBonjour: false,\r\n        manfDescription: \"Name of the accessory\",\r\n        designedMaxLength: 255\r\n      },{\r\n        cType: types.MANUFACTURER_CTYPE,\r\n        onUpdate: null,\r\n        perms: [\"pr\"],\r\n        format: \"string\",\r\n        initialValue: \"LiftMaster\",\r\n        supportEvents: false,\r\n        supportBonjour: false,\r\n        manfDescription: \"Manufacturer\",\r\n        designedMaxLength: 255\r\n      },{\r\n        cType: types.MODEL_CTYPE,\r\n        onUpdate: null,\r\n        perms: [\"pr\"],\r\n        format: \"string\",\r\n        initialValue: \"Rev-1\",\r\n        supportEvents: false,\r\n        supportBonjour: false,\r\n        manfDescription: \"Model\",\r\n        designedMaxLength: 255\r\n      },{\r\n        cType: types.SERIAL_NUMBER_CTYPE,\r\n        onUpdate: null,\r\n        perms: [\"pr\"],\r\n        format: \"string\",\r\n        initialValue: \"A1S2NASF88EW\",\r\n        supportEvents: false,\r\n        supportBonjour: false,\r\n        manfDescription: \"SN\",\r\n        designedMaxLength: 255\r\n      },{\r\n        cType: types.IDENTIFY_CTYPE,\r\n        onUpdate: null,\r\n        perms: [\"pw\"],\r\n        format: \"bool\",\r\n        initialValue: false,\r\n        supportEvents: false,\r\n        supportBonjour: false,\r\n        manfDescription: \"Identify Accessory\",\r\n        designedMaxLength: 1\r\n      }]\r\n    },{\r\n      sType: types.GARAGE_DOOR_OPENER_STYPE,\r\n      characteristics: [{\r\n        cType: types.NAME_CTYPE,\r\n        onUpdate: null,\r\n        perms: [\"pr\"],\r\n        format: \"string\",\r\n        initialValue: \"Garage Door Opener Control\",\r\n        supportEvents: false,\r\n        supportBonjour: false,\r\n        manfDescription: \"Name of service\",\r\n        designedMaxLength: 255\r\n      },{\r\n        cType: types.CURRENT_DOOR_STATE_CTYPE,\r\n        onUpdate: function(value) { that.log(\"Update current state to \" + value); },\r\n        perms: [\"pr\",\"ev\"],\r\n        format: \"int\",\r\n        initialValue: 0,\r\n        supportEvents: false,\r\n        supportBonjour: false,\r\n        manfDescription: \"BlaBla\",\r\n        designedMinValue: 0,\r\n        designedMaxValue: 4,\r\n        designedMinStep: 1,\r\n        designedMaxLength: 1\r\n      },{\r\n        cType: types.TARGET_DOORSTATE_CTYPE,\r\n        onUpdate: function(value) { that.setState(value); },\r\n        perms: [\"pr\",\"pw\",\"ev\"],\r\n        format: \"int\",\r\n        initialValue: 0,\r\n        supportEvents: false,\r\n        supportBonjour: false,\r\n        manfDescription: \"BlaBla\",\r\n        designedMinValue: 0,\r\n        designedMaxValue: 1,\r\n        designedMinStep: 1,\r\n        designedMaxLength: 1\r\n      },{\r\n        cType: types.OBSTRUCTION_DETECTED_CTYPE,\r\n        onUpdate: function(value) { that.log(\"Obstruction detected: \" + value); },\r\n        perms: [\"pr\",\"ev\"],\r\n        format: \"bool\",\r\n        initialValue: false,\r\n        supportEvents: false,\r\n        supportBonjour: false,\r\n        manfDescription: \"BlaBla\"\r\n      }]\r\n    }];\r\n  }\r\n};\r\n\r\nmodule.exports.accessory = LiftMasterAccessory;\r\n\r\n"}}