'use strict';
const SerialPort = require('serialport');
const Transport = require('../classes/transport');
const SafeBuffer = require('safe-buffer').Buffer;
const _isNil = require('lodash/isNil');
/**
* @class
* @extends Transport
*/
class Serial extends Transport {
/**
* @constructor
* @description A transport for data that involves reading and writing to a
serial port
* @param {Object} opts General settings for the Serial Transport
* @param {Object/String} opts.port The serial device to open
* @param {String} [opts.port.product] The serial device's product id
* @param {String} [opts.port.vendor] The serial device's vendor id
* @param {Number} opts.baudRate
* @param {Number} opts.dataBits
* @param {Number} opts.stopBits
* @param {String} opts.parity
* @param {Object} [opts.parser] Optional serial parser
* @param {Adapter} adapter Adapter for which this is a plugin
*/
constructor(opts, adapter) {
super(opts, adapter);
this._open = false;
this._parser = opts.parser;
this._format = this._adapter.opts.format;
}
/**
* @description init Configure, open, and setup the serial port
* @private
* @return {Promise}
*/
init() {
return new Promise((resolve, reject) => {
this.getSerial().then((port) => {
let options = {
baudRate: this._opts.baudRate,
autoOpen: false
};
this._adapter.k4.logging.debug('Serial transport opening:', port);
this._adapter.k4.logging.debug('Serial transport options:', options);
// optional flow control opts
if (typeof this._opts.dataBits == 'number') {
options.dataBits = this._opts.dataBits;
}
if (typeof this._opts.stopBits == 'number') {
options.stopBits = this._opts.stopBits;
}
if (typeof this._opts.parity == 'string') {
options.parity = this._opts.parity;
}
this._port = new SerialPort(port, options);
// if a parser is specified, assign data event to it
if (this._parser) {
this._port.pipe(this._parser);
this._parser.on('data', (data) => {
this.received(data);
});
} else {
this._port.on('data', (data) => {
this.received(data);
});
}
this._port.on('error', (error) => {
this._adapter.k4.logging.fatal('Serial transport error', error.message);
});
// resolve promise when port is open
this._port.open((error) => {
if (error) {
reject(error);
} else {
this._open = true;
this._adapter.k4.logging.debug('Serial transport open');
resolve();
}
});
}).catch((e) => {
reject(e);
});
});
}
/**
* @description Get the correct path for the serial port
* @private
* @return {Promise}
*/
getSerial() {
return new Promise((resolve, reject) => {
if (typeof this._opts.port == 'object') {
let product = this._opts.port.product;
let vendor = this._opts.port.vendor;
let udev = require('udev');
let devices = udev.list('tty');
let found;
for (let i=0; i < devices.length; i++) {
let device = devices[i];
if (device.ID_MODEL_ID == product && device.ID_VENDOR_ID == vendor && device.SUBSYSTEM == 'tty') {
found = device.DEVNAME;
break;
}
}
if (found) {
resolve(found);
} else {
reject(new Error('Serial port not found'));
}
} else {
resolve(this._opts.port);
}
});
}
/**
* @description Send a command to the serial port
* @param {Object} address Not used by serial
* @param {Command} command The command to send
*/
send(address, command) {
if (!this._open) {
this._adapter.k4.logging.fatal('Serial transport send: port not open');
return;
}
const body = command.body;
let headerBuf;
if (!SafeBuffer.isBuffer(command.header) &&
!_isNil(command) &&
!_isNil(command.header) &&
!_isNil(command.header.packet)) {
headerBuf = command.header.packet;
}
if (_isNil(headerBuf)) {
headerBuf = SafeBuffer.from([]);
}
if (_isNil(body)) {
return Promise.reject(new Error(`There was no body supplied for command: ${command.name}`));
}
return this.write(SafeBuffer.concat([headerBuf, body]));
}
/**
* @description Write data to the serial port
* @param {Buffer|Buffer[]} data The data buffer or the Array of data
* buffers for a multipart message
* @returns {Promise} Resolves after the data in the first Buffer has been
* written to the serial port
* @private
*/
write(data) {
let dataToWrite = data;
if (Array.isArray(data)) {
dataToWrite = data.shift();
this._multipart = (data.length > 0) ? data : null;
}
this._adapter.k4.logging.debug('Serial transport send', dataToWrite.toString(this._format));
return new Promise((resolve, reject) => {
this._port.write(dataToWrite, (err, bytesWritten) => {
if (err) {
reject(err);
} else {
resolve(bytesWritten);
}
});
});
}
/**
* @description Process incoming data
* @param {Buffer} data The data buffer
* @private
*/
received(data) {
this._adapter.k4.logging.debug('Serial transport received', data.toString(this._format));
if (this._multipart && data.toString() == this._opts.multipartDelimiter) {
this.write(this._multipart)
.catch(e => this._adapter.k4.logging.error(`Error writing multipart data: ${JSON.stringify(this._multipart)}`, e instanceof Error ? e.stack.split('\n') : e.toString()));
} else {
this._adapter.received(this._address, data);
}
}
/**
* @description Close the listener
*/
close() {
return new Promise((resolve, reject) => {
this._port.close((err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
};
module.exports = Serial;