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"
|
||||
},
|
||||
{
|
||||
"accessory": "XfinityHome",
|
||||
"accessory": "iControl",
|
||||
"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.",
|
||||
"email": "your-comcast-email@example.com",
|
||||
"description": "This shim supports iControl-based security systems like Xfinity Home.",
|
||||
"system": "XFINITY_HOME",
|
||||
"email": "your-comcast-email",
|
||||
"password": "your-comcast-password",
|
||||
"dsig": "your-digital-signature",
|
||||
"pin": "your-security-system-pin-code"
|
||||
},
|
||||
{
|
||||
{
|
||||
"accessory": "HomeMatic",
|
||||
"name": "Light",
|
||||
"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",
|
||||
"mdns": "^2.2.4",
|
||||
"node-hue-api": "^1.0.5",
|
||||
"node-icontrol": "^0.1.0",
|
||||
"node-milight-promise": "0.0.x",
|
||||
"node-persist": "0.0.x",
|
||||
"request": "2.49.x",
|
||||
"sonos": "0.8.x",
|
||||
"telldus-live": "0.2.x",
|
||||
"teslams": "1.0.1",
|
||||
"unofficial-nest-api": "git+https://github.com/hachidorii/unofficial_nodejs_nest.git#d8d48edc952b049ff6320ef99afa7b2f04cdee98",
|
||||
"wemo": "0.2.x",
|
||||
"wink-js": "0.0.5",
|
||||
"xml2js": "0.4.x",
|
||||
"xmldoc": "0.1.x",
|
||||
"yamaha-nodejs": "0.4.x",
|
||||
"teslams": "1.0.1"
|
||||
"yamaha-nodejs": "0.4.x"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user