voisus
Version:
This module provides access to maintenance and oversight over the voisus client and server.
876 lines (766 loc) • 22.5 kB
JavaScript
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
var _ = require('underscore');
var util = require('util');
var io = require('socket.io-client');
var events2 = require('eventemitter2');
var bunyan = require('bunyan');
var clone = require('node-asti').tools.clone;
var EMSG = {
'ready': 'ready',
'connect': 'connect',
'disconnect': 'disconnect',
'unknownMsg': 'unknownMsg',
'error': 'error',
'roles': 'roles',
'role_set': 'role_set',
'radio': 'radio',
'commpanel': 'commpanel'
};
var SMSG = {
'connect': 'client:connect',
'response': 'client:response',
'request': 'client:request'
};
var RADIO_FIELDS = ['netid', 'transmitting', 'receiving', 'nets', 'radioid'];
/*
* Public API Methods
*/
module.exports = WebClient = function(options) {
events2.EventEmitter2.call(this);
this.host = options.host;
this._DEBUG = options.debug;
if (options.logger) {
this.log = options.logger.child({widget: 'webclient', user: options.name});
}
else {
if (this._DEBUG) {
this.log = bunyan.createLogger({
name: 'webclient',
stream: process.stdout,
level: this._DEBUG_LEVEL || 'trace'
});
}
else {
this.log = bunyan.createLogger({
name: 'webclient',
streams: []
});
}
}
this._url = 'https://'+this.host+'/webclient';
this.EVENTS = _.clone(EMSG);
this._roles = {};
//this._roles = [];
this._client = {};
this._client.name = options.name || 'node-voisus.WebClient';
this._client.radios = {};
this._socket = io.connect(this._url, {'force new connection': true});
this._setupEventHandlers();
};
util.inherits(WebClient, events2.EventEmitter2);
WebClient.prototype.connect = function connect() {
var method = 'authorize_client';
var data = {
name: this._client.name
};
this._send(SMSG.connect, method, data);
};
WebClient.prototype.disconnect = function disconnect() {
this._socket.disconnect();
};
WebClient.prototype.setRole = function setRole(role) {
this.tmpRole = role;
this._send(SMSG.request, 'set_role', {
roleid: role.roleid
});
};
WebClient.prototype.getRoles = function getRoles() {
return clone(this._client.roles);
};
WebClient.prototype.getRadios = function getRadios() {
return clone(this._client.radios);
};
WebClient.prototype.getCommpanel = function getCommpanel() {
return clone(this._client.commpanel);
};
WebClient.prototype.setTx = function setTx(radioidx, value) {
var client = this;
if (!this._client.commpanel) {
setTimeout(function() {
client.setTx(radioidx, value);
}, 1000);
}
else {
var d = {
commpanelid: this._client.commpanel.commpanelid,
radioidx: radioidx,
txenable: value
};
this._send(SMSG.request, 'set_txenable', d);
}
};
WebClient.prototype.setRx = function setRx(radioidx, value) {
var client = this;
if (!this._client.commpanel) {
setTimeout(function() {
client.setRx(radioidx, value);
}, 1000);
}
else {
var d = {
commpanelid: this._client.commpanel.commpanelid,
radioidx: radioidx,
rxenable: value
};
this._send(SMSG.request, 'set_rxenable', d);
}
};
WebClient.prototype.setPtt = function(value) {
var client = this;
if (!this._client.commpanel) {
setTimeout(function() {
client.setPtt(value);
}, 1000);
}
else {
var d = {
commpanelid: this._client.commpanel.commpanelid,
ptt: value
};
this._send(SMSG.request, 'set_ptt', d);
}
//self.audioClient.ptt = value;
};
WebClient.prototype.audioSetup = function audioSetup(port, sampleRate) {
var d = {
name: this._client.name,
port: port,
sample_rate: sampleRate
};
this._send(SMSG.request, 'setup_audio', d);
};
/*
* this.connect = function(name) {
var msg = self._wrap('authorize_client', {
name: name
});
client.name = name;
self.socket.emit("client:connect", msg);
$cookieStore.put("webClientName", name);
};
this.disconnect = function() {
self.socket.emit("client:disconnect");
var name = client.name;
var role = client.role;
resetClient();
client.name = name;
angular.extend(client.role,role);
self.audioClient.disconnect();
client.connected = false;
if (client.heartbeat_timeout) {
$interval.cancel(client.heartbeat_timeout);
}
client.heartbeat_timeout = null;
};
*/
/*
* Utility Methods
*/
WebClient.prototype._emit = function _emit(type, data) {
this.log.debug({event: type, data: data}, 'WebClient emitting event');
this.emit(type, data);
};
WebClient.prototype._send = function _send(type, method, data) {
var msg = {
method: method,
data: data || {}
}
this.log.debug({type: type, payload: msg}, 'WebClient sending data to server');
this._socket.emit(type, msg);
}
WebClient.prototype._getRoles = function() {
this._send(SMSG.request, 'get_roles');
};
WebClient.prototype._sendHeartbeat = function _sendHeartbeat() {
this._send(SMSG.request, 'client_heartbeat');
};
WebClient.prototype._doHeartbeat = function _doHeartbeat() {
this._sendHeartbeat();
this._client._heartbeat_timeout = setInterval(this._sendHeartbeat.bind(this), 5000);
};
/*
* Event handlers
*/
WebClient.prototype._setupEventHandlers = function _setupEventHandlers() {
this._socket.on('connect', this._connectHandler.bind(this));
this._socket.on('disconnect', this._disconnectHandler.bind(this));
this._socket.on('client:response', this._parseResponseHandler.bind(this));
this._socket.on('error', this._errorHandler.bind(this));
//this._socket.on('client:response', this._parseResponseHandler.bind(this));
}
WebClient.prototype._errorHandler = function _errorHandler(err) {
this.log.error({err: err}, 'errorHandler executing');
this._emit(EMSG.error, err);
}
WebClient.prototype._connectHandler = function _connectHandler() {
this.log.trace('Connection to Voisus server enabled, emitting READY');
this._emit(EMSG.ready);
}
WebClient.prototype._disconnectHandler = function _disconnectHandler() {
this.log.info('Disconnecting from Voisus Server');
if (this._client._heartbeat_timeout) {
clearInterval(this._client._heartbeat_timeout);
}
this._emit(EMSG.disconnect);
};
WebClient.prototype._authorizeClient = function(data) {
if (data.redirect) {
//If we have a redirect, we need to connect the client
//to another target box. Either directly connect his client
//or broadcast a message to controller to have the browser
//redirect to that box?
var pass;
}
this._client.connected = data.authorized;
this._emit(EMSG.connect);
this._doHeartbeat();
this._getRoles();
/*var path = $location.host() + ":8080";
try {
self.audioClient = new AudioClient(path, function(data, sampleRate) {
self.sendRequest(self._wrap('setup_audio', {
name: client.name,
port: data.port,
sample_rate: sampleRate
}));
self.audioEnabled.value = true;
});
} catch (e) {
console.log("Error trying to create Audio Client", e);
self.audioEnabled.value = false;
}*/
};
WebClient.prototype._updateRoles = function(data) {
this._client.roles = data.roles;
this._emit(EMSG.roles, this._client.roles);
/*if (data.roles.length === 1 && !client.role) {
client.role = data.roles[0].roleid;
self.setRole(data.roles[0].roleid);
}*/
};
WebClient.prototype._parseRadio = function _parseRadio(data) {
// WARNING!!
// This doesn't properly delete fields that have been removed
// i.e. if a net is removed from a radio, this won't update properly
var radio = {};
_.each(RADIO_FIELDS, function(prop) {
radio[prop] = data[prop];
}, this);
if (!this._client.radios[radio.radioid]) {
this._client.radios[radio.radioid] = {};
}
_.extend(this._client.radios[radio.radioid], radio);
this._emit(EMSG.radio, radio);
this.log.trace({radio: radio, client_name: this._client.name},
'parsed updated radio for client');
};
WebClient.prototype._parseCommpanel = function _parseCommpanel(data) {
this._client.commpanel = data;
this._emit(EMSG.commpanel);
this.log.debug({commpanel: this._client.commpanel}, 'parsed updated commpanel for client');
// WARNING!!
// This doesn't properly delete fields that have been removed
// i.e. if a net is removed from a radio, this won't update properly
};
WebClient.prototype._resetClientRole = function _resetClientRole() {
this._client.role = _.clone(this.tmpRole);
this._emit(EMSG.role_set, this._client.role);
this.tmpRole = null;
};
WebClient.prototype._parseResponseHandler = function _parseResponseHandler(data) {
var d = data.data;
var method = data.method;
if (!method) {
this.log.warn(data, "Unrecognized client response ");
this._emit(EMSG.unknownMsg, data);
}
this.log.trace({method: method, data: d},"response from voisus server");
switch (method) {
case "authorize_client":
this._authorizeClient(d);
break;
case "update_roles":
this._updateRoles(d);
break;
case "set_role":
if (d) {
this._resetClientRole();
}
break;
case "update_radio":
this._parseRadio(d);
break;
case "update_commpanel_ptt":
this._client.ptt = d;
break;
case "update_commpanel":
this._parseCommpanel(d);
break;
/*
case "setup_audio":
self.audioClient.start();
break;
*/
case "server_heartbeat":
this.log.trace("server_heartbeat");
break;
}
};
/*
var self = this;
this._roles = {};
this.roles = [];
this.socket = svcSocket.getWebClientSocket();
this.socket.on("client:response", function(data) {
self.parseResponse(data);
});
this.audioEnabled = {value: false};
var tempRole = null;
var client = {};
client.role = {};
client._radios = {};
client.radios = [];
client.state = {};
client.heartbeat_timeout = null;
client.connected = false;
var resetClient = function () {
client.name = null;
client.ptt = false;
angular.extend(client.role, {
autotune_enabled : false,
roleid : '',
rolename : ''
});
angular.extend(client.state, {
pttOpt : 'momentary',
compressed : false
});
client._radios = {};
client.radios.empty();
};
resetClient();
this.getClient = function () {
return client;
};
this.getClientState = function () {
return client.state;
};
this.getClientRole = function () {
return client.role;
};
this.parseResponse = function(data) {
var d = data.data;
var method = data.method;
if (angular.isUndefined(method)) {
console.log("Unrecognized client response ", data);
return;
}
//console.log(method,"response:",d);
switch (method) {
case "authorize_client":
this.authorizeClient(d);
break;
case "setup_audio":
self.audioClient.start();
break;
case "get_vehicles":
break;
case "set_role":
if (d) {
self.resetClientRole(tempRole);
}
break;
case "update_roles":
this.updateRoles(d);
break;
case "update_vehicles":
break;
case "update_radio":
this.parseRadio(d);
break;
case "update_vehicle_radio":
break;
case "update_commpanel":
this.parseCommpanel(d);
break;
case "update_commpanel_ptt":
client.ptt = d;
break;
case "update_vehicle_commpanel":
break;
case "server_heartbeat":
//console.log("server_heartbeat");
break;
}
};
this._wrap = function(method, data) {
if (angular.isUndefined(data)) {
data = {};
}
return {
method: method,
data: data
};
};
this.sendHeartbeat = function() {
self.sendRequest(self._wrap('client_heartbeat', {}));
};
this.doHeartbeat = function() {
self.sendHeartbeat();
client.heartbeat_timeout = $interval(self.sendHeartbeat, 5000);
};
this.authorizeClient = function(data) {
if (data.redirect) {
//If we have a redirect, we need to connect the client
//to another target box. Either directly connect his client
//or broadcast a message to controller to have the browser
//redirect to that box?
var pass;
}
client.connected = data.authorized;
self.doHeartbeat();
self.getRoles();
var path = $location.host() + ":8080";
try {
self.audioClient = new AudioClient(path, function(data, sampleRate) {
self.sendRequest(self._wrap('setup_audio', {
name: client.name,
port: data.port,
sample_rate: sampleRate
}));
self.audioEnabled.value = true;
});
} catch (e) {
console.log("Error trying to create Audio Client", e);
self.audioEnabled.value = false;
}
var cR = $cookieStore.get("webClientRole");
if (cR) {
self.setRole(cR);
var cS = $cookieStore.get("webClientState");
if (cS) {
angular.extend(client.state, cS);
}
}
};
this.sendRequest = function(msg) {
self.socket.emit("client:request", msg);
};
this.connect = function(name) {
var msg = self._wrap('authorize_client', {
name: name
});
client.name = name;
self.socket.emit("client:connect", msg);
$cookieStore.put("webClientName", name);
};
this.disconnect = function() {
self.socket.emit("client:disconnect");
var name = client.name;
var role = client.role;
resetClient();
client.name = name;
angular.extend(client.role,role);
self.audioClient.disconnect();
client.connected = false;
if (client.heartbeat_timeout) {
$interval.cancel(client.heartbeat_timeout);
}
client.heartbeat_timeout = null;
};
this.resetClientRole = function(role) {
var name = client.name;
var state = angular.copy(client.state);
resetClient();
client.name = name;
angular.extend(client.role, role);
angular.extend(client.state, state);
};
this.getRoles = function() {
self.sendRequest(self._wrap('get_roles'));
};
this.updateArrayMapper = function(M, A, idattr, items, ufunc, itemGetter) {
if (angular.isUndefined(ufunc)) {
ufunc = function(o, n) {
angular.extend(o, n);
};
}
var currItems = _.map(A, function(item) {
return item[idattr];
});
var newItems = _.map(items, function(item) {
return item[idattr];
});
//Remove deleted items
var todelete = _.difference(currItems, newItems);
angular.forEach(todelete, function(id) {
delete M[id];
//find the object in the array and remove it
var index, i;
for (i = 0; i < A.length; i++) {
if (A[i][idattr] === id) {
index = i;
break;
}
}
if (index) {
A.remove(index);
}
});
//Add new items and update existing ones
var item;
angular.forEach(items, function(obj, index) {
if (angular.isDefined(itemGetter)) {
item = itemGetter(obj[idattr]);
} else {
item = M[obj[idattr]];
if (angular.isUndefined(item)) {
item = {};
M[obj[idattr]] = item;
A.push(item);
}
}
item.__index__ = index;
ufunc(item, obj);
});
};
this.updateRoles = function(data) {
self.updateArrayMapper(self._roles, self.roles, "roleid", data.roles);
if (data.roles.length === 1 && !client.role) {
client.role = data.roles[0].roleid;
self.setRole(data.roles[0].roleid);
}
};
this.getRadio = function(rid) {
var radio = client._radios[rid];
if (angular.isUndefined(radio)) {
radio = {
nets: [],
_nets: {}
};
client._radios[rid] = radio;
client.radios.push(radio);
}
return radio;
};
this.parseCommpanel = function(data) {
client.ptt = data.ptt;
client.commpanelid = data.commpanelid;
self.updateArrayMapper(client._radios, client.radios, "radioid", data.radios,
undefined, self.getRadio);
};
this.parseRadio = function(data) {
var radio = self.getRadio(data.radioid);
angular.forEach(['netlock', 'netid', 'receiving',
'transmitting', 'radiotype', 'cipherenabled'
],
function(attr) {
radio[attr] = data[attr];
});
self.updateArrayMapper(radio._nets, radio.nets, "netid", data.nets);
};
this.setRole = function(role) {
self.sendRequest(self._wrap('set_role', {
roleid: role.roleid
}));
tempRole = role;
client.state.compressed = true;
var save = angular.extend({}, role);
delete save.__index__;
$cookieStore.put("webClientRole", save);
};
this.setTx = function(radio) {
var tx = !radio.txenable;
var rx = radio.rxenable;
if (tx) {
rx = true;
}
self.updateRxTx(radio, rx, tx);
};
this.setRx = function(radio) {
var tx = radio.txenable;
var rx = !radio.rxenable;
if (!rx) {
tx = false;
}
self.updateRxTx(radio, rx, tx);
};
this.updateRxTx = function (radio, rx, tx) {
var d = {
commpanelid: client.commpanelid,
radioidx: radio.__index__,
rxenable: rx
};
self.sendRequest(self._wrap('set_rxenable', d));
d = {
commpanelid: client.commpanelid,
radioidx: radio.__index__,
txenable: tx
};
self.sendRequest(self._wrap('set_txenable', d));
};
this.setVol = function(radio, delta) {
var vol = radio.volume + delta;
if (vol < 0) {
vol = 0;
} else if (vol > 10) {
vol = 10;
}
var d = {
commpanelid: client.commpanelid,
radioidx: radio.__index__,
volume: vol
};
self.sendRequest(self._wrap('set_volume', d));
};
this.setPtt = function(value) {
var d = {
commpanelid: client.commpanelid,
ptt: value
};
self.sendRequest(self._wrap('set_ptt', d));
self.audioClient.ptt = value;
};
this.setRadioNet = function(radio, net) {
var d = {
radioid: radio.radioid,
netid: net.netid
};
self.sendRequest(self._wrap('set_radio_net', d));
};
this.setClientState = function () {
$cookieStore.put("webClientState", client.state);
};
var cN = $cookieStore.get("webClientName");
if (cN) {
self.connect(cN);
}
return self;
});
wc.controller("WebClientCtrl", function($scope, svcWebClient, svcFeatures) {
$scope.roles = svcWebClient.roles;
$scope.client = svcWebClient.getClient();
$scope.cState = svcWebClient.getClientState();
$scope.cRole = svcWebClient.getClientRole();
$scope.audioEnabled = svcWebClient.audioEnabled;
$scope.clientNames = [];
$scope.pttPressed = false;
$scope.pttLatched = false;
$scope.browserSupport = !window.Modernizr.webaudio && !window.Modernizr.websockets;
$scope.webclientFeature = svcFeatures.getFeature('webaudio');
$scope.connectClient = function() {
if ($scope.connClientForm.$invalid) {
return;
}
svcWebClient.connect($scope.client.name);
};
$scope.disconnectClient = function() {
svcWebClient.disconnect();
};
$scope.setRole = function(role) {
svcWebClient.setRole(role);
};
$scope.radioEvenSpan = function(r) {
if (angular.isUndefined(r)) {
return false;
}
return r.__index__ % 2 === 0;
};
$scope.radioOddSpan = function(r) {
if (angular.isUndefined(r)) {
return false;
}
return r.__index__ % 2 === 1;
};
$scope.pttDown = function() {
if (_.find($scope.client.radios, function (r) {
return r.txenable === true;
})) {
$scope.pttPressed = true;
svcWebClient.setPtt(true);
}
};
$scope.pttUp = function(latch) {
if (latch) {
$scope.pttLatched = !$scope.pttLatched;
if ($scope.pttLatched){
return;
}
}
$scope.pttPressed = false;
svcWebClient.setPtt(false);
};
$scope.setClientState = function (attr, value) {
svcWebClient.setClientState(attr, value);
};
});
wc.directive("cpradio", function(svcWebClient) {
return {
scope: {
radio: '='
},
replace: true,
restrict: 'E',
templateUrl: '/webclient/html/cp_radio.html',
controller: function($scope) {
$scope._volUpPress = false;
$scope._voldnPress = false;
$scope._netpress = false;
$scope._pnlPress = false;
$scope.setTx = function() {
svcWebClient.setTx($scope.radio);
};
$scope.setRx = function() {
svcWebClient.setRx($scope.radio);
};
$scope.incVol = function() {
$scope._volUpPress = false;
svcWebClient.setVol($scope.radio, +1);
};
$scope.decVol = function() {
$scope._volDnPress = false;
svcWebClient.setVol($scope.radio, -1);
};
$scope.changeToPanel = function() {
$scope._pnlPress = false;
};
$scope.showNetList = function() {
$scope._netpress = false;
$scope._shownetlist = !$scope._shownetlist;
};
$scope.setRadioNet = function(net) {
$scope._shownetlist = false;
svcWebClient.setRadioNet($scope.radio, net);
};
}
};
});
wc.filter("radioTypeLabel", function(svcConstants) {
var map = {};
map[svcConstants.ROLE_PRC117F] = '117F';
map[svcConstants.ROLE_PRC117G] = '117G';
map[svcConstants.ROLE_PRC148] = '148';
map[svcConstants.ROLE_PRC152] = '152';
map[svcConstants.ROLE_PRC119] = '119';
map[svcConstants.ROLE_SINCGARS] = 'SINCGARS';
map[svcConstants.ROLE_GENERIC_RADIO] = 'RADIO';
return function(val) {
var v = map[val];
if (angular.isUndefined(v)) {
return val;
}
return v;
};
});
}(window, window.angular));
*/