From 7245abe2fb51583adba7db0057e1a1e328162309 Mon Sep 17 00:00:00 2001 From: Tim Roberts Date: Mon, 5 Jun 2017 09:15:41 -0400 Subject: [PATCH] Initial Contribution --- device/GlobalCache/constants.js | 30 +++ .../GlobalCache/contactClosureController.js | 70 ++++++ device/GlobalCache/contactClosureFactory.js | 15 ++ device/GlobalCache/definitions/Lutron QED.ir | 13 ++ device/GlobalCache/device.js | 204 ++++++++++++++++++ device/GlobalCache/deviceDiscovery.js | 99 +++++++++ device/GlobalCache/devicePort.js | 26 +++ device/GlobalCache/gcUtils.js | 74 +++++++ device/GlobalCache/globalCacheFactory.js | 24 +++ device/GlobalCache/index.js | 96 +++++++++ device/GlobalCache/irController.js | 69 ++++++ device/GlobalCache/irFactory.js | 28 +++ device/GlobalCache/serialController.js | 69 ++++++ device/GlobalCache/serialFactory.js | 28 +++ 14 files changed, 845 insertions(+) create mode 100644 device/GlobalCache/constants.js create mode 100644 device/GlobalCache/contactClosureController.js create mode 100644 device/GlobalCache/contactClosureFactory.js create mode 100644 device/GlobalCache/definitions/Lutron QED.ir create mode 100644 device/GlobalCache/device.js create mode 100644 device/GlobalCache/deviceDiscovery.js create mode 100644 device/GlobalCache/devicePort.js create mode 100644 device/GlobalCache/gcUtils.js create mode 100644 device/GlobalCache/globalCacheFactory.js create mode 100644 device/GlobalCache/index.js create mode 100644 device/GlobalCache/irController.js create mode 100644 device/GlobalCache/irFactory.js create mode 100644 device/GlobalCache/serialController.js create mode 100644 device/GlobalCache/serialFactory.js diff --git a/device/GlobalCache/constants.js b/device/GlobalCache/constants.js new file mode 100644 index 0000000..dd82f70 --- /dev/null +++ b/device/GlobalCache/constants.js @@ -0,0 +1,30 @@ +'use strict'; + +module.exports = Object.freeze({ + BEACON_TYPE_CC: "CC", + BEACON_TYPE_IR: "IR", + BEACON_TYPE_SR: "SR", + + BEACON_UUID: "UUID", + BEACON_MODEL: "Model", + BEACON_URL: "Config-URL", + + LINK_TYPE_3RELAY: "3 RELAY", + LINK_TYPE_3IR: "3 IR", + LINK_TYPE_1SERIAL: "1 SERIAL", + LINK_TYPE_1IR: "1 IR", + LINK_TYPE_1IRBLASTER: "1 IR_BLASTER", + LINK_TYPE_1IRTRIPORT: "1 IRTRIPORT", + LINK_TYPE_1IRTRIPORTBLASTER: "1 IRTRIPORT_BLASTER", + + COMMAND_GETDEVICES: "getdevices", + COMMAND_GETSTATE: "getstate", + COMMAND_SETSTATE: "setstate", + COMMAND_SENDIR: "sendir", + + RESPONSE_SETSTATE: "setstate", + RESPONSE_STATE: "state", + RESPONSE_IR: "completeir", + + PORT_NUMBER: 4998 +}); \ No newline at end of file diff --git a/device/GlobalCache/contactClosureController.js b/device/GlobalCache/contactClosureController.js new file mode 100644 index 0000000..1f44f51 --- /dev/null +++ b/device/GlobalCache/contactClosureController.js @@ -0,0 +1,70 @@ +'use strict'; + +const BluePromise = require('bluebird'); + +const discovery = require('./deviceDiscovery'); +const constants = require('./constants'); +const gcUtils = require('./gcUtils'); + +module.exports = class ContactClosureController { + constructor(neeoapi) { + this._neeoapi = neeoapi; + } + + buildDevice() { + return this._neeoapi.buildDevice('IP2CC') + .setManufacturer('GlobalCache') + .addAdditionalSearchToken('itach') + .addAdditionalSearchToken('simple blaster') + .addSwitch({ name: 'closeContact', label: 'Contact' }, + { setter: this._setSwitch, getter: this._getSwitch } + ) + .enableDiscovery({ + headerText: 'Contact Closure Settings', + description: 'No additional setup for contact closure' + }, this._discoverDevices) + .setType('ACCESSOIRE'); + } + + _setSwitch(deviceid, value) { + const valueToSet = value === "true" ? '1' : '0'; + return new BluePromise((resolve, reject) => { + const device = gcUtils.parseDeviceId(deviceid) + + return discovery.getDevice(device.uuid) + .then(results => { + return results.setState(device.module, device.port, valueToSet) + }) + .then(rc => { + if (rc !== valueToSet) { + throw new Error("State was not correctly set on the device - expecting " + valueToSet + " but got " + rc); + } + }) + .catch(e => { + console.error("Error setting switch value. ", e.message); + }); + }); + } + + _getSwitch(deviceid) { + return new BluePromise((resolve, reject) => { + const device = gcUtils.parseDeviceId(deviceid) + + return discovery.getDevice(device.uuid) + .then(results => { + return results.getState(device.module, device.port) + }) + .then(state => { + return state !== '0'; + }); + }); + } + + _discoverDevices() { + return discovery + .getDevicePorts(constants.BEACON_TYPE_CC) + .map(port => { + return gcUtils.createDeviceDiscovery(port); + }); + } +}; \ No newline at end of file diff --git a/device/GlobalCache/contactClosureFactory.js b/device/GlobalCache/contactClosureFactory.js new file mode 100644 index 0000000..6ba4ae2 --- /dev/null +++ b/device/GlobalCache/contactClosureFactory.js @@ -0,0 +1,15 @@ +'use strict'; + +const ContactClosureController = require('./contactClosureController'); + +module.exports = class ContactClosureFactory { + constructor(neeoapi) { + this._device = new ContactClosureController(neeoapi).buildDevice(); + } + + buildDevices() { + return [ + this._device + ]; + } +}; \ No newline at end of file diff --git a/device/GlobalCache/definitions/Lutron QED.ir b/device/GlobalCache/definitions/Lutron QED.ir new file mode 100644 index 0000000..8241d54 --- /dev/null +++ b/device/GlobalCache/definitions/Lutron QED.ir @@ -0,0 +1,13 @@ +[ + { "name": "QED Raise", "label": "QED Raise", "cmd": "1,40000,1,15,732,91,457,91,91,183,91,274,91,274,91,274,91,183,732,91,457,91,91,183,91,274,91,274,91,274,91,183" }, + { "name": "QED Lower", "label": "QED Lower", "cmd": "1,40000,1,15,732,91,457,91,91,183,91,274,91,366,91,91,91,274,732,91,457,91,91,183,91,274,91,366,91,91,91,274" }, + { "name": "QED Open and Preset", "label": "QED Open and Preset", "cmd": "1,40000,1,15,732,91,457,91,91,183,91,91,91,366,91,274,91,274,732,91,457,91,91,183,91,91,91,366,91,274,91,274" }, + { "name": "QED Open", "label": "QED Open", "cmd": "1,40000,1,15,732,91,457,91,91,274,274,91,91,274,274,91,91,91,732,91,457,91,91,274,274,91,91,274,274,91,91,91" }, + { "name": "QED Close", "label": "QED Close", "cmd": "1,40000,1,15,732,91,457,91,91,274,274,91,91,274,91,183,91,183,732,91,457,91,91,274,274,91,91,274,91,183,91,183" }, + { "name": "QED Preset1", "label": "QED Preset1", "cmd": "1,40000,1,15,732,91,457,91,91,274,274,91,91,183,91,183,91,274,732,91,457,91,91,274,274,91,91,183,91,183,91,274" }, + { "name": "QED Preset2", "label": "QED Preset2", "cmd": "1,40000,1,17,732,91,457,91,91,274,274,91,91,183,91,91,91,91,183,91,732,91,457,91,91,274,274,91,91,183,91,91,91,91,183,91" }, + { "name": "QED Preset3", "label": "QED Preset3", "cmd": "1,40000,1,15,732,91,457,91,91,274,274,91,91,91,274,274,91,91,732,91,457,91,91,274,274,91,91,91,274,274,91,91" }, + { "name": "QED Open and Close", "label": "QED Open and Close", "cmd": "1,40000,1,15,732,91,457,91,91,183,91,91,91,457,91,91,91,366,732,91,457,91,91,183,91,91,91,457,91,91,91,366" }, + { "name": "QED Open and Raise", "label": "QED Open and Raise", "cmd": "1,40000,1,15,732,91,457,91,91,183,91,91,91,366,274,183,91,183,732,91,457,91,91,183,91,91,91,366,274,183,91,183" }, + { "name": "QED Open and Lower", "label": "QED Open and Lower", "cmd": "1,40000,1,15,732,91,457,91,91,183,91,91,91,366,457,91,91,91,732,91,457,91,91,183,91,91,91,366,457,91,91,91" } +] \ No newline at end of file diff --git a/device/GlobalCache/device.js b/device/GlobalCache/device.js new file mode 100644 index 0000000..c5edb9a --- /dev/null +++ b/device/GlobalCache/device.js @@ -0,0 +1,204 @@ +'use strict'; + +const BluePromise = require('bluebird'); +const net = require('net'); +const rl = require('readline'); +const util = require('util'); + +const constants = require('./constants'); +const DevicePort = require('./devicePort'); + +module.exports = Device; + +function Device(beacon) { + this.beacon = beacon; + this.created = Date.now(); +} + +Device.prototype.getUUID = function() { + return this.beacon[constants.BEACON_UUID]; +} + +Device.prototype.getModel = function() { + return this.beacon[constants.BEACON_MODEL]; +} + +Device.prototype.getUrl = function() { + return this.beacon[constants.BEACON_URL]; +} + +Device.prototype.getIpAddress = function() { + const url = this.beacon[constants.BEACON_URL]; + const idx = url.indexOf('://'); + if (idx >= 0) { + return url.substring(idx + 3); + } else { + return url; + } +} + +Device.prototype.getCreated = function() { + return this.created; +} + +Device.prototype.getModulePorts = function() { + const device = this; + return new BluePromise((resolve, reject) => { + const socket = net.createConnection(constants.PORT_NUMBER, device.getIpAddress()); + socket + .on('connect', function () { + const devices = []; + const cmd = util.format('%s', constants.COMMAND_GETDEVICES); + + rl.createInterface(socket, socket).on('line', line => { + console.log('Received (%s): %s', cmd, line); + if (line === 'endlistdevices') { + resolve(devices); + socket.destroy(); + } else { + const args = line.split(','); + if (args.length === 3 && args[0] === 'device') { + if (args[1] !== '0') { // ignore the root module (ethernet/wifi) + switch (args[2]) { + case constants.LINK_TYPE_3RELAY: + devices.push(new DevicePort(device, args[1], 1, constants.BEACON_TYPE_CC)); + devices.push(new DevicePort(device, args[1], 2, constants.BEACON_TYPE_CC)); + devices.push(new DevicePort(device, args[1], 3, constants.BEACON_TYPE_CC)); + break; + case constants.LINK_TYPE_1SERIAL: + devices.push(new DevicePort(device, args[1], args[1], constants.BEACON_TYPE_SR)); + break; + case constants.LINK_TYPE_1IR: + case constants.LINK_TYPE_1IRBLASTER: + devices.push(new DevicePort(device, args[1], 1, constants.BEACON_TYPE_IR)); + break; + case constants.LINK_TYPE_3IR: + case constants.LINK_TYPE_1IRTRIPORT: + case constants.LINK_TYPE_1IRTRIPORTBLASTER: + devices.push(new DevicePort(device, args[1], 1, constants.BEACON_TYPE_IR)); + devices.push(new DevicePort(device, args[1], 2, constants.BEACON_TYPE_IR)); + devices.push(new DevicePort(device, args[1], 3, constants.BEACON_TYPE_IR)); + break; + default: + console.error('Unknown port type: ' + line); + break; + } + } + } else { + reject('Unknown response: ' + line); + socket.destroy(); + } + } + }); + console.log('Sending %s', cmd); + socket.write(cmd + '\r'); + }) + .on('error', err => { + socket.destroy(); + reject(err); + }); + }); +}; + +Device.prototype.setState = function (module, port, state) { + const device = this; + return new BluePromise((resolve, reject) => { + const socket = net.createConnection(constants.PORT_NUMBER, device.getIpAddress()); + socket + .on('connect', function() { + const cmd = util.format('%s,%s:%s,%s', constants.COMMAND_SETSTATE, module, port, state); + + rl.createInterface(socket, socket).on('line', line => { + console.log('Received (%s): %s', cmd, line); + const args = line.split(','); + if (args.length === 3 && (args[0] === constants.RESPONSE_STATE || args[0] === constants.RESPONSE_SETSTATE)) { + resolve(args[2]); + } else { + reject('Unknown response: ' + line); + } + socket.destroy(); + }); + console.log('Sending %s', cmd); + socket.write(cmd + '\r'); + }) + .on('error', err => { + socket.destroy(); + reject(err); + }); + }); +}; + +Device.prototype.getState = function (module, port) { + const device = this; + return new BluePromise((resolve, reject) => { + const socket = net.createConnection(constants.PORT_NUMBER, device.getIpAddress()); + socket + .on('connect', function() { + const cmd = util.format('%s,%s:%s', constants.COMMAND_GETSTATE, module, port); + + rl.createInterface(socket, socket).on('line', line => { + console.log('Received (%s): %s', cmd, line); + const args = line.split(','); + if (args.length === 3 && args[0] === constants.RESPONSE_STATE) { + resolve(args[2]); + } else { + reject('Unknown response: ' + line); + } + socket.destroy(); + }); + console.log('Sending %s', cmd); + socket.write(cmd + '\r'); + }) + .on('error', err => { + socket.destroy(); + reject(err); + }); + }); +}; + +Device.prototype.sendIR = function (module, port, state) { + const device = this; + return new BluePromise((resolve, reject) => { + const socket = net.createConnection(constants.PORT_NUMBER, device.getIpAddress()); + socket + .on('connect', function() { + const cmd = util.format('%s,%s:%s,%s', constants.COMMAND_SENDIR, module, port, state); + + rl.createInterface(socket, socket).on('line', line => { + console.log('Received (%s): %s', cmd, line); + const args = line.split(','); + if (args.length === 3 && (args[0] === constants.RESPONSE_IR)) { + resolve(args[2]); + } else { + reject('Unknown response: ' + line); + } + socket.destroy(); + }); + console.log('Sending %s', cmd); + socket.write(cmd + '\r'); + }) + .on('error', err => { + socket.destroy(); + reject(err); + }); + }); +}; + +Device.prototype.sendSerial = function (module, serial) { + const device = this; + return new BluePromise((resolve, reject) => { + const socket = net.createConnection(constants.PORT_NUMBER + module, device.getIpAddress()); + socket + .on('connect', function() { + console.log('Sending %s', serial); + socket.write(serial); + socket.destroy(); + }) + .on('error', err => { + socket.destroy(); + reject(err); + }); + }); +}; + + diff --git a/device/GlobalCache/deviceDiscovery.js b/device/GlobalCache/deviceDiscovery.js new file mode 100644 index 0000000..3a3024d --- /dev/null +++ b/device/GlobalCache/deviceDiscovery.js @@ -0,0 +1,99 @@ +'use strict'; + +const BluePromise = require('bluebird'); + +const net = require('net'); +const dgram = require('dgram'); +const constants = require('./constants'); +const Device = require('./device'); + +const ITACH_BROADCAST_ADDR = '239.255.250.250'; +const ITACH_BROADCAST_PORT = 9131; +const BEACON_TIMEOUT = 1000 * 60 * 5; + +const knownDevices = {}; + +const server = dgram.createSocket(net.isIPv6(ITACH_BROADCAST_ADDR) ? 'udp6' : 'udp4') + .on('listening', () => { + const address = server.address(); + console.log('GlobalCache/ITACH beacon listener started on ' + address.port); + server.addMembership(ITACH_BROADCAST_ADDR); + }) + .on('message', (message, remote) => { + checkStaleBeacons(); + if (message.toString().startsWith("AMXB")) { + parseBeacon(message.toString()); + } + }) + .on('error', err => { + console.error("Error creating beach listener. ", err); + }) + .bind(ITACH_BROADCAST_PORT); + +module.exports.getDevice = uuid => { + return new BluePromise((resolve, reject) => { + const device = knownDevices[uuid]; + if (device === undefined) { + reject("Unknown device UUID: " + uuid); + } else { + resolve(device); + } + }); +} + +module.exports.getDevicePorts = type => { + const devicePorts = []; + for (let uuid in knownDevices) { + if (knownDevices.hasOwnProperty(uuid)) { + const device = knownDevices[uuid]; + devicePorts.push(device.getModulePorts()); + } + } + + return BluePromise.all(devicePorts).then(ports => { + return [].concat.apply([], ports) + .filter( port => { return port.type === type; }); + }); +} + +function parseBeacon(message) { + //console.log("Potential beacon found " + message); + const msgParts = message.replace(/>/g, '').substring(4).split("<-"); + const max = msgParts.length; + + const beacon = { created: Date.now() }; + for (let i = 0; i < max; i++) { + const idx = msgParts[i].indexOf("="); + if (idx >= 0) { + beacon[msgParts[i].substring(0, idx).trim()] = msgParts[i].substring(idx + 1).trim(); + } + } + + const device = new Device(beacon); + + // purposely used '==' to catch null/undefined/empty + if (device.getModel() == undefined || device.getUrl() == undefined) { + //console.log("Beacon message is invalid or incomplete %s", message); + return; + } + + if (knownDevices.hasOwnProperty(device.getUUID())) { + //console.log("Beacon %s being refreshed: %s at %s", device.getUUID(), device.getModel(), device.getUrl()); + } else { + console.log("New Beacon %s found: %s at %s", device.getUUID(), device.getModel(), device.getUrl()); + } + knownDevices[device.getUUID()] = device; +} + +function checkStaleBeacons() { + for (let uuid in knownDevices) { + if (knownDevices.hasOwnProperty(uuid)) { + const device = knownDevices[uuid]; + if (device.getCreated() + BEACON_TIMEOUT < Date.now()) { + console.log("Beacon %s has expired and is being removed", device.getUUID()); + delete knownDevices[uuid]; + } + } + } +} + diff --git a/device/GlobalCache/devicePort.js b/device/GlobalCache/devicePort.js new file mode 100644 index 0000000..4d7e9e3 --- /dev/null +++ b/device/GlobalCache/devicePort.js @@ -0,0 +1,26 @@ +'use strict'; + +module.exports = DevicePort; + +function DevicePort(device, module, port, type) { + this.device = device; + this.module = module; + this.port = port; + this.type = type; +} + +DevicePort.prototype.getDevice = function() { + return this.device; +} + +DevicePort.prototype.getModule = function() { + return this.module; +} + +DevicePort.prototype.getPort = function() { + return this.port; +} + +DevicePort.prototype.getType = function() { + return this.type; +} diff --git a/device/GlobalCache/gcUtils.js b/device/GlobalCache/gcUtils.js new file mode 100644 index 0000000..1d44158 --- /dev/null +++ b/device/GlobalCache/gcUtils.js @@ -0,0 +1,74 @@ +'use strict'; + +const BluePromise = require('bluebird'); +const util = require('util'); +const fs = require('fs'); +const readline = require('readline'); + +module.exports.createDeviceDiscovery = port => { + const portId = port.getModule() + ":" + port.getPort(); + return { + id: port.getDevice().getUUID() + "." + portId, + name: port.getDevice().getModel() + " " + portId + " (" + port.getDevice().getIpAddress() + ")", + reachable: true + }; +} + +module.exports.parseDeviceId = deviceId => { + const uuidPorts = deviceId.split("."); + if (uuidPorts.length === 2) { + const modulePort = uuidPorts[1].split(":"); + if (modulePort.length === 2) { + return { + uuid: uuidPorts[0], + module: modulePort[0], + port: modulePort[1] + }; + } + } + + throw new Error(util.format("DeviceID cannot be parsed: %s", deviceId)); +} + +module.exports.clearObject = obj => { + for (var prop in obj) { if (obj.hasOwnProperty(prop)) { delete obj[prop]; } } +} + +function fileToObject(fileName) { + return new BluePromise((resolve, reject) => { + var lineReader = readline.createInterface({ + input: fs.createReadStream(fileName) + }); + + const returnMap = new Map(); + lineReader + .on("line", line => { + if (!line.trim().startsWith("#")) { + const idx = line.indexOf("="); + if (idx >= 0) { + const cmdName = line.substring(0, idx).trim(); + const irCmd = line.substring(idx + 1).trim(); + returnMap.set(cmdName, irCmd); + } + } + }) + .on('close', () => { + resolve(returnMap); + }); + }); +} + +module.exports.fileToObject = fileToObject; + +module.exports.getCmdFromFile = (fileName, cmdName, cache) => { + return fileToObject(fileName, cache) + .then(cmds => { + return new BluePromise((resolve, reject) => { + if (cmds.hasOwnProperty(cmdName)) { + resolve(cmds[cmdName]); + } + reject("No command defined for " + cmdName); + }); + }) + +} \ No newline at end of file diff --git a/device/GlobalCache/globalCacheFactory.js b/device/GlobalCache/globalCacheFactory.js new file mode 100644 index 0000000..fd38bc0 --- /dev/null +++ b/device/GlobalCache/globalCacheFactory.js @@ -0,0 +1,24 @@ +'use strict'; + +const BluePromise = require('bluebird'); +const fs = require('fs'); +const path = require('path'); + +const SerialFactory = require('./serialFactory'); +const IrFactory = require('./irFactory'); +const ContactClosureFactory = require('./contactClosureFactory'); + + +module.exports = class GlobalCacheFactory { + constructor(neeoapi, dirPath) { + this._neeoapi = neeoapi; + this._dirPath = dirPath ? dirPath : "./definitions"; + } + + buildDevices() { + let devices = new IrFactory(this._neeoapi, this._dirPath).buildDevices(); + devices = devices.concat(new SerialFactory(this._neeoapi, this._dirPath).buildDevices()); + devices = devices.concat(new ContactClosureFactory(this._neeoapi).buildDevices()); + return devices; + } +}; \ No newline at end of file diff --git a/device/GlobalCache/index.js b/device/GlobalCache/index.js new file mode 100644 index 0000000..bb9cdb0 --- /dev/null +++ b/device/GlobalCache/index.js @@ -0,0 +1,96 @@ +'use strict'; + +const neeoapi = require('neeo-sdk'); + +console.log('NEEO SDK Example "GlobalCache/ITach" adapter'); +console.log('---------------------------------------------'); + +/** + * The following defines the directory where IR/Serial definitions are found. + * If undefined - will default to "./definitions" + */ +//const definitionsDirectory = undefined; +const definitionsDirectory = "./device/GlobalCache/definitions"; + +/** + * Defines the devices that can be discovered + */ +let devices = []; + +/** + * The following line will define a single contact closure device, + * zero to many IR devices (depending on how many ".ir" files are in + * the definitions directory) + * zero to many serial devices (depending on how many ".ser" files are + * in the definitions directory) + */ +const GlobalCacheFactory = require('./globalCacheFactory'); +devices = devices.concat(new GlobalCacheFactory(neeoapi, definitionsDirectory).buildDevices()); + +/** + * The following lines allow you to use the individual factories with + * different definition directories if needed and potentially different + * file extensions if needed + */ +//const ContactClosureFactory = require('./contactClosureFactory'); +//devices = devices.concat(new ContactClosureFactory(neeoapi).buildDevices()); + +//const IrFactory = require('./irFactory'); +//devices = devices.concat(new IrFactory(neeoapi, definitionsDirectory, ".ir").buildDevices()); + +//const SerialFactory = require('./serialFactory'); +//devices = devices.concat(new SerialFactory(neeoapi, definitionsDirectory, ".ser").buildDevices()); + + +/** + * The following lines allow you to ignore the factories and construct devices directly. + * The contact closure requires no additional setup but the IR/Serial devices are + * constructed directly from the specified file + */ +//const ContactClosureController = require('./contactClosureController'); +//devices.push(new ContactClosureController(neeoapi).buildDevice()); + +//const IrController = require('./irController'); +//devices.push(new IrController(neeoapi, definitionsDirectory + "/Lutron QED.ir").buildDevice()); + +//const SerialController = require('./serialController'); +//devices.push(new IrController(neeoapi, definitionsDirectory + "/mySerial.txt").buildDevice()); + +/***************************************************************** + * SDK server setup - make sure you modify the port to something + * unique if running multiple version + ******************************************************************/ +function startSdkExample(brain) { + console.log('- Start server'); + neeoapi.startServer({ + brain, + port: 6336, + name: 'global-cache', + devices: devices + }) + .then(() => { + console.log('# READY! use the NEEO app to search for "GlobalCache".'); + }) + .catch((error) => { + //if there was any error, print message out to console + console.error('ERROR!', error.message); + process.exit(1); + }); +} + +/***************************************************************** + * Start the SDK server + ******************************************************************/ +//const brainIp = '192.168.1.29'; +const brainIp = process.env.BRAINIP; +if (brainIp) { + console.log('- use NEEO Brain IP from env variable', brainIp); + startSdkExample(brainIp); +} else { + console.log('- discover one NEEO Brain...'); + neeoapi.discoverOneBrain() + .then((brain) => { + console.log('- Brain discovered:', brain.name); + startSdkExample(brain); + }); +} diff --git a/device/GlobalCache/irController.js b/device/GlobalCache/irController.js new file mode 100644 index 0000000..f35f3e5 --- /dev/null +++ b/device/GlobalCache/irController.js @@ -0,0 +1,69 @@ +'use strict'; + +const BluePromise = require('bluebird'); +const fs = require('fs'); +const path = require('path'); + +const discovery = require('./deviceDiscovery'); +const constants = require('./constants'); +const gcUtils = require('./gcUtils'); + +module.exports = class IrController { + constructor(neeoapi, fullPath) { + this._fileName = path.parse(fullPath).name; + this._neeoapi = neeoapi; + this._cache = {}; + this._mappings = new Map(); + + const json = fs.readFileSync(fullPath, 'utf8'); + const mappings = JSON.parse(json.trim()); + for (let i = 0; i < mappings.length; i++) { + this._mappings.set(mappings[i].name, mappings[i]); + } + } + + buildDevice() { + let device = this._neeoapi.buildDevice('IP2IR (' + this._fileName + ')') + .setManufacturer('GlobalCache') + .addAdditionalSearchToken('itach') + .addAdditionalSearchToken('simple blaster') + .addButtonHander((deviceId, name) => { + return this._onButtonPressed(deviceId, name); + }) + .enableDiscovery({ + headerText: 'IR Settings', + description: 'Modify the ' + this._fileName + ' file to include IR commands' + }, this._discoverDevices) + .setType('ACCESSOIRE'); + + for (const [name, mapping] of this._mappings) { + device = device.addButton({ name: mapping.name, label: mapping.label }); + } + + return device; + } + + _onButtonPressed(deviceId, name) { + return new BluePromise((resolve, reject) => { + const mapping = this._mappings.get(deviceId); + if (mapping) { + const device = gcUtils.parseDeviceId(name) + + return discovery.getDevice(device.uuid) + .then(results => { + return results.sendIR(device.module, device.port, mapping.cmd); + }); + } else { + reject("unknown deviceid: " + deviceId); + } + }); + } + + _discoverDevices() { + return discovery + .getDevicePorts(constants.BEACON_TYPE_IR) + .map(port => { + return gcUtils.createDeviceDiscovery(port); + }); + } +}; \ No newline at end of file diff --git a/device/GlobalCache/irFactory.js b/device/GlobalCache/irFactory.js new file mode 100644 index 0000000..34c382f --- /dev/null +++ b/device/GlobalCache/irFactory.js @@ -0,0 +1,28 @@ +'use strict'; + +const BluePromise = require('bluebird'); +const fs = require('fs'); +const path = require('path'); + +const IrController = require('./irController'); + +module.exports = class IrFactory { + constructor(neeoapi, dirPath, ext) { + this._neeoapi = neeoapi; + this._dirPath = dirPath ? dirPath : "./definitions"; + this._ext = ext ? ext : ".ir"; + } + + buildDevices() { + const devices = []; + + const files = fs.readdirSync(this._dirPath); + for (let i = 0; i < files.length; i++) { + if (path.extname(files[i]) === this._ext) { + devices.push(new IrController(this._neeoapi, path.format({ dir: this._dirPath, base: files[i] })).buildDevice()); + } + } + + return devices; + } +}; \ No newline at end of file diff --git a/device/GlobalCache/serialController.js b/device/GlobalCache/serialController.js new file mode 100644 index 0000000..ac892e7 --- /dev/null +++ b/device/GlobalCache/serialController.js @@ -0,0 +1,69 @@ +'use strict'; + +const BluePromise = require('bluebird'); +const fs = require('fs'); +const path = require('path'); + +const discovery = require('./deviceDiscovery'); +const constants = require('./constants'); +const gcUtils = require('./gcUtils'); + +module.exports = class SerialController { + constructor(neeoapi, fullPath) { + this._fileName = path.parse(fullPath).name; + this._neeoapi = neeoapi; + this._cache = {}; + this._mappings = new Map(); + + const json = fs.readFileSync(fullPath, 'utf8'); + const mappings = JSON.parse(json.trim()); + for (let i = 0; i < mappings.length; i++) { + this._mappings.set(mappings[i].name, mappings[i]); + } + } + + buildDevice() { + let device = this._neeoapi.buildDevice('IP2SL (' + this._fileName + ')') + .setManufacturer('GlobalCache') + .addAdditionalSearchToken('itach') + .addAdditionalSearchToken('simple blaster') + .addButtonHander((deviceId, name) => { + return this._onButtonPressed(deviceId, name); + }) + .enableDiscovery({ + headerText: 'Serial Settings', + description: 'Modify the ' + this._fileName + ' file to include serial commands' + }, this._discoverDevices) + .setType('ACCESSOIRE'); + + for (const [name, mapping] of this._mappings) { + device = device.addButton({ name: mapping.name, label: mapping.label }); + } + + return device; + } + + _onButtonPressed(deviceId, name) { + return new BluePromise((resolve, reject) => { + const mapping = this._mappings.get(deviceId); + if (mapping) { + const device = gcUtils.parseDeviceId(name) + + return discovery.getDevice(device.uuid) + .then(results => { + return results.sendSerial(device.module, mapping.cmd); + }); + } else { + reject("unknown deviceid: " + deviceId); + } + }); + } + + _discoverDevices() { + return discovery + .getDevicePorts(constants.BEACON_TYPE_SR) + .map(port => { + return gcUtils.createDeviceDiscovery(port); + }); + } +}; \ No newline at end of file diff --git a/device/GlobalCache/serialFactory.js b/device/GlobalCache/serialFactory.js new file mode 100644 index 0000000..e7fc23e --- /dev/null +++ b/device/GlobalCache/serialFactory.js @@ -0,0 +1,28 @@ +'use strict'; + +const BluePromise = require('bluebird'); +const fs = require('fs'); +const path = require('path'); + +const SerialController = require('./serialController'); + +module.exports = class SerialFactory { + constructor(neeoapi, dirPath, ext) { + this._neeoapi = neeoapi; + this._dirPath = dirPath ? dirPath : "./definitions"; + this._ext = ext ? ext : ".ser"; + } + + buildDevices() { + const devices = []; + + const files = fs.readdirSync(this._dirPath); + for (let i = 0; i < files.length; i++) { + if (path.extname(files[i]) === this._ext) { + devices.push(new SerialController(this._neeoapi, path.format({ dir: this._dirPath, base: files[i] })).buildDevice()); + } + } + + return devices; + } +}; \ No newline at end of file