/**
* @fileOverview Sets up web socket server and handles client connections by creating and deleting Client objects and SerialPort objects
*
* @author Shawn Van Every
* @author Jiwon Shin
*
* @requires classes/Client.js:Client
* @requires classes/SerialPort.js:SerialPort
* @requires NPM:ws
*/
let Client = require("./classes/Client");
let SerialPort = require("./classes/SerialPort");
/**
* Variable to decide whether to console.log detailed messages
* @type {Boolean}
* */
let LOGGING = true;
/**
* Web socket server. Initialized in start() function.
* @type {ws}
* */
let wss = null;
/**
* Array of all opened serial port names in string.
* @type {String[]}
*/
let serialPortsList = [];
/**
* Array of all opened {@link SerialPort SerialPort} objects.
* @type {SerialPort[]}
* */
let serialPorts = [];
/**
* Array of all connected {@link Client Client} objects.
* @type {Client[]}
* */
let clients = [];
/**
* console.log log messages when LOGGING == true
* @function logit
* @param {String} mess - String to log when LOGGING == true*/
let logit = function(mess) {
if (LOGGING) {
console.log(mess);
}
};
/**
* @function start
* @desc Initialize web socket server at port 8081. Initialize web socket clients on connection by creating a {@link Client Client} object and create a (@link SerialPort SerialPort} object after determining that it has not been opened already. Initialize web socket client message events.
* @param {Number} port - port number used to open web socket server.
* */
/**
* @event Client#message
* @param {Object} inmessage - Type of message emitted from {@link Client Client}. Defined message types are: echo, list, openserial, write, close and error. Undefined message types are treated as error messages
* */
let start = function (port) {
logit("start()");
let SERVER_PORT = port;
let WebSocketServer = require('ws').Server;
wss = new WebSocketServer({perMessageDeflate: false, port: SERVER_PORT});
wss.on("connection", (ws) => {
// Push the connection into the array of clients
let client = new Client(ws);
clients.push(client);
// Create an object to hold information about the connection
logit(`${clients.length} clients connected`);
client.ws.on('message', function(inmessage) {
let message = JSON.parse(inmessage);
if (typeof message !== "undefined" && typeof message.method !== "undefined" && typeof message.data !== "undefined") {
if(message.method === "echo"){
client.echo(message.data);
} else if(message.method === "list"){
logit("message.method === list");
client.list();
} else if(message.method === "openserial"){
logit("message.method === openserial");
if(typeof message.data.serialport === 'string'){
let newPort = message.data.serialport;
let newPortOptions = message.data.serialoptions;
//before opening new port, clean up array
for(let i = 0; i < serialPortsList; i++){
if(serialPortsList[i].serialPort === null){
serialPorts.splice(i, 1);
}
}
if(serialPortsList.length > 0){
//specified serial port is already opened
if(serialPortsList.indexOf(newPort) > -1){
let portIndex = serialPortsList.indexOf(newPort);
if(client.serialPortsList.indexOf(newPort) > -1){
client.sendit({method: 'error', data: "Already open"});
logit(`serialPort ${newPort} is already open`);
client.sendit({method: 'openserial', data : {}});
}else{
//add existing serialPort to the client
serialPorts[portIndex].addClient(client);
client.openSerial(serialPorts[portIndex]);
client.sendit({method: 'openserial', data : {}});
}
}else{
serialPortsList.push(newPort);
let newSerialPort = new SerialPort(newPort, newPortOptions);
serialPorts.push(newSerialPort);
newSerialPort.addClient(client);
client.openSerial(newSerialPort);
}
}else{
//first serial connection
serialPortsList.push(newPort);
let newSerialPort = new SerialPort(newPort, newPortOptions);
serialPorts.push(newSerialPort);
newSerialPort.addClient(client);
client.openSerial(newSerialPort);
}
}else{
logit("User didn't specify a port to open");
client.sendit({method: 'error', data: "You must specify a serial port to open"});
}
} else if(message.method === "write"){
client.write(message.data);
} else if(message.method === "close"){
logit("message.method === close");
for(let i = 0; i < client.serialPortsList.length; i++){
let portIndex = serialPortsList.indexOf(client.serialPortsList[i]);
if(serialPorts[portIndex] != null){
serialPorts[portIndex].closeSerial();
serialPorts.splice(portIndex, 1);
serialPortsList.splice(portIndex, 1);
}
}
}
}else{
console.log("Not a message I understand: " + JSON.stringify(message));
}
});
//check if this is called if browser closed
//needs to be explicitly closed
//other clients need to receive broadcast that it was closed
ws.on('close', function() {
logit(`ws.on close - ${clients.length} client left`);
for (let c = 0; c < clients.length; c++) {
if (clients[c].ws === ws) {
logit("removing client from array");
serialPorts.forEach(port => port.removeClient(clients[c]));
clients.splice(c, 1);
logit(`clients.splice - ${clients.length} clients left`);
break;
}
}
if (clients.length === 0) {
logit("clients.length == 0 checking to see if we should close serial port");
// Should close serial ports
for(let i = 0; i < serialPorts.length; i++){
serialPorts[i].closeSerial();
}
serialPorts = [];
serialPortsList = [];
}
});
});
};
/**
* @function stop
* @desc Stops web socket server after closing all {@link SerialPort SerialPort} connections and {@link Client Client} connections
* */
let stop = function() {
logit("stop()");
for(let i = 0; i < serialPorts.length; i++){
if(serialPorts[i].serialPort != null && typeof serialPorts[i].serialPort === "object" && serialPorts[i].serialPort.isOpen){
logit("serialPort != null && serialPort.isOpen is true");
logit("serialPort.flush, drain, close");
serialPorts[i].serialPort.flush();
serialPorts[i].serialPort.drain();
serialPorts[i].serialPort.close(
function(error){
if(error){
console.log("Serial Close Error: " + error);
}
}
);
serialPorts.splice(i, 1);
serialPortsList.splice(i, 1);
}
}
try {
for (let c = 0; c < clients.length; c++) {
if (clients[c] != null) {
logit("clients[" + c + "] != null, close");
clients[c].ws.close();
}
}
} catch (e) {
console.log("Client Close Error: " + e);
}
if (wss != null) {
logit("wss != null so wss.close()");
wss.close();
}
// Let's try to close a different way
for(let i = 0; i < serialPorts.length; i++) {
if (serialPorts[i].serialPort != null && typeof serialPorts[i].serialPort === "object" && serialPorts[i].serialPort.isOpen) {
logit("serialPort != null && serialPort.isOpen is true so serialPort = null");
serialPorts[i].serialPort = null;
}
}
};
module.exports.start = start;
module.exports.stop = stop;