diff --git a/accessories/Carwings.js b/accessories/Carwings.js new file mode 100644 index 0000000..2c0e015 --- /dev/null +++ b/accessories/Carwings.js @@ -0,0 +1,167 @@ +var types = require("../lib/HAP-NodeJS/accessories/types.js"); +var carwings = require("carwingsjs"); + +function CarwingsAccessory(log, config) { + this.log = log; + this.siriName = config["siri_name"]; + this.username = config["username"]; + this.password = config["password"]; +} + +CarwingsAccessory.prototype = { + + setPowerState: function(powerOn) { + var that = this; + + carwings.login(this.username, this.password, function(err, result) { + if (!err) { + that.vin = result.vin; + that.log("Got VIN: " + that.vin); + + if (powerOn) { + carwings.startClimateControl(that.vin, null, function(err, result) { + if (!err) + that.log("Started climate control."); + else + that.log("Error starting climate control: " + err); + }); + } + else { + carwings.stopClimateControl(that.vin, function(err, result) { + if (!err) + that.log("Stopped climate control."); + else + that.log("Error stopping climate control: " + err); + }); + } + } + else { + that.log("Error logging in: " + err); + } + }); + }, + + accessoryData: 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 + }] + },{ + 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 + }] + }] + } + } +}; + +module.exports.accessory = CarwingsAccessory; + +// +// Monkey-patch carwings to support climate control - remove if this PR is merged: +// https://github.com/crtr0/carwingsjs/pull/2 +// + +carwings.startClimateControl = function(vin, date, callback) { + + var payload = ['ns4:SmartphoneRemoteACTimerRequest', { + _attr: { + 'xmlns:ns4' : 'urn:com:airbiquity:smartphone.vehicleservice:v1', + 'xmlns:ns3' : 'urn:com:hitachi:gdc:type:vehicle:v1', + 'xmlns:ns2' : 'urn:com:hitachi:gdc:type:portalcommon:v1' }, + 'ns3:ACRemoteRequest' : { + 'ns3:VehicleServiceRequestHeader': { + 'ns2:VIN': vin }, + 'ns3:NewACRemoteRequest': { + 'ns3:ExecuteTime': (date || new Date()).toISOString() } + } + }]; + + _post('vehicleService', payload, callback); +}; + +carwings.stopClimateControl = function(vin, callback) { + + var payload = ['ns4:SmartphoneRemoteACOffRequest', { + _attr: { + 'xmlns:ns4' : 'urn:com:airbiquity:smartphone.vehicleservice:v1', + 'xmlns:ns3' : 'urn:com:hitachi:gdc:type:vehicle:v1', + 'xmlns:ns2' : 'urn:com:hitachi:gdc:type:portalcommon:v1' }, + 'ns3:ACRemoteOffRequest' : { + 'ns3:VehicleServiceRequestHeader': { + 'ns2:VIN': vin } + } + }]; + + _post('vehicleService', payload, callback); +}; diff --git a/accessories/Lockitron.js b/accessories/Lockitron.js index 313f252..ef9d005 100644 --- a/accessories/Lockitron.js +++ b/accessories/Lockitron.js @@ -1,12 +1,9 @@ var types = require("../lib/HAP-NodeJS/accessories/types.js"); var request = require("request"); -// -// Lockitron Accessory -// - function LockitronAccessory(log, config) { this.log = log; + this.siriName = config["siri_name"]; this.lockID = config["lock_id"]; this.accessToken = config["api_token"]; } @@ -47,7 +44,7 @@ LockitronAccessory.prototype = { onUpdate: null, perms: ["pr"], format: "string", - initialValue: "Lockitron", + initialValue: this.siriName, supportEvents: false, supportBonjour: false, manfDescription: "Name of the accessory", diff --git a/accessories/Sonos.js b/accessories/Sonos.js index 151d269..796733f 100644 --- a/accessories/Sonos.js +++ b/accessories/Sonos.js @@ -3,6 +3,7 @@ var sonos = require('sonos'); function SonosAccessory(log, config) { this.log = log; + this.siriName = config["siri_name"]; this.playVolume = config["play_volume"]; this.device = null; this.search(); @@ -70,7 +71,7 @@ SonosAccessory.prototype = { onUpdate: null, perms: ["pr"], format: "string", - initialValue: "Sonos", + initialValue: this.siriName, supportEvents: false, supportBonjour: false, manfDescription: "Name of the accessory", diff --git a/accessories/WeMo.js b/accessories/WeMo.js index 47deeb8..b82f254 100644 --- a/accessories/WeMo.js +++ b/accessories/WeMo.js @@ -3,9 +3,10 @@ var wemo = require('wemo'); function WeMoAccessory(log, config) { this.log = log; - this.friendlyName = config["wemo_name"]; + this.siriName = config["siri_name"]; + this.wemoName = config["wemo_name"]; this.device = null; - this.log("Searching for WeMo device with exact name '" + this.friendlyName + "'..."); + this.log("Searching for WeMo device with exact name '" + this.wemoName + "'..."); this.search(); } @@ -14,8 +15,8 @@ WeMoAccessory.prototype = { search: function() { var that = this; - wemo.Search(this.friendlyName, function(err, device) { - that.log("Found '"+that.friendlyName+"' device at " + device.ip); + wemo.Search(this.wemoName, function(err, device) { + that.log("Found '"+that.wemoName+"' device at " + device.ip); that.device = new wemo(device.ip, device.port); }); }, @@ -23,21 +24,21 @@ WeMoAccessory.prototype = { setPowerState: function(powerOn) { if (!this.device) { - this.log("No '"+this.friendlyName+"' device found (yet?)"); + this.log("No '"+this.wemoName+"' device found (yet?)"); return; } var binaryState = powerOn ? 1 : 0; var that = this; - this.log("Setting power state on the '"+this.friendlyName+"' to " + binaryState); + this.log("Setting power state on the '"+this.wemoName+"' to " + binaryState); this.device.setBinaryState(binaryState, function(err, result) { if (!err) { - that.log("Successfully set power state on the '"+that.friendlyName+"' to " + binaryState); + that.log("Successfully set power state on the '"+that.wemoName+"' to " + binaryState); } else { - that.log("Error setting power state on the '"+that.friendlyName+"'") + that.log("Error setting power state on the '"+that.wemoName+"'") } }); }, @@ -52,7 +53,7 @@ WeMoAccessory.prototype = { onUpdate: null, perms: ["pr"], format: "string", - initialValue: this.friendlyName, + initialValue: this.siriName, supportEvents: false, supportBonjour: false, manfDescription: "Name of the accessory", @@ -105,7 +106,7 @@ WeMoAccessory.prototype = { onUpdate: null, perms: ["pr"], format: "string", - initialValue: this.friendlyName, + initialValue: this.siriName, supportEvents: false, supportBonjour: false, manfDescription: "Name of service", diff --git a/accessories/XfinityHome.js b/accessories/XfinityHome.js index 0ccacd4..3ac7556 100644 --- a/accessories/XfinityHome.js +++ b/accessories/XfinityHome.js @@ -4,6 +4,7 @@ var xmldoc = require("xmldoc"); function XfinityHomeAccessory(log, config) { this.log = log; + this.siriName = config["siri_name"]; this.email = config["email"]; this.password = config["password"]; this.dsig = config["dsig"]; @@ -153,7 +154,7 @@ XfinityHomeAccessory.prototype = { onUpdate: null, perms: ["pr"], format: "string", - initialValue: "Xfinity Home", + initialValue: this.siriName, supportEvents: false, supportBonjour: false, manfDescription: "Name of the accessory", diff --git a/config-sample.json b/config-sample.json index 1575cb3..2641f8f 100644 --- a/config-sample.json +++ b/config-sample.json @@ -21,6 +21,13 @@ "lock_id": "your-lock-id", "api_token" : "your-lockitron-api-access-token" }, + { + "accessory": "Carwings", + "description": "This shim supports controlling climate control on Nissan cars with Carwings. Note that Carwings is super slow and it may take up to 5 minutes for your command to be processed by the Carwings system.", + "siri_name": "Leaf", + "username": "your-carwings-username", + "password" : "your-carwings-password" + }, { "accessory": "XfinityHome", "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.",