modbus-connect
Version:
Modbus RTU over Web Serial and Node.js SerialPort
132 lines (117 loc) • 3.64 kB
JavaScript
// transport/web-serialport.js
const logger = require("../logger.js");
class WebSerialTransport {
constructor(port, options = {}) {
this.port = port;
this.options = {
baudRate: 9600,
dataBits: 8,
stopBits: 1,
parity: 'none',
readTimeout: 1000,
writeTimeout: 1000,
...options
};
this.reader = null;
this.writer = null;
this.readBuffer = new Uint8Array(0);
}
async connect() {
try {
await this.port.open({
baudRate: this.options.baudRate,
dataBits: this.options.dataBits,
stopBits: this.options.stopBits,
parity: this.options.parity,
flowControl: 'none',
});
const readable = this.port.readable;
const writable = this.port.writable;
if (!readable || !writable) {
logger.error('WebSerial port not readable/writable');
throw new Error('Serial port not readable/writable');
}
this.reader = readable.getReader();
this.writer = writable.getWriter();
this._startReading();
logger.info('WebSerial port opened');
} catch (err) {
logger.error(`Failed to open WebSerial port: ${err.message}`);
throw err;
}
}
_startReading() {
const loop = async () => {
try {
while (true) {
const { value, done } = await this.reader.read();
if (done) break;
if (value) {
const newBuffer = new Uint8Array(this.readBuffer.length + value.length);
newBuffer.set(this.readBuffer, 0);
newBuffer.set(value, this.readBuffer.length);
this.readBuffer = newBuffer;
}
}
} catch (err) {
logger.warn(`Read loop error: ${err.message}`);
}
};
loop();
}
async write(buffer) {
const timeout = this.options.writeTimeout;
const abort = new AbortController();
const timer = setTimeout(() => abort.abort(), timeout);
try {
await this.writer.write(buffer);
logger.debug(`Wrote ${buffer.length} bytes to WebSerial port`);
} catch (err) {
logger.warn(`Write timeout on WebSerial port`);
throw new Error('Write timeout');
} finally {
clearTimeout(timer);
}
}
async read(length, timeout = this.options.readTimeout) {
const start = Date.now();
return new Promise((resolve, reject) => {
const check = () => {
if (this.readBuffer.length >= length) {
const data = this.readBuffer.slice(0, length);
this.readBuffer = this.readBuffer.slice(length);
logger.debug(`Read ${length} bytes from WebSerial port`);
return resolve(data);
}
if (Date.now() - start > timeout) {
logger.warn(`Read timeout on WebSerial port`);
return reject(new Error('Read timeout'));
}
setTimeout(check, 10);
};
check();
});
}
async disconnect() {
try {
if (this.reader) {
try {
await this.reader.cancel();
} catch (err) {
logger.warn(`Error when cancel(): ${err.message}`);
}
this.reader.releaseLock();
this.reader = null;
}
if (this.writer) {
this.writer.releaseLock();
this.writer = null;
}
await this.port.close();
logger.info('Disconnected from WebSerial port');
} catch (err) {
logger.error(`Shutdown error on WebSerial: ${err.message}`);
}
}
}
module.exports = { WebSerialTransport }