homebridge-dooyashade
Version:
Dooya Shade RS485 TO TCP
174 lines • 5.2 kB
JavaScript
import { Socket } from 'net';
export class TCPManager {
host;
port;
log;
onData;
onConnect;
onDisconnect;
sendIntervalMs;
socket = null;
reconnectTimer = null;
keepAliveTimer = null;
isConnecting = false;
isConnected = false;
sendQueue = [];
queueTimer = null;
constructor(host, port, log, onData, onConnect, onDisconnect, sendIntervalMs = 500) {
this.host = host;
this.port = port;
this.log = log;
this.onData = onData;
this.onConnect = onConnect;
this.onDisconnect = onDisconnect;
this.sendIntervalMs = sendIntervalMs;
}
connect() {
if (this.isConnecting || this.socket?.connecting) {
return;
}
this.isConnecting = true;
this.socket = new Socket();
this.socket.on('connect', () => {
this.log.info(`Connected to ${this.host}:${this.port}`);
this.isConnecting = false;
this.isConnected = true;
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
this.reconnectTimer = null;
}
this.startKeepAlive();
this.onConnect();
this.processQueue();
});
this.socket.on('data', (data) => {
this.onData(data);
});
this.socket.on('error', (error) => {
this.log.error('Socket error:', error);
});
this.socket.on('close', () => {
this.log.warn('Connection closed');
this.isConnecting = false;
this.isConnected = false;
if (this.socket) {
this.socket.destroy();
this.socket = null;
}
if (this.keepAliveTimer) {
clearInterval(this.keepAliveTimer);
this.keepAliveTimer = null;
}
if (this.queueTimer) {
clearTimeout(this.queueTimer);
this.queueTimer = null;
}
this.onDisconnect();
this.scheduleReconnect();
});
this.socket.connect(this.port, this.host);
}
scheduleReconnect() {
if (!this.reconnectTimer) {
this.reconnectTimer = setTimeout(() => {
this.connect();
}, 60000); // 60秒后重连
}
}
startKeepAlive() {
if (!this.keepAliveTimer) {
this.keepAliveTimer = setInterval(() => {
this.checkConnection();
}, 30000); // 30秒检查一次连接
}
}
checkConnection() {
if (!this.socket?.writable || !this.isConnected) {
this.log.warn('Connection lost, reconnecting...');
if (this.socket) {
this.socket.destroy();
this.socket = null;
}
this.isConnected = false;
this.onDisconnect();
this.scheduleReconnect();
}
}
processQueue() {
if (this.queueTimer) {
return;
}
if (this.sendQueue.length === 0) {
return;
}
if (!this.socket?.writable || !this.isConnected) {
return;
}
const data = this.sendQueue.shift();
try {
this.socket.write(data);
}
catch (error) {
this.log.error('Failed to send data:', error);
}
this.queueTimer = setTimeout(() => {
this.queueTimer = null;
this.processQueue();
}, this.sendIntervalMs);
}
send(data) {
this.sendQueue.push(data);
this.processQueue();
return true;
}
disconnect() {
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
this.reconnectTimer = null;
}
if (this.keepAliveTimer) {
clearInterval(this.keepAliveTimer);
this.keepAliveTimer = null;
}
if (this.socket) {
this.socket.destroy();
this.socket = null;
}
}
}
export class CRC16 {
static POLYNOMIAL = 0xA001;
static calculate(data) {
let crc = 0xFFFF;
for (let i = 0; i < data.length; i++) {
crc ^= data[i];
for (let j = 0; j < 8; j++) {
if ((crc & 0x0001) !== 0) {
crc = (crc >> 1) ^ this.POLYNOMIAL;
}
else {
crc = crc >> 1;
}
}
}
// 高低位交换
return ((crc & 0xFF) << 8) | ((crc >> 8) & 0xFF);
}
static verify(data) {
if (data.length < 3) {
return false;
}
const messageData = data.slice(0, -2);
const receivedCrc = data.readUInt16BE(data.length - 2);
const calculatedCrc = this.calculate(messageData);
return receivedCrc === calculatedCrc;
}
static appendCRC(data) {
const crc = this.calculate(data);
const result = Buffer.alloc(data.length + 2);
data.copy(result);
result.writeUInt16BE(crc, data.length);
return result;
}
}
//# sourceMappingURL=utils.js.map