From d8e27910ccfc9d2f6cc22a48075828ef4a4bb91f Mon Sep 17 00:00:00 2001 From: Nick Farina Date: Sat, 22 Aug 2015 21:37:42 -0700 Subject: [PATCH] Support new API in getServices() See `getServices()` implementation in `accessories/Http.js` for an example of how to use. Fixes #114 Fixes #57 --- accessories/Http.js | 162 ++++++++++++-------------------------------- app.js | 128 +++++++++++++++++++--------------- 2 files changed, 117 insertions(+), 173 deletions(-) diff --git a/accessories/Http.js b/accessories/Http.js index bfd418b..f1b15ce 100644 --- a/accessories/Http.js +++ b/accessories/Http.js @@ -1,6 +1,11 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var Service = require("HAP-NodeJS").Service; +var Characteristic = require("HAP-NodeJS").Characteristic; var request = require("request"); +module.exports = { + accessory: HttpAccessory +} + function HttpAccessory(log, config) { this.log = log; @@ -9,9 +14,6 @@ function HttpAccessory(log, config) { this.off_url = config["off_url"]; this.brightness_url = config["brightness_url"]; this.http_method = config["http_method"]; - - // device info - this.name = config["name"]; } HttpAccessory.prototype = { @@ -26,135 +28,59 @@ HttpAccessory.prototype = { }) }, - setPowerState: function(powerOn) { + setPowerState: function(powerOn, callback) { var url; if (powerOn) { - url = this.on_url - this.log("Setting power state on the '"+this.name+"' to on"); - }else{ - url = this.off_url - this.log("Setting power state on the '"+this.name+"' to off"); + url = this.on_url; + this.log("Setting power state to on"); + } + else { + url = this.off_url; + this.log("Setting power state to off"); } - this.httpRequest(url, this.http_method, function(error, response, body){ + this.httpRequest(url, this.http_method, function(error, response, body) { if (error) { - return console.error('http power function failed:', error); - }else{ - return console.log('http power function succeeded!'); + this.log('HTTP power function failed: %s', error.message); + callback(error); } - }); - + else { + this.log('HTTP power function succeeded!'); + callback(); + } + }.bind(this)); }, - setBrightness: function(level) { + setBrightness: function(level, callback) { var url = this.brightness_url.replace("%b", level) - this.log("Setting brightness on the '"+this.name+"' to " + level); + this.log("Setting brightness to %s", level); - this.httpRequest(url, this.http_method, function(error, response, body){ + this.httpRequest(url, this.http_method, function(error, response, body) { if (error) { - return console.error('http brightness function failed:', error); - }else{ - return console.log('http brightness function succeeded!'); + this.log('HTTP brightness function failed: %s', error); + callback(error); } - }); - + else { + this.log('HTTP brightness function succeeded!'); + callback(); + } + }.bind(this)); }, - + 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: "Http", - 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.LIGHTBULB_STYPE, - characteristics: [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: true, - 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: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Change the power state", - designedMaxLength: 1 - },{ - cType: types.BRIGHTNESS_CTYPE, - onUpdate: function(value) { that.setBrightness(value); }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Adjust Brightness", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - }] - }]; + + var lightbulbService = new Service.Lightbulb(); + + lightbulbService + .getCharacteristic(Characteristic.On) + .on('set', this.setPowerState.bind(this)); + + lightbulbService + .addCharacteristic(new Characteristic.Brightness()) + .on('set', this.setBrightness.bind(this)); + + return [lightbulbService]; } }; - -module.exports.accessory = HttpAccessory; diff --git a/app.js b/app.js index f4423b3..e678fb9 100644 --- a/app.js +++ b/app.js @@ -5,6 +5,7 @@ var hap = require('HAP-NodeJS'); var uuid = require('HAP-NodeJS').uuid; var Bridge = require('HAP-NodeJS').Bridge; var Accessory = require('HAP-NodeJS').Accessory; +var Service = require('HAP-NodeJS').Service; var accessoryLoader = require('HAP-NodeJS').AccessoryLoader; console.log("Starting HomeBridge server..."); @@ -72,28 +73,19 @@ function loadAccessories() { var accessoryConfig = config.accessories[i]; // Load up the class for this accessory - var accessoryName = accessoryConfig["accessory"]; // like "WeMo" - var accessoryModule = require('./accessories/' + accessoryName + ".js"); // like "./accessories/WeMo.js" + var accessoryType = accessoryConfig["accessory"]; // like "WeMo" + var accessoryModule = require('./accessories/' + accessoryType + ".js"); // like "./accessories/WeMo.js" var accessoryConstructor = accessoryModule.accessory; // like "WeMoAccessory", a JavaScript constructor // Create a custom logging function that prepends the device display name for debugging - var name = accessoryConfig["name"]; - var log = function(name) { return function(s) { console.log("[" + name + "] " + s); }; }(name); + var accessoryName = accessoryConfig["name"]; + var log = createLog(accessoryName); - log("Initializing " + accessoryName + " accessory..."); + log("Initializing %s accessory...", accessoryType); var accessoryInstance = new accessoryConstructor(log, accessoryConfig); - - // Extract the raw "services" for this accessory which is a big array of objects describing the various - // hooks in and out of HomeKit for the HAP-NodeJS server. - var services = accessoryInstance.getServices(); + var accessory = createAccessory(accessoryInstance, accessoryName); - // Create the actual HAP-NodeJS "Accessory" instance - var accessory = accessoryLoader.parseAccessoryJSON({ - displayName: name, - services: services - }); - // add it to the bridge bridge.addBridgedAccessory(accessory); } @@ -108,54 +100,80 @@ function loadPlatforms() { var platformConfig = config.platforms[i]; // Load up the class for this accessory - var platformName = platformConfig["platform"]; // like "Wink" - var platformModule = require('./platforms/' + platformName + ".js"); // like "./platforms/Wink.js" + var platformType = platformConfig["platform"]; // like "Wink" + var platformName = platformConfig["name"]; + var platformModule = require('./platforms/' + platformType + ".js"); // like "./platforms/Wink.js" var platformConstructor = platformModule.platform; // like "WinkPlatform", a JavaScript constructor - // Create a custom logging function that prepends the platform display name for debugging - var name = platformConfig["name"]; - var log = function(name) { return function(s) { console.log("[" + name + "] " + s); }; }(name); + // Create a custom logging function that prepends the platform name for debugging + var log = createLog(platformName); - log("Initializing " + platformName + " platform..."); + log("Initializing %s platform...", platformType); var platformInstance = new platformConstructor(log, platformConfig); - - // wrap name and log in a closure so they don't change in the callback - function getAccessories(name, log) { - asyncCalls++; - platformInstance.accessories(function(foundAccessories){ - asyncCalls--; - // loop through accessories adding them to the list and registering them - for (var i = 0; i < foundAccessories.length; i++) { - var accessoryInstance = foundAccessories[i]; - - log("Initializing device with name " + accessoryInstance.name + "...") - - // Extract the raw "services" for this accessory which is a big array of objects describing the various - // hooks in and out of HomeKit for the HAP-NodeJS server. - var services = accessoryInstance.getServices(); - - // Create the actual HAP-NodeJS "Accessory" instance - var accessory = accessoryLoader.parseAccessoryJSON({ - displayName: name, - services: services - }); - - // add it to the bridge - bridge.addBridgedAccessory(accessory); - } - - // were we the last callback? - if (asyncCalls === 0 && !asyncWait) - publish(); - }) - } - - // query for devices - getAccessories(name, log); + loadPlatformAccessories(platformInstance, log); } } +function loadPlatformAccessories(platformInstance, log) { + asyncCalls++; + platformInstance.accessories(function(foundAccessories){ + asyncCalls--; + + // loop through accessories adding them to the list and registering them + for (var i = 0; i < foundAccessories.length; i++) { + var accessoryInstance = foundAccessories[i]; + var accessoryName = accessoryInstance.name; // assume this property was set + + log("Initializing platform accessory '%s'...", accessoryName); + + var accessory = createAccessory(accessoryInstance, accessoryName); + + // add it to the bridge + bridge.addBridgedAccessory(accessory); + } + + // were we the last callback? + if (asyncCalls === 0 && !asyncWait) + publish(); + }); +} + +function createAccessory(accessoryInstance, displayName) { + + var services = accessoryInstance.getServices(); + + if (!(services[0] instanceof Service)) { + // The returned "services" for this accessory is assumed to be the old style: a big array + // of JSON-style objects that will need to be parsed by HAP-NodeJS's AccessoryLoader. + + // Create the actual HAP-NodeJS "Accessory" instance + return accessoryLoader.parseAccessoryJSON({ + displayName: displayName, + services: services + }); + } + else { + // The returned "services" for this accessory are simply an array of new-API-style + // Service instances which we can add to a created HAP-NodeJS Accessory directly. + + var accessoryUUID = uuid.generate(accessoryInstance.constructor.name + ":" + displayName); + + var accessory = new Accessory(displayName, accessoryUUID); + services.forEach(function(service) { accessory.addService(service); }); + return accessory; + } +} + +// Returns a logging function that prepends messages with the given name in [brackets]. +function createLog(name) { + return function(message) { + var rest = Array.prototype.slice.call(arguments, 1 ); // any arguments after message + var args = ["[%s] " + message, name].concat(rest); + console.log.apply(console, args); + } +} + function publish() { bridge.publish({ username: bridgeConfig.username || "CC:22:3D:E3:CE:30",