mirror of
https://github.com/mtan93/homebridge.git
synced 2026-03-08 05:31:55 +00:00
New iControl accessory, supports Xfinity Home.
- No more mysterious "dsig" param
This commit is contained in:
@@ -1,284 +0,0 @@
|
|||||||
var types = require("HAP-NodeJS/accessories/types.js");
|
|
||||||
var request = require("request");
|
|
||||||
var xmldoc = require("xmldoc");
|
|
||||||
|
|
||||||
function XfinityHomeAccessory(log, config) {
|
|
||||||
this.log = log;
|
|
||||||
this.name = config["name"];
|
|
||||||
this.email = config["email"];
|
|
||||||
this.password = config["password"];
|
|
||||||
this.dsig = config["dsig"];
|
|
||||||
this.pinCode = config["pin"];
|
|
||||||
}
|
|
||||||
|
|
||||||
XfinityHomeAccessory.prototype = {
|
|
||||||
|
|
||||||
armWithType: function(armed, type) {
|
|
||||||
this.log("Arming with type " + type + " = " + armed + "...");
|
|
||||||
this.targetArmed = armed;
|
|
||||||
this.targetArmType = type;
|
|
||||||
this.getLoginToken();
|
|
||||||
},
|
|
||||||
|
|
||||||
getLoginToken: function() {
|
|
||||||
this.log("Retrieving login token...");
|
|
||||||
|
|
||||||
var that = this;
|
|
||||||
|
|
||||||
request.post({
|
|
||||||
url: "https://login.comcast.net/api/login",
|
|
||||||
form: {
|
|
||||||
appkey:"iControl",
|
|
||||||
dsig: this.dsig,
|
|
||||||
u: this.email,
|
|
||||||
p: this.password
|
|
||||||
}
|
|
||||||
}, function(err, response, body) {
|
|
||||||
|
|
||||||
if (!err && response.statusCode == 200) {
|
|
||||||
|
|
||||||
var doc = new xmldoc.XmlDocument(body);
|
|
||||||
that.loginToken = doc.valueWithPath("LoginToken");
|
|
||||||
that.refreshLoginCookie();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
that.log("Error '"+err+"' getting login token: " + body);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
refreshLoginCookie: function() {
|
|
||||||
this.log("Refreshing login cookie...");
|
|
||||||
|
|
||||||
var that = this;
|
|
||||||
|
|
||||||
request.post({
|
|
||||||
url: "https://www.xfinityhomesecurity.com/rest/icontrol/login",
|
|
||||||
form: {
|
|
||||||
token: this.loginToken
|
|
||||||
}
|
|
||||||
}, function(err, response, body) {
|
|
||||||
|
|
||||||
if (!err && response.statusCode == 200) {
|
|
||||||
|
|
||||||
// extract our "site" from the login response
|
|
||||||
var json = JSON.parse(body);
|
|
||||||
that.siteHref = json["login"]["site"]["href"];
|
|
||||||
|
|
||||||
// manual cookie handling
|
|
||||||
that.loginCookie = response.headers["set-cookie"];
|
|
||||||
|
|
||||||
that.getInstances();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
that.log("Error '"+err+"' refreshing login cookie: " + body);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
getInstances: function() {
|
|
||||||
this.log("Getting instances for site " + this.siteHref + "...");
|
|
||||||
|
|
||||||
this.panelHref = null;
|
|
||||||
var that = this;
|
|
||||||
|
|
||||||
request.get({
|
|
||||||
url: "https://www.xfinityhomesecurity.com/"+that.siteHref+"/network/instances",
|
|
||||||
headers: { Cookie: this.loginCookie },
|
|
||||||
json: true
|
|
||||||
}, function(err, response, json) {
|
|
||||||
|
|
||||||
if (!err && response.statusCode == 200) {
|
|
||||||
|
|
||||||
// extract our "instance" from the response. look for the first "panel"
|
|
||||||
var instances = json["instances"]["instance"];
|
|
||||||
for (var i=0; i<instances.length; i++) {
|
|
||||||
var instance = instances[i];
|
|
||||||
|
|
||||||
if (instance["mediaType"] == "instance/panel") {
|
|
||||||
that.panelHref = instance.href;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (that.panelHref) {
|
|
||||||
that.log("Found panel " + that.panelHref + ". Ready to arm.");
|
|
||||||
that.finishArm();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
that.log("Couldn't find a panel.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
that.log("Error '"+err+"' getting instances: " + JSON.stringify(json));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
finishArm: function() {
|
|
||||||
this.log("Finish arming with type " + this.targetArmType + " = " + this.targetArmed + "...");
|
|
||||||
|
|
||||||
var path, form;
|
|
||||||
var that = this;
|
|
||||||
|
|
||||||
if (!this.targetArmed) {
|
|
||||||
path = this.panelHref + "/functions/disarm";
|
|
||||||
form = {code: this.pinCode};
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
path = this.panelHref + "/functions/arm";
|
|
||||||
form = {code: this.pinCode, armType: this.targetArmType };
|
|
||||||
}
|
|
||||||
|
|
||||||
request.post({
|
|
||||||
url: "https://www.xfinityhomesecurity.com"+path,
|
|
||||||
headers: { Cookie: this.loginCookie },
|
|
||||||
form: form
|
|
||||||
}, function(err, response, body) {
|
|
||||||
|
|
||||||
if (!err && response.statusCode >= 200 && response.statusCode < 300) {
|
|
||||||
that.log("Arm response: " + response);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
that.log("Error '"+err+"' performing arm request: " + 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: "Comcast",
|
|
||||||
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: "Away Mode",
|
|
||||||
supportEvents: false,
|
|
||||||
supportBonjour: false,
|
|
||||||
manfDescription: "Away Mode service",
|
|
||||||
designedMaxLength: 255
|
|
||||||
},{
|
|
||||||
cType: types.POWER_STATE_CTYPE,
|
|
||||||
onUpdate: function(value) { that.armWithType(value, "away"); },
|
|
||||||
perms: ["pw","pr","ev"],
|
|
||||||
format: "bool",
|
|
||||||
initialValue: false,
|
|
||||||
supportEvents: false,
|
|
||||||
supportBonjour: false,
|
|
||||||
manfDescription: "Turn on the Away alarm",
|
|
||||||
designedMaxLength: 1
|
|
||||||
}]
|
|
||||||
},{
|
|
||||||
sType: types.SWITCH_STYPE,
|
|
||||||
characteristics: [{
|
|
||||||
cType: types.NAME_CTYPE,
|
|
||||||
onUpdate: null,
|
|
||||||
perms: ["pr"],
|
|
||||||
format: "string",
|
|
||||||
initialValue: "Night Mode",
|
|
||||||
supportEvents: false,
|
|
||||||
supportBonjour: false,
|
|
||||||
manfDescription: "Night Mode service",
|
|
||||||
designedMaxLength: 255
|
|
||||||
},{
|
|
||||||
cType: types.POWER_STATE_CTYPE,
|
|
||||||
onUpdate: function(value) { that.armWithType(value, "night"); },
|
|
||||||
perms: ["pw","pr","ev"],
|
|
||||||
format: "bool",
|
|
||||||
initialValue: false,
|
|
||||||
supportEvents: false,
|
|
||||||
supportBonjour: false,
|
|
||||||
manfDescription: "Turn on the Night alarm",
|
|
||||||
designedMaxLength: 1
|
|
||||||
}]
|
|
||||||
},{
|
|
||||||
sType: types.SWITCH_STYPE,
|
|
||||||
characteristics: [{
|
|
||||||
cType: types.NAME_CTYPE,
|
|
||||||
onUpdate: null,
|
|
||||||
perms: ["pr"],
|
|
||||||
format: "string",
|
|
||||||
initialValue: "Stay Mode",
|
|
||||||
supportEvents: false,
|
|
||||||
supportBonjour: false,
|
|
||||||
manfDescription: "Stay Mode service",
|
|
||||||
designedMaxLength: 255
|
|
||||||
},{
|
|
||||||
cType: types.POWER_STATE_CTYPE,
|
|
||||||
onUpdate: function(value) { that.armWithType(value, "stay"); },
|
|
||||||
perms: ["pw","pr","ev"],
|
|
||||||
format: "bool",
|
|
||||||
initialValue: false,
|
|
||||||
supportEvents: false,
|
|
||||||
supportBonjour: false,
|
|
||||||
manfDescription: "Turn on the Stay alarm",
|
|
||||||
designedMaxLength: 1
|
|
||||||
}]
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Enable cookie handling and append our expected headers
|
|
||||||
request = request.defaults({
|
|
||||||
headers: {
|
|
||||||
"X-appkey": "comcastTokenKey",
|
|
||||||
"X-ClientInfo": "5.2.51",
|
|
||||||
"X-format": "json"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports.accessory = XfinityHomeAccessory;
|
|
||||||
130
accessories/iControl.js
Normal file
130
accessories/iControl.js
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
@@ -104,15 +104,15 @@
|
|||||||
"password" : "your-carwings-password"
|
"password" : "your-carwings-password"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"accessory": "XfinityHome",
|
"accessory": "iControl",
|
||||||
"name": "Xfinity Home",
|
"name": "Xfinity Home",
|
||||||
"description": "This shim supports the 'Xfinity Home' security system. Unfortunately I don't know how to generate the 'dsig' property, so you'll need to figure yours out by running the Xfinity Home app on your iOS device while connected to a proxy server like Charles. If you didn't understand any of that, sorry! I welcome any suggestions for how to figure out dsig automatically.",
|
"description": "This shim supports iControl-based security systems like Xfinity Home.",
|
||||||
"email": "your-comcast-email@example.com",
|
"system": "XFINITY_HOME",
|
||||||
|
"email": "your-comcast-email",
|
||||||
"password": "your-comcast-password",
|
"password": "your-comcast-password",
|
||||||
"dsig": "your-digital-signature",
|
|
||||||
"pin": "your-security-system-pin-code"
|
"pin": "your-security-system-pin-code"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"accessory": "HomeMatic",
|
"accessory": "HomeMatic",
|
||||||
"name": "Light",
|
"name": "Light",
|
||||||
"description": "Control HomeMatic devices (The XMP-API addon for the CCU is required)",
|
"description": "Control HomeMatic devices (The XMP-API addon for the CCU is required)",
|
||||||
|
|||||||
@@ -20,17 +20,18 @@
|
|||||||
"harmonyhubjs-discover": "git+https://github.com/swissmanu/harmonyhubjs-discover.git",
|
"harmonyhubjs-discover": "git+https://github.com/swissmanu/harmonyhubjs-discover.git",
|
||||||
"mdns": "^2.2.4",
|
"mdns": "^2.2.4",
|
||||||
"node-hue-api": "^1.0.5",
|
"node-hue-api": "^1.0.5",
|
||||||
|
"node-icontrol": "^0.1.0",
|
||||||
"node-milight-promise": "0.0.x",
|
"node-milight-promise": "0.0.x",
|
||||||
"node-persist": "0.0.x",
|
"node-persist": "0.0.x",
|
||||||
"request": "2.49.x",
|
"request": "2.49.x",
|
||||||
"sonos": "0.8.x",
|
"sonos": "0.8.x",
|
||||||
"telldus-live": "0.2.x",
|
"telldus-live": "0.2.x",
|
||||||
|
"teslams": "1.0.1",
|
||||||
"unofficial-nest-api": "git+https://github.com/hachidorii/unofficial_nodejs_nest.git#d8d48edc952b049ff6320ef99afa7b2f04cdee98",
|
"unofficial-nest-api": "git+https://github.com/hachidorii/unofficial_nodejs_nest.git#d8d48edc952b049ff6320ef99afa7b2f04cdee98",
|
||||||
"wemo": "0.2.x",
|
"wemo": "0.2.x",
|
||||||
"wink-js": "0.0.5",
|
"wink-js": "0.0.5",
|
||||||
"xml2js": "0.4.x",
|
"xml2js": "0.4.x",
|
||||||
"xmldoc": "0.1.x",
|
"xmldoc": "0.1.x",
|
||||||
"yamaha-nodejs": "0.4.x",
|
"yamaha-nodejs": "0.4.x"
|
||||||
"teslams": "1.0.1"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user