'use strict';
/**
* @class
* @description Plugin to add and remove devices from bridge and model on pairing and unpairing.
*/
class Pairer {
/**
* @constructor
* @param {Object} opts General settings for Pairer plugin
* @param {Adapter} adapter The adapter object associated with this Pairer plugin
*
*/
constructor(opts, adapter) {
this._opts = opts;
this.adapter = adapter;
this._deviceFingerprintUtility = new this.adapter.bridge.classes.DeviceFingerprintUtility(adapter);
}
/**
* @description The adapter instance
* @type {Adapter}
*/
set adapter(adapter) {
this._adapter = adapter;
}
get adapter() {
return this._adapter;
}
/**
* @description The instance of the DeviceFingerprintUtility that the Pairer
* can harness
* @type {DeviceFingerprintUtility}
*/
get deviceFingerprintUtility() {
return this._deviceFingerprintUtility;
}
/**
* @description Checks if device should be configured on
* pairing
* @param {String} nodePath K4Model node for desired device
*/
shouldConfigureOnPair(nodePath) {
if (!nodePath) {
throw new Error('No K4Model node path supplied');
}
const modelNode = this.adapter.k4.model.child(nodePath);
if (!modelNode) {
throw new Error('Unable to obtain K4Model node from path supplied');
}
const reply = {
autoSet: true,
autoConfig: false
};
const mapping = this.adapter.mapping[modelNode.cls()];
if (mapping && mapping.config && typeof mapping.config.onPairing !== 'undefined'
&& mapping.config.onPairing !== null) {
reply.autoConfig = mapping.config.onPairing;
}
return reply;
}
/**
* @description Populates properties on node for paired device
* @param {String} nodePath K4Model node to populate (node or path)
* @param {Object} properties The device properties to set on K4Model
* @returns {Promise}
*/
setNode(nodePath, properties) {
if (!nodePath) {
return Promise.reject('No K4Model node or path supplied');
}
const modelNode = this.adapter.k4.model.child(nodePath);
if (!modelNode) {
return Promise.reject('Unable to obtain K4Model node from path supplied');
}
if (!properties) {
return Promise.reject('No properties supplied: unable to set properties');
}
const setNodePropsPromises = [];
Object.keys(properties).forEach((propName) => {
const propVal = properties[propName];
setNodePropsPromises.push(new Promise((resolve, reject) => {
if (typeof propVal === 'object') {
modelNode.child(`properties/${propName}`).update(propVal, (err) => {
if (err) {
reject(new Error(err));
} else {
resolve();
}
});
} else {
modelNode.child(`properties/${propName}`).set(propVal, (err) => {
if (err) {
reject(new Error(err));
} else {
resolve();
}
});
}
}));
});
return Promise.all(setNodePropsPromises);
}
/**
* @description Nullifies properties on node for unpaired device
* @param {String} nodePath K4Model node to clear (node or path)
* @param {Object} [properties] Device properties to clear on K4Model
* @returns {Promise}
*/
clearNode(nodePath, properties) {
if (!nodePath) {
throw new Error('No K4Model node path supplied');
}
const modelNode = this.adapter.k4.model.child(nodePath);
if (!modelNode) {
throw new Error('Unable to obtain K4Model node from path supplied');
}
// TODO: Will we need to clear other properties in addition to
// networkId?
if (properties) {
const clearNodePropsPromises = [];
Object.keys(properties).forEach((propName) => {
clearNodePropsPromises.push(new Promise((resolve, reject) => {
const propVal = properties[propName];
if (typeof propVal === 'object') {
modelNode.child(`properties/${propName}`).update({}, (err) => {
if (err) {
reject(new Error(err));
} else {
resolve();
}
});
} else {
modelNode.child(`properties/${propName}`).set(null, (err) => {
if (err) {
reject(new Error(err));
} else {
resolve();
}
});
}
}));
});
return Promise.all(clearNodePropsPromises);
}
return new Promise((resolve, reject) => {
modelNode.child('properties/networkId').set(null, (err) => {
if (err) {
reject(new Error(err));
} else {
resolve();
}
});
});
}
/**
* @description Builds a bridge Device object from K4Model node
* @param {String} devicePath The K4Model node for the device
* @returns {Device} The actual device object built by the bridge
*/
constructDevice(devicePath) {
if (!devicePath) {
throw new Error('No K4Model node or path supplied');
}
const modelNode = this.adapter.k4.model.child(devicePath);
if (!modelNode) {
throw new Error(`Unable to obtain K4Model node from supplied path: ${devicePath}`);
}
// the node is a new device to add to device list
this.adapter.bridge.buildDevice(modelNode);
const device = this.adapter.bridge.devices[modelNode.path()];
if (!device) {
throw new Error(`Unable to add device: ${modelNode.path()}`);
}
return device;
}
/**
* @description Builds a bridge Device object from a
* K4Model Class Name (cls)
* @param {String} deviceClsName The K4Model Class Name (cls)
* @param {Number} deviceId The device id
* @returns {Device} The actual device object built by the bridge
*/
constructTemporaryDevice(deviceClsName, deviceId) {
if (!deviceClsName ||
typeof deviceClsName !== 'string' ||
deviceClsName.length === 0) {
throw new Error('[constructTemporaryDevice] No device cls supplied');
}
if (!deviceId ||
typeof deviceId !== 'number' ||
!Number.isSafeInteger(deviceId)) {
throw new Error('[constructTemporaryDevice] Node device id supplied');
}
if (!this.adapter.mapping ||
!this.adapter.mapping[deviceClsName]) {
throw new Error('[constructTemporaryDevice] No mapping available for cls: ' + deviceClsName);
}
const tempDevice = new this.adapter.bridge.classes.Device(this.adapter);
tempDevice.mapping = this.adapter.mapping[deviceClsName];
tempDevice.devicePath = `${this.adapter.bridge.classes.Device.constants.TEMPORARY_DEVICE_PATH_GENERIC_PREFIX}${deviceClsName}_${Date.now()}`;
tempDevice.id = deviceId;
tempDevice.isTemporaryDevice = true;
// Actually add device to the bridge
this.adapter.bridge.devices[tempDevice.devicePath] = tempDevice;
return tempDevice;
}
};
module.exports = Pairer;