mirror of
https://github.com/mtan93/homebridge.git
synced 2026-03-08 05:31:55 +00:00
Compare commits
145 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a561f8754 | ||
|
|
d9d58855cd | ||
|
|
3acded3ea2 | ||
|
|
8a35d75cb5 | ||
|
|
c70cb8be07 | ||
|
|
7406f6c9f3 | ||
|
|
cbae68afdd | ||
|
|
495143e1a1 | ||
|
|
7585cf3f83 | ||
|
|
d521755a49 | ||
|
|
6f4ce80aea | ||
|
|
efda0fac11 | ||
|
|
600760884d | ||
|
|
3a4830ee57 | ||
|
|
582e00a6ef | ||
|
|
debba05d2f | ||
|
|
027a693c0d | ||
|
|
b5e1fc52a8 | ||
|
|
f17fe59590 | ||
|
|
69e3ed5ee4 | ||
|
|
3354842e81 | ||
|
|
e5e2a400ec | ||
|
|
4884087041 | ||
|
|
e1867b2bc0 | ||
|
|
4d0f9d86f6 | ||
|
|
ecda18029f | ||
|
|
7acac442a8 | ||
|
|
efc570e5a9 | ||
|
|
7955049337 | ||
|
|
8c6cb53dcb | ||
|
|
b6cfe3ba7c | ||
|
|
f836d4a42c | ||
|
|
f893322887 | ||
|
|
63ab1025e9 | ||
|
|
9a25a363d4 | ||
|
|
dc43d0b7c4 | ||
|
|
1513e5398f | ||
|
|
7c3543ba61 | ||
|
|
5adb5f3282 | ||
|
|
ffe343c65f | ||
|
|
fedd341970 | ||
|
|
c7c9aa0150 | ||
|
|
a2baa93801 | ||
|
|
e3e08414f6 | ||
|
|
ea9df45d2d | ||
|
|
7425f8beca | ||
|
|
8192fc2672 | ||
|
|
dbb7b89cf9 | ||
|
|
4f000485db | ||
|
|
c0884f484e | ||
|
|
363c997058 | ||
|
|
332385d605 | ||
|
|
0ccd80c8e7 | ||
|
|
ef8fe5ced2 | ||
|
|
4a531ede5c | ||
|
|
ff35ece65c | ||
|
|
66ea6e640d | ||
|
|
ecd06d7239 | ||
|
|
c89ff97ac5 | ||
|
|
ceec4c74fd | ||
|
|
925c1630c4 | ||
|
|
41c53f8f10 | ||
|
|
4eabc4ad52 | ||
|
|
c0859a29d3 | ||
|
|
c15707e875 | ||
|
|
8c476b45a0 | ||
|
|
f49229d73c | ||
|
|
fbccc031f4 | ||
|
|
d70fa741d8 | ||
|
|
4740bf1fc5 | ||
|
|
da57b29972 | ||
|
|
5944365bc6 | ||
|
|
a8908fd9b8 | ||
|
|
8ef7e62094 | ||
|
|
15c8eaaf29 | ||
|
|
e6648375c7 | ||
|
|
4251b15291 | ||
|
|
a52bc9e437 | ||
|
|
b78c081cd4 | ||
|
|
3f2cd08383 | ||
|
|
87050a2267 | ||
|
|
c8cb0731ff | ||
|
|
35dfaabc69 | ||
|
|
77ce39e157 | ||
|
|
0af8a43dc9 | ||
|
|
f203a2ac6f | ||
|
|
39af2ebbef | ||
|
|
620c8473b8 | ||
|
|
b2f476f833 | ||
|
|
c6d2f889fc | ||
|
|
f73783787d | ||
|
|
2ea2052769 | ||
|
|
64e8c83d9c | ||
|
|
b94c3caa3b | ||
|
|
1a710badef | ||
|
|
73fdec5928 | ||
|
|
13333999f3 | ||
|
|
87c48d7267 | ||
|
|
9b42fafdaf | ||
|
|
911f088df9 | ||
|
|
6fade3c3cc | ||
|
|
842ec105be | ||
|
|
df8508a38f | ||
|
|
191c75c281 | ||
|
|
1fb58be2b9 | ||
|
|
9d7c1de9dd | ||
|
|
195255bf0d | ||
|
|
6b182fc4e7 | ||
|
|
c7b2500518 | ||
|
|
1f1030766a | ||
|
|
8cb22efb83 | ||
|
|
ca66cc3499 | ||
|
|
6ae2a19d37 | ||
|
|
ffe4232c3b | ||
|
|
f6df85695d | ||
|
|
32e776203f | ||
|
|
c3c2f8815d | ||
|
|
6500912f54 | ||
|
|
2e2c8eb207 | ||
|
|
fa9561d98a | ||
|
|
16a29f302d | ||
|
|
012005ddc7 | ||
|
|
27ffd6e944 | ||
|
|
0b28387cb1 | ||
|
|
815ea7abea | ||
|
|
cf80e4f2da | ||
|
|
40266af8b2 | ||
|
|
57beabf0b4 | ||
|
|
d3c77a4cda | ||
|
|
8e360491cf | ||
|
|
e546440575 | ||
|
|
902fdded65 | ||
|
|
8de375a4b0 | ||
|
|
c02e212b4c | ||
|
|
7436be9b44 | ||
|
|
2ad7932fbc | ||
|
|
7dd8e12791 | ||
|
|
c93b0b0df1 | ||
|
|
b49fd2d6a5 | ||
|
|
9c8812da70 | ||
|
|
9e6bf028ba | ||
|
|
aebd152ff9 | ||
|
|
5b9c5192fe | ||
|
|
e1334c5196 | ||
|
|
40fc7acbed |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -9,4 +9,6 @@ npm-debug.log
|
||||
|
||||
# Ignore any extra plugins in the example directory that aren't in Git already
|
||||
# (this is a sandbox for the user)
|
||||
example-plugins
|
||||
example-plugins
|
||||
|
||||
.idea
|
||||
49
README.md
49
README.md
@@ -1,5 +1,5 @@
|
||||
|
||||
[](https://homebridge-slackin.herokuapp.com)
|
||||
[](https://slackin-xiwztisllv.now.sh)
|
||||
|
||||
# Homebridge
|
||||
|
||||
@@ -29,7 +29,11 @@ You can also chat with us in our nascent [Slack instance](http://homebridge-slac
|
||||
|
||||
Homebridge is published through [NPM](https://www.npmjs.com/package/homebridge) and should be installed "globally" by typing:
|
||||
|
||||
sudo npm install -g homebridge
|
||||
sudo npm install -g --unsafe-perm homebridge
|
||||
|
||||
You may need to use the `--unsafe-perm` flag if you receive an error similar to this:
|
||||
|
||||
gyp WARN EACCES user "root" does not have permission to access the dev dir "/root/.node-gyp/5.5.0"
|
||||
|
||||
Now you should be able to run Homebridge:
|
||||
|
||||
@@ -45,9 +49,9 @@ Once you've installed a Plugin or two, you can run Homebridge again:
|
||||
|
||||
However, Homebridge won't do anything until you've created a `config.json` file containing your accessories and/or platforms. You can start by copying and modifying the included `config-sample.json` file which includes declarations for some example accessories and platforms. Each Plugin will have its own expected configuration; the documentation for Plugins should give you some real-world examples for that plugin.
|
||||
|
||||
**NOTE**: Your `config.json` file MUST live in your home directory inside `.homebridge`. The full error message will contain the exact path where your config is expected to be found.
|
||||
**NOTE**: Your `config.json` file MUST be inside of `.homebridge`, which is inside of your home folder. On macOS and Linux, the full path for your `config.json` would be `~/.homebridge/config.json`. Any error messages will contain the exact path where your config is expected to be found.
|
||||
|
||||
**REALLY IMPORTANT**: You must use a "plain text" editor to create or modify `config.json`. Do NOT use apps like TextEdit on Mac or Wordpad on Windows; these apps will corrupt the formatting of the file in hard-to-debug ways. I suggest using the free [Atom text editor](http://atom.io).
|
||||
**REALLY IMPORTANT**: You must use a "plain text" editor to create or modify `config.json`. Do NOT use apps like TextEdit on Mac or Wordpad on Windows. Apps like these will corrupt the formatting of the file in hard-to-debug ways, making improper `"` signs is an example. I suggest using the free [Atom text editor](http://atom.io).
|
||||
|
||||
Once you've added your config file, you should be able to run Homebridge again:
|
||||
|
||||
@@ -79,15 +83,11 @@ You can explore all available plugins at the NPM website by [searching for the k
|
||||
|
||||
# Adding Homebridge to iOS
|
||||
|
||||
HomeKit is actually not an app; it's a "database" similar to HealthKit and PassKit. But where HealthKit has the companion _Health_ app and PassKit has _Passbook_, Apple has supplied no app for managing your HomeKit database (at least [not yet](http://9to5mac.com/2015/05/20/apples-planned-ios-9-home-app-uses-virtual-rooms-to-manage-homekit-accessories/)). However, the HomeKit API is open for developers to write their own apps for adding devices to HomeKit.
|
||||
HomeKit itself is actually not an app; it's a "database" similar to HealthKit and PassKit. Where HealthKit has the companion _Health_ app and PassKit has _Passbook_, HomeKit has the _Home_ app, introduced with iOS 10.
|
||||
|
||||
Fortunately, there are now a few apps in the App Store that can manage your HomeKit devices. The most comprehensive one I've used is [MyTouchHome](https://itunes.apple.com/us/app/mytouchhome/id965142360?mt=8&at=11lvmd&ct=mhweb) which costs $2.
|
||||
If you are a member of the iOS developer program, you might also find Apple's [HomeKit Catalog](https://developer.apple.com/library/ios/samplecode/HomeKitCatalog/Introduction/Intro.html) app to be useful, as it provides straightforward and comprehensive management of all HomeKit database "objects".
|
||||
|
||||
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).
|
||||
|
||||
Once you've gotten a HomeKit app running on your iOS device, it should "discover" the single accessory "Homebridge", assuming that you're still running Homebridge and you're on the same Wifi network. Adding this accessory will automatically add all accessories and platforms defined in `config.json`.
|
||||
Using the Home app (or most other HomeKit apps), you should be able to add the single accessory "Homebridge", assuming that you're still running Homebridge 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 Homebridge, it will ask for a "PIN code". The default code is `031-45-154` (but this can be changed, see `config-sample.json`).
|
||||
|
||||
@@ -101,18 +101,32 @@ One final thing to remember is that Siri will almost always prefer its default p
|
||||
|
||||
We don't have a lot of documentation right now for creating plugins, but there are many existing plugins you can study.
|
||||
|
||||
The best place to start is the included [Example Plugins](https://github.com/nfarina/homebridge/tree/master/example-plugins). Right now this contains a single plugin that registers a fake door lock Accessory. This will show you how to use the Homebridge Plugin API.
|
||||
The best place to start is the included [Example Plugins](https://github.com/nfarina/homebridge/tree/master/example-plugins). Right now this contains a single plugin that registers a platform that offers fake light accessories. This will show you how to use the Homebridge Plugin API.
|
||||
|
||||
For more example on how to construct HomeKit Services and Characteristics, see the many Accessories in the [Legacy Plugins](https://github.com/nfarina/homebridge-legacy-plugins/tree/master/accessories) repository.
|
||||
|
||||
You can also view the [full list of supported HomeKit Services and Characteristics in the HAP-NodeJS protocol repository](https://github.com/KhaosT/HAP-NodeJS/blob/master/lib/gen/HomeKitTypes.js).
|
||||
|
||||
There isn't currently an example for how to publish a Platform (which allows the user to bridge many discovered devices at once, like a house full of smart lightbulbs), but the process is almost identical to registering an Accessory. Simply modify the example `index.js` in [homebridge-lockitron](https://github.com/nfarina/homebridge/tree/master/example-plugins/homebridge-lockitron) to say something like:
|
||||
|
||||
homebridge.registerPlatform("homebridge-myplugin", "MyPlatform", MyPlatform);
|
||||
And you can find an example plugin that publishes an individual accessory at [here](https://github.com/nfarina/homebridge/tree/6500912f54a70ff479e63e2b72760ab589fa558a/example-plugins/homebridge-lockitron).
|
||||
|
||||
See more examples on how to create Platform classes in the [Legacy Plugins](https://github.com/nfarina/homebridge-legacy-plugins/tree/master/platforms) repository.
|
||||
|
||||
# Plugin Development
|
||||
|
||||
When writing your plugin, you'll want Homebridge to load it from your development directory instead of publishing it to `npm` each time. You can tell Homebridge to look for your plugin at a specific location using the command-line parameter `-P`. For example, if you are in the Homebridge directory (as checked out from Github), you might type:
|
||||
|
||||
```sh
|
||||
DEBUG=* ./bin/homebridge -D -P ../my-great-plugin/
|
||||
```
|
||||
|
||||
This will start up Homebridge and load your in-development plugin from a nearby directory. Note that you can also direct Homebridge to load your configuration from somewhere besides the default `~/.homebridge`, for example:
|
||||
|
||||
```sh
|
||||
DEBUG=* ./bin/homebridge -D -U ~/.homebridge-dev -P ../my-great-plugin/
|
||||
```
|
||||
|
||||
This is very useful when you are already using your development machine to host a "real" Homebridge instance (with all your accessories) that you don't want to disturb.
|
||||
|
||||
# Common Issues
|
||||
|
||||
### My iOS App Can't Find Homebridge
|
||||
@@ -136,6 +150,11 @@ The following errors are experienced when starting Homebridge and can be safely
|
||||
*** WARNING *** For more information see http://0pointerde/avahi-compat?s=libdns_sd&e=nodejs&f=DNSServiceRegister
|
||||
```
|
||||
|
||||
### Limitations
|
||||
|
||||
* One installation of Homebridge can only expose 100 accessories due to a HomeKit limit. You can however run multiple Homebridge instances by pointing them to different config and persistence paths (see issue #827).
|
||||
* Once an accessory has been added to the Home app, changing its name via Homebridge won't be automatically reflected in iOS. You must change it via the Home app as well.
|
||||
|
||||
# Why Homebridge?
|
||||
|
||||
Technically, the device manufacturers should be the ones implementing the HomeKit API. And I'm sure they will - eventually. When they do, this project will be obsolete, and I hope that happens soon. In the meantime, Homebridge is a fun way to get a taste of the future, for those who just can't bear to wait until "real" HomeKit devices are on the market.
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
This is an example plugin for homebridge. It is a fully-working implementation of a Lockitron door lock accessory.
|
||||
|
||||
Remember to run `npm install` in this directory in order to install the dependencies needed by this plugin. If a user is installing your plugin from npm, this will be done automatically for them.
|
||||
@@ -1,82 +0,0 @@
|
||||
var request = require("request");
|
||||
var Service, Characteristic;
|
||||
|
||||
module.exports = function(homebridge) {
|
||||
Service = homebridge.hap.Service;
|
||||
Characteristic = homebridge.hap.Characteristic;
|
||||
|
||||
homebridge.registerAccessory("homebridge-lockitron", "Lockitron", LockitronAccessory);
|
||||
}
|
||||
|
||||
function LockitronAccessory(log, config) {
|
||||
this.log = log;
|
||||
this.name = config["name"];
|
||||
this.accessToken = config["api_token"];
|
||||
this.lockID = config["lock_id"];
|
||||
|
||||
this.service = new Service.LockMechanism(this.name);
|
||||
|
||||
this.service
|
||||
.getCharacteristic(Characteristic.LockCurrentState)
|
||||
.on('get', this.getState.bind(this));
|
||||
|
||||
this.service
|
||||
.getCharacteristic(Characteristic.LockTargetState)
|
||||
.on('get', this.getState.bind(this))
|
||||
.on('set', this.setState.bind(this));
|
||||
}
|
||||
|
||||
LockitronAccessory.prototype.getState = function(callback) {
|
||||
this.log("Getting current state...");
|
||||
|
||||
request.get({
|
||||
url: "https://api.lockitron.com/v2/locks/"+this.lockID,
|
||||
qs: { access_token: this.accessToken }
|
||||
}, function(err, response, body) {
|
||||
|
||||
if (!err && response.statusCode == 200) {
|
||||
var json = JSON.parse(body);
|
||||
var state = json.state; // "lock" or "unlock"
|
||||
this.log("Lock state is %s", state);
|
||||
var locked = state == "lock"
|
||||
callback(null, locked); // success
|
||||
}
|
||||
else {
|
||||
this.log("Error getting state (status code %s): %s", response.statusCode, err);
|
||||
callback(err);
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
LockitronAccessory.prototype.setState = function(state, callback) {
|
||||
var lockitronState = (state == Characteristic.LockTargetState.SECURED) ? "lock" : "unlock";
|
||||
|
||||
this.log("Set state to %s", lockitronState);
|
||||
|
||||
request.put({
|
||||
url: "https://api.lockitron.com/v2/locks/"+this.lockID,
|
||||
qs: { access_token: this.accessToken, state: lockitronState }
|
||||
}, function(err, response, body) {
|
||||
|
||||
if (!err && response.statusCode == 200) {
|
||||
this.log("State change complete.");
|
||||
|
||||
// we succeeded, so update the "current" state as well
|
||||
var currentState = (state == Characteristic.LockTargetState.SECURED) ?
|
||||
Characteristic.LockCurrentState.SECURED : Characteristic.LockCurrentState.UNSECURED;
|
||||
|
||||
this.service
|
||||
.setCharacteristic(Characteristic.LockCurrentState, currentState);
|
||||
|
||||
callback(null); // success
|
||||
}
|
||||
else {
|
||||
this.log("Error '%s' setting lock state. Response: %s", err, body);
|
||||
callback(err || new Error("Error setting lock state."));
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
LockitronAccessory.prototype.getServices = function() {
|
||||
return [this.service];
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"name": "homebridge-lockitron",
|
||||
"version": "0.0.1",
|
||||
"description": "Lockitron plugin for homebridge: https://github.com/nfarina/homebridge",
|
||||
"license": "ISC",
|
||||
"keywords": [
|
||||
"homebridge-plugin"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/example/homebridge-lockitron.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "http://github.com/example/homebridge-lockitron/issues"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.12.0",
|
||||
"homebridge": ">=0.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"request": "^2.65.0"
|
||||
}
|
||||
}
|
||||
219
example-plugins/homebridge-samplePlatform/index.js
Normal file
219
example-plugins/homebridge-samplePlatform/index.js
Normal file
@@ -0,0 +1,219 @@
|
||||
var http = require('http');
|
||||
var Accessory, Service, Characteristic, UUIDGen;
|
||||
|
||||
module.exports = function(homebridge) {
|
||||
console.log("homebridge API version: " + homebridge.version);
|
||||
|
||||
// Accessory must be created from PlatformAccessory Constructor
|
||||
Accessory = homebridge.platformAccessory;
|
||||
|
||||
// Service and Characteristic are from hap-nodejs
|
||||
Service = homebridge.hap.Service;
|
||||
Characteristic = homebridge.hap.Characteristic;
|
||||
UUIDGen = homebridge.hap.uuid;
|
||||
|
||||
// For platform plugin to be considered as dynamic platform plugin,
|
||||
// registerPlatform(pluginName, platformName, constructor, dynamic), dynamic must be true
|
||||
homebridge.registerPlatform("homebridge-samplePlatform", "SamplePlatform", SamplePlatform, true);
|
||||
}
|
||||
|
||||
// Platform constructor
|
||||
// config may be null
|
||||
// api may be null if launched from old homebridge version
|
||||
function SamplePlatform(log, config, api) {
|
||||
log("SamplePlatform Init");
|
||||
var platform = this;
|
||||
this.log = log;
|
||||
this.config = config;
|
||||
this.accessories = [];
|
||||
|
||||
this.requestServer = http.createServer(function(request, response) {
|
||||
if (request.url === "/add") {
|
||||
this.addAccessory(new Date().toISOString());
|
||||
response.writeHead(204);
|
||||
response.end();
|
||||
}
|
||||
|
||||
if (request.url == "/reachability") {
|
||||
this.updateAccessoriesReachability();
|
||||
response.writeHead(204);
|
||||
response.end();
|
||||
}
|
||||
|
||||
if (request.url == "/remove") {
|
||||
this.removeAccessory();
|
||||
response.writeHead(204);
|
||||
response.end();
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
this.requestServer.listen(18081, function() {
|
||||
platform.log("Server Listening...");
|
||||
});
|
||||
|
||||
if (api) {
|
||||
// Save the API object as plugin needs to register new accessory via this object.
|
||||
this.api = api;
|
||||
|
||||
// Listen to event "didFinishLaunching", this means homebridge already finished loading cached accessories
|
||||
// Platform Plugin should only register new accessory that doesn't exist in homebridge after this event.
|
||||
// Or start discover new accessories
|
||||
this.api.on('didFinishLaunching', function() {
|
||||
platform.log("DidFinishLaunching");
|
||||
}.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
// Function invoked when homebridge tries to restore cached accessory
|
||||
// Developer can configure accessory at here (like setup event handler)
|
||||
// Update current value
|
||||
SamplePlatform.prototype.configureAccessory = function(accessory) {
|
||||
this.log(accessory.displayName, "Configure Accessory");
|
||||
var platform = this;
|
||||
|
||||
// set the accessory to reachable if plugin can currently process the accessory
|
||||
// otherwise set to false and update the reachability later by invoking
|
||||
// accessory.updateReachability()
|
||||
accessory.reachable = true;
|
||||
|
||||
accessory.on('identify', function(paired, callback) {
|
||||
platform.log(accessory.displayName, "Identify!!!");
|
||||
callback();
|
||||
});
|
||||
|
||||
if (accessory.getService(Service.Lightbulb)) {
|
||||
accessory.getService(Service.Lightbulb)
|
||||
.getCharacteristic(Characteristic.On)
|
||||
.on('set', function(value, callback) {
|
||||
platform.log(accessory.displayName, "Light -> " + value);
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
this.accessories.push(accessory);
|
||||
}
|
||||
|
||||
//Handler will be invoked when user try to config your plugin
|
||||
//Callback can be cached and invoke when nessary
|
||||
SamplePlatform.prototype.configurationRequestHandler = function(context, request, callback) {
|
||||
this.log("Context: ", JSON.stringify(context));
|
||||
this.log("Request: ", JSON.stringify(request));
|
||||
|
||||
// Check the request response
|
||||
if (request && request.response && request.response.inputs && request.response.inputs.name) {
|
||||
this.addAccessory(request.response.inputs.name);
|
||||
|
||||
// Invoke callback with config will let homebridge save the new config into config.json
|
||||
// Callback = function(response, type, replace, config)
|
||||
// set "type" to platform if the plugin is trying to modify platforms section
|
||||
// set "replace" to true will let homebridge replace existing config in config.json
|
||||
// "config" is the data platform trying to save
|
||||
callback(null, "platform", true, {"platform":"SamplePlatform", "otherConfig":"SomeData"});
|
||||
return;
|
||||
}
|
||||
|
||||
// - UI Type: Input
|
||||
// Can be used to request input from user
|
||||
// User response can be retrieved from request.response.inputs next time
|
||||
// when configurationRequestHandler being invoked
|
||||
|
||||
var respDict = {
|
||||
"type": "Interface",
|
||||
"interface": "input",
|
||||
"title": "Add Accessory",
|
||||
"items": [
|
||||
{
|
||||
"id": "name",
|
||||
"title": "Name",
|
||||
"placeholder": "Fancy Light"
|
||||
}//,
|
||||
// {
|
||||
// "id": "pw",
|
||||
// "title": "Password",
|
||||
// "secure": true
|
||||
// }
|
||||
]
|
||||
}
|
||||
|
||||
// - UI Type: List
|
||||
// Can be used to ask user to select something from the list
|
||||
// User response can be retrieved from request.response.selections next time
|
||||
// when configurationRequestHandler being invoked
|
||||
|
||||
// var respDict = {
|
||||
// "type": "Interface",
|
||||
// "interface": "list",
|
||||
// "title": "Select Something",
|
||||
// "allowMultipleSelection": true,
|
||||
// "items": [
|
||||
// "A","B","C"
|
||||
// ]
|
||||
// }
|
||||
|
||||
// - UI Type: Instruction
|
||||
// Can be used to ask user to do something (other than text input)
|
||||
// Hero image is base64 encoded image data. Not really sure the maximum length HomeKit allows.
|
||||
|
||||
// var respDict = {
|
||||
// "type": "Interface",
|
||||
// "interface": "instruction",
|
||||
// "title": "Almost There",
|
||||
// "detail": "Please press the button on the bridge to finish the setup.",
|
||||
// "heroImage": "base64 image data",
|
||||
// "showActivityIndicator": true,
|
||||
// "showNextButton": true,
|
||||
// "buttonText": "Login in browser",
|
||||
// "actionURL": "https://google.com"
|
||||
// }
|
||||
|
||||
// Plugin can set context to allow it track setup process
|
||||
context.ts = "Hello";
|
||||
|
||||
//invoke callback to update setup UI
|
||||
callback(respDict);
|
||||
}
|
||||
|
||||
// Sample function to show how developer can add accessory dynamically from outside event
|
||||
SamplePlatform.prototype.addAccessory = function(accessoryName) {
|
||||
this.log("Add Accessory");
|
||||
var platform = this;
|
||||
var uuid;
|
||||
|
||||
uuid = UUIDGen.generate(accessoryName);
|
||||
|
||||
var newAccessory = new Accessory(accessoryName, uuid);
|
||||
newAccessory.on('identify', function(paired, callback) {
|
||||
platform.log(accessory.displayName, "Identify!!!");
|
||||
callback();
|
||||
});
|
||||
// Plugin can save context on accessory
|
||||
// To help restore accessory in configureAccessory()
|
||||
// newAccessory.context.something = "Something"
|
||||
|
||||
// Make sure you provided a name for service otherwise it may not visible in some HomeKit apps.
|
||||
newAccessory.addService(Service.Lightbulb, "Test Light")
|
||||
.getCharacteristic(Characteristic.On)
|
||||
.on('set', function(value, callback) {
|
||||
platform.log(accessory.displayName, "Light -> " + value);
|
||||
callback();
|
||||
});
|
||||
|
||||
this.accessories.push(newAccessory);
|
||||
this.api.registerPlatformAccessories("homebridge-samplePlatform", "SamplePlatform", [newAccessory]);
|
||||
}
|
||||
|
||||
SamplePlatform.prototype.updateAccessoriesReachability = function() {
|
||||
this.log("Update Reachability");
|
||||
for (var index in this.accessories) {
|
||||
var accessory = this.accessories[index];
|
||||
accessory.updateReachability(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Sample function to show how developer can remove accessory dynamically from outside event
|
||||
SamplePlatform.prototype.removeAccessory = function() {
|
||||
this.log("Remove Accessory");
|
||||
this.api.unregisterPlatformAccessories("homebridge-samplePlatform", "SamplePlatform", this.accessories);
|
||||
|
||||
this.accessories = [];
|
||||
}
|
||||
20
example-plugins/homebridge-samplePlatform/package.json
Normal file
20
example-plugins/homebridge-samplePlatform/package.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "homebridge-samplePlatform",
|
||||
"version": "0.0.1",
|
||||
"description": "Sample Platform plugin for homebridge: https://github.com/nfarina/homebridge",
|
||||
"license": "ISC",
|
||||
"keywords": [
|
||||
"homebridge-plugin"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/example/homebridge.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "http://github.com/example/homebridge/issues"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.12.0",
|
||||
"homebridge": ">=0.2.0"
|
||||
}
|
||||
}
|
||||
69
lib/api.js
69
lib/api.js
@@ -1,7 +1,11 @@
|
||||
var inherits = require('util').inherits;
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var hap = require("hap-nodejs");
|
||||
var hapLegacyTypes = require("hap-nodejs/accessories/types.js");
|
||||
var log = require("./logger")._system;
|
||||
var User = require("./user").User;
|
||||
var PlatformAccessory = require("./platformAccessory").PlatformAccessory;
|
||||
var serverVersion = require("./version");
|
||||
|
||||
// The official homebridge API is the object we feed the plugin's exported initializer function.
|
||||
|
||||
@@ -12,7 +16,16 @@ module.exports = {
|
||||
function API() {
|
||||
this._accessories = {}; // this._accessories[pluginName.accessoryName] = accessory constructor
|
||||
this._platforms = {}; // this._platforms[pluginName.platformName] = platform constructor
|
||||
|
||||
this._configurableAccessories = {};
|
||||
this._dynamicPlatforms = {}; // this._dynamicPlatforms[pluginName.platformName] = platform constructor
|
||||
|
||||
// expose the homebridge API version
|
||||
this.version = 2.2;
|
||||
|
||||
// expose the homebridge server version
|
||||
this.serverVersion = serverVersion;
|
||||
|
||||
// expose the User class methods to plugins to get paths. Example: homebridge.user.storagePath()
|
||||
this.user = User;
|
||||
|
||||
@@ -24,8 +37,12 @@ function API() {
|
||||
// we also need to "bolt on" the legacy "types" constants for older accessories/platforms
|
||||
// still using the "object literal" style JSON.
|
||||
this.hapLegacyTypes = hapLegacyTypes;
|
||||
|
||||
this.platformAccessory = PlatformAccessory;
|
||||
}
|
||||
|
||||
inherits(API, EventEmitter);
|
||||
|
||||
API.prototype.accessory = function(name) {
|
||||
|
||||
// if you passed the "short form" name like "Lockitron" instead of "homebridge-lockitron.Lockitron",
|
||||
@@ -56,7 +73,7 @@ API.prototype.accessory = function(name) {
|
||||
}
|
||||
}
|
||||
|
||||
API.prototype.registerAccessory = function(pluginName, accessoryName, constructor) {
|
||||
API.prototype.registerAccessory = function(pluginName, accessoryName, constructor, configurationRequestHandler) {
|
||||
var fullName = pluginName + "." + accessoryName;
|
||||
|
||||
if (this._accessories[fullName])
|
||||
@@ -65,6 +82,23 @@ API.prototype.registerAccessory = function(pluginName, accessoryName, constructo
|
||||
log.info("Registering accessory '%s'", fullName);
|
||||
|
||||
this._accessories[fullName] = constructor;
|
||||
|
||||
// The plugin supports configuration
|
||||
if (configurationRequestHandler) {
|
||||
this._configurableAccessories[fullName] = configurationRequestHandler;
|
||||
}
|
||||
}
|
||||
|
||||
API.prototype.publishCameraAccessories = function(pluginName, accessories) {
|
||||
for (var index in accessories) {
|
||||
var accessory = accessories[index];
|
||||
if (!(accessory instanceof PlatformAccessory)) {
|
||||
throw new Error(pluginName + " attempt to register an accessory that isn\'t PlatformAccessory!");
|
||||
}
|
||||
accessory._associatedPlugin = pluginName;
|
||||
}
|
||||
|
||||
this.emit('publishCameraAccessories', accessories);
|
||||
}
|
||||
|
||||
API.prototype.platform = function(name) {
|
||||
@@ -97,7 +131,7 @@ API.prototype.platform = function(name) {
|
||||
}
|
||||
}
|
||||
|
||||
API.prototype.registerPlatform = function(pluginName, platformName, constructor) {
|
||||
API.prototype.registerPlatform = function(pluginName, platformName, constructor, dynamic) {
|
||||
var fullName = pluginName + "." + platformName;
|
||||
|
||||
if (this._platforms[fullName])
|
||||
@@ -106,4 +140,35 @@ API.prototype.registerPlatform = function(pluginName, platformName, constructor)
|
||||
log.info("Registering platform '%s'", fullName);
|
||||
|
||||
this._platforms[fullName] = constructor;
|
||||
|
||||
if (dynamic) {
|
||||
this._dynamicPlatforms[fullName] = constructor;
|
||||
}
|
||||
}
|
||||
|
||||
API.prototype.registerPlatformAccessories = function(pluginName, platformName, accessories) {
|
||||
for (var index in accessories) {
|
||||
var accessory = accessories[index];
|
||||
if (!(accessory instanceof PlatformAccessory)) {
|
||||
throw new Error(pluginName + " - " + platformName + " attempt to register an accessory that isn\'t PlatformAccessory!");
|
||||
}
|
||||
accessory._associatedPlugin = pluginName;
|
||||
accessory._associatedPlatform = platformName;
|
||||
}
|
||||
|
||||
this.emit('registerPlatformAccessories', accessories);
|
||||
}
|
||||
|
||||
API.prototype.updatePlatformAccessories = function(accessories) {
|
||||
this.emit('updatePlatformAccessories', accessories);
|
||||
}
|
||||
|
||||
API.prototype.unregisterPlatformAccessories = function(pluginName, platformName, accessories) {
|
||||
for (var index in accessories) {
|
||||
var accessory = accessories[index];
|
||||
if (!(accessory instanceof PlatformAccessory)) {
|
||||
throw new Error(pluginName + " - " + platformName + " attempt to unregister an accessory that isn\'t PlatformAccessory!");
|
||||
}
|
||||
}
|
||||
this.emit('unregisterPlatformAccessories', accessories);
|
||||
}
|
||||
96
lib/bridgeSetupManager.js
Normal file
96
lib/bridgeSetupManager.js
Normal file
@@ -0,0 +1,96 @@
|
||||
var inherits = require('util').inherits;
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var Service = require("hap-nodejs").Service;
|
||||
var Characteristic = require("hap-nodejs").Characteristic;
|
||||
var SetupSession = require("./bridgeSetupSession").SetupSession;
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
BridgeSetupManager: BridgeSetupManager
|
||||
}
|
||||
|
||||
function BridgeSetupManager() {
|
||||
this.session;
|
||||
|
||||
this.service = new Service(null, "49FB9D4D-0FEA-4BF1-8FA6-E7B18AB86DCE");
|
||||
|
||||
this.stateCharacteristic = new Characteristic("State", "77474A2F-FA98-485E-97BE-4762458774D8", {
|
||||
format: Characteristic.Formats.UINT8,
|
||||
minValue: 0,
|
||||
maxValue: 1,
|
||||
minStep: 1,
|
||||
perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY]
|
||||
});
|
||||
this.stateCharacteristic.value = 0;
|
||||
this.service.addCharacteristic(this.stateCharacteristic);
|
||||
|
||||
this.versionCharacteristic = new Characteristic("Version", "FD9FE4CC-D06F-4FFE-96C6-595D464E1026", {
|
||||
format: Characteristic.Formats.STRING,
|
||||
perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY]
|
||||
});
|
||||
this.versionCharacteristic.value = "1.0";
|
||||
this.service.addCharacteristic(this.versionCharacteristic);
|
||||
|
||||
this.controlPointCharacteristic = new Characteristic("Control Point", "5819A4C2-E1B0-4C9D-B761-3EB1AFF43073", {
|
||||
format: Characteristic.Formats.DATA,
|
||||
perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY]
|
||||
})
|
||||
this.controlPointCharacteristic.on('get', function(callback, context) {
|
||||
this.handleReadRequest(callback, context);
|
||||
}.bind(this));
|
||||
this.controlPointCharacteristic.on('set', function(newValue, callback, context) {
|
||||
this.handleWriteRequest(newValue, callback, context);
|
||||
}.bind(this));
|
||||
|
||||
this.controlPointCharacteristic.value = null;
|
||||
this.service.addCharacteristic(this.controlPointCharacteristic);
|
||||
}
|
||||
|
||||
inherits(BridgeSetupManager, EventEmitter);
|
||||
|
||||
BridgeSetupManager.prototype.handleReadRequest = function(callback, context) {
|
||||
if (!context) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.session) {
|
||||
callback(null, null);
|
||||
} else {
|
||||
this.session.handleReadRequest(callback);
|
||||
}
|
||||
}
|
||||
|
||||
BridgeSetupManager.prototype.handleWriteRequest = function(value, callback, context) {
|
||||
if (!context) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
var data = new Buffer(value, 'base64');
|
||||
var request = JSON.parse(data.toString());
|
||||
callback();
|
||||
|
||||
if (!this.session || this.session.sessionUUID !== request.sid) {
|
||||
if (this.session) {
|
||||
this.session.removeAllListeners();
|
||||
this.session.validSession = false;
|
||||
}
|
||||
|
||||
this.session = new SetupSession(this.stateCharacteristic, this.controlPointCharacteristic);
|
||||
this.session.configurablePlatformPlugins = this.configurablePlatformPlugins;
|
||||
this.session.on('newConfig', function(type, name, replace, config) {
|
||||
this.emit('newConfig', type, name, replace, config);
|
||||
}.bind(this));
|
||||
|
||||
this.session.on('requestCurrentConfig', function(callback) {
|
||||
this.emit('requestCurrentConfig', callback);
|
||||
}.bind(this));
|
||||
|
||||
this.session.on('end', function() {
|
||||
this.session = null;
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
this.session.handleWriteRequest(request);
|
||||
}
|
||||
191
lib/bridgeSetupSession.js
Normal file
191
lib/bridgeSetupSession.js
Normal file
File diff suppressed because one or more lines are too long
18
lib/cli.js
18
lib/cli.js
@@ -17,11 +17,25 @@ module.exports = function() {
|
||||
.option('-P, --plugin-path [path]', 'look for plugins installed at [path] as well as the default locations ([path] can also point to a single plugin)', function(p) { Plugin.addPluginPath(p); })
|
||||
.option('-U, --user-storage-path [path]', 'look for homebridge user files at [path] instead of the default location (~/.homebridge)', function(p) { User.setStoragePath(p); })
|
||||
.option('-D, --debug', 'turn on debug level logging', function() { require('./logger').setDebugEnabled(true) })
|
||||
.option('-I, --insecure', 'allow insecure access to homebridge', function() { insecureAccess = true; })
|
||||
.option('-I, --insecure', 'allow unauthenticated requests (for easier hacking)', function() { insecureAccess = true })
|
||||
.parse(process.argv);
|
||||
|
||||
// Initialize HAP-NodeJS with a custom persist directory
|
||||
hap.init(User.persistPath());
|
||||
|
||||
new Server(insecureAccess).run();
|
||||
var server = new Server(insecureAccess);
|
||||
|
||||
var signals = { 'SIGINT': 2, 'SIGTERM': 15 };
|
||||
Object.keys(signals).forEach(function (signal) {
|
||||
process.on(signal, function () {
|
||||
log.info("Got %s, shutting down Homebridge...", signal);
|
||||
|
||||
// Save cached accessories to persist storage.
|
||||
server._updateCachedAccessories();
|
||||
|
||||
process.exit(128 + signals[signal]);
|
||||
});
|
||||
});
|
||||
|
||||
server.run();
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ Logger.prototype.debug = function(msg) {
|
||||
if (DEBUG_ENABLED)
|
||||
this.log.apply(this, ['debug'].concat(Array.prototype.slice.call(arguments)));
|
||||
}
|
||||
|
||||
|
||||
Logger.prototype.info = function(msg) {
|
||||
this.log.apply(this, ['info'].concat(Array.prototype.slice.call(arguments)));
|
||||
}
|
||||
@@ -43,31 +43,35 @@ Logger.prototype.warn = function(msg) {
|
||||
Logger.prototype.error = function(msg) {
|
||||
this.log.apply(this, ['error'].concat(Array.prototype.slice.call(arguments)));
|
||||
}
|
||||
|
||||
|
||||
Logger.prototype.log = function(level, msg) {
|
||||
|
||||
|
||||
msg = util.format.apply(util, Array.prototype.slice.call(arguments, 1));
|
||||
func = console.log;
|
||||
|
||||
|
||||
if (level == 'debug') {
|
||||
msg = chalk.gray(msg);
|
||||
}
|
||||
else if (level == 'warn') {
|
||||
msg = chalk.yellow(msg);
|
||||
func = console.error;
|
||||
func = console.error;
|
||||
}
|
||||
else if (level == 'error') {
|
||||
msg = chalk.bold.red(msg);
|
||||
func = console.error;
|
||||
}
|
||||
|
||||
|
||||
// prepend prefix if applicable
|
||||
if (this.prefix)
|
||||
msg = chalk.cyan("[" + this.prefix + "]") + " " + msg;
|
||||
|
||||
|
||||
// prepend timestamp
|
||||
var date = new Date();
|
||||
msg = chalk.white("[" + date.toLocaleString() + "]") + " " + msg;
|
||||
|
||||
func(msg);
|
||||
}
|
||||
|
||||
|
||||
Logger.withPrefix = function(prefix) {
|
||||
|
||||
if (!loggerCache[prefix]) {
|
||||
@@ -83,6 +87,6 @@ Logger.withPrefix = function(prefix) {
|
||||
log.prefix = logger.prefix;
|
||||
loggerCache[prefix] = log;
|
||||
}
|
||||
|
||||
|
||||
return loggerCache[prefix];
|
||||
}
|
||||
|
||||
228
lib/platformAccessory.js
Normal file
228
lib/platformAccessory.js
Normal file
@@ -0,0 +1,228 @@
|
||||
var uuid = require("hap-nodejs").uuid;
|
||||
var Accessory = require("hap-nodejs").Accessory;
|
||||
var Service = require("hap-nodejs").Service;
|
||||
var Characteristic = require("hap-nodejs").Characteristic;
|
||||
var inherits = require('util').inherits;
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
PlatformAccessory: PlatformAccessory
|
||||
}
|
||||
|
||||
function PlatformAccessory(displayName, UUID, category) {
|
||||
if (!displayName) throw new Error("Accessories must be created with a non-empty displayName.");
|
||||
if (!UUID) throw new Error("Accessories must be created with a valid UUID.");
|
||||
if (!uuid.isValid(UUID)) throw new Error("UUID '" + UUID + "' is not a valid UUID. Try using the provided 'generateUUID' function to create a valid UUID from any arbitrary string, like a serial number.");
|
||||
|
||||
this.displayName = displayName;
|
||||
this.UUID = UUID;
|
||||
this.category = category || Accessory.Categories.OTHER;
|
||||
this.services = [];
|
||||
this.reachable = false;
|
||||
this.context = {};
|
||||
|
||||
this._associatedPlugin;
|
||||
this._associatedPlatform;
|
||||
this._associatedHAPAccessory;
|
||||
|
||||
this
|
||||
.addService(Service.AccessoryInformation)
|
||||
.setCharacteristic(Characteristic.Name, displayName)
|
||||
.setCharacteristic(Characteristic.Manufacturer, "Default-Manufacturer")
|
||||
.setCharacteristic(Characteristic.Model, "Default-Model")
|
||||
.setCharacteristic(Characteristic.SerialNumber, "Default-SerialNumber");
|
||||
}
|
||||
|
||||
inherits(PlatformAccessory, EventEmitter);
|
||||
|
||||
PlatformAccessory.prototype.addService = function(service) {
|
||||
// service might be a constructor like `Service.AccessoryInformation` instead of an instance
|
||||
// of Service. Coerce if necessary.
|
||||
if (typeof service === 'function')
|
||||
service = new (Function.prototype.bind.apply(service, arguments));
|
||||
|
||||
// check for UUID+subtype conflict
|
||||
for (var index in this.services) {
|
||||
var existing = this.services[index];
|
||||
if (existing.UUID === service.UUID) {
|
||||
// OK we have two Services with the same UUID. Check that each defines a `subtype` property and that each is unique.
|
||||
if (!service.subtype)
|
||||
throw new Error("Cannot add a Service with the same UUID '" + existing.UUID + "' as another Service in this Accessory without also defining a unique 'subtype' property.");
|
||||
|
||||
if (service.subtype.toString() === existing.subtype.toString())
|
||||
throw new Error("Cannot add a Service with the same UUID '" + existing.UUID + "' and subtype '" + existing.subtype + "' as another Service in this Accessory.");
|
||||
}
|
||||
}
|
||||
|
||||
this.services.push(service);
|
||||
|
||||
if (this._associatedHAPAccessory) {
|
||||
this._associatedHAPAccessory.addService(service);
|
||||
}
|
||||
return service;
|
||||
}
|
||||
|
||||
PlatformAccessory.prototype.removeService = function(service) {
|
||||
var targetServiceIndex;
|
||||
|
||||
for (var index in this.services) {
|
||||
var existingService = this.services[index];
|
||||
|
||||
if (existingService === service) {
|
||||
targetServiceIndex = index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (targetServiceIndex) {
|
||||
this.services.splice(targetServiceIndex, 1);
|
||||
service.removeAllListeners();
|
||||
|
||||
if (this._associatedHAPAccessory) {
|
||||
this._associatedHAPAccessory.removeService(service);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* searchs for a Service in the services collection and returns the first Service object that matches.
|
||||
* If multiple services of the same type are present in one accessory, use getServiceByUUIDAndSubType instead.
|
||||
* @param {ServiceConstructor|string} name
|
||||
* @returns Service
|
||||
*/
|
||||
PlatformAccessory.prototype.getService = function(name) {
|
||||
for (var index in this.services) {
|
||||
var service = this.services[index];
|
||||
|
||||
if (typeof name === 'string' && (service.displayName === name || service.name === name))
|
||||
return service;
|
||||
else if (typeof name === 'function' && ((service instanceof name) || (name.UUID === service.UUID)))
|
||||
return service;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* searchs for a Service in the services collection and returns the first Service object that matches.
|
||||
* If multiple services of the same type are present in one accessory, use getServiceByUUIDAndSubType instead.
|
||||
* @param {string} UUID Can be an UUID, a service.displayName, or a constructor of a Service
|
||||
* @param {string} subtype A subtype string to match
|
||||
* @returns Service
|
||||
*/
|
||||
PlatformAccessory.prototype.getServiceByUUIDAndSubType = function(UUID, subtype) {
|
||||
for (var index in this.services) {
|
||||
var service = this.services[index];
|
||||
|
||||
if (typeof UUID === 'string' && (service.displayName === UUID || service.name === UUID) && service.subtype === subtype )
|
||||
return service;
|
||||
else if (typeof UUID === 'function' && ((service instanceof UUID) || (UUID.UUID === service.UUID)) && service.subtype === subtype)
|
||||
return service;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
PlatformAccessory.prototype.updateReachability = function(reachable) {
|
||||
this.reachable = reachable;
|
||||
|
||||
if (this._associatedHAPAccessory) {
|
||||
this._associatedHAPAccessory.updateReachability(reachable);
|
||||
}
|
||||
}
|
||||
|
||||
PlatformAccessory.prototype.configureCameraSource = function(cameraSource) {
|
||||
this.cameraSource = cameraSource;
|
||||
for (var index in cameraSource.services) {
|
||||
var service = cameraSource.services[index];
|
||||
this.addService(service);
|
||||
}
|
||||
}
|
||||
|
||||
PlatformAccessory.prototype._prepareAssociatedHAPAccessory = function () {
|
||||
this._associatedHAPAccessory = new Accessory(this.displayName, this.UUID);
|
||||
|
||||
if (this.cameraSource) {
|
||||
this._associatedHAPAccessory.configureCameraSource(this.cameraSource);
|
||||
}
|
||||
|
||||
this._associatedHAPAccessory._sideloadServices(this.services);
|
||||
this._associatedHAPAccessory.category = this.category;
|
||||
this._associatedHAPAccessory.reachable = this.reachable;
|
||||
this._associatedHAPAccessory.on('identify', function(paired, callback) {
|
||||
if (this.listeners('identify').length > 0) {
|
||||
// allow implementors to identify this Accessory in whatever way is appropriate, and pass along
|
||||
// the standard callback for completion.
|
||||
this.emit('identify', paired, callback);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
PlatformAccessory.prototype._dictionaryPresentation = function() {
|
||||
var accessory = {};
|
||||
|
||||
accessory.plugin = this._associatedPlugin;
|
||||
accessory.platform = this._associatedPlatform;
|
||||
accessory.displayName = this.displayName;
|
||||
accessory.UUID = this.UUID;
|
||||
accessory.category = this.category;
|
||||
accessory.context = this.context;
|
||||
|
||||
var services = [];
|
||||
for (var index in this.services) {
|
||||
var service = this.services[index];
|
||||
var servicePresentation = {};
|
||||
servicePresentation.displayName = service.displayName;
|
||||
servicePresentation.UUID = service.UUID;
|
||||
servicePresentation.subtype = service.subtype;
|
||||
|
||||
var characteristics = [];
|
||||
for (var cIndex in service.characteristics) {
|
||||
var characteristic = service.characteristics[cIndex];
|
||||
var characteristicPresentation = {};
|
||||
characteristicPresentation.displayName = characteristic.displayName;
|
||||
characteristicPresentation.UUID = characteristic.UUID;
|
||||
characteristicPresentation.props = characteristic.props;
|
||||
characteristicPresentation.value = characteristic.value;
|
||||
characteristicPresentation.eventOnlyCharacteristic = characteristic.eventOnlyCharacteristic;
|
||||
characteristics.push(characteristicPresentation);
|
||||
}
|
||||
|
||||
servicePresentation.characteristics = characteristics;
|
||||
services.push(servicePresentation);
|
||||
}
|
||||
|
||||
accessory.services = services;
|
||||
return accessory;
|
||||
}
|
||||
|
||||
PlatformAccessory.prototype._configFromData = function(data) {
|
||||
this._associatedPlugin = data.plugin;
|
||||
this._associatedPlatform = data.platform;
|
||||
this.displayName = data.displayName;
|
||||
this.UUID = data.UUID;
|
||||
this.category = data.category;
|
||||
this.context = data.context;
|
||||
this.reachable = false;
|
||||
|
||||
var services = [];
|
||||
for (var index in data.services) {
|
||||
var service = data.services[index];
|
||||
var hapService = new Service(service.displayName, service.UUID, service.subtype);
|
||||
|
||||
var characteristics = [];
|
||||
for (var cIndex in service.characteristics) {
|
||||
var characteristic = service.characteristics[cIndex];
|
||||
var hapCharacteristic = new Characteristic(characteristic.displayName, characteristic.UUID, characteristic.props);
|
||||
hapCharacteristic.eventOnlyCharacteristic = characteristic.eventOnlyCharacteristic;
|
||||
hapCharacteristic.value = characteristic.value;
|
||||
characteristics.push(hapCharacteristic);
|
||||
}
|
||||
|
||||
hapService._sideloadCharacteristics(characteristics);
|
||||
services.push(hapService);
|
||||
}
|
||||
|
||||
this.services = services;
|
||||
}
|
||||
@@ -13,7 +13,7 @@ module.exports = {
|
||||
|
||||
/**
|
||||
* Homebridge Plugin.
|
||||
*
|
||||
*
|
||||
* Allows for discovering and loading installed Homebridge plugins.
|
||||
*/
|
||||
|
||||
@@ -28,39 +28,39 @@ Plugin.prototype.name = function() {
|
||||
|
||||
Plugin.prototype.load = function(options) {
|
||||
options = options || {};
|
||||
|
||||
|
||||
// does this plugin exist at all?
|
||||
if (!fs.existsSync(this.pluginPath)) {
|
||||
throw new Error("Plugin " + this.pluginPath + " was not found. Make sure the module '" + this.pluginPath + "' is installed.");
|
||||
}
|
||||
|
||||
|
||||
// attempt to load package.json
|
||||
var pjson = Plugin.loadPackageJSON(this.pluginPath);
|
||||
|
||||
|
||||
// very temporary fix for first wave of plugins
|
||||
if (pjson.peerDepdendencies && (!pjson.engines || !pjson.engines.homebridge)) {
|
||||
var engines = pjson.engines || {}
|
||||
engines.homebridge = pjson.peerDepdendencies.homebridge;
|
||||
pjson.engines = engines;
|
||||
}
|
||||
|
||||
|
||||
// pluck out the HomeBridge version requirement
|
||||
if (!pjson.engines || !pjson.engines.homebridge) {
|
||||
throw new Error("Plugin " + this.pluginPath + " does not contain the 'homebridge' package in 'engines'.");
|
||||
}
|
||||
|
||||
|
||||
var versionRequired = pjson.engines.homebridge;
|
||||
|
||||
// make sure the version is satisfied by the currently running version of HomeBridge
|
||||
if (!semver.satisfies(version, versionRequired)) {
|
||||
throw new Error("Plugin " + this.pluginPath + " requires a HomeBridge version of " + versionRequired + " which does not satisfy the current HomeBridge version of " + version + ". You may need to upgrade your installation of HomeBridge.");
|
||||
}
|
||||
|
||||
|
||||
// figure out the main module - index.js unless otherwise specified
|
||||
var main = pjson.main || "./index.js";
|
||||
|
||||
var mainPath = path.join(this.pluginPath, main);
|
||||
|
||||
|
||||
// try to require() it and grab the exported initialization hook
|
||||
this.initializer = require(mainPath);
|
||||
}
|
||||
@@ -69,11 +69,11 @@ Plugin.loadPackageJSON = function(pluginPath) {
|
||||
// check for a package.json
|
||||
var pjsonPath = path.join(pluginPath, "package.json");
|
||||
var pjson = null;
|
||||
|
||||
|
||||
if (!fs.existsSync(pjsonPath)) {
|
||||
throw new Error("Plugin " + pluginPath + " does not contain a package.json.");
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
// attempt to parse package.json
|
||||
pjson = JSON.parse(fs.readFileSync(pjsonPath));
|
||||
@@ -81,7 +81,7 @@ Plugin.loadPackageJSON = function(pluginPath) {
|
||||
catch (err) {
|
||||
throw new Error("Plugin " + pluginPath + " contains an invalid package.json. Error: " + err);
|
||||
}
|
||||
|
||||
|
||||
// make sure the name is prefixed with 'homebridge-'
|
||||
if (!pjson.name || pjson.name.indexOf('homebridge-') != 0) {
|
||||
throw new Error("Plugin " + pluginPath + " does not have a package name that begins with 'homebridge-'.");
|
||||
@@ -91,7 +91,7 @@ Plugin.loadPackageJSON = function(pluginPath) {
|
||||
if (!pjson.keywords || pjson.keywords.indexOf("homebridge-plugin") == -1) {
|
||||
throw new Error("Plugin " + pluginPath + " package.json does not contain the keyword 'homebridge-plugin'.");
|
||||
}
|
||||
|
||||
|
||||
return pjson;
|
||||
}
|
||||
|
||||
@@ -119,9 +119,10 @@ Plugin.getDefaultPaths = function() {
|
||||
} else {
|
||||
paths.push('/usr/local/lib/node_modules');
|
||||
paths.push('/usr/lib/node_modules');
|
||||
const exec = require('child_process').execSync;
|
||||
paths.push(exec('/bin/echo -n "$(npm -g prefix)/lib/node_modules"').toString('utf8'));
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
@@ -138,17 +139,17 @@ Plugin.installed = function() {
|
||||
var plugins = [];
|
||||
var pluginsByName = {}; // don't add duplicate plugins
|
||||
var searchedPaths = {}; // don't search the same paths twice
|
||||
|
||||
|
||||
// search for plugins among all known paths, in order
|
||||
for (var index in Plugin.paths) {
|
||||
var requirePath = Plugin.paths[index];
|
||||
|
||||
|
||||
// did we already search this path?
|
||||
if (searchedPaths[requirePath])
|
||||
continue;
|
||||
|
||||
|
||||
searchedPaths[requirePath] = true;
|
||||
|
||||
|
||||
// just because this path is in require.main.paths doesn't mean it necessarily exists!
|
||||
if (!fs.existsSync(requirePath))
|
||||
continue;
|
||||
@@ -158,17 +159,19 @@ Plugin.installed = function() {
|
||||
// does this path point inside a single plugin and not a directory containing plugins?
|
||||
if (fs.existsSync(path.join(requirePath, "package.json")))
|
||||
names = [""];
|
||||
|
||||
|
||||
// read through each directory in this node_modules folder
|
||||
for (var index2 in names) {
|
||||
var name = names[index2];
|
||||
|
||||
|
||||
// reconstruct full path
|
||||
var pluginPath = path.join(requirePath, name);
|
||||
|
||||
// we only care about directories
|
||||
if (!fs.statSync(pluginPath).isDirectory()) continue;
|
||||
|
||||
try {
|
||||
// we only care about directories
|
||||
if (!fs.statSync(pluginPath).isDirectory()) continue;
|
||||
} catch (e) {
|
||||
continue;
|
||||
}
|
||||
// does this module contain a package.json?
|
||||
var pjson;
|
||||
try {
|
||||
@@ -180,14 +183,14 @@ Plugin.installed = function() {
|
||||
if (!name || name.indexOf('homebridge-') == 0) {
|
||||
log.warn(err.message);
|
||||
}
|
||||
|
||||
|
||||
// skip this module
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// get actual name if this path points inside a single plugin
|
||||
if (!name) name = pjson.name;
|
||||
|
||||
|
||||
// add it to the return list
|
||||
if (!pluginsByName[name]) {
|
||||
pluginsByName[name] = pluginPath;
|
||||
@@ -198,6 +201,6 @@ Plugin.installed = function() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return plugins;
|
||||
}
|
||||
|
||||
324
lib/server.js
324
lib/server.js
@@ -1,6 +1,7 @@
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
var uuid = require("hap-nodejs").uuid;
|
||||
var accessoryStorage = require('node-persist').create();
|
||||
var Bridge = require("hap-nodejs").Bridge;
|
||||
var Accessory = require("hap-nodejs").Accessory;
|
||||
var Service = require("hap-nodejs").Service;
|
||||
@@ -10,8 +11,12 @@ var once = require("hap-nodejs/lib/util/once").once;
|
||||
var Plugin = require('./plugin').Plugin;
|
||||
var User = require('./user').User;
|
||||
var API = require('./api').API;
|
||||
var PlatformAccessory = require("./platformAccessory").PlatformAccessory;
|
||||
var BridgeSetupManager = require("./bridgeSetupManager").BridgeSetupManager;
|
||||
var log = require("./logger")._system;
|
||||
var Logger = require('./logger').Logger;
|
||||
var mac = require("./util/mac");
|
||||
var chalk = require('chalk');
|
||||
|
||||
'use strict';
|
||||
|
||||
@@ -19,12 +24,50 @@ module.exports = {
|
||||
Server: Server
|
||||
}
|
||||
|
||||
function Server(insecureAccess) {
|
||||
function Server(insecureAccess, opts) {
|
||||
opts = opts || {};
|
||||
|
||||
// Setup Accessory Cache Storage
|
||||
accessoryStorage.initSync({ dir: User.cachedAccessoryPath() });
|
||||
|
||||
this._api = new API(); // object we feed to Plugins
|
||||
|
||||
this._api.on('registerPlatformAccessories', function(accessories) {
|
||||
this._handleRegisterPlatformAccessories(accessories);
|
||||
}.bind(this));
|
||||
|
||||
this._api.on('updatePlatformAccessories', function(accessories) {
|
||||
this._handleUpdatePlatformAccessories(accessories);
|
||||
}.bind(this));
|
||||
|
||||
this._api.on('unregisterPlatformAccessories', function(accessories) {
|
||||
this._handleUnregisterPlatformAccessories(accessories);
|
||||
}.bind(this));
|
||||
|
||||
this._api.on('publishCameraAccessories', function(accessories) {
|
||||
this._handlePublishCameraAccessories(accessories);
|
||||
}.bind(this));
|
||||
|
||||
this._plugins = this._loadPlugins(); // plugins[name] = Plugin instance
|
||||
this._config = this._loadConfig();
|
||||
this._config = opts.config || this._loadConfig();
|
||||
this._cachedPlatformAccessories = this._loadCachedPlatformAccessories();
|
||||
this._bridge = this._createBridge();
|
||||
|
||||
this._activeDynamicPlugins = {};
|
||||
this._configurablePlatformPlugins = {};
|
||||
this._publishedCameras = {};
|
||||
this._setupManager = new BridgeSetupManager();
|
||||
this._setupManager.on('newConfig', this._handleNewConfig.bind(this));
|
||||
|
||||
this._setupManager.on('requestCurrentConfig', function(callback) {
|
||||
callback(this._config);
|
||||
}.bind(this));
|
||||
|
||||
// Server is "secure by default", meaning it creates a top-level Bridge accessory that
|
||||
// will not allow unauthenticated requests. This matches the behavior of actual HomeKit
|
||||
// accessories. However you can set this to true to allow all requests without authentication,
|
||||
// which can be useful for easy hacking. Note that this will expose all functions of your
|
||||
// bridged accessories, like changing charactersitics (i.e. flipping your lights on and off).
|
||||
this._allowInsecureAccess = insecureAccess || false;
|
||||
}
|
||||
|
||||
@@ -36,27 +79,44 @@ Server.prototype.run = function() {
|
||||
|
||||
if (this._config.platforms) this._loadPlatforms();
|
||||
if (this._config.accessories) this._loadAccessories();
|
||||
this._loadDynamicPlatforms();
|
||||
this._configCachedPlatformAccessories();
|
||||
this._setupManager.configurablePlatformPlugins = this._configurablePlatformPlugins;
|
||||
this._bridge.addService(this._setupManager.service);
|
||||
|
||||
this._asyncWait = false;
|
||||
|
||||
// publish now unless we're waiting on anyone
|
||||
if (this._asyncCalls == 0)
|
||||
this._publish();
|
||||
|
||||
this._api.emit('didFinishLaunching');
|
||||
}
|
||||
|
||||
Server.prototype._publish = function() {
|
||||
// pull out our custom Bridge settings from config.json, if any
|
||||
var bridgeConfig = this._config.bridge || {};
|
||||
|
||||
var info = this._bridge.getService(Service.AccessoryInformation);
|
||||
if (bridgeConfig.manufacturer)
|
||||
info.setCharacteristic(Characteristic.Manufacturer, bridgeConfig.manufacturer);
|
||||
if (bridgeConfig.model)
|
||||
info.setCharacteristic(Characteristic.Model, bridgeConfig.model);
|
||||
if (bridgeConfig.serialNumber)
|
||||
info.setCharacteristic(Characteristic.SerialNumber, bridgeConfig.serialNumber);
|
||||
|
||||
this._printPin(bridgeConfig.pin);
|
||||
|
||||
this._bridge.on('listening', function(port) {
|
||||
log.info("Homebridge is running on port %s.", port);
|
||||
});
|
||||
|
||||
this._bridge.publish({
|
||||
username: bridgeConfig.username || "CC:22:3D:E3:CE:30",
|
||||
port: bridgeConfig.port || 51826,
|
||||
port: bridgeConfig.port || 0,
|
||||
pincode: bridgeConfig.pin || "031-45-154",
|
||||
category: Accessory.Categories.BRIDGE
|
||||
}, this._allowInsecureAccess);
|
||||
|
||||
log.info("Homebridge is running on port %s.", bridgeConfig.port || 51826);
|
||||
}
|
||||
|
||||
Server.prototype._loadPlugins = function(accessories, platforms) {
|
||||
@@ -110,8 +170,19 @@ Server.prototype._loadConfig = function() {
|
||||
|
||||
// Complain and exit if it doesn't exist yet
|
||||
if (!fs.existsSync(configPath)) {
|
||||
log.error("Couldn't find a config.json file at '"+configPath+"'. Look at config-sample.json for examples of how to format your config.js and add your home accessories.");
|
||||
process.exit(1);
|
||||
log.warn("config.json (%s) not found.", configPath);
|
||||
|
||||
var config = {};
|
||||
|
||||
config.bridge = {
|
||||
"name": "Homebridge",
|
||||
"username": "CC:22:3D:E3:CE:30",
|
||||
"pin": "031-45-154"
|
||||
};
|
||||
|
||||
return config;
|
||||
// log.error("Couldn't find a config.json file at '"+configPath+"'. Look at config-sample.json for examples of how to format your config.js and add your home accessories.");
|
||||
// process.exit(1);
|
||||
}
|
||||
|
||||
// Load up the configuration file
|
||||
@@ -144,6 +215,23 @@ Server.prototype._loadConfig = function() {
|
||||
return config;
|
||||
}
|
||||
|
||||
Server.prototype._loadCachedPlatformAccessories = function() {
|
||||
var cachedAccessories = accessoryStorage.getItem("cachedAccessories");
|
||||
var platformAccessories = [];
|
||||
|
||||
if (cachedAccessories) {
|
||||
for (var index in cachedAccessories) {
|
||||
var serializedAccessory = cachedAccessories[index];
|
||||
var platformAccessory = new PlatformAccessory(serializedAccessory.displayName, serializedAccessory.UUID, serializedAccessory.category);
|
||||
platformAccessory._configFromData(serializedAccessory);
|
||||
|
||||
platformAccessories.push(platformAccessory);
|
||||
}
|
||||
}
|
||||
|
||||
return platformAccessories;
|
||||
}
|
||||
|
||||
Server.prototype._createBridge = function() {
|
||||
// pull out our custom Bridge settings from config.json, if any
|
||||
var bridgeConfig = this._config.bridge || {};
|
||||
@@ -203,11 +291,64 @@ Server.prototype._loadPlatforms = function() {
|
||||
|
||||
platformLogger("Initializing %s platform...", platformType);
|
||||
|
||||
var platformInstance = new platformConstructor(platformLogger, platformConfig);
|
||||
this._loadPlatformAccessories(platformInstance, platformLogger, platformType);
|
||||
var platformInstance = new platformConstructor(platformLogger, platformConfig, this._api);
|
||||
|
||||
if (platformInstance.configureAccessory == undefined) {
|
||||
// Plugin 1.0, load accessories
|
||||
this._loadPlatformAccessories(platformInstance, platformLogger, platformType);
|
||||
} else {
|
||||
this._activeDynamicPlugins[platformType] = platformInstance;
|
||||
}
|
||||
|
||||
if (platformInstance.configurationRequestHandler != undefined) {
|
||||
this._configurablePlatformPlugins[platformType] = platformInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Server.prototype._loadDynamicPlatforms = function() {
|
||||
for (var dynamicPluginName in this._api._dynamicPlatforms) {
|
||||
if (!this._activeDynamicPlugins[dynamicPluginName] && !this._activeDynamicPlugins[dynamicPluginName.split(".")[1]]) {
|
||||
console.log("Load " + dynamicPluginName);
|
||||
var platformConstructor = this._api._dynamicPlatforms[dynamicPluginName];
|
||||
var platformLogger = Logger.withPrefix(dynamicPluginName);
|
||||
var platformInstance = new platformConstructor(platformLogger, null, this._api);
|
||||
this._activeDynamicPlugins[dynamicPluginName] = platformInstance;
|
||||
|
||||
if (platformInstance.configurationRequestHandler != undefined) {
|
||||
this._configurablePlatformPlugins[dynamicPluginName] = platformInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Server.prototype._configCachedPlatformAccessories = function() {
|
||||
for (var index in this._cachedPlatformAccessories) {
|
||||
var accessory = this._cachedPlatformAccessories[index];
|
||||
|
||||
if (!(accessory instanceof PlatformAccessory)) {
|
||||
console.log("Unexpected Accessory!");
|
||||
continue;
|
||||
}
|
||||
|
||||
var fullName = accessory._associatedPlugin + "." + accessory._associatedPlatform;
|
||||
var platformInstance = this._activeDynamicPlugins[fullName];
|
||||
|
||||
if (!platformInstance) {
|
||||
platformInstance = this._activeDynamicPlugins[accessory._associatedPlatform];
|
||||
}
|
||||
|
||||
if (platformInstance) {
|
||||
platformInstance.configureAccessory(accessory);
|
||||
} else {
|
||||
console.log("Failed to find plugin to handle accessory " + accessory.displayName);
|
||||
}
|
||||
|
||||
accessory._prepareAssociatedHAPAccessory();
|
||||
this._bridge.addBridgedAccessory(accessory._associatedHAPAccessory);
|
||||
}
|
||||
}
|
||||
|
||||
Server.prototype._loadPlatformAccessories = function(platformInstance, log, platformType) {
|
||||
this._asyncCalls++;
|
||||
platformInstance.accessories(once(function(foundAccessories){
|
||||
@@ -282,12 +423,167 @@ Server.prototype._createAccessory = function(accessoryInstance, displayName, acc
|
||||
}
|
||||
}
|
||||
|
||||
Server.prototype._handleRegisterPlatformAccessories = function(accessories) {
|
||||
var hapAccessories = [];
|
||||
for (var index in accessories) {
|
||||
var accessory = accessories[index];
|
||||
|
||||
accessory._prepareAssociatedHAPAccessory();
|
||||
hapAccessories.push(accessory._associatedHAPAccessory);
|
||||
|
||||
this._cachedPlatformAccessories.push(accessory);
|
||||
}
|
||||
|
||||
this._bridge.addBridgedAccessories(hapAccessories);
|
||||
this._updateCachedAccessories();
|
||||
}
|
||||
|
||||
Server.prototype._handleUpdatePlatformAccessories = function(accessories) {
|
||||
// Update persisted accessories
|
||||
this._updateCachedAccessories();
|
||||
}
|
||||
|
||||
Server.prototype._handleUnregisterPlatformAccessories = function(accessories) {
|
||||
var hapAccessories = [];
|
||||
for (var index in accessories) {
|
||||
var accessory = accessories[index];
|
||||
|
||||
if (accessory._associatedHAPAccessory) {
|
||||
hapAccessories.push(accessory._associatedHAPAccessory);
|
||||
}
|
||||
|
||||
for (var targetIndex in this._cachedPlatformAccessories) {
|
||||
var existing = this._cachedPlatformAccessories[targetIndex];
|
||||
if (existing.UUID === accessory.UUID) {
|
||||
this._cachedPlatformAccessories.splice(targetIndex, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._bridge.removeBridgedAccessories(hapAccessories);
|
||||
this._updateCachedAccessories();
|
||||
}
|
||||
|
||||
Server.prototype._handlePublishCameraAccessories = function(accessories) {
|
||||
var accessoryPin = (this._config.bridge || {}).pin || "031-45-154";
|
||||
|
||||
for (var index in accessories) {
|
||||
var accessory = accessories[index];
|
||||
|
||||
accessory._prepareAssociatedHAPAccessory();
|
||||
var hapAccessory = accessory._associatedHAPAccessory;
|
||||
var advertiseAddress = mac.generate(accessory.UUID);
|
||||
|
||||
if (this._publishedCameras[advertiseAddress]) {
|
||||
throw new Error("Camera accessory " + accessory.displayName + " experienced an address collision.");
|
||||
} else {
|
||||
this._publishedCameras[advertiseAddress] = accessory;
|
||||
}
|
||||
|
||||
hapAccessory.on('listening', function(port) {
|
||||
log.info("%s is running on port %s.", accessory.displayName, port);
|
||||
});
|
||||
|
||||
hapAccessory.publish({
|
||||
username: advertiseAddress,
|
||||
pincode: accessoryPin,
|
||||
category: accessory.category
|
||||
}, this._allowInsecureAccess);
|
||||
}
|
||||
}
|
||||
|
||||
Server.prototype._updateCachedAccessories = function() {
|
||||
var serializedAccessories = [];
|
||||
|
||||
for (var index in this._cachedPlatformAccessories) {
|
||||
var accessory = this._cachedPlatformAccessories[index];
|
||||
serializedAccessories.push(accessory._dictionaryPresentation());
|
||||
}
|
||||
|
||||
accessoryStorage.setItemSync("cachedAccessories", serializedAccessories);
|
||||
}
|
||||
|
||||
Server.prototype._handleNewConfig = function(type, name, replace, config) {
|
||||
if (type === "accessory") {
|
||||
// TODO: Load new accessory
|
||||
if (!this._config.accessories) {
|
||||
this._config.accessories = [];
|
||||
}
|
||||
|
||||
if (!replace) {
|
||||
this._config.accessories.push(config);
|
||||
} else {
|
||||
var targetName;
|
||||
if (name.indexOf('.') !== -1) {
|
||||
targetName = name.split(".")[1];
|
||||
}
|
||||
var found = false;
|
||||
for (var index in this._config.accessories) {
|
||||
var accessoryConfig = this._config.accessories[index];
|
||||
if (accessoryConfig.accessory === name) {
|
||||
this._config.accessories[index] = config;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (targetName && (accessoryConfig.accessory === targetName)) {
|
||||
this._config.accessories[index] = config;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
this._config.accessories.push(config);
|
||||
}
|
||||
}
|
||||
} else if (type === "platform") {
|
||||
if (!this._config.platforms) {
|
||||
this._config.platforms = [];
|
||||
}
|
||||
|
||||
if (!replace) {
|
||||
this._config.platforms.push(config);
|
||||
} else {
|
||||
var targetName;
|
||||
if (name.indexOf('.') !== -1) {
|
||||
targetName = name.split(".")[1];
|
||||
}
|
||||
|
||||
var found = false;
|
||||
for (var index in this._config.platforms) {
|
||||
var platformConfig = this._config.platforms[index];
|
||||
if (platformConfig.platform === name) {
|
||||
this._config.platforms[index] = config;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (targetName && (platformConfig.platform === targetName)) {
|
||||
this._config.platforms[index] = config;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
this._config.platforms.push(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var serializedConfig = JSON.stringify(this._config, null, ' ');
|
||||
var configPath = User.configPath();
|
||||
fs.writeFileSync(configPath, serializedConfig, 'utf8');
|
||||
}
|
||||
|
||||
// Returns the setup code in a scannable format.
|
||||
Server.prototype._printPin = function(pin) {
|
||||
console.log("Scan this code with your HomeKit App on your iOS device to pair with Homebridge:");
|
||||
console.log("\x1b[30;47m%s\x1b[0m", " ");
|
||||
console.log("\x1b[30;47m%s\x1b[0m", " ┌────────────┐ ");
|
||||
console.log("\x1b[30;47m%s\x1b[0m", " │ " + pin + " │ ");
|
||||
console.log("\x1b[30;47m%s\x1b[0m", " └────────────┘ ");
|
||||
console.log("\x1b[30;47m%s\x1b[0m", " ");
|
||||
console.log(chalk.black.bgWhite(" "));
|
||||
console.log(chalk.black.bgWhite(" ┌────────────┐ "));
|
||||
console.log(chalk.black.bgWhite(" │ " + pin + " │ "));
|
||||
console.log(chalk.black.bgWhite(" └────────────┘ "));
|
||||
console.log(chalk.black.bgWhite(" "));
|
||||
}
|
||||
|
||||
@@ -38,6 +38,10 @@ User.persistPath = function() {
|
||||
return path.join(User.storagePath(), "persist");
|
||||
}
|
||||
|
||||
User.cachedAccessoryPath = function() {
|
||||
return path.join(User.storagePath(), "accessories");
|
||||
}
|
||||
|
||||
User.setStoragePath = function(path) {
|
||||
customStoragePath = path;
|
||||
}
|
||||
18
lib/util/mac.js
Normal file
18
lib/util/mac.js
Normal file
@@ -0,0 +1,18 @@
|
||||
var crypto = require('crypto');
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
generate: generate
|
||||
}
|
||||
|
||||
function generate(data) {
|
||||
var sha1sum = crypto.createHash('sha1');
|
||||
sha1sum.update(data);
|
||||
var s = sha1sum.digest('hex');
|
||||
var i = -1;
|
||||
return 'xx:xx:xx:xx:xx:xx'.replace(/[x]/g, function(c) {
|
||||
i += 1;
|
||||
return s[i];
|
||||
}).toUpperCase();
|
||||
};
|
||||
16
package.json
16
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "homebridge",
|
||||
"description": "HomeKit support for the impatient",
|
||||
"version": "0.2.18",
|
||||
"version": "0.4.22",
|
||||
"scripts": {
|
||||
"dev": "DEBUG=* ./bin/homebridge -D -P example-plugins/ || true"
|
||||
},
|
||||
@@ -15,23 +15,19 @@
|
||||
"bugs": {
|
||||
"url": "http://github.com/nfarina/homebridge/issues"
|
||||
},
|
||||
"licenses": [
|
||||
{
|
||||
"type": "ISC",
|
||||
"url": "http://github.com/nfarina/homebridge/blob/master/LICENSE"
|
||||
}
|
||||
],
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"homebridge": "bin/homebridge"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
"node": ">=4.3.2"
|
||||
},
|
||||
"preferGlobal": true,
|
||||
"dependencies": {
|
||||
"chalk": "^1.1.1",
|
||||
"commander": "2.8.1",
|
||||
"hap-nodejs": "0.2.3",
|
||||
"semver": "5.0.3"
|
||||
"hap-nodejs": "0.4.27",
|
||||
"semver": "5.0.3",
|
||||
"node-persist": "^0.0.8"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user