node-red-contrib-symi-mesh 1.7.1 → 1.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +274 -20
- package/examples/knx-sync-example.json +122 -58
- package/examples/rs485-sync-example.json +76 -0
- package/lib/device-manager.js +96 -51
- package/lib/serial-client.js +23 -4
- package/nodes/rs485-debug.html +2 -1
- package/nodes/symi-485-bridge.html +233 -32
- package/nodes/symi-485-bridge.js +874 -98
- package/nodes/symi-485-config.html +44 -21
- package/nodes/symi-485-config.js +49 -11
- package/nodes/symi-cloud-sync.html +2 -0
- package/nodes/symi-device.html +5 -3
- package/nodes/symi-gateway.html +49 -1
- package/nodes/symi-gateway.js +43 -3
- package/nodes/symi-knx-bridge.html +3 -2
- package/nodes/symi-knx-bridge.js +3 -3
- package/nodes/symi-knx-ha-bridge.html +4 -3
- package/nodes/symi-knx-ha-bridge.js +2 -2
- package/nodes/symi-mqtt-brand.html +75 -0
- package/nodes/symi-mqtt-brand.js +238 -0
- package/nodes/symi-mqtt-sync.html +381 -0
- package/nodes/symi-mqtt-sync.js +473 -0
- package/nodes/symi-rs485-sync.html +361 -0
- package/nodes/symi-rs485-sync.js +765 -0
- package/package.json +5 -2
|
@@ -29,24 +29,45 @@
|
|
|
29
29
|
}
|
|
30
30
|
}).trigger('change');
|
|
31
31
|
|
|
32
|
-
//
|
|
33
|
-
function
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
32
|
+
// 串口搜索
|
|
33
|
+
$('#btn-refresh-ports').on('click', function() {
|
|
34
|
+
var $btn = $(this);
|
|
35
|
+
var $select = $('#serial-port-select');
|
|
36
|
+
$btn.prop('disabled', true);
|
|
37
|
+
$select.empty().append('<option value="">搜索中...</option>').show();
|
|
38
|
+
|
|
39
|
+
$.getJSON('/symi-gateway/serial-ports', function(ports) {
|
|
40
|
+
$select.empty();
|
|
41
|
+
if (ports && ports.length > 0) {
|
|
42
|
+
$select.append('<option value="">-- 选择串口 --</option>');
|
|
43
|
+
ports.forEach(function(p) {
|
|
44
|
+
var label = p.path;
|
|
45
|
+
if (p.manufacturer) label += ' (' + p.manufacturer + ')';
|
|
46
|
+
var sel = (p.path === node.serialPort) ? ' selected' : '';
|
|
47
|
+
$select.append('<option value="' + p.path + '"' + sel + '>' + label + '</option>');
|
|
48
|
+
});
|
|
49
|
+
} else {
|
|
50
|
+
$select.append('<option value="">未发现串口</option>');
|
|
51
|
+
}
|
|
52
|
+
$btn.prop('disabled', false);
|
|
53
|
+
}).fail(function() {
|
|
54
|
+
$select.empty().append('<option value="">搜索失败</option>');
|
|
55
|
+
$btn.prop('disabled', false);
|
|
45
56
|
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// 串口选择
|
|
60
|
+
$('#serial-port-select').on('change', function() {
|
|
61
|
+
var val = $(this).val();
|
|
62
|
+
if (val) {
|
|
63
|
+
$('#node-config-input-serialPort').val(val);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// 初始化时如果已有串口配置,显示在下拉框
|
|
68
|
+
if (node.serialPort) {
|
|
69
|
+
$('#serial-port-select').append('<option value="' + node.serialPort + '" selected>' + node.serialPort + '</option>').show();
|
|
46
70
|
}
|
|
47
|
-
|
|
48
|
-
$('#btn-refresh-ports').on('click', loadSerialPorts);
|
|
49
|
-
loadSerialPorts();
|
|
50
71
|
}
|
|
51
72
|
});
|
|
52
73
|
</script>
|
|
@@ -67,11 +88,13 @@
|
|
|
67
88
|
|
|
68
89
|
<div class="serial-config">
|
|
69
90
|
<div class="form-row">
|
|
70
|
-
<label for="node-config-input-serialPort"><i class="fa fa-usb"></i>
|
|
71
|
-
<
|
|
72
|
-
<button type="button" id="btn-refresh-ports" class="red-ui-button" style="margin-left:5px">
|
|
73
|
-
|
|
74
|
-
|
|
91
|
+
<label for="node-config-input-serialPort"><i class="fa fa-usb"></i> 串口路径</label>
|
|
92
|
+
<input type="text" id="node-config-input-serialPort" placeholder="/dev/ttyUSB0 或 COM3" style="width:50%;">
|
|
93
|
+
<button type="button" id="btn-refresh-ports" class="red-ui-button" style="margin-left:5px">搜索串口</button>
|
|
94
|
+
</div>
|
|
95
|
+
<div class="form-row">
|
|
96
|
+
<label> </label>
|
|
97
|
+
<select id="serial-port-select" style="width:70%;display:none;"></select>
|
|
75
98
|
</div>
|
|
76
99
|
|
|
77
100
|
<div class="form-row">
|
package/nodes/symi-485-config.js
CHANGED
|
@@ -159,7 +159,7 @@ module.exports = function(RED) {
|
|
|
159
159
|
node.client.on('error', (err) => {
|
|
160
160
|
// AggregateError特殊处理(Node.js 18+的IPv4/IPv6连接失败)
|
|
161
161
|
if (err.name === 'AggregateError' || err.errors) {
|
|
162
|
-
node.
|
|
162
|
+
node.debug(`RS485 TCP连接失败: 无法连接到 ${node.host}:${node.port}`);
|
|
163
163
|
} else {
|
|
164
164
|
node.error(`RS485 TCP错误: ${err.message}`);
|
|
165
165
|
}
|
|
@@ -213,18 +213,20 @@ module.exports = function(RED) {
|
|
|
213
213
|
node.connected = false;
|
|
214
214
|
};
|
|
215
215
|
|
|
216
|
-
// 处理接收数据(支持Modbus RTU
|
|
216
|
+
// 处理接收数据(支持Modbus RTU、杜亚协议和自定义码)
|
|
217
217
|
node.handleData = function(data) {
|
|
218
|
+
const hexIn = data.toString('hex').toUpperCase().match(/.{1,2}/g)?.join(' ') || '';
|
|
219
|
+
node.debug(`[RS485 RX] 收到数据: ${hexIn} (${data.length}字节)`);
|
|
218
220
|
node.receiveBuffer = Buffer.concat([node.receiveBuffer, data]);
|
|
219
221
|
|
|
220
222
|
// 防止缓冲区过大(最大8KB)
|
|
221
223
|
if (node.receiveBuffer.length > 8192) {
|
|
222
224
|
node.receiveBuffer = node.receiveBuffer.subarray(-2048);
|
|
223
|
-
node.
|
|
225
|
+
node.debug('接收缓冲区溢出,已截断');
|
|
224
226
|
}
|
|
225
227
|
|
|
226
|
-
// 最小帧长度为
|
|
227
|
-
while (node.receiveBuffer.length >=
|
|
228
|
+
// 最小帧长度为2字节
|
|
229
|
+
while (node.receiveBuffer.length >= 2) {
|
|
228
230
|
// ===== 杜亚协议检测 (55开头) =====
|
|
229
231
|
if (node.receiveBuffer[0] === 0x55 && node.receiveBuffer.length >= 7) {
|
|
230
232
|
// 杜亚窗帘协议:55 [地址高] [地址低] 03 [数据] [CRC16低] [CRC16高]
|
|
@@ -255,16 +257,52 @@ module.exports = function(RED) {
|
|
|
255
257
|
break;
|
|
256
258
|
}
|
|
257
259
|
|
|
260
|
+
// ===== SYMI协议检测 (7E开头7D结尾) =====
|
|
261
|
+
if (node.receiveBuffer[0] === 0x7E) {
|
|
262
|
+
// 查找结束符7D
|
|
263
|
+
let endIdx = -1;
|
|
264
|
+
for (let i = 1; i < node.receiveBuffer.length; i++) {
|
|
265
|
+
if (node.receiveBuffer[i] === 0x7D) {
|
|
266
|
+
endIdx = i;
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (endIdx > 0) {
|
|
271
|
+
const frame = node.receiveBuffer.subarray(0, endIdx + 1);
|
|
272
|
+
node.receiveBuffer = node.receiveBuffer.subarray(endIdx + 1);
|
|
273
|
+
node.emit('frame', frame);
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
// 等待更多数据
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
|
|
258
280
|
// ===== 标准Modbus RTU协议 =====
|
|
259
281
|
const frameLen = node.getFrameLength(node.receiveBuffer);
|
|
260
|
-
if (frameLen
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
282
|
+
if (frameLen > 0 && node.receiveBuffer.length >= frameLen) {
|
|
283
|
+
const frame = node.receiveBuffer.subarray(0, frameLen);
|
|
284
|
+
node.receiveBuffer = node.receiveBuffer.subarray(frameLen);
|
|
285
|
+
|
|
286
|
+
if (node.validateCRC(frame)) {
|
|
287
|
+
node.emit('frame', frame);
|
|
288
|
+
}
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
264
291
|
|
|
265
|
-
|
|
266
|
-
|
|
292
|
+
// 无法识别为标准协议,启动超时处理(用于自定义码)
|
|
293
|
+
if (!node._frameTimeout) {
|
|
294
|
+
node._frameTimeout = setTimeout(() => {
|
|
295
|
+
node._frameTimeout = null;
|
|
296
|
+
if (node.receiveBuffer.length > 0) {
|
|
297
|
+
const frame = Buffer.from(node.receiveBuffer);
|
|
298
|
+
const hexStr = frame.toString('hex').toUpperCase().match(/.{1,2}/g)?.join(' ') || '';
|
|
299
|
+
node.debug(`[RS485] 自定义帧超时发出: ${hexStr} (${frame.length}字节)`);
|
|
300
|
+
node.receiveBuffer = Buffer.alloc(0);
|
|
301
|
+
node.emit('frame', frame);
|
|
302
|
+
}
|
|
303
|
+
}, 50);
|
|
267
304
|
}
|
|
305
|
+
break;
|
|
268
306
|
}
|
|
269
307
|
};
|
|
270
308
|
|
package/nodes/symi-device.html
CHANGED
|
@@ -15,13 +15,15 @@
|
|
|
15
15
|
},
|
|
16
16
|
inputs: 1,
|
|
17
17
|
outputs: 1,
|
|
18
|
-
icon: '
|
|
18
|
+
icon: 'font-awesome/fa-cube',
|
|
19
|
+
align: 'left',
|
|
20
|
+
paletteLabel: '设备控制',
|
|
19
21
|
label: function() {
|
|
20
22
|
if (this.name) return this.name;
|
|
21
23
|
if (this.deviceMac && this.channel > 1) {
|
|
22
|
-
return '
|
|
24
|
+
return '设备控制-' + this.channel + '路';
|
|
23
25
|
}
|
|
24
|
-
return '
|
|
26
|
+
return '设备控制';
|
|
25
27
|
},
|
|
26
28
|
labelStyle: function() {
|
|
27
29
|
return this.name ? 'node_label_italic' : '';
|
package/nodes/symi-gateway.html
CHANGED
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
: `Symi Gateway (${this.serialPort})`);
|
|
16
16
|
},
|
|
17
17
|
oneditprepare: function() {
|
|
18
|
+
var node = this;
|
|
19
|
+
|
|
18
20
|
$('#node-config-input-connectionType').on('change', function() {
|
|
19
21
|
if ($(this).val() === 'tcp') {
|
|
20
22
|
$('.tcp-config').show();
|
|
@@ -24,6 +26,47 @@
|
|
|
24
26
|
$('.serial-config').show();
|
|
25
27
|
}
|
|
26
28
|
}).trigger('change');
|
|
29
|
+
|
|
30
|
+
// 串口自动发现
|
|
31
|
+
$('#serial-discover-btn').on('click', function() {
|
|
32
|
+
var $btn = $(this);
|
|
33
|
+
var $select = $('#serial-port-select');
|
|
34
|
+
$btn.prop('disabled', true).text('搜索中...');
|
|
35
|
+
$select.empty().append('<option value="">搜索中...</option>');
|
|
36
|
+
|
|
37
|
+
$.getJSON('/symi-gateway/serial-ports', function(ports) {
|
|
38
|
+
$select.empty();
|
|
39
|
+
if (ports && ports.length > 0) {
|
|
40
|
+
$select.append('<option value="">-- 选择串口 --</option>');
|
|
41
|
+
ports.forEach(function(port) {
|
|
42
|
+
var label = port.path;
|
|
43
|
+
if (port.manufacturer) label += ' (' + port.manufacturer + ')';
|
|
44
|
+
var selected = (node.serialPort === port.path) ? 'selected' : '';
|
|
45
|
+
$select.append('<option value="' + port.path + '" ' + selected + '>' + label + '</option>');
|
|
46
|
+
});
|
|
47
|
+
$select.show();
|
|
48
|
+
} else {
|
|
49
|
+
$select.append('<option value="">未发现串口</option>');
|
|
50
|
+
}
|
|
51
|
+
$btn.prop('disabled', false).text('搜索串口');
|
|
52
|
+
}).fail(function() {
|
|
53
|
+
$select.empty().append('<option value="">搜索失败</option>');
|
|
54
|
+
$btn.prop('disabled', false).text('搜索串口');
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// 串口选择
|
|
59
|
+
$('#serial-port-select').on('change', function() {
|
|
60
|
+
var val = $(this).val();
|
|
61
|
+
if (val) {
|
|
62
|
+
$('#node-config-input-serialPort').val(val);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// 初始化时如果已有串口配置,显示在下拉框
|
|
67
|
+
if (node.serialPort) {
|
|
68
|
+
$('#serial-port-select').append('<option value="' + node.serialPort + '" selected>' + node.serialPort + '</option>').show();
|
|
69
|
+
}
|
|
27
70
|
}
|
|
28
71
|
});
|
|
29
72
|
</script>
|
|
@@ -54,7 +97,12 @@
|
|
|
54
97
|
|
|
55
98
|
<div class="form-row serial-config">
|
|
56
99
|
<label for="node-config-input-serialPort"><i class="fa fa-usb"></i> 串口路径</label>
|
|
57
|
-
<input type="text" id="node-config-input-serialPort" placeholder="/dev/ttyUSB0 或 COM3">
|
|
100
|
+
<input type="text" id="node-config-input-serialPort" placeholder="/dev/ttyUSB0 或 COM3" style="width:50%;">
|
|
101
|
+
<button type="button" id="serial-discover-btn" class="red-ui-button" style="margin-left:5px;">搜索串口</button>
|
|
102
|
+
</div>
|
|
103
|
+
<div class="form-row serial-config">
|
|
104
|
+
<label> </label>
|
|
105
|
+
<select id="serial-port-select" style="width:70%;display:none;"></select>
|
|
58
106
|
</div>
|
|
59
107
|
|
|
60
108
|
<div class="form-row serial-config">
|
package/nodes/symi-gateway.js
CHANGED
|
@@ -53,6 +53,13 @@ module.exports = function(RED) {
|
|
|
53
53
|
this.deviceListComplete = false;
|
|
54
54
|
this.isQueryingStates = false; // 标记是否正在查询状态
|
|
55
55
|
|
|
56
|
+
// 转发deviceManager的device-state-changed事件(用于0x1C fanMode等直接触发的事件)
|
|
57
|
+
this.deviceManager.on('device-state-changed', (eventData) => {
|
|
58
|
+
if (!this.isQueryingStates) {
|
|
59
|
+
this.emit('device-state-changed', eventData);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
56
63
|
// 状态事件处理队列 - 确保场景执行后的大量状态更新能被正确处理
|
|
57
64
|
this.stateEventQueue = [];
|
|
58
65
|
this.maxQueueSize = 100; // 最大队列大小,防止内存泄漏
|
|
@@ -169,7 +176,7 @@ module.exports = function(RED) {
|
|
|
169
176
|
// 规则:53 B0 后面紧跟的第一个窗帘帧就是真实用户控制
|
|
170
177
|
// 注意:必须用时间戳而不是标记,因为队列中可能有旧帧等待处理
|
|
171
178
|
this.lastB0Time = Date.now();
|
|
172
|
-
this.
|
|
179
|
+
this.debug(`[控制响应] 0xB0: ${frameHex},记录时间戳`)
|
|
173
180
|
} else if (frame.opcode === 0xB4) {
|
|
174
181
|
// 场景控制响应
|
|
175
182
|
if (frame.status === 0) {
|
|
@@ -403,7 +410,7 @@ module.exports = function(RED) {
|
|
|
403
410
|
// 6-8路开关使用0x45,1-4路使用0x02
|
|
404
411
|
const msgType = (device.channels >= 6) ? 0x45 : 0x02;
|
|
405
412
|
queryAttrs = [msgType];
|
|
406
|
-
this.
|
|
413
|
+
this.debug(`查询开关设备: ${device.name} (地址=0x${device.networkAddress.toString(16).toUpperCase()}, 路数=${device.channels}, msgType=0x${msgType.toString(16).toUpperCase()})`);
|
|
407
414
|
} else if (device.deviceType === 9) {
|
|
408
415
|
queryAttrs = [0x02, 0x0E]; // 插卡取电:开关状态、插卡状态
|
|
409
416
|
} else if (device.deviceType === 4 || device.deviceType === 0x18) {
|
|
@@ -487,7 +494,7 @@ module.exports = function(RED) {
|
|
|
487
494
|
const frame = this.protocolHandler.buildDeviceControlFrame(device.networkAddress, 0x10, Buffer.from([0x01]));
|
|
488
495
|
await this.client.sendFrame(frame, 2);
|
|
489
496
|
await this.sleep(50);
|
|
490
|
-
this.
|
|
497
|
+
this.debug(`已启用设备${device.name}的状态上报`);
|
|
491
498
|
} catch(e) {
|
|
492
499
|
this.error(`启用设备${device.name}状态上报失败: ${e.message}`);
|
|
493
500
|
}
|
|
@@ -675,6 +682,39 @@ module.exports = function(RED) {
|
|
|
675
682
|
res.json([]);
|
|
676
683
|
}
|
|
677
684
|
});
|
|
685
|
+
|
|
686
|
+
// 串口自动发现API - 兼容不同版本serialport
|
|
687
|
+
RED.httpAdmin.get('/symi-gateway/serial-ports', async function(req, res) {
|
|
688
|
+
try {
|
|
689
|
+
let ports = [];
|
|
690
|
+
try {
|
|
691
|
+
// 尝试新版API (serialport v10+)
|
|
692
|
+
const { SerialPort } = require('serialport');
|
|
693
|
+
ports = await SerialPort.list();
|
|
694
|
+
} catch (e1) {
|
|
695
|
+
try {
|
|
696
|
+
// 尝试旧版API (serialport v9)
|
|
697
|
+
const SerialPort = require('serialport');
|
|
698
|
+
if (typeof SerialPort.list === 'function') {
|
|
699
|
+
ports = await SerialPort.list();
|
|
700
|
+
}
|
|
701
|
+
} catch (e2) {
|
|
702
|
+
// 静默处理
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
// 过滤常见串口设备
|
|
706
|
+
const filteredPorts = (ports || []).filter(function(p) {
|
|
707
|
+
const path = (p.path || '').toLowerCase();
|
|
708
|
+
// 排除蓝牙和内部设备
|
|
709
|
+
if (path.includes('bluetooth') || path.includes('bt-')) return false;
|
|
710
|
+
// 包含USB、tty、COM端口
|
|
711
|
+
return path.includes('usb') || path.includes('tty') || path.includes('com') || path.includes('serial');
|
|
712
|
+
});
|
|
713
|
+
res.json(filteredPorts);
|
|
714
|
+
} catch (e) {
|
|
715
|
+
res.json([]);
|
|
716
|
+
}
|
|
717
|
+
});
|
|
678
718
|
}
|
|
679
719
|
};
|
|
680
720
|
|
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
inputs: 1,
|
|
12
12
|
outputs: 2,
|
|
13
13
|
outputLabels: ['KNX输出', '调试信息'],
|
|
14
|
-
icon: '
|
|
15
|
-
|
|
14
|
+
icon: 'font-awesome/fa-sitemap',
|
|
15
|
+
align: 'left',
|
|
16
16
|
paletteLabel: 'KNX桥接',
|
|
17
|
+
label: function() { return this.name || 'KNX桥接'; },
|
|
17
18
|
oneditprepare: function() {
|
|
18
19
|
const node = this;
|
|
19
20
|
let mappings = [], devices = [], knxEntities = [];
|
package/nodes/symi-knx-bridge.js
CHANGED
|
@@ -463,7 +463,7 @@ module.exports = function(RED) {
|
|
|
463
463
|
node.queueCommand = function(cmd) {
|
|
464
464
|
// 队列大小限制
|
|
465
465
|
if (node.commandQueue.length >= MAX_QUEUE_SIZE) {
|
|
466
|
-
node.
|
|
466
|
+
node.debug(`[KNX Bridge] 命令队列已满(${MAX_QUEUE_SIZE}),丢弃最旧命令`);
|
|
467
467
|
node.commandQueue.shift();
|
|
468
468
|
}
|
|
469
469
|
|
|
@@ -766,7 +766,7 @@ module.exports = function(RED) {
|
|
|
766
766
|
}
|
|
767
767
|
|
|
768
768
|
if (!meshDevice) {
|
|
769
|
-
node.
|
|
769
|
+
node.debug(`[KNX->Mesh] 未找到Mesh设备: ${meshMac}`);
|
|
770
770
|
return;
|
|
771
771
|
}
|
|
772
772
|
|
|
@@ -1032,7 +1032,7 @@ module.exports = function(RED) {
|
|
|
1032
1032
|
// 收集所有已映射的唯一设备地址
|
|
1033
1033
|
const mappedAddresses = new Set();
|
|
1034
1034
|
for (const mapping of node.mappings) {
|
|
1035
|
-
const mac =
|
|
1035
|
+
const mac = (mapping.meshMac || '').toLowerCase().replace(/:/g, '');
|
|
1036
1036
|
const device = node.gateway.deviceManager.getDeviceByMac(mac);
|
|
1037
1037
|
if (device && device.networkAddress) {
|
|
1038
1038
|
mappedAddresses.add(device.networkAddress);
|
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
inputs: 1,
|
|
12
12
|
outputs: 2,
|
|
13
13
|
outputLabels: ['KNX输出', '调试信息'],
|
|
14
|
-
icon: '
|
|
15
|
-
|
|
16
|
-
paletteLabel: 'KNX
|
|
14
|
+
icon: 'font-awesome/fa-home',
|
|
15
|
+
align: 'left',
|
|
16
|
+
paletteLabel: 'KNX HA桥接',
|
|
17
|
+
label: function() { return this.name || 'KNX HA桥接'; },
|
|
17
18
|
oneditprepare: function() {
|
|
18
19
|
const node = this;
|
|
19
20
|
let mappings = [], haEntities = [], knxEntities = [];
|
|
@@ -318,7 +318,7 @@ module.exports = function(RED) {
|
|
|
318
318
|
node.recordSyncTime('knx-to-ha', key);
|
|
319
319
|
|
|
320
320
|
if (!node.haServer || !node.haServer.credentials) {
|
|
321
|
-
node.
|
|
321
|
+
node.debug('[KNX->HA] HA服务器未配置');
|
|
322
322
|
return;
|
|
323
323
|
}
|
|
324
324
|
|
|
@@ -328,7 +328,7 @@ module.exports = function(RED) {
|
|
|
328
328
|
const token = node.haServer.credentials.access_token;
|
|
329
329
|
|
|
330
330
|
if (!token) {
|
|
331
|
-
node.
|
|
331
|
+
node.debug('[KNX->HA] HA访问令牌未配置');
|
|
332
332
|
return;
|
|
333
333
|
}
|
|
334
334
|
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
RED.nodes.registerType('symi-mqtt-brand', {
|
|
3
|
+
category: 'config',
|
|
4
|
+
defaults: {
|
|
5
|
+
name: { value: '' },
|
|
6
|
+
brand: { value: 'hyqw' },
|
|
7
|
+
mqttBroker: { value: 'mqtt://localhost:1883', required: true },
|
|
8
|
+
mqttUsername: { value: '' },
|
|
9
|
+
mqttPassword: { value: '' },
|
|
10
|
+
projectCode: { value: '', required: true },
|
|
11
|
+
deviceSn: { value: '', required: true }
|
|
12
|
+
},
|
|
13
|
+
label: function() {
|
|
14
|
+
if (this.name) return this.name;
|
|
15
|
+
if (this.projectCode && this.deviceSn) {
|
|
16
|
+
return 'HYQW: ' + this.projectCode;
|
|
17
|
+
}
|
|
18
|
+
return '品牌MQTT';
|
|
19
|
+
},
|
|
20
|
+
oneditprepare: function() {
|
|
21
|
+
var node = this;
|
|
22
|
+
|
|
23
|
+
// 品牌切换
|
|
24
|
+
function updateBrandUI() {
|
|
25
|
+
var brand = $('#node-config-input-brand').val();
|
|
26
|
+
if (brand === 'hyqw') {
|
|
27
|
+
$('.brand-hyqw-config').show();
|
|
28
|
+
} else {
|
|
29
|
+
$('.brand-hyqw-config').hide();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
$('#node-config-input-brand').on('change', updateBrandUI);
|
|
33
|
+
updateBrandUI();
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<script type="text/html" data-template-name="symi-mqtt-brand">
|
|
39
|
+
<div class="form-row">
|
|
40
|
+
<label for="node-config-input-name"><i class="fa fa-tag"></i> 名称</label>
|
|
41
|
+
<input type="text" id="node-config-input-name" placeholder="如:花语前湾MQTT">
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<div class="form-row">
|
|
45
|
+
<label for="node-config-input-brand"><i class="fa fa-plug"></i> 品牌协议</label>
|
|
46
|
+
<select id="node-config-input-brand">
|
|
47
|
+
<option value="hyqw">HYQW (花语前湾)</option>
|
|
48
|
+
</select>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<div class="form-row">
|
|
52
|
+
<label for="node-config-input-mqttBroker"><i class="fa fa-server"></i> MQTT地址</label>
|
|
53
|
+
<input type="text" id="node-config-input-mqttBroker" placeholder="mqtt://192.168.1.100:1883">
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<div class="form-row">
|
|
57
|
+
<label for="node-config-input-mqttUsername"><i class="fa fa-user"></i> 用户名</label>
|
|
58
|
+
<input type="text" id="node-config-input-mqttUsername" placeholder="可选">
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<div class="form-row">
|
|
62
|
+
<label for="node-config-input-mqttPassword"><i class="fa fa-lock"></i> 密码</label>
|
|
63
|
+
<input type="password" id="node-config-input-mqttPassword" placeholder="可选">
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<div class="form-row brand-hyqw-config">
|
|
67
|
+
<label for="node-config-input-projectCode"><i class="fa fa-folder"></i> 项目代码</label>
|
|
68
|
+
<input type="text" id="node-config-input-projectCode" placeholder="如: SH-485-V22">
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<div class="form-row brand-hyqw-config">
|
|
72
|
+
<label for="node-config-input-deviceSn"><i class="fa fa-barcode"></i> 设备SN</label>
|
|
73
|
+
<input type="text" id="node-config-input-deviceSn" placeholder="如: FB485V222024110500000377">
|
|
74
|
+
</div>
|
|
75
|
+
</script>
|