UNPKG

modbus-connect

Version:

Modbus RTU over Web Serial and Node.js SerialPort

131 lines (117 loc) 4.5 kB
// transport/web-transports/web-tcp-serialport.js class WebTcpSerialTransport { constructor(host, options = {}) { this.host = host; this.portNumber = options.port || 80; // Порт для WebSocket, по умолчанию 80 this.url = `ws://${host}:${this.portNumber}`; this.options = { readTimeout: 1000, writeTimeout: 1000, reconnectInterval: 3000, maxReconnectAttempts: Infinity, ...options }; this.socket = null; this.readBuffer = new Uint8Array(0); this.isOpen = false; this._reconnectAttempts = 0; this._shouldReconnect = true; } async connect() { return new Promise((resolve, reject) => { try { this.socket = new WebSocket(this.url); this.socket.binaryType = 'arraybuffer'; // Устанавливаем бинарный тип данных this.socket.onopen = () => { this.isOpen = true; this._reconnectAttempts = 0; console.info(`WebSocket connection to ${this.url} established`); resolve(); }; this.socket.onmessage = (event) => { const data = new Uint8Array(event.data); const newBuffer = new Uint8Array(this.readBuffer.length + data.length); newBuffer.set(this.readBuffer, 0); newBuffer.set(data, this.readBuffer.length); this.readBuffer = newBuffer; }; this.socket.onerror = (err) => { console.error(`WebSocket error ${this.url}: ${err.message || 'Unknown error'}`); reject(new Error('WebSocket connection failed')); }; this.socket.onclose = () => { console.warn(`WebSocket connection closed ${this.url}`); this.isOpen = false; if (this._shouldReconnect) { this._scheduleReconnect(); } }; } catch (err) { console.error(`Failed to open WebSocket: ${err.message}`); reject(err); } }); } _scheduleReconnect() { if (this._reconnectAttempts >= this.options.maxReconnectAttempts) { console.error(`Max reconnect attempts reached for WebSocket ${this.url}`); return; } this._reconnectAttempts++; console.info(`Reconnecting to WebSocket ${this.url} in ${this.options.reconnectInterval} ms`); setTimeout(() => { this.connect().catch((err) => { console.warn(`Reconnect attempt failed: ${err.message}`); }); }, this.options.reconnectInterval); } async write(buffer) { if (!this.isOpen) { console.error('WebSocket is closed'); throw new Error('WebSocket is closed'); } const timeout = this.options.writeTimeout; const abort = new AbortController(); const timer = setTimeout(() => abort.abort(), timeout); try { this.socket.send(buffer); console.debug(`Wrote ${buffer.length} bytes to WebSocket ${this.url}`); } catch (err) { console.warn(`Write timeout on WebSocket ${this.url}`); 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); console.debug(`Read ${length} bytes from WebSocket ${this.url}`); return resolve(data); } if (Date.now() - start > timeout) { console.warn(`Read timeout on WebSocket ${this.url}`); return reject(new Error('Read timeout')); } setTimeout(check, 10); }; check(); }); } async disconnect() { this._shouldReconnect = false; if (!this.isOpen) return; try { this.socket.close(); this.isOpen = false; console.info(`Disconnected from WebSocket ${this.url}`); } catch (err) { console.error(`Shutdown error on WebSocket: ${err.message}`); } } } module.exports = { WebTcpSerialTransport };