UNPKG

modbus-connect

Version:

Modbus RTU over Web Serial and Node.js SerialPort

126 lines (113 loc) 4.19 kB
const net = require('net'); const { concatUint8Arrays, sliceUint8Array, allocUint8Array, isUint8Array } = require('../utils/utils.js'); const logger = require('../logger.js'); class TcpSerialTransport { constructor(host, options = {}) { this.host = host; this.portNumber = options.port || 23; // стандартный TCP порт this.options = { readTimeout: 100, writeTimeout: 100, reconnectInterval: 3000, maxReconnectAttempts: Infinity, ...options }; this.socket = null; this.readBuffer = allocUint8Array(0); this.isOpen = false; this._reconnectAttempts = 0; this._shouldReconnect = true; } async connect() { return new Promise((resolve, reject) => { this.socket = new net.Socket(); this.socket.connect(this.portNumber, this.host, () => { this.isOpen = true; this._reconnectAttempts = 0; logger.info(`TCP connection to ${this.host}:${this.portNumber} established`); resolve(); }); this.socket.on('data', this._onData.bind(this)); this.socket.on('error', this._onError.bind(this)); this.socket.on('close', this._onClose.bind(this)); }); } _onData(data) { const chunk = new Uint8Array(data.buffer, data.byteOffset, data.byteLength); this.readBuffer = concatUint8Arrays([this.readBuffer, chunk]); } _onError(err) { logger.error(`TCP socket error ${this.host}:${this.portNumber}: ${err.message}`); } _onClose(hadError) { logger.warn(`TCP connection closed ${this.host}:${this.portNumber}`); this.isOpen = false; if (this._shouldReconnect) { this._scheduleReconnect(); } } _scheduleReconnect() { if (this._reconnectAttempts >= this.options.maxReconnectAttempts) { logger.error(`Max reconnect attempts reached for TCP ${this.host}:${this.portNumber}`); return; } this._reconnectAttempts++; logger.info(`Reconnecting to TCP ${this.host}:${this.portNumber} in ${this.options.reconnectInterval} ms`); setTimeout(() => { this.connect().catch(err => { logger.warn(`Reconnect attempt failed: ${err.message}`); }); }, this.options.reconnectInterval); } async write(buffer) { if (!this.isOpen) { throw new Error('TCP socket is closed'); } return new Promise((resolve, reject) => { this.socket.write(buffer, err => { if (err) { logger.error(`Write error on TCP ${this.host}:${this.portNumber}: ${err.message}`); return reject(err); } resolve(); }); }); } async read(length, timeout = this.options.readTimeout) { const start = Date.now(); return new Promise((resolve, reject) => { const checkData = () => { if (this.readBuffer.length >= length) { const data = sliceUint8Array(this.readBuffer, 0, length); this.readBuffer = sliceUint8Array(this.readBuffer, length); logger.debug(`Read ${length} bytes from TCP ${this.host}:${this.portNumber}`); return resolve(data); } if (Date.now() - start > timeout) { logger.warn(`Read timeout on TCP ${this.host}:${this.portNumber}`); return reject(new Error('Read timeout')); } setTimeout(checkData, 10); }; checkData(); }); } async disconnect() { this._shouldReconnect = false; if (!this.isOpen) return; return new Promise((resolve, reject) => { this.socket.end(() => { this.isOpen = false; logger.info(`TCP connection to ${this.host}:${this.portNumber} closed`); resolve(); }); this.socket.destroy(); // ensure it's closed }); } } module.exports = { TcpSerialTransport }