modbus-connect
Version:
Modbus RTU over Web Serial and Node.js SerialPort
131 lines (117 loc) • 4.5 kB
JavaScript
// 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 };