var path = require('path'); var fs = require('fs'); var uuid = require("hap-nodejs").uuid; var Bridge = require("hap-nodejs").Bridge; var Accessory = require("hap-nodejs").Accessory; var Service = require("hap-nodejs").Service; var Characteristic = require("hap-nodejs").Characteristic; var AccessoryLoader = require("hap-nodejs").AccessoryLoader; 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 log = require("./logger")._system; var Logger = require('./logger').Logger; 'use strict'; module.exports = { Server: Server } function Server(insecureAccess) { 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(); // 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; } Server.prototype.run = function() { // keep track of async calls we're waiting for callbacks on before we can start up this._asyncCalls = 0; this._asyncWait = true; if (this._config.platforms) this._loadPlatforms(); if (this._config.accessories) this._loadAccessories(); this._asyncWait = false; // publish now unless we're waiting on anyone if (this._asyncCalls == 0) this._publish(); } Server.prototype._publish = function() { // pull out our custom Bridge settings from config.json, if any var bridgeConfig = this._config.bridge || {}; this._printPin(bridgeConfig.pin); this._bridge.publish({ username: bridgeConfig.username || "CC:22:3D:E3:CE:30", port: bridgeConfig.port || 51826, 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) { var plugins = {}; var foundOnePlugin = false; // load and validate plugins - check for valid package.json, etc. Plugin.installed().forEach(function(plugin) { // attempt to load it try { plugin.load(); } catch (err) { log.error("====================") log.error("ERROR LOADING PLUGIN " + plugin.name() + ":") log.error(err.stack); log.error("====================") plugin.loadError = err; } if (!plugin.loadError) { // add it to our dict for easy lookup later plugins[plugin.name()] = plugin; log.info("Loaded plugin: " + plugin.name()); // call the plugin's initializer and pass it our API instance plugin.initializer(this._api); log.info("---"); foundOnePlugin = true; } }.bind(this)); // Complain if you don't have any plugins. if (!foundOnePlugin) { log.warn("No plugins found. See the README for information on installing plugins.") } return plugins; } Server.prototype._loadConfig = function() { // Look for the configuration file var configPath = User.configPath(); // 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); } // Load up the configuration file var config; try { config = JSON.parse(fs.readFileSync(configPath)); } catch (err) { 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 username = config.bridge.username; var validMac = /^([0-9A-F]{2}:){5}([0-9A-F]{2})$/; if (!validMac.test(username)){ throw new Error('Not a valid username: ' + username + '. Must be 6 pairs of colon-' + 'separated hexadecimal chars (A-F 0-9), like a MAC address.'); } var accessoryCount = (config.accessories && config.accessories.length) || 0; var platformCount = (config.platforms && config.platforms.length) || 0; log.info("Loaded config.json with %s accessories and %s platforms.", accessoryCount, platformCount); log.info("---"); return config; } Server.prototype._createBridge = function() { // pull out our custom Bridge settings from config.json, if any var bridgeConfig = this._config.bridge || {}; // Create our Bridge which will host all loaded Accessories return new Bridge(bridgeConfig.name || 'Homebridge', uuid.generate("HomeBridge")); } Server.prototype._loadAccessories = function() { // Instantiate all accessories in the config log.info("Loading " + this._config.accessories.length + " accessories..."); for (var i=0; i