diff --git a/.gitignore b/.gitignore index 2f5c281..137b758 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,7 @@ node_modules/ npm-debug.log .node-version + +# Ignore any extra plugins in the example directory that aren't in Git already +# (this is a sandbox for the user) +example-plugins \ No newline at end of file diff --git a/example-plugins/homebridge-lockitron/index.js b/example-plugins/homebridge-lockitron/index.js index 1c04f63..1cb7db5 100644 --- a/example-plugins/homebridge-lockitron/index.js +++ b/example-plugins/homebridge-lockitron/index.js @@ -1,11 +1,11 @@ -var Service = require("hap-nodejs").Service; -var Characteristic = require("hap-nodejs").Characteristic; var request = require("request"); +var Service, Characteristic; -module.exports = { - accessories: { - Lockitron: LockitronAccessory - } +module.exports = function(homebridge) { + Service = homebridge.hap.Service; + Characteristic = homebridge.hap.Characteristic; + + homebridge.registerAccessory("Lockitron", LockitronAccessory); } function LockitronAccessory(log, config) { diff --git a/lib/api.js b/lib/api.js new file mode 100644 index 0000000..3bb96ec --- /dev/null +++ b/lib/api.js @@ -0,0 +1,55 @@ +var hap = require("hap-nodejs"); +var hapLegacyTypes = require("hap-nodejs/accessories/types.js"); +var log = require("./logger")._system; + +// The official homebridge API is the object we feed the plugin's exported initializer function. + +module.exports = { + API: API +} + +function API() { + this._accessories = {}; // this._accessories[name] = accessory constructor + this._platforms = {}; // this._platforms[name] = platform constructor + + // expose HAP-NodeJS in its entirely for plugins to use instead of making Plugins + // require() it as a dependency - it's a heavy dependency so we don't want it in + // every single plugin. + this.hap = hap; + + // we also need to "bolt on" the legacy "types" constants for older accessories/platforms + // still using the "object literal" style JSON. + this.hapLegacyTypes = hapLegacyTypes; +} + +API.prototype.accessory = function(name) { + if (!this._accessories[name]) + throw new Error("The requested accessory '" + name + "' was not registered by any plugin."); + + return this._accessories[name]; +} + +API.prototype.registerAccessory = function(name, constructor) { + if (this._accessories[name]) + throw new Error("Attempting to register an accessory '" + name + "' which has already been registered!"); + + log.info("Registering accessory '%s'", name); + + this._accessories[name] = constructor; +} + +API.prototype.platform = function(name) { + if (!this._platforms[name]) + throw new Error("The requested platform '" + name + "' was not registered by any plugin."); + + return this._platforms[name]; +} + +API.prototype.registerPlatform = function(name, constructor) { + if (this._platforms[name]) + throw new Error("Attempting to register a platform '" + name + "' which has already been registered!"); + + log.info("Registering platform '%s'", name); + + this._platforms[name] = constructor; +} \ No newline at end of file diff --git a/lib/cli.js b/lib/cli.js index 594914f..f921a48 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -4,17 +4,18 @@ var version = require('./version'); var Server = require('./server').Server; var Plugin = require('./plugin').Plugin; var User = require('./user').User; +var log = require("./logger")._system; 'use strict'; module.exports = function() { - console.log("_____________________________________________________________________"); - console.log("IMPORTANT: Homebridge is in the middle of some big changes."); - console.log(" Read more about it here:"); - console.log(" https://github.com/nfarina/homebridge/wiki/Migration-Guide"); - console.log("_____________________________________________________________________"); - console.log(""); + log.warn("_____________________________________________________________________"); + log.warn("IMPORTANT: Homebridge is in the middle of some big changes."); + log.warn(" Read more about it here:"); + log.warn(" https://github.com/nfarina/homebridge/wiki/Migration-Guide"); + log.warn("_____________________________________________________________________"); + log.warn(""); program .version(version) diff --git a/lib/logger.js b/lib/logger.js index fecd081..9e8c4ca 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -1,4 +1,5 @@ var chalk = require('chalk'); +var util = require('util'); 'use strict'; @@ -22,43 +23,66 @@ var loggerCache = {}; * Logger class */ -function Logger(pluginName) { - this.pluginName = pluginName; +function Logger(prefix) { + this.prefix = prefix; } Logger.prototype.debug = function(msg) { if (DEBUG_ENABLED) - this.log('debug', msg); + this.log.apply(this, ['debug'].concat(Array.prototype.slice.call(arguments))); } Logger.prototype.info = function(msg) { - this.log('info', msg); + this.log.apply(this, ['info'].concat(Array.prototype.slice.call(arguments))); } Logger.prototype.warn = function(msg) { - this.log('warn', msg); + this.log.apply(this, ['warn'].concat(Array.prototype.slice.call(arguments))); } Logger.prototype.error = function(msg) { - this.log('error', msg); + this.log.apply(this, ['error'].concat(Array.prototype.slice.call(arguments))); } Logger.prototype.log = function(level, msg) { - if (level == 'debug') + 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') + } + else if (level == 'warn') { msg = chalk.yellow(msg); - else if (level == 'error') + func = console.error; + } + else if (level == 'error') { msg = chalk.bold.red(msg); + func = console.error; + } - // prepend plugin name if applicable - if (this.pluginName) - msg = chalk.cyan("[" + this.pluginName + "]") + " " + msg; - - console.log(msg); + // prepend prefix if applicable + if (this.prefix) + msg = chalk.cyan("[" + this.prefix + "]") + " " + msg; + + func(msg); } -Logger.forPlugin = function(pluginName) { - return loggerCache[pluginName] || (loggerCache[pluginName] = new Logger(pluginName)); +Logger.withPrefix = function(prefix) { + + if (!loggerCache[prefix]) { + // create a class-like logger thing that acts as a function as well + // as an instance of Logger. + var logger = new Logger(prefix); + var log = logger.info.bind(logger); + log.debug = logger.debug; + log.info = logger.info; + log.warn = logger.warn; + log.error = logger.error; + log.log = logger.log; + log.prefix = logger.prefix; + loggerCache[prefix] = log; + } + + return loggerCache[prefix]; } diff --git a/lib/plugin.js b/lib/plugin.js index 41d6819..c0f7a05 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -18,12 +18,7 @@ module.exports = { function Plugin(pluginPath) { this.pluginPath = pluginPath; // like "/usr/local/lib/node_modules/plugin-lockitron" - - // these are exports pulled from the loaded plugin module - this.accessory = null; // single exposed accessory - this.platform = null; // single exposed platform - this.accessories = []; // array of exposed accessories - this.platforms = []; // array of exposed platforms + this.initializer; // exported function from the plugin that initializes it } Plugin.prototype.name = function() { @@ -58,12 +53,8 @@ Plugin.prototype.load = function(options) { var mainPath = path.join(this.pluginPath, main); - // try to require() it - var pluginModule = require(mainPath); - - // extract all exposed accessories and platforms - this.accessories = pluginModule.accessories || {}; - this.platforms = pluginModule.platforms || {}; + // try to require() it and grab the exported initialization hook + this.initializer = require(mainPath); } Plugin.loadPackageJSON = function(pluginPath) { @@ -141,8 +132,12 @@ Plugin.installed = function() { // just because this path is in require.main.paths doesn't mean it necessarily exists! if (!fs.existsSync(requirePath)) continue; - - var names = fs.readdirSync(requirePath); + + var names = fs.readdirSync(requirePath); + + // 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) { diff --git a/lib/server.js b/lib/server.js index c2cbd7b..36e7cb7 100644 --- a/lib/server.js +++ b/lib/server.js @@ -7,6 +7,9 @@ var Service = require("hap-nodejs").Service; var Characteristic = require("hap-nodejs").Characteristic; var Plugin = require('./plugin').Plugin; var User = require('./user').User; +var API = require('./api').API; +var log = require("./logger")._system; +var Logger = require('./logger').Logger; 'use strict'; @@ -15,9 +18,8 @@ module.exports = { } function Server() { - this._accessories = {}; // this._accessories[name] = accessory constructor - this._platforms = {}; // this._platforms[name] = platform constructor - this._plugins = this._loadPlugins(this._accessories, this._platforms); // plugins[name] = plugin + this._api = new API(); // object we feed to Plugins + this._plugins = this._loadPlugins(); // plugins[name] = Plugin instance this._config = this._loadConfig(); this._bridge = this._createBridge(); } @@ -49,6 +51,8 @@ Server.prototype._publish = function() { pincode: bridgeConfig.pin || "031-45-154", category: Accessory.Categories.OTHER }); + + log.info("Homebridge is running on port %s.", bridgeConfig.port || 51826); } Server.prototype._loadPlugins = function(accessories, platforms) { @@ -63,44 +67,22 @@ Server.prototype._loadPlugins = function(accessories, platforms) { plugin.load(); } catch (err) { - console.error(err); + log.error("====================") + log.error("ERROR LOADING PLUGIN " + plugin.name() + ":") + log.error(err); + log.error("====================") plugin.loadError = err; } // add it to our dict for easy lookup later plugins[plugin.name()] = plugin; - console.log("Loaded plugin: " + plugin.name()); - - if (plugin.accessories) { - var sep = "" - var line = "Accessories: ["; - for (var name in plugin.accessories) { - if (accessories[name]) - throw new Error("Plugin " + plugin.name() + " wants to publish an accessory '" + name + "' which has already been published by another plugin!"); - - accessories[name] = plugin.accessories[name]; // copy to global dict - line += sep + name; sep = ","; - } - line += "]"; - if (sep) console.log(line); - } + log.info("Loaded plugin: " + plugin.name()); - if (plugin.platforms) { - var sep = "" - var line = "Platforms: ["; - for (var name in plugin.platforms) { - if (plugin.platforms[name]) - throw new Error("Plugin " + plugin.name() + " wants to publish a platform '" + name + "' which has already been published by another plugin!"); - - platforms[name] = plugin.platforms[name]; // copy to global dict - line += sep + name; sep = ","; - } - line += "]"; - if (sep) console.log(line); - } + // call the plugin's initializer and pass it our API instance + plugin.initializer(this._api); - console.log("---"); + log.info("---"); }.bind(this)); @@ -114,7 +96,7 @@ Server.prototype._loadConfig = function() { // Complain and exit if it doesn't exist yet if (!fs.existsSync(configPath)) { - console.log("Couldn't find a config.json file in the same directory as app.js. Look at config-sample.json for examples of how to format your config.js and add your home accessories."); + log.error("Couldn't find a config.json file in the same directory as app.js. Look at config-sample.json for examples of how to format your config.js and add your home accessories."); process.exit(1); } @@ -124,17 +106,17 @@ Server.prototype._loadConfig = function() { config = JSON.parse(fs.readFileSync(configPath)); } catch (err) { - console.log("There was a problem reading your config.json file."); - console.log("Please try pasting your config.json file here to validate it: http://jsonlint.com"); - console.log(""); + log.error("There was a problem reading your config.json file."); + log.error("Please try pasting your config.json file here to validate it: http://jsonlint.com"); + log.error(""); throw err; } var accessoryCount = (config.accessories && config.accessories.length) || 0; var platformCount = (config.platforms && config.platforms.length) || 0; - console.log("Loaded config.json with %s accessories and %s platforms.", accessoryCount, platformCount); + log.info("Loaded config.json with %s accessories and %s platforms.", accessoryCount, platformCount); - console.log("---"); + log.info("---"); return config; } @@ -150,7 +132,7 @@ Server.prototype._createBridge = function() { Server.prototype._loadAccessories = function() { // Instantiate all accessories in the config - console.log("Loading " + this._config.accessories.length + " accessories..."); + log.info("Loading " + this._config.accessories.length + " accessories..."); for (var i=0; i