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
277 lines (242 loc) • 9.2 kB
JavaScript
module.exports = function (RED) {
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
const tcpPort = require('./lib/tcp')
const SerialConnection = require('./lib/serial')
const handleTcp = require('./lib/handleTcp')
const {
ClimateObjToSerivce,
SwitchObjToSerivce,
PanelObjToSerivce,
LightObjToSerivce,
CurtainObjToSerivce,
VentilationObjToSerivce,
SceneObjToSerivce
} = require('./lib/toHaService')
let globalContext = null
const nodeStatus = {
yellow: { fill: "yellow", shape: "ring", text: "node-red:common.status.connecting" },
red: { fill: "red", shape: "ring", text: "node-red:common.status.disconnected" },
green: { fill: "green", shape: "dot", text: "node-red:common.status.connected" }
}
function symiConfig(n) {
RED.nodes.createNode(this, n);
/**
* Symi设备连接配置
*/
globalContext = this.context().global
this.connectionType = n.connectionType || 'tcp'
this.host = n.host || '127.0.0.1'
this.port = n.port || 8666
this.serialPort = n.serialPort || '/dev/ttyUSB0'
this.baudRate = n.baudRate || 9600
this.dataBits = n.dataBits || 8
this.stopBits = n.stopBits || 1
this.parity = n.parity || 'none'
this.nodes = [] //设备实例
this.handleTcp = {
'hasskit': null,
'clowire': null,
'alive': null,
'lf': null,
'symi': null,
getObjToService: function(domain){
switch(domain) {
case 'climate':
return ClimateObjToSerivce
case 'panel':
return PanelObjToSerivce
case 'light':
return LightObjToSerivce
case 'hvac':
return ClimateObjToSerivce
case 'curtain':
return CurtainObjToSerivce
case 'heating':
return ClimateObjToSerivce
case 'ventilation':
return VentilationObjToSerivce
case 'scene':
return SceneObjToSerivce
default:
return SwitchObjToSerivce
}
}
}
//处理通信对象集合
if (this.connectionType === 'serial') {
this.serialClient = new SerialConnection({
path: this.serialPort,
baudRate: this.baudRate,
dataBits: this.dataBits,
stopBits: this.stopBits,
parity: this.parity
})
this.communicationClient = this.serialClient
} else {
this.tcpClient = new tcpPort(this.host, this.port, this)
this.communicationClient = this.tcpClient
}
this.on('error', function (e) {
console.log(e)
})
this.repeatSend = 0
let self = this
this.on('close', function (done) {
self.nodes = []
clearInterval(self.repeatSend)
if (self.connectionType === 'serial') {
self.serialClient.disconnect().then(() => done())
} else {
self.tcpClient.close(function () {
done()
})
}
});
// 设置连接事件处理
if (this.connectionType === 'serial') {
this.serialClient.on('disconnect', function () {
self.nodes.forEach(it => {
it.status(nodeStatus.red);
})
})
this.serialClient.on('connect', function () {
self.nodes.forEach(it => {
it.status(nodeStatus.green);
})
console.log("串口连接成功")
})
this.serialClient.on('error', function () {
console.log("串口连接错误")
self.nodes.forEach(it => {
it.status(nodeStatus.red);
})
})
} else {
this.tcpClient.onClose = function () {
self.nodes.forEach(it => {
it.status(nodeStatus.red);
})
}
this.tcpClient.onConnect = function () {
self.nodes.forEach(it => {
it.status(nodeStatus.green);
})
console.log("TCP连接成功")
}
this.tcpClient.onError = function () {
console.log("TCP连接错误")
self.nodes.forEach(it => {
it.status(nodeStatus.red);
})
}
}
this.register = function (cp) {
if (!this.handleTcp[cp.devtype]) {
this.handleTcp[cp.devtype] = new handleTcp(this.communicationClient, cp.devtype)
this.handleTcp[cp.devtype].onData = function (data) {
self.nodes.forEach(it => {
if (it.devtype === cp.devtype)
it.ondata(data)
})
}
}
if (this.nodes.indexOf(cp) == -1) {
cp.status(nodeStatus.yellow);
this.nodes.push(cp)
}
}
// 启动连接
if (this.connectionType === 'serial') {
this.serialClient.connect().catch(err => {
console.error('串口连接失败:', err)
})
} else {
this.tcpClient.connect()
}
}
RED.nodes.registerType("symi config", symiConfig);
function ceckInputData(msg, domain) {
if (typeof msg.payload !== 'object') return
if (msg.payload.event_type !== 'state_changed') return
if (!msg.payload.event.new_state) return
const curDomain = msg.payload.entity_id.split('.')[0]
if(curDomain === 'light') return true
if (domain === curDomain ) return true
}
function boardInit(n, domain){
RED.nodes.createNode(this, n);
this.server = RED.nodes.getNode(n.server);
this.uid = parseInt(n.uid)
this.address = parseInt(n.address) || 0
this.channel = parseInt(n.channel) || 0
this.devtype = n.devtype
this.entity_id = n.entity_id
let node = this
if (this.server) {
this.server.register(this)
const buf2obj = node.server.handleTcp[n.devtype].getBufToObj(domain)
const obj2buf = node.server.handleTcp[n.devtype].getObjToBuf(domain)
const ObjToService = node.server.handleTcp.getObjToService(domain)
//收到消息
this.ondata = function (data) {
let obj = buf2obj(data, node)
if (!obj) return
console.log(obj)
obj = ObjToService(obj)//转成HA服务数据
if (!obj) return
node.send({ call_service: obj.call_service, payload: obj.data })
}
node.on('input', function (msg) {
if (!ceckInputData(msg, domain)) return
let buf = obj2buf(msg.payload.event.new_state, node)
if (buf) {
if(Array.isArray(buf[0]))
for (let i of buf)
node.server.handleTcp[n.devtype].write_lock(i)
else node.server.handleTcp[n.devtype].write_lock(buf)
}
})
} else {
node.status(nodeStatus.red);
}
}
function climateBoard(n) {
boardInit.call(this, n, 'climate')
}
RED.nodes.registerType("climate board", climateBoard);
function switchBoard(n) {
boardInit.call(this, n, 'switch')
}
RED.nodes.registerType("switch board", switchBoard);
function lightBoard(n) {
boardInit.call(this, n, 'light')
}
RED.nodes.registerType("light board", lightBoard);
function hvacBoard(n) {
boardInit.call(this, n, 'hvac')
}
RED.nodes.registerType("hvac board", hvacBoard);
function curtainBoard(n) {
boardInit.call(this, n, 'curtain')
}
RED.nodes.registerType("curtain board", curtainBoard);
function heatingBoard(n) {
boardInit.call(this, n, 'heating')
}
RED.nodes.registerType("heating board", heatingBoard);
function ventilationBoard(n) {
boardInit.call(this, n, 'ventilation')
}
RED.nodes.registerType("ventilation board", ventilationBoard);
function sceneBoard(n) {
boardInit.call(this, n, 'scene')
}
RED.nodes.registerType("scene board", sceneBoard);
function panelBoard(n) {
boardInit.call(this, n, 'panel')
}
RED.nodes.registerType("panel board", panelBoard);
RED.httpAdmin.get("/GetHaDevList", function (req, res) {
res.json({ data: globalContext.get('homeassistant') })
});
}