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..e6bdd9b --- /dev/null +++ b/device/GlobalCache/contactClosureController.js @@ -0,0 +1,47 @@ +'use strict'; + +const BluePromise = require('bluebird'); + +const discovery = require('./deviceDiscovery'); +const constants = require('./constants'); +const gcUtils = require('./gcUtils'); + +module.exports.switchSet = function (deviceid, value) { + const valueToSet = value === "true" ? '1' : '0'; + gcUtils.parseDeviceId(deviceid) + .then(function (args) { + return Promise.all([args, discovery.getDevice(args.uuid)]); + }) + .then(function (results) { + return results[1].setState(results[0].module, results[0].port, valueToSet); + }) + .then(function (rc) { + if (rc !== valueToSet) { + throw new Error("State was not correctly set on the device - expecting " + valueToSet + " but got " + rc); + } + }) + .catch(function (e) { + console.error("Error setting switch value. ", e.message); + }); +} + +module.exports.switchGet = function (deviceid) { + return gcUtils.parseDeviceId(deviceid) + .then(function (args) { + return Promise.all([args, discovery.getDevice(args.uuid)]); + }) + .then(function (results) { + return results[1].getState(results[0].module, results[0].port); + }) + .then(function (state) { + return state !== '0'; + }); +} + +module.exports.discoverDevices = function () { + return discovery + .getDevicePorts(constants.BEACON_TYPE_CC) + .map(function (port) { + return gcUtils.createDeviceDiscovery(port); + }); +} \ No newline at end of file diff --git a/device/GlobalCache/device.js b/device/GlobalCache/device.js new file mode 100644 index 0000000..e8bd09b --- /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(function (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', function (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', function (err) { + socket.destroy(); + reject(err); + }); + }); +}; + +Device.prototype.setState = function (module, port, state) { + const device = this; + return new BluePromise(function (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', function (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', function (err) { + socket.destroy(); + reject(err); + }); + }); +}; + +Device.prototype.getState = function (module, port) { + const device = this; + return new BluePromise(function (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', function (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', function (err) { + socket.destroy(); + reject(err); + }); + }); +}; + +Device.prototype.sendIR = function (module, port, state) { + const device = this; + return new BluePromise(function (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', function (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', function (err) { + socket.destroy(); + reject(err); + }); + }); +}; + +Device.prototype.sendSerial = function (module, serial) { + const device = this; + return new BluePromise(function (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', function (err) { + socket.destroy(); + reject(err); + }); + }); +}; + + diff --git a/device/GlobalCache/deviceDiscovery.js b/device/GlobalCache/deviceDiscovery.js new file mode 100644 index 0000000..3442dc2 --- /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', function () { + const address = server.address(); + console.log('GlobalCache/ITACH beacon listener started on ' + address.port); + server.addMembership(ITACH_BROADCAST_ADDR); + }) + .on('message', function (message, remote) { + checkStaleBeacons(); + if (message.toString().startsWith("AMXB")) { + parseBeacon(message.toString()); + } + }) + .on('error', function (err) { + console.error("Error creating beach listener. ", err); + }) + .bind(ITACH_BROADCAST_PORT); + +module.exports.getDevice = function (uuid) { + return new BluePromise(function (resolve, reject) { + const device = knownDevices[uuid]; + if (device === undefined) { + reject("Unknown device UUID: " + uuid); + } else { + resolve(device); + } + }); +} + +module.exports.getDevicePorts = function (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(function (ports) { + return [].concat.apply([], ports) + .filter(function (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..b706bdb --- /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..693b77e --- /dev/null +++ b/device/GlobalCache/gcUtils.js @@ -0,0 +1,79 @@ +'use strict'; + +const BluePromise = require('bluebird'); +const util = require('util'); +const fs = require('fs'); +const readline = require('readline'); + +module.exports.createDeviceDiscovery = function (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 = function (deviceId) { + return new BluePromise(function (resolve, reject) { + const uuidPorts = deviceId.split("."); + if (uuidPorts.length === 2) { + const modulePort = uuidPorts[1].split(":"); + if (modulePort.length === 2) { + resolve({ + uuid: uuidPorts[0], + module: modulePort[0], + port: modulePort[1] + }) + return; + } + } + reject(util.format("DeviceID cannot be parsed: %s", deviceId)); + }); + +} + +module.exports.clearObject = function (obj) { + for (var prop in obj) { if (obj.hasOwnProperty(prop)) { delete obj[prop]; } } +} + +function fileToObject(fileName, cache) { + return new BluePromise(function (resolve, reject) { + if (Object.keys(cache).length === 0) { + var lineReader = readline.createInterface({ + input: fs.createReadStream(fileName) + }); + lineReader + .on("line", function (line) { + if (!line.startsWith("#")) { + const idx = line.indexOf("="); + if (idx >= 0) { + const cmdName = line.substring(0, idx).trim(); + const irCmd = line.substring(idx + 1).trim(); + cache[cmdName] = irCmd; + } + } + }) + .on('close', function () { + resolve(cache); + }); + } else { + resolve(cache); + } + }); +} + +module.exports.fileToObject = fileToObject; + +module.exports.getCmdFromFile = function (fileName, cmdName, cache) { + return fileToObject(fileName, cache) + .then(function (cmds) { + return new BluePromise(function (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/index.js b/device/GlobalCache/index.js new file mode 100644 index 0000000..7f5798b --- /dev/null +++ b/device/GlobalCache/index.js @@ -0,0 +1,119 @@ +'use strict'; + +const neeoapi = require('neeo-sdk'); + +console.log('NEEO SDK Example "GlobalCache/ITach" adapter'); +console.log('---------------------------------------------'); + +/***************************************************************** + * IP2CC setup + ******************************************************************/ +const ccController = require('./contactClosureController'); +const closeContactDevice = neeoapi.buildDevice('IP2CC') + .setManufacturer('GlobalCache') + .addAdditionalSearchToken('itach') + .addAdditionalSearchToken('simple blaster') + .addAdditionalSearchToken('close contact') + .setType('ACCESSOIRE') + + // Keep this switch to activate the close contact - feel free to name + // it what you want (will be shared across all IP2CC devices). + .addSwitch({ name: 'closeContact', label: 'Contact' }, + { setter: ccController.switchSet, getter: ccController.switchGet }) + .enableDiscovery({ + headerText: 'Close Contact', + description: 'No special setup needed for Close Contact' + }, ccController.discoverDevices); + + +/***************************************************************** + * IP2IR setup + ******************************************************************/ +const irController = require('./irController'); + +// Uncomment the following if you need to specify a path (or name) to the map file +// irController.setIrMapPath("device/GlobalCache/ir.map"); + +const irDevice = neeoapi.buildDevice('IP2IR') + .setManufacturer('GlobalCache') + .addAdditionalSearchToken('itach') + .addAdditionalSearchToken('simple blaster') + .addAdditionalSearchToken('close contact') + .setType('ACCESSOIRE') + + // Add as many buttons as you need - just make sure the name + // exists in the associated ir.map file + .addButton({ name: 'QED Lower', label: 'QED Lower' }) + .addButton({ name: 'QED Raise', label: 'QED Raise' }) + + .addButtonHander(irController.onButtonPressed) + .enableDiscovery({ + headerText: 'IR Settings', + description: 'Modify the ir.map file to include IR commands' + }, irController.discoverDevices); + + +/***************************************************************** + * IP2SL setup + ******************************************************************/ +const serialController = require('./serialController'); + +// Uncomment the following if you need to specify a path (or name) to the map file +// serialController.setSerialMapPath("device/GlobalCache/serial.map"); + +const serialDevice = neeoapi.buildDevice('IP2SL') + .setManufacturer('GlobalCache') + .addAdditionalSearchToken('itach') + .addAdditionalSearchToken('simple blaster') + .addAdditionalSearchToken('close contact') + .setType('ACCESSOIRE') + + // Add as many buttons as you need - just make sure the name + // exists in the associated serial.map file + .addButton({ name: 'KeyPad Button1', label: 'Keypad Button 1' }) + + .addButtonHander(serialController.onButtonPressed) + .enableDiscovery({ + headerText: 'Serial Settings', + description: 'Modify the serial.map file to include serial commands' + }, serialController.discoverDevices); + + +/***************************************************************** + * 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: [closeContactDevice, irDevice, serialDevice] + }) + .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/ir.map b/device/GlobalCache/ir.map new file mode 100644 index 0000000..9be3e9d --- /dev/null +++ b/device/GlobalCache/ir.map @@ -0,0 +1,26 @@ +######################################################################################################################################## +# +# This file contains the mapping between a button name and the associated Global Cache (IR) command (use the IConvert utility to +# convert pronto, hex, etc to global cache format) that will be sent when the associated button is pressed. +# +# The format of the file is: +# Name=Command +# +# 1) 'Name' is the name associated with the command. Specify the name as the button name +# in index.js +# 2) 'Command' is the global cache command +# +# Here is an example of Lutron Sivoia QED shade commands: +# QED Raise=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 +# QED Lower=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 +# QED Open and Preset=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 +# QED Open=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 +# QED Close=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 +# QED Preset1=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 +# QED Preset2=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 +# QED Preset3=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 +# QED Open and Close=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 +# QED Open and Raise=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 +# QED Open and Lower=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 +######################################################################################################################################## + diff --git a/device/GlobalCache/irController.js b/device/GlobalCache/irController.js new file mode 100644 index 0000000..3de6cda --- /dev/null +++ b/device/GlobalCache/irController.js @@ -0,0 +1,40 @@ +'use strict'; + +const BluePromise = require('bluebird'); + +const discovery = require('./deviceDiscovery'); +const constants = require('./constants'); +const gcUtils = require('./gcUtils'); + +let irMapPath = "ir.map"; + +const cmdsCache = {}; + +module.exports.setIrMapPath = function (path) { + irMapPath = path; + gcUtils.clearObject(cmdsCache); +} + +module.exports.onButtonPressed = function (deviceid, name) { + gcUtils.getCmdFromFile(irMapPath, deviceid, cmdsCache) + .then(function (cmd) { + return Promise.all([cmd, gcUtils.parseDeviceId(name)]); + }) + .then(function (results) { + return Promise.all([results[0], results[1], discovery.getDevice(results[1].uuid)]); + }) + .then(function (results) { + return results[2].sendIR(results[1].module, results[1].port, results[0]); + }) + .catch(function (error) { + console.error("Error sending IR command", error || error.message); + }); +}; + +module.exports.discoverDevices = function () { + return discovery + .getDevicePorts(constants.BEACON_TYPE_IR) + .map(function (port) { + return gcUtils.createDeviceDiscovery(port); + }); +} \ No newline at end of file diff --git a/device/GlobalCache/serial.map b/device/GlobalCache/serial.map new file mode 100644 index 0000000..4872aac --- /dev/null +++ b/device/GlobalCache/serial.map @@ -0,0 +1,16 @@ +######################################################################################################################################## +# +# This file contains the mapping between a button name and the associated RS-232 commands that will be send when the associated +# button is pressed. +# +# The format of the file is: +# Name=Command +# +# 1) 'Name' is the name associated with the command. Specify the name as the button name +# in index.js +# 2) 'Command' is the RS-232 command to be sent +# +# Here is an example of Lutron Homeworks keypad button press (on processor 1, link 4, keypad address 4): +# KeyPad Button1=KBP, [01:04:04], 1 +######################################################################################################################################## + diff --git a/device/GlobalCache/serialController.js b/device/GlobalCache/serialController.js new file mode 100644 index 0000000..345b201 --- /dev/null +++ b/device/GlobalCache/serialController.js @@ -0,0 +1,40 @@ +'use strict'; + +const BluePromise = require('bluebird'); + +const discovery = require('./deviceDiscovery'); +const constants = require('./constants'); +const gcUtils = require('./gcUtils'); + +let serialMapPath = "serial.map"; + +const cmdsCache = {}; + +module.exports.setSerialMapPath = function (path) { + serialMapPath = path; + gcUtils.clearObject(cmdsCache); +} + +module.exports.onButtonPressed = function (deviceid, name) { + gcUtils.getCmdFromFile(serialMapPath, deviceid, cmdsCache) + .then(function (cmd) { + return Promise.all([cmd, gcUtils.parseDeviceId(name)]); + }) + .then(function (results) { + return Promise.all([results[0], results[1], discovery.getDevice(results[1].uuid)]); + }) + .then(function (results) { + return results[2].sendSerial(results[1].module, results[0]); + }) + .catch(function (error) { + console.error("Error sending serial command", error || error.message); + }); +}; + +module.exports.discoverDevices = function () { + return discovery + .getDevicePorts(constants.BEACON_TYPE_SR) + .map(function (port) { + return gcUtils.createDeviceDiscovery(port); + }); +} \ No newline at end of file