-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
726 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
}); | ||
}); | ||
}; | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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]; | ||
} | ||
} | ||
} | ||
} | ||
|
Oops, something went wrong.