Skip to content

Commit

Permalink
Initial Contribution
Browse files Browse the repository at this point in the history
  • Loading branch information
tmrobert8 committed Jun 6, 2017
1 parent 41685ce commit ef95bd9
Show file tree
Hide file tree
Showing 11 changed files with 726 additions and 0 deletions.
30 changes: 30 additions & 0 deletions device/GlobalCache/constants.js
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
});
47 changes: 47 additions & 0 deletions device/GlobalCache/contactClosureController.js
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);
});
}
204 changes: 204 additions & 0 deletions device/GlobalCache/device.js
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);
});
});
};


99 changes: 99 additions & 0 deletions device/GlobalCache/deviceDiscovery.js
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];
}
}
}
}

Loading

0 comments on commit ef95bd9

Please sign in to comment.