Merge branch 'nfarina/master'

This commit is contained in:
S'pht'Kr
2015-08-24 06:46:46 +02:00
6 changed files with 117 additions and 134 deletions

View File

@@ -1,9 +1,9 @@
# HomeBridge
# Homebridge
HomeBridge is a lightweight NodeJS server you can run on your home network that emulates the iOS HomeKit API. It includes a set of "shims" (found in the [accessories](accessories/) and [platforms](platforms/) folders) that provide a basic bridge from HomeKit to various 3rd-party APIs provided by manufacturers of "smart home" devices.
Homebridge is a lightweight NodeJS server you can run on your home network that emulates the iOS HomeKit API. It includes a set of "shims" (found in the [accessories](accessories/) and [platforms](platforms/) folders) that provide a basic bridge from HomeKit to various 3rd-party APIs provided by manufacturers of "smart home" devices.
Since Siri supports devices added through HomeKit, this means that with HomeBridge you can ask Siri to control devices that don't have any support for HomeKit at all. For instance, using the included shims, you can say things like:
Since Siri supports devices added through HomeKit, this means that with Homebridge you can ask Siri to control devices that don't have any support for HomeKit at all. For instance, using the included shims, you can say things like:
* _Siri, unlock the front door._ ([Lockitron](https://lockitron.com))
* _Siri, open the garage door._ ([LiftMaster MyQ](https://www.myliftmaster.com))
@@ -17,7 +17,7 @@ Since Siri supports devices added through HomeKit, this means that with HomeBrid
If you would like to support any other devices, please write a shim and create a pull request and I'd be happy to add it to this official list.
# Shim types
There are 2 types of shims supported in HomeBridge.
There are 2 types of shims supported in Homebridge.
* Accessory - Individual device
* Platform - A full bridge to another system
@@ -28,9 +28,9 @@ Accessories are individual devices you would like to bridge to HomeKit. You set
## Platforms
Platforms bridge entire systems to HomeKit. Platforms can be things like Wink or SmartThings or Vera. By adding a platform to your `config.json`, HomeBridge will automatically detect all of your devices for you.
Platforms bridge entire systems to HomeKit. Platforms can be things like Wink or SmartThings or Vera. By adding a platform to your `config.json`, Homebridge will automatically detect all of your devices for you.
All you have to do is add the right config options so HomeBridge can authenticate and communicate with your other system, and voila, your devices will be available to HomeKit via HomeBridge.
All you have to do is add the right config options so Homebridge can authenticate and communicate with your other system, and voila, your devices will be available to HomeKit via Homebridge.
# Why?
@@ -38,7 +38,7 @@ Technically, the device manufacturers should be the ones implementing the HomeKi
# Credit
HomeBridge itself is basically just a set of shims and a README. The actual HomeKit API work was done by [KhaosT](http://twitter.com/khaost) in his [HAP-NodeJS](https://github.com/KhaosT/HAP-NodeJS) project. Additionally, many of the shims benefit from amazing NodeJS projects out there like `sonos` and `wemo` that implement all the interesting functionality.
Homebridge itself is basically just a set of shims and a README. The actual HomeKit API work was done by [KhaosT](http://twitter.com/khaost) in his [HAP-NodeJS](https://github.com/KhaosT/HAP-NodeJS) project. Additionally, many of the shims benefit from amazing NodeJS projects out there like `sonos` and `wemo` that implement all the interesting functionality.
# Before you Begin
@@ -50,8 +50,6 @@ I would call this project a "novelty" in its current form, and is for **intrepid
You'll also need some patience, as Siri can be very strict about sentence structure, and occasionally she will forget about HomeKit altogether. But it's not surprising that HomeKit isn't rock solid, since almost no one can actually use it today besides developers who are creating hardware accessories for it. There are, to my knowledge, exactly zero licensed HomeKit devices on the market right now, so Apple can easily get away with this all being a work in progress.
Additionally, the shims I've created implement the bare minimum of HomeKit needed to provide basic functionality like turning things off and on. I haven't written any kind of good feedback or error handling, and although they support changing state, they don't support reading the current state, so if you ask questions like "Is my door unlocked?" Siri will respond with the default of "Nope!" no matter what.
# Getting Started
OK, if you're still excited enough about ordering Siri to make your coffee (which, who wouldn't be!) then here's how to set things up. First, clone this repo:
@@ -66,7 +64,7 @@ Now you should be able to run the homebridge server:
$ cd homebridge
$ npm run start
Starting HomeBridge server...
Starting Homebridge server...
Couldn't find a config.json file [snip]
The server won't do anything until you've created a `config.json` file containing your home devices (or _accessories_ in HomeKit parlance) or platforms you wish to make available to iOS. You can start by copying and modifying the included `config-sample.json` file which includes declarations for all supported accessories and platforms.
@@ -74,7 +72,7 @@ The server won't do anything until you've created a `config.json` file containin
Once you've added your devices and/or platforms, you should be able to run the server again and see them initialize:
$ npm run start
Starting HomeBridge server...
Starting Homebridge server...
Loading 6 accessories...
[Speakers] Initializing 'Sonos' accessory...
[Coffee Maker] Initializing 'WeMo' accessory...
@@ -94,18 +92,18 @@ Fortunately, there are now a few apps in the App Store that can manage your Home
There are also some free apps that work OK. Try [Insteon+](https://itunes.apple.com/US/app/id919270334?mt=8) or [Lutron](https://itunes.apple.com/us/app/lutron-app-for-caseta-wireless/id886753021?mt=8) or a number of others.
If you are a member of the iOS developer program, I highly recommend Apple's [HomeKit Catalog](https://developer.apple.com/library/ios/samplecode/HomeKitCatalog/Introduction/Intro.html) app, as it is reliable and comprehensive and free (and open source).
## Adding HomeKit Accessories
Once you've gotten a HomeKit app running on your iOS device, you can begin adding accessories. The app should "discover" the accessories defined in your `config.json` file, assuming that you're still running the HomeBridge server and you're on the same Wifi network.
Once you've gotten a HomeKit app running on your iOS device, you can use it to add your Homebridge devices. The app should "discover" the single accessory "Homebridge", assuming that you're still running the Homebridge server and you're on the same Wifi network. Adding this accessory will automatically add all accessories and platforms defined in `config.json`.
When you attempt to add a device, it will ask for a "PIN code". The default code for _all_ HomeBridge accessories is `031-45-154`. Adding the device should create some files in the `persist` directory of the HomeBridge server, which stores the pairing relationship.
When you attempt to add Homebridge, it will ask for a "PIN code". The default code is `031-45-154` (but this can be changed, see `config-sample.json`). This process will create some files in the `persist` directory of the Homebridge server, which stores the pairing relationship.
# Interacting with your Devices
Once your device has been added to HomeKit, you should be able to tell Siri to control your devices. However, realize that Siri is a cloud service, and iOS may need some time to synchronize your device information with iCloud.
Also, keep in mind HomeKit is not very robust yet, and it is common for it to fail intermittently ("Sorry, I wasn't able to control your devices" etc.) then start working again for no reason. Also I've noticed that it will get cranky and stop working altogether sometimes. The usual voodoo applies here: reboot your device, restart the homebridge server, run your HomeKit iOS app and poke around, etc.
One final thing to remember is that Siri will almost always prefer its default phrase handling over HomeKit devices. For instance, if you name your Sonos device "Radio" and try saying "Siri, turn on the Radio" then Siri will probably start playing an iTunes Radio station on your phone. Even if you name it "Sonos" and say "Siri, turn on the Sonos", Siri will probably just launch the Sonos app instead. This is why, for instance, the suggested `name` for the Sonos shim in `config-samples.json` is "Speakers".
# Final Notes

View File

@@ -68,7 +68,7 @@ WeMoAccessory.prototype = {
if (!err) {
var binaryState = parseInt(result)
that.log("power state for " + that.wemoName + " is: " + binaryState)
callback(binaryState)
callback(binaryState > 0 ? 1 : 0);
}
else {
that.log(err)

207
app.js
View File

@@ -1,10 +1,21 @@
var fs = require('fs');
var path = require('path');
var storage = require('node-persist');
var crypto = require('crypto');
var hap = require('HAP-NodeJS');
var uuid = require('HAP-NodeJS').uuid;
var Bridge = require('HAP-NodeJS').Bridge;
var Accessory = require('HAP-NodeJS').Accessory;
var accessoryLoader = require('HAP-NodeJS').AccessoryLoader;
console.log("Starting HomeBridge server...");
console.log("_____________________________________________________________________");
console.log("IMPORTANT: Homebridge is in the middle of some big changes.");
console.log(" Read more about it here:");
console.log(" https://github.com/nfarina/homebridge/wiki/Migration-Guide");
console.log("_____________________________________________________________________");
console.log("");
// Look for the configuration file
var configPath = path.join(__dirname, "config.json");
@@ -14,24 +25,48 @@ if (!fs.existsSync(configPath)) {
process.exit(1);
}
// Initialize persistent storage
storage.initSync();
// Initialize HAP-NodeJS
hap.init();
// Load up the configuration file
var config = JSON.parse(fs.readFileSync(configPath));
var config;
try {
config = JSON.parse(fs.readFileSync(configPath));
}
catch (err) {
console.log("There was a problem reading your config.json file.");
console.log("Please try pasting your config.json file here to validate it: http://jsonlint.com");
console.log("");
throw err;
}
// Just to prevent them getting garbage collected
var accessories = [];
// pull out our custom Bridge settings from config.json, if any
var bridgeConfig = config.bridge || {};
// Start by creating our Bridge which will host all loaded Accessories
var bridge = new Bridge(bridgeConfig.name || 'Homebridge', uuid.generate("HomeBridge"));
// keep track of async calls we're waiting for callbacks on before we can start up
// this is hacky but this is all going away once we build proper plugin support
var asyncCalls = 0;
var asyncWait = false;
function startup() {
asyncWait = true;
if (config.platforms) loadPlatforms();
if (config.accessories) loadAccessories();
asyncWait = false;
// publish now unless we're waiting on anyone
if (asyncCalls == 0)
publish();
}
function loadAccessories() {
// Instantiate all accessories in the config
console.log("Loading " + config.accessories.length + " accessories...");
for (var i=0; i<config.accessories.length; i++) {
var accessoryConfig = config.accessories[i];
@@ -46,21 +81,28 @@ function loadAccessories() {
var log = function(name) { return function(s) { console.log("[" + name + "] " + s); }; }(name);
log("Initializing " + accessoryName + " accessory...");
var accessory = new accessoryConstructor(log, accessoryConfig);
accessories.push(accessory);
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 = accessory.getServices();
var services = accessoryInstance.getServices();
// Create the actual HAP-NodeJS "Accessory" instance
var accessory = accessoryLoader.parseAccessoryJSON({
displayName: name,
services: services
});
// Create the HAP server for this accessory
createHAPServer(name, services, accessory.transportCategory);
// add it to the bridge
bridge.addBridgedAccessory(accessory);
}
}
function loadPlatforms() {
console.log("Loading " + config.platforms.length + " platforms...");
for (var i=0; i<config.platforms.length; i++) {
var platformConfig = config.platforms[i];
@@ -76,114 +118,51 @@ function loadPlatforms() {
log("Initializing " + platformName + " platform...");
var platform = new platformConstructor(log, platformConfig);
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
platform.accessories(function(foundAccessories){
// loop through accessories adding them to the list and registering them
for (var i = 0; i < foundAccessories.length; i++) {
accessory = foundAccessories[i]
accessories.push(accessory);
log("Initializing device with name " + accessory.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 = accessory.getServices();
// Create the HAP server for this accessory
createHAPServer(accessory.name, services, accessory.transportCategory);
}
accessories.push.apply(accessories, foundAccessories);
})
getAccessories(name, log);
}
}
//
// Creates the actual HAP servers which listen on different sockets
//
// Pull in required HAP-NodeJS stuff
var accessory_Factor = new require("HAP-NodeJS/Accessory.js");
var accessoryController_Factor = new require("HAP-NodeJS/AccessoryController.js");
var service_Factor = new require("HAP-NodeJS/Service.js");
var characteristic_Factor = new require("HAP-NodeJS/Characteristic.js");
// Each accessory has its own little server. We'll need to allocate some ports for these servers
var nextPort = 51826;
var nextServer = 0;
var accessoryServers = [];
var accessoryControllers = [];
var usernames = {};
function createHAPServer(name, services, transportCategory) {
var accessoryController = new accessoryController_Factor.AccessoryController();
//loop through services
for (var j = 0; j < services.length; j++) {
var service = new service_Factor.Service(services[j].sType);
//loop through characteristics
for (var k = 0; k < services[j].characteristics.length; k++) {
var options = {
onRead: services[j].characteristics[k].onRead,
onRegister: services[j].characteristics[k].onRegister,
type: services[j].characteristics[k].cType,
perms: services[j].characteristics[k].perms,
format: services[j].characteristics[k].format,
initialValue: services[j].characteristics[k].initialValue,
supportEvents: services[j].characteristics[k].supportEvents,
supportBonjour: services[j].characteristics[k].supportBonjour,
manfDescription: services[j].characteristics[k].manfDescription,
designedMaxLength: services[j].characteristics[k].designedMaxLength,
designedMinValue: services[j].characteristics[k].designedMinValue,
designedMaxValue: services[j].characteristics[k].designedMaxValue,
designedMinStep: services[j].characteristics[k].designedMinStep,
unit: services[j].characteristics[k].unit
};
var characteristic = new characteristic_Factor.Characteristic(options, services[j].characteristics[k].onUpdate);
service.addCharacteristic(characteristic);
}
accessoryController.addService(service);
}
// create a unique "username" for this accessory based on the default display name
var username = createUsername(name);
if (usernames[username]) {
console.log("Cannot create another accessory with the same name '" + name + "'. The 'name' property must be unique for each accessory.");
return;
}
// remember that we used this name already
usernames[username] = name;
// increment ports for each accessory
nextPort = nextPort + (nextServer*2);
// hardcode the PIN to something random (same PIN as HAP-NodeJS sample accessories)
var pincode = "031-45-154";
var accessory = new accessory_Factor.Accessory(name, username, storage, parseInt(nextPort), pincode, accessoryController, transportCategory);
accessoryServers[nextServer] = accessory;
accessoryControllers[nextServer] = accessoryController;
accessory.publishAccessory();
nextServer++;
}
// Creates a unique "username" for HomeKit from a hash of the given string
function createUsername(str) {
// Hash str into something like "098F6BCD4621D373CADE4E832627B4F6"
var hash = crypto.createHash('md5').update(str).digest("hex").toUpperCase();
// Turn it into a MAC-address-looking "username" for HomeKit
return hash[0] + hash[1] + ":" +
hash[2] + hash[3] + ":" +
hash[4] + hash[5] + ":" +
hash[6] + hash[7] + ":" +
hash[8] + hash[9] + ":" +
hash[10] + hash[11];
function publish() {
bridge.publish({
username: bridgeConfig.username || "CC:22:3D:E3:CE:30",
port: bridgeConfig.port || 51826,
pincode: bridgeConfig.pin || "031-45-154",
category: Accessory.Categories.OTHER
});
}
startup();

View File

@@ -1,4 +1,11 @@
{
"bridge": {
"name": "Homebridge",
"username": "CC:22:3D:E3:CE:30",
"port": 51826,
"pin": "031-45-154"
},
"description": "This is an example configuration file with all supported devices. You can use this as a template for creating your own configuration file containing devices you actually own.",
"platforms": [

View File

@@ -1,9 +1,9 @@
{
"name": "homebridge",
"description": "HomeKit support for the impatient",
"version": "0.0.0",
"version": "0.1.1",
"scripts": {
"start": "node app.js"
"start": "DEBUG=* node app.js || true"
},
"repository": {
"type": "git",
@@ -15,7 +15,7 @@
"carwingsjs": "0.0.x",
"color": "0.10.x",
"elkington": "kevinohara80/elkington",
"hap-nodejs": "git+https://github.com/khaost/HAP-NodeJS#2a1bc8d99a2009317ab5da93faebea34c89f197c",
"hap-nodejs": "git+https://github.com/KhaosT/HAP-NodeJS#187174846dc4b8970efba74b9eb2968b35f15d87",
"harmonyhubjs-client": "^1.1.4",
"harmonyhubjs-discover": "git+https://github.com/swissmanu/harmonyhubjs-discover.git",
"mdns": "^2.2.4",

View File

@@ -21,7 +21,6 @@ SonosPlatform.prototype = {
device.deviceDescription(function (err, description) {
if (description["zoneType"] != '11' && description["zoneType"] != '8') { // 8 is the Sonos SUB
var roomName = description["roomName"];
if (!roomNamesFound[roomName]) {