mirror of
https://github.com/mtan93/homebridge.git
synced 2026-03-08 05:31:55 +00:00
Compare commits
99 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 | ||
|
|
f203a2ac6f | ||
|
|
39af2ebbef | ||
|
|
f73783787d | ||
|
|
b94c3caa3b | ||
|
|
1a710badef | ||
|
|
73fdec5928 | ||
|
|
911f088df9 | ||
|
|
6fade3c3cc | ||
|
|
191c75c281 | ||
|
|
1fb58be2b9 | ||
|
|
ca66cc3499 | ||
|
|
6ae2a19d37 | ||
|
|
ffe4232c3b | ||
|
|
fa9561d98a | ||
|
|
16a29f302d | ||
|
|
40fc7acbed |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -10,3 +10,5 @@ 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
|
||||
|
||||
.idea
|
||||
29
README.md
29
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`).
|
||||
|
||||
@@ -150,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.
|
||||
@@ -157,5 +162,3 @@ Technically, the device manufacturers should be the ones implementing the HomeKi
|
||||
# Credit
|
||||
|
||||
The original HomeKit API work was done by [KhaosT](http://twitter.com/khaost) in his [HAP-NodeJS](https://github.com/KhaosT/HAP-NodeJS) project.
|
||||
|
||||
|
||||
|
||||
@@ -21,14 +21,15 @@ module.exports = function(homebridge) {
|
||||
// config may be null
|
||||
// api may be null if launched from old homebridge version
|
||||
function SamplePlatform(log, config, api) {
|
||||
console.log("SamplePlatform Init");
|
||||
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();
|
||||
this.addAccessory(new Date().toISOString());
|
||||
response.writeHead(204);
|
||||
response.end();
|
||||
}
|
||||
@@ -47,7 +48,7 @@ function SamplePlatform(log, config, api) {
|
||||
}.bind(this));
|
||||
|
||||
this.requestServer.listen(18081, function() {
|
||||
console.log("Server Listening...");
|
||||
platform.log("Server Listening...");
|
||||
});
|
||||
|
||||
if (api) {
|
||||
@@ -58,7 +59,7 @@ function SamplePlatform(log, config, api) {
|
||||
// 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() {
|
||||
console.log("Plugin - DidFinishLaunching");
|
||||
platform.log("DidFinishLaunching");
|
||||
}.bind(this));
|
||||
}
|
||||
}
|
||||
@@ -67,7 +68,8 @@ function SamplePlatform(log, config, api) {
|
||||
// Developer can configure accessory at here (like setup event handler)
|
||||
// Update current value
|
||||
SamplePlatform.prototype.configureAccessory = function(accessory) {
|
||||
console.log("Plugin - Configure Accessory: " + accessory.displayName);
|
||||
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
|
||||
@@ -75,7 +77,7 @@ SamplePlatform.prototype.configureAccessory = function(accessory) {
|
||||
accessory.reachable = true;
|
||||
|
||||
accessory.on('identify', function(paired, callback) {
|
||||
console.log("Identify!!!");
|
||||
platform.log(accessory.displayName, "Identify!!!");
|
||||
callback();
|
||||
});
|
||||
|
||||
@@ -83,7 +85,7 @@ SamplePlatform.prototype.configureAccessory = function(accessory) {
|
||||
accessory.getService(Service.Lightbulb)
|
||||
.getCharacteristic(Characteristic.On)
|
||||
.on('set', function(value, callback) {
|
||||
console.log("Light -> " + value);
|
||||
platform.log(accessory.displayName, "Light -> " + value);
|
||||
callback();
|
||||
});
|
||||
}
|
||||
@@ -94,8 +96,8 @@ SamplePlatform.prototype.configureAccessory = function(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) {
|
||||
console.log("Context: ", JSON.stringify(context));
|
||||
console.log("Request: ", JSON.stringify(request));
|
||||
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) {
|
||||
@@ -173,28 +175,26 @@ SamplePlatform.prototype.configurationRequestHandler = function(context, request
|
||||
|
||||
// Sample function to show how developer can add accessory dynamically from outside event
|
||||
SamplePlatform.prototype.addAccessory = function(accessoryName) {
|
||||
console.log("Add Accessory");
|
||||
this.log("Add Accessory");
|
||||
var platform = this;
|
||||
var uuid;
|
||||
|
||||
if (!accessoryName) {
|
||||
accessoryName = "Test Accessory"
|
||||
}
|
||||
|
||||
uuid = UUIDGen.generate(accessoryName);
|
||||
|
||||
var newAccessory = new Accessory(accessoryName, uuid);
|
||||
newAccessory.on('identify', function(paired, callback) {
|
||||
console.log("Identify!!!");
|
||||
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) {
|
||||
console.log("Light -> " + value);
|
||||
platform.log(accessory.displayName, "Light -> " + value);
|
||||
callback();
|
||||
});
|
||||
|
||||
@@ -203,7 +203,7 @@ SamplePlatform.prototype.addAccessory = function(accessoryName) {
|
||||
}
|
||||
|
||||
SamplePlatform.prototype.updateAccessoriesReachability = function() {
|
||||
console.log("Update Reachability");
|
||||
this.log("Update Reachability");
|
||||
for (var index in this.accessories) {
|
||||
var accessory = this.accessories[index];
|
||||
accessory.updateReachability(false);
|
||||
@@ -212,8 +212,8 @@ SamplePlatform.prototype.updateAccessoriesReachability = function() {
|
||||
|
||||
// Sample function to show how developer can remove accessory dynamically from outside event
|
||||
SamplePlatform.prototype.removeAccessory = function() {
|
||||
console.log("Remove Accessory");
|
||||
this.log("Remove Accessory");
|
||||
this.api.unregisterPlatformAccessories("homebridge-samplePlatform", "SamplePlatform", this.accessories);
|
||||
|
||||
this.accessories = [];
|
||||
}
|
||||
}
|
||||
|
||||
18
lib/api.js
18
lib/api.js
@@ -5,6 +5,7 @@ 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.
|
||||
|
||||
@@ -20,7 +21,10 @@ function API() {
|
||||
this._dynamicPlatforms = {}; // this._dynamicPlatforms[pluginName.platformName] = platform constructor
|
||||
|
||||
// expose the homebridge API version
|
||||
this.version = 2.0;
|
||||
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;
|
||||
@@ -85,6 +89,18 @@ API.prototype.registerAccessory = function(pluginName, accessoryName, constructo
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
// if you passed the "short form" name like "Lockitron" instead of "homebridge-lockitron.Lockitron",
|
||||
|
||||
@@ -178,7 +178,7 @@ BridgeSetupSession.prototype.handleManageAccessory = function(request) {
|
||||
BridgeSetupSession.prototype.sendResponse = function(response) {
|
||||
if (this.validSession) {
|
||||
var serializedReponse = JSON.stringify(response);
|
||||
var respData = Buffer(serializedReponse).toString('base64');
|
||||
var respData = new Buffer(serializedReponse).toString('base64');
|
||||
this.lastResponse = respData;
|
||||
setTimeout(function() {
|
||||
this.controlChar.setValue(respData);
|
||||
|
||||
@@ -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,35 +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 = "[" + date.toLocaleString() + "]" + " " + msg;
|
||||
msg = chalk.white("[" + date.toLocaleString() + "]") + " " + msg;
|
||||
|
||||
func(msg);
|
||||
}
|
||||
|
||||
|
||||
Logger.withPrefix = function(prefix) {
|
||||
|
||||
if (!loggerCache[prefix]) {
|
||||
@@ -87,6 +87,6 @@ Logger.withPrefix = function(prefix) {
|
||||
log.prefix = logger.prefix;
|
||||
loggerCache[prefix] = log;
|
||||
}
|
||||
|
||||
|
||||
return loggerCache[prefix];
|
||||
}
|
||||
|
||||
@@ -130,8 +130,21 @@ PlatformAccessory.prototype.updateReachability = function(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;
|
||||
@@ -172,6 +185,7 @@ PlatformAccessory.prototype._dictionaryPresentation = function() {
|
||||
characteristicPresentation.UUID = characteristic.UUID;
|
||||
characteristicPresentation.props = characteristic.props;
|
||||
characteristicPresentation.value = characteristic.value;
|
||||
characteristicPresentation.eventOnlyCharacteristic = characteristic.eventOnlyCharacteristic;
|
||||
characteristics.push(characteristicPresentation);
|
||||
}
|
||||
|
||||
@@ -201,6 +215,7 @@ PlatformAccessory.prototype._configFromData = function(data) {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ 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';
|
||||
|
||||
@@ -22,7 +24,9 @@ module.exports = {
|
||||
Server: Server
|
||||
}
|
||||
|
||||
function Server(insecureAccess) {
|
||||
function Server(insecureAccess, opts) {
|
||||
opts = opts || {};
|
||||
|
||||
// Setup Accessory Cache Storage
|
||||
accessoryStorage.initSync({ dir: User.cachedAccessoryPath() });
|
||||
|
||||
@@ -40,16 +44,21 @@ function Server(insecureAccess) {
|
||||
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));
|
||||
@@ -88,15 +97,26 @@ 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) {
|
||||
@@ -150,12 +170,13 @@ Server.prototype._loadConfig = function() {
|
||||
|
||||
// Complain and exit if it doesn't exist yet
|
||||
if (!fs.existsSync(configPath)) {
|
||||
log.warn("config.json (%s) not found.", configPath);
|
||||
|
||||
var config = {};
|
||||
|
||||
config.bridge = {
|
||||
"name": "Homebridge",
|
||||
"username": "CC:22:3D:E3:CE:30",
|
||||
"port": 51826,
|
||||
"pin": "031-45-154"
|
||||
};
|
||||
|
||||
@@ -409,7 +430,7 @@ Server.prototype._handleRegisterPlatformAccessories = function(accessories) {
|
||||
|
||||
accessory._prepareAssociatedHAPAccessory();
|
||||
hapAccessories.push(accessory._associatedHAPAccessory);
|
||||
|
||||
|
||||
this._cachedPlatformAccessories.push(accessory);
|
||||
}
|
||||
|
||||
@@ -444,6 +465,34 @@ Server.prototype._handleUnregisterPlatformAccessories = function(accessories) {
|
||||
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 = [];
|
||||
|
||||
@@ -467,7 +516,7 @@ Server.prototype._handleNewConfig = function(type, name, replace, config) {
|
||||
} else {
|
||||
var targetName;
|
||||
if (name.indexOf('.') !== -1) {
|
||||
targetName = name.split(".")[1];
|
||||
targetName = name.split(".")[1];
|
||||
}
|
||||
var found = false;
|
||||
for (var index in this._config.accessories) {
|
||||
@@ -499,7 +548,7 @@ Server.prototype._handleNewConfig = function(type, name, replace, config) {
|
||||
} else {
|
||||
var targetName;
|
||||
if (name.indexOf('.') !== -1) {
|
||||
targetName = name.split(".")[1];
|
||||
targetName = name.split(".")[1];
|
||||
}
|
||||
|
||||
var found = false;
|
||||
@@ -532,9 +581,9 @@ Server.prototype._handleNewConfig = function(type, name, replace, config) {
|
||||
// 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(" "));
|
||||
}
|
||||
|
||||
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();
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "homebridge",
|
||||
"description": "HomeKit support for the impatient",
|
||||
"version": "0.3.3",
|
||||
"version": "0.4.22",
|
||||
"scripts": {
|
||||
"dev": "DEBUG=* ./bin/homebridge -D -P example-plugins/ || true"
|
||||
},
|
||||
@@ -20,13 +20,13 @@
|
||||
"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.8",
|
||||
"hap-nodejs": "0.4.27",
|
||||
"semver": "5.0.3",
|
||||
"node-persist": "^0.0.8"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user