From d2dcaff3633708e4e41a3eb621543111680a6c45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?aar=C3=B3n=20montoya-moraga?= Date: Tue, 2 Aug 2022 14:46:00 -0400 Subject: [PATCH] restore classes Client and SerialPort from repo --- lib/classes/Client.js | 114 +++++++++++++++++++++ lib/classes/SerialPort.js | 205 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 319 insertions(+) create mode 100644 lib/classes/Client.js create mode 100644 lib/classes/SerialPort.js diff --git a/lib/classes/Client.js b/lib/classes/Client.js new file mode 100644 index 0000000..51f9d53 --- /dev/null +++ b/lib/classes/Client.js @@ -0,0 +1,114 @@ +/** + * @fileOverview Client object that gets created when new web socket client connects to server. Maintains SerialPort objects that the client subscribes to. + * + * @author Shawn Van Every + * @author Jiwon Shin + * + * @requires NPM:serialport + * @requires NPM:ws + * @requires classes/SerialPort.js:SerialPort + * */ + +let sp = require('serialport'); +let SerialPort = require('./SerialPort'); +let WebSocketServer = require('ws').Server; + +/** + * Represents a web socket client. Maintains {@link SerialPort SerialPort} objects that the client subscribes to. + */ +class Client { + /** + * create a web socket client + * @constructor + * @param {ws} ws - Web socket object + * @property {SerialPort[]} serialPorts - list of subscribed {@link SerialPort SerialPort} objects + * @property {string[]} serialPortsList - list of string names of subscribed {@link SerialPort SerialPort} objects. Used for checking whether duplicate serial port is requested to open. + */ + constructor(ws) { + this.LOGGING = true; + + //websocket object for this client + this.ws = ws; + //list of serial ports that this client has opened + this.serialPorts = []; + this.serialPortsList = []; + } + + /** echo received message back to web client */ + echo(msg) { + this.sendit({ method: 'echo', data: msg }); + } + + /** list all available serial ports and send it to the client*/ + list() { + let self = this; + + sp.list(function (err, ports) { + let portNames = []; + ports.forEach(function (port) { + portNames.push(port.comName); + }); + + self.sendit({ method: 'list', data: portNames }); + }); + } + + /** + * add opened SerialPort object and its name + * @param {SerialPort} port - SerialPort object opened by client + * */ + openSerial(port) { + this.serialPortsList.push(port.serialPortName); + this.serialPorts.push(port); + } + + /** + * write received data to subscribed serial ports. + * @param {String} msg - received string data from client + * */ + write(msg) { + for (let i = 0; i < this.serialPorts.length; i++) { + this.serialPorts[i].serialPort.write(msg); + } + } + + /** + * close client connection. Set serialPorts and serialPortsList array to null. + */ + close() { + //this needs to be updated per port + //also with client-side library + this.serialPorts = []; + this.serialPortsList = []; + + //close message via sendit? + //close ws? + //this.sendit() + } + + /** + * console.log log messages when LOGGING == true + * @function logit + * @param {String} mess - String to log when LOGGING == true*/ + logit(mess) { + if (this.LOGGING) { + console.log(mess); + } + } + + /** + * send data via websocket to the client + * @param {Object} toSend - JSON object received to be sent. Contains message method and data. + */ + sendit(toSend) { + let dataToSend = JSON.stringify(toSend); + + try { + this.ws.send(dataToSend); + } catch (error) { + console.log('Error sending: ', error); + } + } +} + +module.exports = Client; diff --git a/lib/classes/SerialPort.js b/lib/classes/SerialPort.js new file mode 100644 index 0000000..331bb0b --- /dev/null +++ b/lib/classes/SerialPort.js @@ -0,0 +1,205 @@ +/** + * @fileOverview SerialPort class that gets created when new serial port is opened. Maintains list of connected Client objects to forward data received from the serial port. + * + * @author Shawn Van Every + * @author Jiwon Shin + * + * @requires NPM:serialport + * */ + +let sp = require('serialport'); + +/** + * Represents a serialport object. Maintains an array of {@link Client Client} objects subscribed to the serial port. + * @see https://www.npmjs.com/package/serialport + * */ +class SerialPort { + /** + * creates a SerialPort object + * @constructor + * @param {serialport} serialport - serialport object + * @param {Object} serialoptions - JSON object of options for the serialport object + * @property {Boolean} LOGGING - sets whether to console.log detailed information + * @property {String} serialPortName - name of the connected serialport + * @property {Object} serialOptions - JSON array of options for the serialport + * @property {Client[]} messageListeners - array of subscribed {@link Client Client} objects + * */ + constructor(serialport, serialoptions) { + this.LOGGING = true; + + this.serialPortName = serialport; + this.serialOptions = serialoptions; + + this.serialPort = null; + + this.messageListeners = []; + + this.openSerial(); + + this.logit(`initialize with serial port ${this.serialPortName}`); + } + + /** + * add a web client to messageListeners array + * @param {Client} client - {@link Client Client} object subscribing to the SerialPort + * */ + addClient(client) { + this.messageListeners.push(client); + + console.log( + `total number of ${this.messageListeners.length} clients subscribed`, + ); + //this.onMessage({method: 'clientNumber', data: this.messageListeners.length}); + } + + /** + * remove client from messageListeners array to unsubscribe client + * @param {Client} client - {@link Client Client} object unsubscribing to the SerialPort + * */ + removeClient(client) { + this.messageListeners = this.messageListeners.filter( + (clientToRemove) => clientToRemove !== client, + ); + console.log( + `removeClient - total number of ${this.messageListeners.length} clients subscribed`, + ); + } + + /** + * Forwards message emitted by SerialPort events to the susbscribed clients in the messageListeners array + * @param {Object} msg - JSON object containing message method and data + * */ + onMessage(msg) { + this.messageListeners.forEach((client) => client.sendit(msg)); + } + + /** + * Opens SerialPort of serialPortName with serialOptions. + * Sets serialport event listeners of method 'data', 'close' and 'error' and sends messages to the client via onMessage function + * */ + openSerial() { + let self = this; + + if (!self.serialOptions.hasOwnProperty('autoOpen')) { + self.serialOptions.autoOpen = false; + } + + self.serialPort = new sp( + self.serialPortName, + self.serialOptions, + function (err) { + if (err) { + console.log(err); + self.onMessage({ method: 'error', data: err }); + } + }, + ); + + self.serialPort.on('data', function (incoming) { + for (let i = 0; i < incoming.length; i++) { + self.onMessage({ method: 'data', data: incoming[i] }); + } + }); + + self.serialPort.on('close', function (data) { + self.logit('serialPort.on close'); + self.onMessage({ method: 'close', data: data }); + + for (let i = 0; i < self.messageListeners.length; i++) { + let serialIndex = self.messageListeners[ + i + ].serialPortsList.indexOf(self.serialPortName); + + console.log( + 'need to take out ' + + self.serialPortName + + ' from client at index ' + + serialIndex, + ); + + self.messageListeners[i].serialPorts.splice(serialIndex, 1); + self.messageListeners[i].serialPortsList.splice( + serialIndex, + 1, + ); + } + + self.closeSerial(); + }); + + self.serialPort.on('error', function (data) { + self.logit('serialPort.on error ' + data, true); + self.onMessage({ method: 'error', data: data }); + }); + + self.serialPort.open(function (err) { + self.logit('serialPort.open'); + + if (err) { + console.log(err); + self.onMessage({ + method: 'error', + data: "Couldn't open port: " + this.serialport, + }); + } else { + self.onMessage({ method: 'openserial', data: {} }); + } + }); + } + + /** + * closes the serialport connection and sends message to the connected clients that the serialport is closed. + * */ + closeSerial() { + let self = this; + + //need to emit back to client that this port was forcefully closed + + self.logit(`closeSerial for ${self.serialPortName}`); + + if ( + self.serialPort != null && + typeof self.serialPort === 'object' && + self.serialPort.isOpen + ) { + self.logit('serialPort != null && serialPort.isOpen so close'); + self.logit('serialPort.flush, drain, close'); + + self.serialPort.flush(); + self.serialPort.drain(); + self.serialPort.close(function (error) { + if (error) { + self.onMessage({ method: 'error', data: error }); + console.log(error); + } + }); + + self.onMessage({ + method: 'close', + data: `${self.serialPort} is closed`, + }); + + self.serialPort = null; + } + + // if (self.serialPort != null && typeof self.serialPort === "object" && self.serialPort.isOpen) { + // self.logit("serialPort != null && serialPort.isOpen is true so serialPort = null"); + // + // self.serialPort = null; + // } + + self.logit('serialPort closed'); + } + + /** + * console.log log messages when LOGGING == true + * @function logit + * @param {String} mess - String to log when LOGGING == true*/ + logit(mess) { + if (this.LOGGING) { + console.log(mess); + } + } +} + +module.exports = SerialPort;