node-red-contrib-symi
Version:
Node-RED nodes for smart home device communication with Home Assistant integration, supporting HassKit, Clowire, Alive, LF and complete Symi protocols
196 lines (173 loc) • 4.83 kB
JavaScript
"use strict";
var net = require("net");
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
const Lock = require('async-lock')
/**
* TCP连接端口类
* @param {string} ip - ip地址
* @param {number} port - 端口号
* @param {object} node - Node-RED节点实例
* @param {number} timeout - 超时时间
* @constructor
*/
class TcpBufferedPort {
constructor(ip, port, node, timeout) {
this.openFlag = false;
this.callback = null;
this.timeout = timeout
this.heartbeatInterval = 0
this.heartbeatTime = 120000 //心跳间隔
this.heartbeatPack = Buffer.from([0x00, 0x00, 0x00, 0x00, 0x00]) //心跳包
this._client = null
this._onData = []
this._onError = null
this._onConnect = null
this._onClose = null
this.node = node
this.reconnectTime = 0
this.lock = new Lock()
this.connectOptions = {
host: ip || "",
port: port || 8666,
};
}
get isOpen() {
return this.openFlag;
}
/**
* 消息回调
*/
set onData(cb) {
this._onData.push(cb)
}
set onClose(cb) {
this._onClose = cb
}
set onConnect(cb) {
this._onConnect = cb
}
set onError(cb) {
this._onError = cb
}
write_lock(data) {
let self = this
self.lock.acquire('key', function (done) {
self.write(data, function(){
setTimeout(done, 25);
})
}, function () { })
}
_listener() {
var self = this;
this._client = new net.Socket();
var handleCallback = function (had_error) {
if (self.callback) {
self.callback(had_error);
self.callback = null;
}
};
this._client.on('data', function (data) {
for(let it of self._onData){
it[0].call(it[1], data)
}
})
if (this.timeout) this._client.setTimeout(this.timeout);
this._client.on("connect", function () {
self.openFlag = true;
handleCallback();
if (self._onConnect)
self._onConnect()
});
this._client.on("close", function (had_error) {
self.openFlag = false;
handleCallback(had_error);
if (self._onClose)
self._onClose()
});
this._client.on("error", function (had_error) {
self.openFlag = false;
handleCallback(had_error);
if (self._onError)
self._onError()
});
this._client.on("timeout", function () {
handleCallback(new Error("TCP连接超时"));
});
}
/**
* TCP心跳方法
*/
_heartbeat() {
let self = this
this.heartbeatInterval = setInterval(() => {
self.write(self.heartbeatPack)
}, this.heartbeatTime);
}
/**
* 重连方法
*/
async reconnect() {
let self = this
this.close()
await sleep(1000)
if (!self.isOpen)
self.connect()
}
connect() {
let self = this
this._listener()
this.open(function (err) {
if (!err) {
self.node.log('TCP连接成功')
self._heartbeat()//心跳启动
} else {
self.node.error("TCP连接错误")
self.reconnectTime = setTimeout(() => {
self.close()
self.connect()
}, 10000)
}
})
}
/**
* 打开端口连接
*/
open(callback) {
if (this.openFlag) {
console.log("TCP端口已打开");
callback(); // 继续使用现有socket
} else {
this.callback = callback;
this._client.connect(this.connectOptions);
}
}
close(callback) {
this.callback = callback;
this._client.end(callback);
if (this.heartbeatInterval)
clearInterval(this.heartbeatInterval)
if (this.reconnectTime)
clearTimeout(this.reconnectTime)
}
destroy(callback) {
this.callback = callback;
if (!this._client.destroyed) {
this._client.destroy();
}
};
write(data, cb) {
let self = this
this._client.write(data, function (err) {
if (err) {
console.log(err)
if (cb)
cb(true)
self.reconnect()
} else {
if (cb)
cb()
}
});
};
}
module.exports = TcpBufferedPort;