Skip to content

Commit

Permalink
Initial implementation of Adapter IPC (#287)
Browse files Browse the repository at this point in the history
Initial implementation of Adapter-IPC
  • Loading branch information
dhylands authored Oct 3, 2017
1 parent 1a18dd8 commit 48f655c
Show file tree
Hide file tree
Showing 31 changed files with 1,354 additions and 105 deletions.
5 changes: 5 additions & 0 deletions config/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ module.exports = {
},
}
},
example_plugin: {
enabled: true,
plugin: true,
path: './adapters/example-plugin',
},
zigbee: {
enabled: true,
path: './adapters/zigbee',
Expand Down
3 changes: 2 additions & 1 deletion config/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ module.exports = {
http: 0,
},
adapters : {
mock: {
Mock: {
enabled: true,
plugin: 'test',
path: './adapters/mock',
},
zwave: {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"greenlock": "^2.1.15",
"jsonwebtoken": "^7.4.1",
"le-challenge-dns": "https://github.com/mozilla-iot/le-challenge-dns",
"nanomsg": "^3.3.0",
"node-fetch": "^1.7.1",
"node-getopt": "^0.2.3",
"node-persist": "^2.1.0",
Expand Down
73 changes: 73 additions & 0 deletions src/adapter-loader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Adapter Plugin Loader app.
*
* This app will load an adapter plugin as a standalone process.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

'use strict';

var config = require('config');
const Constants = require('./constants');
const GetOpt = require('node-getopt');
const PluginClient = require('./adapters/plugin/plugin-client');


// Use webpack provided require for dynamic includes from the bundle .
const dynamicRequire = (() => {
if (typeof __non_webpack_require__ !== 'undefined') {
// eslint-disable-next-line no-undef
return __non_webpack_require__;
}
return require;
})();

// Command line arguments
const getopt = new GetOpt([
['h', 'help', 'Display help' ],
['v', 'verbose', 'Show verbose output'],
]);

const opt = getopt.parseSystem();

if (opt.options.verbose) {
console.log(opt);
}

if (opt.options.help) {
getopt.showHelp();
process.exit(1);
}

if (opt.argv.length != 1) {
console.error('Expecting a single adapterId to load');
process.exit(1);
}
var adapterId = opt.argv[0];
var adapterConfig = config.get(Constants.ADAPTERS_CONFIG + '.' + adapterId);
if (!adapterConfig) {
console.error('No configuration for adapter "' + adapterId + '"');
process.exit(1);
}
if (!adapterConfig.enabled) {
console.error('Adapter "' + adapterId + '" is disabled.');
process.exit(1);
}
if (!adapterConfig.plugin) {
console.error('Adapter "' + adapterId + '" isn\'t configured as a plugin.');
process.exit(1);
}

var pluginClient = new PluginClient(adapterId, {verbose: opt.verbose});
pluginClient.register().then(adapterManagerProxy => {
console.log('Loading adapters for', adapterId,
'from', adapterConfig.path);
let adapterLoader = dynamicRequire(adapterConfig.path);
adapterLoader(adapterManagerProxy, adapterId, adapterConfig);
}).catch(err => {
console.error('Failed to register adapter with gateway');
console.error(err);
});
143 changes: 108 additions & 35 deletions src/adapter-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@
'use strict';

var config = require('config');
const Constants = require('./constants');
var EventEmitter = require('events').EventEmitter;
var Deferred = require('./adapters/deferred');
const PluginClient = require('./adapters/plugin/plugin-client');
const PluginServer = require('./adapters/plugin/plugin-server');

// Use webpack provided require for dynamic includes from the bundle .
const dynamicRequire = (() => {
Expand All @@ -34,30 +37,40 @@ class AdapterManager extends EventEmitter {

constructor() {
super();
this.adapters = {};
this.adapters = new Map();
this.devices = {};
this.deferredAdd = null;
this.deferredRemove = null;
this.adaptersLoaded = false;
this.deferredWaitForAdapter = new Map();
this.pluginServer = null;
}

/**
* Adds an adapter to the collection of adapters managed by AdapterManager.
* This function is typically called when loading adapters.
*/
addAdapter(adapter) {
adapter.name = adapter.constructor.name;
this.adapters[adapter.id] = adapter;
if (!adapter.name) {
adapter.name = adapter.constructor.name;
}
this.adapters.set(adapter.id, adapter);

/**
* Adapter added event.
*
* This is event is emitted whenever a new adapter is loaded.
*
* @event adapter-added
* @event adapterAdded
* @type {Adapter}
*/
this.emit('adapter-added', adapter);
this.emit(Constants.ADAPTER_ADDED, adapter);

var deferredWait = this.deferredWaitForAdapter.get(adapter.id);
if (deferredWait) {
this.deferredWaitForAdapter.delete(adapter.id);
deferredWait.resolve(adapter);
}
}

/**
Expand All @@ -76,14 +89,13 @@ class AdapterManager extends EventEmitter {
} else {
this.deferredAdd = deferredAdd;
var pairingTimeout = config.get('adapterManager.pairingTimeout');
for (var adapterId in this.adapters) {
var adapter = this.adapters[adapterId];
this.adapters.forEach(adapter => {
console.log('About to call startPairing on', adapter.name);
adapter.startPairing(pairingTimeout);
}
});
this.pairingTimeout = setTimeout(() => {
console.log('Pairing timeout');
this.emit('pairing-timeout');
this.emit(Constants.PAIRING_TIMEOUT);
this.cancelAddNewThing();
}, pairingTimeout * 1000);
}
Expand All @@ -105,10 +117,9 @@ class AdapterManager extends EventEmitter {
}

if (deferredAdd) {
for (var adapterId in this.adapters) {
var adapter = this.adapters[adapterId];
this.adapters.forEach(adapter => {
adapter.cancelPairing();
}
});
this.deferredAdd = null;
deferredAdd.reject('addNewThing cancelled');
}
Expand Down Expand Up @@ -138,13 +149,13 @@ class AdapterManager extends EventEmitter {
* @method getAdapter
* @returns Returns the adapter with the indicated id.
*/
getAdapter(id) {
return this.adapters[id];
getAdapter(adapterId) {
return this.adapters.get(adapterId);
}

/**
* @method getAdapters
* @returns Returns a dictionary of the loaded adapters. The dictionary
* @returns Returns a Map of the loaded adapters. The dictionary
* key corresponds to the adapter id.
*/
getAdapters() {
Expand Down Expand Up @@ -260,23 +271,22 @@ class AdapterManager extends EventEmitter {
*
* This event is emitted whenever a new thing is added.
*
* @event thing-added
* @event thingAdded
* @type {Thing}
*/
this.emit('thing-added', thing);
this.emit(Constants.THING_ADDED, thing);

// If this device was added in response to addNewThing, then
// We need to cancel pairing mode on all of the "other" adapters.

var deferredAdd = this.deferredAdd;
if (deferredAdd) {
this.deferredAdd = null;
for (var adapterId in this.adapters) {
var adapter = this.adapters[adapterId];
this.adapters.forEach(adapter => {
if (adapter !== device.adapter) {
adapter.cancelPairing();
}
}
});
if (this.pairingTimeout) {
clearTimeout(this.pairingTimeout);
this.pairingTimeout = null;
Expand All @@ -298,10 +308,10 @@ class AdapterManager extends EventEmitter {
*
* This event is emitted whenever a new thing is removed.
*
* @event thing-added
* @event thingRemoved
* @type {Thing}
*/
this.emit('thing-removed', thing);
this.emit(Constants.THING_REMOVED, thing);

var deferredRemove = this.deferredRemove;
if (deferredRemove && deferredRemove.adapter == device.adapter) {
Expand All @@ -310,6 +320,24 @@ class AdapterManager extends EventEmitter {
}
}

/**
* @method loadAdapter
*
* Loads a single adapter.
*/

loadAdapter(adapterManager, adapterId, adapterConfig) {
if (adapterConfig.enabled) {
console.log('Loading adapters for', adapterId,
'from', adapterConfig.path);
let adapterLoader = dynamicRequire(adapterConfig.path);
adapterLoader(adapterManager, adapterId, adapterConfig);
} else {
console.log('Not loading adapters for', adapterId,
'- disabled');
}
}

/**
* @method loadAdapters
* Loads all of the configured adapters from the adapters directory.
Expand All @@ -321,18 +349,37 @@ class AdapterManager extends EventEmitter {
return;
}
this.adaptersLoaded = true;
var adaptersConfig = config.get('adapters');
for (var adapterName in adaptersConfig) {
var adapterConfig = adaptersConfig[adapterName];

if (adapterConfig.enabled) {
console.log('Loading adapters for', adapterName,
'from', adapterConfig.path);
let adapterLoader = dynamicRequire(adapterConfig.path);
adapterLoader(this);

// Load the Adapter Plugin Server

this.pluginServer = new PluginServer(this, {verbose: false});

// Load the Adapters

var adaptersConfig = config.get(Constants.ADAPTERS_CONFIG);
for (var adapterId in adaptersConfig) {
var adapterConfig = adaptersConfig[adapterId];
if (adapterConfig.plugin) {
if (adapterConfig.plugin === 'test') {
// This is a special case where we load the adapter
// directly into the gateway, but we use IPC comms
// to talk to the adapter (i.e. for testing)
var pluginClient = new PluginClient(adapterId, {verbose: false});
pluginClient.register().then(adapterManagerProxy => {
console.log('Loading adapter', adapterId, 'as plugin');
this.loadAdapter(adapterManagerProxy, adapterId, adapterConfig);
}).catch(err => {
console.error('Failed to register adapter with gateway');
console.error(err);
});
} else {
// This is the normal plugin adapter case, and we
// currently don't need to do anything. We assume
// that the adapter is started elsewhere.
}
} else {
console.log('Not loading adapters for', adapterName,
'- disabled');
// Load this adapter directly into the gateway.
this.loadAdapter(this, adapterId, adapterConfig);
}
}
}
Expand Down Expand Up @@ -378,13 +425,39 @@ class AdapterManager extends EventEmitter {
// The adapters are not currently loaded, no need to unload.
return;
}
for (var adapterId in this.adapters) {
var adapter = this.adapters[adapterId];
// unload the adapters in the reverse of the order that they were loaded.
for (var adapterId of Array.from(this.adapters.keys()).reverse()) {
var adapter = this.getAdapter(adapterId);
console.log('Unloading', adapter.name);
adapter.unload();
}
this.adaptersLoaded = false;
}

/**
* @method waitForAdapter
*
* Returns a promise which resolves to the adapter with the indicated id.
* This function is really only used to support testing and
* ensure that tests don't proceed until
*/
waitForAdapter(adapterId) {
var adapter = this.getAdapter(adapterId);
if (adapter) {
// The adapter already exists, just create a Promise
// that resolves to that.
return Promise.resolve(adapter);
}

var deferredWait = this.deferredWaitForAdapter.get(adapterId);
if (!deferredWait) {
// No deferred wait currently setup. Set a new one up.
deferredWait = new Deferred();
this.deferredWaitForAdapter.set(adapterId, deferredWait);
}

return deferredWait.promise;
}
}

module.exports = new AdapterManager();
1 change: 1 addition & 0 deletions src/adapters/adapter-constants.js
4 changes: 3 additions & 1 deletion src/adapters/device.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

'use strict';

const Constants = require('./adapter-constants');

class Device {

constructor(adapter, id) {
Expand Down Expand Up @@ -95,7 +97,7 @@ class Device {
}

notifyPropertyChanged(property) {
this.adapter.manager.emit('property-changed', property);
this.adapter.manager.emit(Constants.PROPERTY_CHANGED, property);
}

setDescription(description) {
Expand Down
Loading

0 comments on commit 48f655c

Please sign in to comment.