web-remote-control
Version:
Fast, real-time, remote control of devices (drones, boats, etc) from the web.
291 lines (238 loc) • 8.87 kB
JavaScript
/*********************************************************************
* *
* Copyright 2016 Simon M. Werner *
* *
* Licensed to the Apache Software Foundation (ASF) under one *
* or more contributor license agreements. See the NOTICE file *
* distributed with this work for additional information *
* regarding copyright ownership. The ASF licenses this file *
* to you under the Apache License, Version 2.0 (the *
* "License"); you may not use this file except in compliance *
* with the License. You may obtain a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, *
* software distributed under the License is distributed on an *
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
* KIND, either express or implied. See the License for the *
* specific language governing permissions and limitations *
* under the License. *
* *
*********************************************************************/
'use strict';
var makeUID = require('shortid').generate;
/**
* The DeviceManager manages toys and controllers (devices). Devices operate on
* a given channel, both the toy and controller require the same channel. There
* can be multiple toys and controllers on the same channel.
*/
function DeviceManager(settings) {
settings = settings ? settings : {};
this.onlyOneControllerPerChannel = settings.onlyOneControllerPerChannel === true ? true : false;
this.onlyOneToyPerChannel = settings.onlyOneToyPerChannel === true ? true : false;
this.allowObservers = settings.allowObservers === true ? true : false;
this.list = {};
}
/**
* Add a new toy/controller to the device manager.
* @param {string} deviceType 'toy' or 'controller' or 'observer'.
* @param {string} channel The channel to operate on (this is provided by the device).
* @param {object} socket The socket details.
* @param {number} seqNum The sequence number from the device.
* @param {string} protocol 'tcp' or 'udp4'
* @return {uid} The UID of the device.
*/
DeviceManager.prototype.add = function(deviceType, channel, socket, seqNum) {
switch (undefined) { // eslint-disable-line default-case
case deviceType:
case channel:
case socket:
case seqNum:
console.error('DeviceManager.add(): one of the inputs are undefined.');
return undefined;
}
if (!this.validDeviceType(deviceType)) {
console.error('DeviceManager.getAll(): "deviceType" should be "toy" or "controller" or "observer" (if allowObservers is true), not: ', deviceType);
return undefined;
}
// Remove the controller / toy that already exists
if ((deviceType === 'controller' && this.onlyOneControllerPerChannel) ||
(deviceType === 'toy' && this.onlyOneToyPerChannel)) {
var devUID = this.findByDeviceTypeAndChannel(deviceType, channel);
if (devUID) {
this.remove(devUID);
}
}
var uid = makeUID();
this.list[uid] = {
deviceType: deviceType,
channel: channel,
socket: socket,
seqNum: seqNum
};
return uid;
};
/**
* Remove a device from the device manager.
* @param {string} uid The UID
* @return {boolean} true if successful, otherwise false.
*/
DeviceManager.prototype.remove = function(uid) {
var dev = this.list[uid];
if (typeof dev === 'undefined') {
return false;
}
try {
dev.socket.close();
} catch (ex) {
// okay if there is an error - socket may already be closed.
}
try {
delete this.list[uid];
} catch (ex) {
return false;
}
return true;
};
/**
* Checks if the device is allowed the given action
* @param {string} uid The UID of the device
* @param {string} actionType The action to test
* @return {boolean} true if action is allowed, otherwise false.
*/
DeviceManager.prototype.isAllowedAction = function(uid, actionType) {
var dev = this.list[uid];
if (typeof dev === 'undefined') {
return false;
}
if (dev.deviceType === 'toy' && actionType === 'status') return true;
if (dev.deviceType === 'controller' && actionType === 'command') return true;
return false;
};
/**
* Remove a device from the device manager.
* @param {string} uid The UID
* @return {boolean} true if successful, otherwise false.
*/
DeviceManager.prototype.removeBySocketId = function(socketId) {
var uid = this.findBySocketId(socketId);
return this.remove(uid);
};
DeviceManager.prototype.findByDeviceTypeAndChannel = function(deviceType, channel) {
var self = this;
var foundUid = null;
Object.keys(this.list).forEach( function(uid) {
var dev = self.list[uid];
if (dev.deviceType === deviceType && dev.channel === channel) {
foundUid = uid;
}
});
return foundUid;
};
DeviceManager.prototype.findBySocketId = function(socketId) {
var self = this;
var foundUid = null;
Object.keys(this.list).forEach( function(uid) {
var dev = self.list[uid];
if (dev.socket.socketId === socketId) {
foundUid = uid;
}
});
return foundUid;
};
/**
* Get a device from a UID.
* @param {string} uid The UID.
* @return {object} The device details.
*/
DeviceManager.prototype.get = function(uid) {
return this.list[uid];
};
/**
* Get a socket from the device with the given UID.
* @param {string} uid The UID.
* @return {object} The socket details.
*/
DeviceManager.prototype.getSocket = function(uid) {
return this.list[uid].socket;
};
/**
* Get all devices on a channel. But only by device type (toy/controller).
* @param {string} deviceType 'toy' or 'controllers'
* @param {string} channel The channel.
* @return {array} An array of UID strings.
*/
DeviceManager.prototype.getAll = function(deviceType, channel) {
if (!this.validDeviceType(deviceType)) {
console.error('DeviceManager.getAll(): "deviceType" should be "toy" or "controller" or "observer" (if allowObservers is true), not: ', deviceType);
return [];
}
var devList = [];
var self = this;
Object.keys(this.list).forEach( function(uid) {
var dev = self.list[uid];
if (dev.deviceType === deviceType && dev.channel === channel) {
devList.push(uid);
}
});
return devList;
};
/**
* Update the device with the given ip and port parameters. Then return the
* device.
* @param {string} uid The UID for the device.
* @param {string} ip The IP the device was last seen on.
* @param {number} port The port the device was last seen on.
* @return {object} The updated device details.
*/
DeviceManager.prototype.update = function(uid, socket, seqNum) {
if (isNaN(seqNum)) {
console.error('DeviceManager.update(): provided "seqNum" is not a number');
return undefined;
}
var device = this.list[uid];
if (!device) {
return undefined;
}
device.socket = socket || device.socket;
device.seqNum = seqNum;
return device;
};
/**
* Make sure the sequence number is okay.
* @param {string} uid UID for the device
* @param {number} seqNum The seqNum to check
* @return {boolean} True or false
*/
DeviceManager.prototype.isLatestSeqNum = function(uid, seqNum) {
if (isNaN(seqNum)) {
console.error('DeviceManager.isLatestSeqNum(): provided "seqNum" is not a number');
return false;
}
var device = this.list[uid];
if (!device) {
return false;
}
if (device.seqNum > seqNum) {
return false;
}
return true;
};
/**
* Confirm the device type is correct.
* @param {string} type The name of the device to check.
* @return {boolean} True if the device type is valid.
*/
DeviceManager.prototype.validDeviceType = function(deviceType) {
switch (deviceType) {
case 'toy':
case 'controller':
return true;
case 'observer': // observer only valid if allowObserver === true
return this.allowObservers;
default:
return false;
}
};
module.exports = DeviceManager;