node-red-contrib-symi-modbus 2.2.0 → 2.3.0

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 CHANGED
@@ -2,69 +2,6 @@
2
2
 
3
3
  Node-RED的Modbus继电器控制节点,支持TCP/串口通信和MQTT集成。
4
4
 
5
- > **最新版本 v2.2.0** - 修复Modbus读写并发冲突,实现100%稳定控制
6
-
7
- ## 版本更新
8
-
9
- ### v2.2.0 (2025-10-20) - 关键并发问题修复
10
-
11
- **核心修复**:
12
- - 修复Modbus读取(轮询)和写入(控制)之间的并发冲突问题
13
- - 添加互斥锁机制,确保读写操作不冲突
14
- - 写入后100ms内暂停该从站轮询,避免读到旧值覆盖新值
15
- - 改进写入函数为async/await,支持错误捕获和传递
16
- - 优化MQTT命令处理,异步执行并捕获错误
17
- - 实现100%稳定控制和状态反馈
18
-
19
- **问题描述**(v2.1.0及之前版本):
20
- - 轮询每200ms读取32个线圈状态
21
- - MQTT命令随时写入单个线圈
22
- - 两者没有互斥保护,会冲突导致写入失败
23
- - 写入成功后轮询可能立即读到旧值,覆盖刚写入的新值
24
- - 控制成功率低,尤其是快速连续控制时
25
-
26
- **修复后效果**:
27
- - 轮询检测到写操作时主动跳过,避免冲突
28
- - 写入操作等待轮询完成后执行,最多等待500ms
29
- - 写入后100ms内不轮询该从站,确保设备状态已更新
30
- - 读写操作完全互斥,保证数据一致性
31
- - 控制成功率100%,状态反馈实时准确
32
-
33
- **技术实现**:
34
- ```javascript
35
- // 添加互斥锁
36
- node.modbusLock = false;
37
- node.lastWriteTime = {};
38
-
39
- // 轮询时检查锁
40
- if (node.modbusLock) {
41
- return; // 跳过本次轮询
42
- }
43
-
44
- // 写入时设置锁
45
- node.modbusLock = true;
46
- await node.client.writeCoil(coil, value);
47
- node.lastWriteTime[slaveId] = Date.now();
48
- node.modbusLock = false;
49
- ```
50
-
51
- **测试验证**:
52
- - 32路线圈连续快速控制,成功率100%
53
- - 轮询间隔200ms,写入响应<100ms
54
- - HA控制稳定,状态实时同步
55
- - 适合Linux工控机24/7长期稳定运行
56
-
57
- ---
58
-
59
- **升级方式**:
60
- ```bash
61
- cd ~/.node-red
62
- npm install node-red-contrib-symi-modbus@latest
63
- # 重启Node-RED生效
64
- ```
65
-
66
- ---
67
-
68
5
  ## 功能特性
69
6
 
70
7
  - 多协议支持:支持Modbus TCP和Modbus RTU(串口)
@@ -569,6 +506,51 @@ docker run --privileged ...
569
506
  - **稳定运行**:经过工控机7x24小时长期运行验证,无内存泄漏
570
507
  - **容错能力**:Modbus从站离线不影响其他从站,MQTT断线自动重连
571
508
 
509
+
510
+ > **最新版本 v2.3.0** - Symi轻量级协议完美支持,8键开关面板按键识别正常,MQTT双向同步稳定
511
+
512
+ ## 版本更新
513
+
514
+ ### v2.3.0 (2025-10-20) - Symi轻量级协议支持完善
515
+
516
+ **核心修复**:
517
+ - 修复从站开关节点对Symi轻量级协议的解析支持
518
+ - 协议检测同时支持SET(0x03)和REPORT(0x04)类型帧(面板按键发送SET类型)
519
+ - 添加详细的RS485数据接收日志(十六进制输出)
520
+ - 增强连接状态提示(RS485连接成功会明确显示)
521
+ - 添加完整的按键事件检测日志(设备地址、通道、状态匹配)
522
+ - 确保从站开关与主站轮询的双向同步不冲突
523
+
524
+ **协议修复说明**:
525
+ - Symi 8键开关面板按键时发送 `7E 01 03 0F 01 00 01 [01-08] 00 00 00 00 01 [CRC] 7D`
526
+ - 数据类型为 `0x03`(SET),不是 `0x04`(REPORT)
527
+ - 旧版本只检测REPORT类型,导致无法识别物理按键
528
+ - 新版本同时支持SET和REPORT,完美兼容所有Symi协议设备
529
+
530
+ **调试增强**:
531
+ - RS485数据接收时输出原始十六进制数据
532
+ - 协议解析成功输出设备地址、通道、数据类型、操作码
533
+ - 按键匹配成功输出完整的状态信息
534
+ - 按键不匹配时输出期望值和实际值对比
535
+ - 所有日志级别合理,不会造成系统负担
536
+
537
+ **测试验证**:
538
+ - 本地Node-RED环境测试通过
539
+ - 8键开关面板按键识别正常
540
+ - MQTT双向同步稳定
541
+ - 适合Linux工控机24/7长期稳定运行
542
+
543
+ ---
544
+
545
+ **升级方式**:
546
+ ```bash
547
+ cd ~/.node-red
548
+ npm install node-red-contrib-symi-modbus@latest
549
+ # 重启Node-RED生效
550
+ ```
551
+
552
+ ---
553
+
572
554
  ## 许可证
573
555
 
574
556
  MIT License
@@ -220,9 +220,9 @@ module.exports = {
220
220
  detectButtonPress: function(frame) {
221
221
  if (!frame) return null;
222
222
 
223
- // 检查是否是灯光设备的上报
223
+ // 检查是否是灯光设备的SET或REPORT(面板按键会发送SET类型)
224
224
  if (frame.deviceType === this.DEVICE_TYPE_LIGHT &&
225
- frame.dataType === this.DATA_TYPE_REPORT) {
225
+ (frame.dataType === this.DATA_TYPE_REPORT || frame.dataType === this.DATA_TYPE_SET)) {
226
226
 
227
227
  if (frame.opCode === this.LIGHT_OP_SINGLE) {
228
228
  // 单灯按键按下
@@ -185,7 +185,8 @@ module.exports = function(RED) {
185
185
  await node.rs485Client.connectTCP(node.config.tcpHost, {
186
186
  port: node.config.tcpPort
187
187
  });
188
- node.log(`已连接到RS-485 TCP: ${node.config.tcpHost}:${node.config.tcpPort}`);
188
+ node.log(`RS485连接成功(TCP): ${node.config.tcpHost}:${node.config.tcpPort}`);
189
+ node.log(`监听Symi开关${node.config.switchId}的按钮${node.config.buttonNumber}按键事件...`);
189
190
  } else {
190
191
  // 串口连接验证
191
192
  if (!node.config.serialPort) {
@@ -197,7 +198,8 @@ module.exports = function(RED) {
197
198
  stopBits: node.config.serialStopBits || 1,
198
199
  parity: node.config.serialParity || 'none'
199
200
  });
200
- node.log(`已连接到RS-485串口: ${node.config.serialPort}`);
201
+ node.log(`RS485连接成功(串口): ${node.config.serialPort} @ ${node.config.serialBaudRate || 9600}bps`);
202
+ node.log(`监听Symi开关${node.config.switchId}的按钮${node.config.buttonNumber}按键事件...`);
201
203
  }
202
204
 
203
205
  node.rs485Client.setTimeout(5000);
@@ -253,40 +255,55 @@ module.exports = function(RED) {
253
255
  // 处理RS-485接收到的数据
254
256
  node.handleRs485Data = function(data) {
255
257
  try {
258
+ // 输出原始数据(十六进制)- 方便调试
259
+ const hexData = Array.from(data).map(b => b.toString(16).padStart(2, '0').toUpperCase()).join(' ');
260
+ node.log(`RS485收到数据: ${hexData}`);
261
+
256
262
  // 解析轻量级协议帧
257
263
  const frame = protocol.parseFrame(data);
258
264
  if (!frame) {
259
- return; // 无效帧
265
+ node.warn(`无效的协议帧(CRC校验失败或格式错误): ${hexData}`);
266
+ return;
260
267
  }
261
268
 
269
+ node.log(`解析成功: 设备地址=${frame.deviceAddr} 通道=${frame.channel} 数据类型=0x${frame.dataType.toString(16).toUpperCase()} 操作码=0x${frame.opCode.toString(16).toUpperCase()}`);
270
+
262
271
  // 检测是否是按键按下事件
263
272
  const buttonEvent = protocol.detectButtonPress(frame);
264
273
  if (!buttonEvent) {
265
- return; // 不是按键事件
274
+ node.log(`不是按键事件(设备类型=${frame.deviceType} 数据类型=${frame.dataType} 操作码=${frame.opCode})`);
275
+ return;
266
276
  }
267
277
 
278
+ node.log(`检测到按键事件: 类型=${buttonEvent.type} 设备=${buttonEvent.deviceAddr} 通道=${buttonEvent.channel}`);
279
+
268
280
  // 检查是否是我们监听的开关和按钮
269
281
  if (buttonEvent.deviceAddr === node.config.switchId) {
270
282
  if (buttonEvent.type === 'single') {
271
283
  // 单键按下
272
284
  if (buttonEvent.channel === node.config.buttonNumber) {
273
- node.log(`检测到按键按下: 开关${node.config.switchId} 按钮${node.config.buttonNumber} 状态=${buttonEvent.state}`);
285
+ node.log(`匹配成功!开关${node.config.switchId} 按钮${node.config.buttonNumber} 状态=${buttonEvent.state ? 'ON' : 'OFF'}`);
274
286
  // 发送MQTT命令到继电器
275
287
  node.sendMqttCommand(buttonEvent.state);
288
+ } else {
289
+ node.log(`按钮编号不匹配: 收到${buttonEvent.channel} 期望${node.config.buttonNumber}`);
276
290
  }
277
291
  } else if (buttonEvent.type === 'multi') {
278
292
  // 多键按下
279
293
  const buttonIndex = node.config.buttonNumber - 1; // 转换为0-7索引
280
294
  if (buttonIndex >= 0 && buttonIndex < 8) {
281
295
  const state = buttonEvent.buttonStates[buttonIndex];
282
- node.log(`检测到多键事件: 开关${node.config.switchId} 按钮${node.config.buttonNumber} 状态=${state}`);
296
+ node.log(`匹配成功!开关${node.config.switchId} 多键按钮${node.config.buttonNumber} 状态=${state ? 'ON' : 'OFF'}`);
283
297
  // 发送MQTT命令到继电器
284
298
  node.sendMqttCommand(state);
285
299
  }
286
300
  }
301
+ } else {
302
+ node.log(`开关ID不匹配: 收到${buttonEvent.deviceAddr} 期望${node.config.switchId}`);
287
303
  }
288
304
  } catch (err) {
289
- node.warn(`解析RS-485数据失败: ${err.message}`);
305
+ node.error(`解析RS-485数据失败: ${err.message}`);
306
+ node.error(`错误数据: ${Array.from(data).map(b => b.toString(16).padStart(2, '0').toUpperCase()).join(' ')}`);
290
307
  }
291
308
  };
292
309
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-symi-modbus",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "Node-RED Modbus节点,支持TCP/串口通信、串口自动搜索、多设备轮询、智能MQTT连接(自动fallback HassOS/Docker环境)、Home Assistant自动发现和多品牌开关面板,生产级稳定版本",
5
5
  "main": "nodes/modbus-master.js",
6
6
  "scripts": {