node-red-contrib-symi-modbus 2.6.6 → 2.6.7

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
@@ -1,30 +1,26 @@
1
1
  # node-red-contrib-symi-modbus
2
2
 
3
- Node-RED的Modbus继电器控制节点,支持TCP/串口通信和MQTT集成。
3
+ Node-RED的Modbus继电器控制节点,支持TCP/串口通信和MQTT集成,专为智能家居场景设计。
4
4
 
5
5
  [![npm version](https://badge.fury.io/js/node-red-contrib-symi-modbus.svg)](https://www.npmjs.com/package/node-red-contrib-symi-modbus)
6
6
  [![Node-RED](https://img.shields.io/badge/Node--RED-%3E%3D2.0.0-red)](https://nodered.org)
7
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
8
 
9
- ## 功能特性
9
+ ## 核心特性
10
10
 
11
+ - **双模式运行**:
12
+ - **本地模式**:纯串口/TCP通信,断网也能稳定运行,无需MQTT
13
+ - **MQTT模式**:可选接入Home Assistant等第三方平台
11
14
  - **多协议支持**:
15
+ - Modbus RTU(串口直连RS485)
12
16
  - Modbus TCP(标准Modbus TCP)
13
17
  - Modbus RTU over TCP(TCP转RS485网关)
14
18
  - Telnet ASCII(推荐用于TCP转RS485网关)
15
- - Modbus RTU(串口直连)
16
- - **Symi开关支持**:自动识别并处理Symi私有协议按键事件,无需额外配置
17
- - **多设备轮询**:最多支持10台Modbus从站设备同时轮询
18
- - **32路继电器**:每台设备支持32个线圈(继电器通道)
19
- - **智能轮询**:从站上报时自动暂停轮询,优先处理数据,完成后继续轮询
20
- - **灵活配置**:可自定义轮询间隔(100-10000ms,默认200ms)、线圈范围、从站地址
21
- - **🔥 双模式支持**:
22
- - **本地模式**:纯串口通信,无需MQTT,断网也能稳定运行
23
- - **MQTT模式**:可选接入Home Assistant等第三方平台
24
- - **MQTT集成**(可选):自动生成Home Assistant兼容的MQTT发现消息
25
- - **实时状态**:实时监控和控制继电器状态
26
- - **主从模式**:提供主站节点和从站控制节点
27
- - **稳定可靠**:完整的内存管理和错误处理,适合工控机长期稳定运行
19
+ - **Symi开关集成**:自动识别并处理Symi私有协议按键事件,实现开关面板与继电器的双向同步
20
+ - **多设备轮询**:支持最多10台Modbus从站设备,每台32路继电器
21
+ - **智能轮询机制**:从站上报时自动暂停轮询,优先处理数据,避免冲突
22
+ - **稳定可靠**:完整的内存管理、错误处理、断线重连,适合7x24小时长期运行
23
+ - **总线数据过滤**:自动忽略总线上的无关数据,只处理本节点相关的数据
28
24
 
29
25
  ## 快速开始
30
26
 
@@ -56,13 +52,14 @@ node-red-restart
56
52
  **配置方法**:
57
53
  1. 主站节点:不启用MQTT或不配置MQTT服务器
58
54
  2. 从站开关节点:不配置MQTT服务器
59
- 3. 将从站开关节点的输出连线到主站节点的输入
55
+ 3. **无需连线**:主站和从站通过内部事件自动通信(v2.6.7+)
60
56
 
61
57
  **优势**:
62
58
  - ✅ 断网也能稳定运行
63
59
  - ✅ 不依赖外部服务
64
60
  - ✅ 响应速度更快
65
61
  - ✅ 配置更简单
62
+ - ✅ 无需手动连线(免连线通信)
66
63
 
67
64
  #### 模式2:MQTT模式(推荐用于Home Assistant集成)
68
65
 
@@ -142,10 +139,12 @@ node-red-restart
142
139
  - 面板品牌: `亖米` (默认)
143
140
  - 开关ID: 物理面板地址 (0-255)
144
141
  - 按钮编号: 按键编号 (1-8)
142
+ - 按钮类型: 开关按钮或场景按钮(也可自动识别)
145
143
  4. 配置映射到的继电器:
146
144
  - 目标从站地址: `10`
147
145
  - 目标线圈编号: `0`
148
- 5. 部署流程
146
+ 5. **无需连线**:主站和从站通过内部事件自动通信,无需手动连线
147
+ 6. 部署流程
149
148
 
150
149
  ## 核心特性说明
151
150
 
@@ -187,6 +186,7 @@ node-red-restart
187
186
  **2. 场景按钮**
188
187
  - **控制方式**:每次按下toggle切换状态(开→关→开)
189
188
  - **LED反馈**:根据当前状态显示LED(开=亮,关=灭)
189
+ - **协议特征**:操作信息为0x11、0x12、0x13等(高4位=1表示场景模式)
190
190
  - **适用场景**:
191
191
  - 场景触发(回家模式、离家模式等)
192
192
  - 一键全开/全关
@@ -196,25 +196,30 @@ node-red-restart
196
196
  - 使用原始设备地址确保LED精确反馈
197
197
  - 支持物理按键和Home Assistant远程控制
198
198
  - 200ms防抖,避免重复触发
199
+ - 自动识别场景模式(根据操作信息高4位判断)
199
200
 
200
201
  **两种模式对比**:
201
202
 
202
203
  | 特性 | 开关按钮 | 场景按钮 |
203
204
  |------|---------|---------|
204
205
  | 控制方式 | 开/关独立 | Toggle切换 |
206
+ | 操作信息 | 0x00/0x01 | 0x11/0x12/0x13等 |
205
207
  | LED反馈协议 | SET (0x03) | REPORT (0x04) |
206
208
  | 按键事件 | 独立开/关码 | 统一触发码 |
207
209
  | LED同步 | ✓ 完美同步 | ✓ 完美同步 |
208
210
  | HA远程控制 | ✓ 支持 | ✓ 支持 |
209
211
  | 推荐场景 | 灯光/插座 | 场景触发 |
212
+ | 自动识别 | ✓ 支持 | ✓ 支持 |
210
213
 
211
214
  **配置说明**:
212
215
 
213
216
  1. **RS-485连接**:选择串口配置节点(波特率9600,8N1)
214
- 2. **MQTT服务器**:选择MQTT配置节点(连接Home Assistant等)
217
+ 2. **MQTT服务器**(可选):
218
+ - 本地模式:不启用MQTT,主站和从站通过内部事件自动通信(免连线)
219
+ - MQTT模式:启用MQTT并选择MQTT配置节点
215
220
  3. **面板配置**:
216
221
  - 面板品牌:选择亖米(Symi)
217
- - 按钮类型:开关按钮或场景按钮
222
+ - 按钮类型:开关按钮或场景按钮(也可自动识别)
218
223
  - 开关ID:物理面板的RS-485地址(0-255)
219
224
  - 按钮编号:面板上的按键序号(1-8)
220
225
  4. **继电器映射**:
@@ -223,15 +228,23 @@ node-red-restart
223
228
 
224
229
  **使用示例**:
225
230
 
226
- **示例1:客厅灯光开关**
231
+ **示例1:客厅灯光开关(本地模式)**
227
232
  - 面板:开关ID=2,按键1(开关按钮)
228
233
  - 继电器:从站10,1路
229
- - 效果:按下面板按键,客厅灯开/关,面板LED同步状态
234
+ - 配置:不启用MQTT,主站和从站自动通信(免连线)
235
+ - 效果:按下面板按键,客厅灯开/关,面板LED同步状态,断网也能正常工作
230
236
 
231
- **示例2:全开场景**
237
+ **示例2:全开场景(MQTT模式)**
232
238
  - 面板:开关ID=2,按键8(场景按钮)
233
239
  - 继电器:从站10,32路
234
- - 效果:按下面板按键,触发全开场景
240
+ - 配置:启用MQTT,连接到Home Assistant
241
+ - 效果:按下面板按键,触发全开场景,HA中可远程控制,面板LED同步状态
242
+
243
+ **示例3:三键场景开关(本地模式)**
244
+ - 面板:开关ID=1,按键1/2/3(场景按钮,自动识别)
245
+ - 继电器:从站10,1/2/3路
246
+ - 配置:不启用MQTT,主站和从站自动通信(免连线)
247
+ - 效果:按下按键1/2/3,分别控制对应继电器,LED指示灯同步状态
235
248
 
236
249
  ### 智能轮询机制
237
250
 
@@ -246,6 +259,28 @@ node-red-restart
246
259
  - 从站上报的数据得到优先处理
247
260
  - 系统响应迅速,状态同步及时
248
261
 
262
+ ### 总线数据过滤与稳定性
263
+
264
+ 本节点具备完善的总线数据过滤机制,确保长期稳定运行:
265
+
266
+ **数据过滤机制**:
267
+ - **CRC校验**:所有Symi协议帧都经过CRC8校验,无效数据自动丢弃
268
+ - **静默忽略**:总线上的非相关数据(其他设备、其他协议)静默忽略,不影响正常通信
269
+ - **精确匹配**:只处理本节点配置的从站地址和线圈范围内的数据
270
+ - **TCP粘包处理**:TCP模式下自动处理粘包和分包问题,确保数据完整性
271
+
272
+ **稳定性保障**:
273
+ - **写入队列**:多个节点同时写入时自动排队,避免并发冲突
274
+ - **断线重连**:TCP和串口断线后自动重连,指数退避策略(最大60秒)
275
+ - **内存管理**:定时清理缓存,防止内存泄漏
276
+ - **错误日志限流**:避免日志刷屏影响性能
277
+ - **MQTT独立性**:MQTT开启或关闭都不影响主站和从站的本地通信
278
+
279
+ **网络适应性**:
280
+ - **离线通信**:串口模式完全脱离网络依赖,断网也能正常工作
281
+ - **TCP稳定性**:TCP模式支持局域网通信,网络恢复后自动重连
282
+ - **MQTT可选**:MQTT仅用于对接第三方平台,不影响本地控制
283
+
249
284
  ## 调试节点(modbus-debug)
250
285
 
251
286
  用于抓取并显示原始RS485字节流数据(HEX),帮助定位串口或TCP网关下的Modbus通信问题。
@@ -304,7 +339,11 @@ node-red-restart
304
339
  - TCP网络故障自动恢复
305
340
  - 连接前彻底清理旧实例,避免资源泄漏
306
341
  - **互斥锁机制**:防止读写冲突导致的数据异常
307
- - **Keep-Alive心跳**:TCP连接启用30秒心跳检测
342
+ - **TCP永久连接**:
343
+ - 禁用TCP超时(永久连接),避免无数据时超时断开
344
+ - Keep-Alive心跳10秒间隔,确保连接活跃
345
+ - 适应客户长期不在家、总线无数据的场景
346
+ - 网络故障自动重连,恢复后立即恢复通信
308
347
 
309
348
  ## 技术规格
310
349
 
@@ -397,6 +436,80 @@ devices:
397
436
  3. **查看Node-RED调试日志**:部署后查看日志中的轮询信息
398
437
  4. **检查串口波特率**:确认波特率为9600(与从站设备一致)
399
438
  5. **检查从站地址**:确认从站地址正确(1-247)
439
+
440
+ ### 轮询超时或频繁报错
441
+
442
+ 1. **轮询永不停止原则**:
443
+ - 即使从站不响应,轮询也会持续运行
444
+ - 不会因超时而跳过任何从站
445
+ - 只记录错误日志,不影响总线轮询机制
446
+ 2. **正常超时**:偶尔超时是正常的,系统会自动重试
447
+ - 超时设置:2-3秒(串口/TCP)
448
+ - 只记录日志,不停止轮询
449
+ - 轮询会持续运行,不受超时影响
450
+ 3. **频繁超时**:
451
+ - 检查Modbus连接是否稳定
452
+ - 调整轮询间隔(默认200ms,可增加到500ms)
453
+ - 检查从站设备是否在线
454
+ - 查看总线是否有其他设备干扰
455
+ 4. **总线干扰**:
456
+ - 总线上的其他数据(如Symi网关通信、其他Modbus设备)可能导致偶尔超时或CRC错误
457
+ - 系统已优化容错机制,会自动忽略异常数据并继续轮询
458
+ - 非超时错误(如CRC错误)不累积计数,只记录日志
459
+ 5. **长期稳定性保障**:
460
+ - 轮询循环采用try-catch包裹,确保异常不会中断轮询
461
+ - 每1000轮输出一次调试日志,证明轮询持续运行
462
+ - 定时清理机制(每小时)防止内存泄漏
463
+ - 所有定时器在节点关闭时正确清除
464
+
465
+ ### 总线上出现未知数据
466
+
467
+ 1. **识别数据来源**:
468
+ - 使用`modbus-debug`节点监听总线数据(必须使用共享的serial-port-config,避免串口冲突)
469
+ - 查看Node-RED日志中的"写入线圈"日志,确认是否是本节点发送的
470
+ - 检查是否有其他Modbus设备或Symi网关在总线上通信
471
+ 2. **常见数据格式**:
472
+ - `01 05 00 01 FF 00 ...`:写单个线圈(功能码0x05)
473
+ - `01 0F 00 00 00 20 ...`:写多个线圈(功能码0x0F)
474
+ - `7E 01 03 ...`:Symi轻量级协议数据
475
+ 3. **影响**:
476
+ - 总线上的其他数据不会影响轮询稳定性
477
+ - 可能导致偶尔的CRC错误或超时,但系统会自动忽略并继续轮询
478
+
479
+ ### 串口冲突错误
480
+
481
+ **错误信息**:
482
+ ```
483
+ 串口监听错误: Port is not open
484
+ 串口监听错误: Error Resource temporarily unavailable Cannot lock port
485
+ ```
486
+
487
+ **原因**:
488
+ - 多个节点试图独立打开同一个串口
489
+ - 串口只能被一个进程同时打开
490
+
491
+ **解决方案**:
492
+ 1. **modbus-debug节点**:必须使用"serial"源类型,选择共享的`serial-port-config`配置节点
493
+ 2. **不要使用独立连接**:不要让多个节点独立打开同一个串口
494
+ 3. **共享连接配置**:所有节点(主站、从站开关、modbus-debug)都应该使用同一个`serial-port-config`配置节点
495
+
496
+ ### 批量更换连接配置
497
+
498
+ **问题**:如何批量更换所有节点的RS-485连接配置?
499
+
500
+ **Node-RED原生方法**(推荐):
501
+ 1. 创建新的`serial-port-config`配置节点
502
+ 2. 点击右上角菜单 → 配置节点
503
+ 3. 找到旧的`serial-port-config`配置节点
504
+ 4. 点击删除按钮
505
+ 5. Node-RED会提示"此配置节点被X个节点使用,是否替换为其他配置?"
506
+ 6. 选择新创建的配置节点
507
+ 7. 点击确认,所有使用旧配置的节点会自动更换为新配置
508
+
509
+ **注意**:
510
+ - 这是Node-RED的原生功能,安全可靠
511
+ - 删除配置节点前必须先创建新配置
512
+ - 替换后需要部署才能生效
400
513
  6. **确认从站设备在线**:使用Modbus调试工具测试从站是否响应
401
514
  7. **检查MQTT连接**:确保MQTT broker地址正确,轮询不依赖MQTT但状态发布需要MQTT
402
515
  8. **测试连接**:
@@ -405,10 +518,28 @@ devices:
405
518
 
406
519
  ### 从站开关无响应
407
520
 
408
- 1. 检查RS-485连接是否正常
409
- 2. 确认开关面板地址和按钮编号正确
410
- 3. 检查MQTT连接状态
411
- 4. 查看Node-RED日志中的协议解析信息
521
+ 1. **检查免连线通信**(v2.6.7+推荐):
522
+ - **无需连线**:主站和从站通过内部事件自动通信,无需手动连线
523
+ - 查看Node-RED日志,确认是否有"内部事件模式:发送命令到从站X 线圈Y"的日志
524
+ - 查看主站节点日志,确认是否有"收到内部事件:从站X 线圈Y"的日志
525
+ - 查看主站节点日志,确认是否有"内部事件写入成功,已广播状态变化"的日志
526
+ - 查看从站开关节点日志,确认是否有"收到状态变化事件"和"LED反馈已发送"的日志
527
+ 2. **检查RS-485连接**:
528
+ - 确认RS-485连接配置正确(串口路径、波特率等)
529
+ - 查看节点状态显示是否为"MQTT未启用 - 使用内部事件模式"
530
+ - 物理面板连接:确认物理面板连接到正确的RS-485总线(TCP或串口)
531
+ 3. **检查开关面板配置**:
532
+ - 确认开关面板地址和按钮编号正确
533
+ - 场景模式开关:操作信息为0x11、0x12、0x13等(高4位=1)
534
+ - 开关模式开关:操作信息为0x00(关)或0x01(开)
535
+ 4. **检查继电器映射**:
536
+ - 确认目标从站地址和线圈编号正确
537
+ - 线圈编号范围:0-31(对应用户输入的1-32路)
538
+ 5. **查看调试日志**:
539
+ - 使用modbus-debug节点抓取原始总线数据
540
+ - 查看Node-RED日志中的协议解析信息
541
+ - 确认按键事件是否被正确识别
542
+ - 确认LED反馈协议帧是否正确发送(查看日志中的HEX字符串)
412
543
 
413
544
  ## 输入消息格式
414
545
 
@@ -507,7 +638,7 @@ msg.payload = 1; // 或 0
507
638
 
508
639
  ## 项目信息
509
640
 
510
- **版本**: v2.6.6
641
+ **版本**: v2.6.7
511
642
 
512
643
  **核心功能**:
513
644
  - 支持多种Modbus协议(Telnet ASCII、RTU over TCP、Modbus TCP、Modbus RTU串口)
@@ -515,10 +646,11 @@ msg.payload = 1; // 或 0
515
646
  - Symi私有协议自动识别(支持两种485开关控制方式)
516
647
  - 智能轮询暂停机制(从站上报时自动暂停,处理完成后恢复)
517
648
  - 🔥 **双模式支持**(本地模式和MQTT模式可选切换,断网也能稳定运行)
649
+ - 🔥 **免连线通信**(主站和从站通过内部事件通信,无需连线,支持本地模式和MQTT模式)
518
650
  - MQTT集成(可选启用,Home Assistant自动发现,实体唯一性保证,QoS=0高性能发布)
519
- - 物理开关面板双向同步(亖米协议支持,LED反馈同步)
651
+ - 物理开关面板双向同步(亖米协议支持,LED反馈同步,支持开关模式和场景模式)
520
652
  - 共享连接架构(多个从站开关节点共享同一个串口/TCP连接,支持500+节点)
521
- - 长期稳定运行(内存管理、智能重连、错误日志限流、异步MQTT发布)
653
+ - 长期稳定运行(内存管理、智能重连、错误日志限流、异步MQTT发布、TCP永久连接)
522
654
 
523
655
  **技术栈**:
524
656
  - modbus-serial: ^8.0.23(内部封装serialport,支持TCP和串口)
@@ -527,7 +659,22 @@ msg.payload = 1; // 或 0
527
659
  - Node.js: >=14.0.0
528
660
  - Node-RED: >=2.0.0
529
661
 
530
- **最新更新(v2.6.6)**:
662
+ **最新更新(v2.6.7)**:
663
+ - **🔥 修复LED反馈功能**:
664
+ - 主站轮询检测到状态变化时自动广播事件
665
+ - 从站开关节点监听状态变化并发送LED反馈到物理面板
666
+ - 支持开关模式(SET协议)和场景模式(REPORT协议)
667
+ - LED反馈队列间隔20ms,避免总线拥堵
668
+ - **🔥 TCP连接稳定性优化**:
669
+ - 禁用TCP超时(永久连接),避免无数据时超时断开
670
+ - Keep-Alive间隔从30秒优化到10秒
671
+ - 适应客户长期不在家、总线无数据的场景
672
+ - **日志优化**:
673
+ - 移除频繁的轮询日志("轮询持续运行中"、"正在轮询从站X")
674
+ - 轮询永久稳定运行,无需频繁确认
675
+ - 减少日志输出,降低硬盘占用
676
+
677
+ **v2.6.6更新**:
531
678
  - **🔥 彻底解决MQTT日志刷屏问题**:
532
679
  - 局域网IP检测优化:配置192.168.x.x等IP后不再尝试fallback地址
533
680
  - 高频MQTT日志改为debug级别:broker候选、认证、重连等
@@ -632,7 +779,11 @@ msg.payload = 1; // 或 0
632
779
  - TCP网络故障自动恢复
633
780
  - 连接前彻底清理旧实例,避免资源泄漏
634
781
  - **互斥锁机制**:防止读写冲突导致的数据异常
635
- - **Keep-Alive心跳**:TCP连接启用30秒心跳检测
782
+ - **TCP永久连接**:
783
+ - 禁用TCP超时(永久连接),避免无数据时超时断开
784
+ - Keep-Alive心跳10秒间隔,确保连接活跃
785
+ - 适应客户长期不在家、总线无数据的场景
786
+ - 网络故障自动重连,恢复后立即恢复通信
636
787
 
637
788
  ## 技术规格
638
789
 
@@ -835,7 +986,7 @@ msg.payload = 1; // 或 0
835
986
 
836
987
  ## 项目信息
837
988
 
838
- **版本**: v2.6.6
989
+ **版本**: v2.6.7
839
990
 
840
991
  **核心功能**:
841
992
  - 支持多种Modbus协议(Telnet ASCII、RTU over TCP、Modbus TCP、Modbus RTU串口)
@@ -843,10 +994,11 @@ msg.payload = 1; // 或 0
843
994
  - Symi私有协议自动识别(支持两种485开关控制方式)
844
995
  - 智能轮询暂停机制(从站上报时自动暂停,处理完成后恢复)
845
996
  - 🔥 **双模式支持**(本地模式和MQTT模式可选切换,断网也能稳定运行)
997
+ - 🔥 **免连线通信**(主站和从站通过内部事件通信,无需连线,支持本地模式和MQTT模式)
846
998
  - MQTT集成(可选启用,Home Assistant自动发现,实体唯一性保证,QoS=0高性能发布)
847
- - 物理开关面板双向同步(亖米协议支持,LED反馈同步)
999
+ - 物理开关面板双向同步(亖米协议支持,LED反馈同步,支持开关模式和场景模式)
848
1000
  - 共享连接架构(多个从站开关节点共享同一个串口/TCP连接,支持500+节点)
849
- - 长期稳定运行(内存管理、智能重连、错误日志限流、异步MQTT发布)
1001
+ - 长期稳定运行(内存管理、智能重连、错误日志限流、异步MQTT发布、TCP永久连接)
850
1002
 
851
1003
  **技术栈**:
852
1004
  - modbus-serial: ^8.0.23(内部封装serialport,支持TCP和串口)
@@ -261,14 +261,26 @@ module.exports = {
261
261
 
262
262
  if (frame.opCode === this.LIGHT_OP_SINGLE) {
263
263
  // 单灯/场景按键按下
264
+ const opInfo = frame.opInfo[0];
265
+
266
+ // 判断按钮类型和状态
267
+ // 开关模式:0x00=关,0x01=开
268
+ // 场景模式:0x11=按键1,0x12=按键2,0x13=按键3等(高4位=1表示场景模式)
269
+ // 特殊场景模式:0x0F也是场景模式(某些面板使用0x0F表示场景触发)
270
+ const isSceneMode = (opInfo & 0xF0) === 0x10 || opInfo === 0x0F; // 高4位为1或0x0F表示场景模式
271
+ const buttonState = isSceneMode ? true : (opInfo === 0x01); // 场景模式总是触发,开关模式根据值判断
272
+
264
273
  // 场景设备操作码0x00:只触发动作,不需要LED反馈
265
274
  const needFeedback = !(frame.deviceType === this.DEVICE_TYPE_SCENE && frame.opCode === 0x00);
275
+
266
276
  return {
267
277
  type: 'single',
268
278
  deviceType: frame.deviceType,
269
279
  deviceAddr: frame.deviceAddr,
270
280
  channel: frame.channel,
271
- state: frame.opInfo[0] === 0x01,
281
+ state: buttonState,
282
+ isSceneMode: isSceneMode, // 标记是否是场景模式
283
+ opInfo: opInfo, // 保留原始操作信息
272
284
  needFeedback: needFeedback, // 是否需要LED反馈
273
285
  raw: frame
274
286
  };
@@ -4,26 +4,11 @@
4
4
  color: '#C0DEED',
5
5
  defaults: {
6
6
  name: { value: '' },
7
- sourceType: { value: 'serial' }, // serial | modbus
8
- serialPortConfig: {
9
- value: '',
10
- type: 'serial-port-config',
11
- required: false,
12
- validate: function(v) {
13
- var t = $("#node-input-sourceType").val() || this.sourceType || 'serial';
14
- if (t === 'serial') return !!v; // 串口模式需要选择配置
15
- return true; // 非串口模式不要求
16
- }
17
- },
18
- modbusServer: {
19
- value: '',
20
- type: 'modbus-server-config',
21
- required: false,
22
- validate: function(v) {
23
- var t = $("#node-input-sourceType").val() || this.sourceType || 'serial';
24
- if (t === 'modbus') return !!v; // 独立Modbus服务器模式需要选择配置
25
- return true; // 非modbus模式不要求
26
- }
7
+ sourceType: { value: 'serial' }, // 仅支持 serial(避免串口冲突)
8
+ serialPortConfig: {
9
+ value: '',
10
+ type: 'serial-port-config',
11
+ required: true
27
12
  },
28
13
  uppercase: { value: true },
29
14
  includeTimestamp: { value: true },
@@ -32,22 +17,7 @@
32
17
  inputs: 1,
33
18
  outputs: 1,
34
19
  icon: 'font-awesome/fa-eye',
35
- label: function() { return this.name || 'modbus-debug'; },
36
- oneditprepare: function() {
37
- function toggleSources() {
38
- var type = $("#node-input-sourceType").val();
39
- if (type === 'serial') {
40
- $("#serial-select-wrap").show();
41
- $("#modbus-select-wrap").hide();
42
- } else {
43
- $("#serial-select-wrap").hide();
44
- $("#modbus-select-wrap").show();
45
- }
46
- }
47
- $("#node-input-sourceType").on('change', toggleSources);
48
- toggleSources();
49
- },
50
- oneditsave: function() {}
20
+ label: function() { return this.name || 'modbus-debug'; }
51
21
  });
52
22
  </script>
53
23
 
@@ -57,19 +27,11 @@
57
27
  <input type="text" id="node-input-name" placeholder="原始485调试">
58
28
  </div>
59
29
  <div class="form-row">
60
- <label for="node-input-sourceType"><i class="fa fa-exchange"></i> 数据来源</label>
61
- <select id="node-input-sourceType">
62
- <option value="serial">共享串口配置 (serial-port-config)</option>
63
- <option value="modbus">Modbus服务器配置 (modbus-server-config)</option>
64
- </select>
65
- </div>
66
- <div class="form-row" id="serial-select-wrap">
67
30
  <label for="node-input-serialPortConfig"><i class="fa fa-plug"></i> 串口配置</label>
68
31
  <input type="text" id="node-input-serialPortConfig">
69
- </div>
70
- <div class="form-row" id="modbus-select-wrap" style="display:none">
71
- <label for="node-input-modbusServer"><i class="fa fa-server"></i> Modbus服务器</label>
72
- <input type="text" id="node-input-modbusServer">
32
+ <div style="font-size: 11px; color: #888; margin-left: 110px; margin-top: 3px;">
33
+ 选择已配置的RS-485连接(与主站/从站节点共享,避免串口冲突)
34
+ </div>
73
35
  </div>
74
36
  <div class="form-row">
75
37
  <label for="node-input-uppercase"><i class="fa fa-text-height"></i> 大写HEX</label>
@@ -88,8 +50,9 @@
88
50
  <script type="text/x-red" data-help-name="modbus-debug">
89
51
  <p>modbus-debug 节点用于抓取并显示原始485字节流,以十六进制格式输出,帮助调试串口或TCP网关下的Modbus通信。</p>
90
52
  <ul>
91
- <li><b>数据来源</b>:选择共享的 <code>serial-port-config</code>(推荐,复用主站/从站的同一连接),或选择 <code>modbus-server-config</code>(独立直连TCP/串口)。</li>
53
+ <li><b>数据来源</b>:选择共享的 <code>serial-port-config</code>(与主站/从站节点共享,避免串口冲突)。</li>
92
54
  <li><b>输出</b>:<code>msg.payload</code> 为格式化HEX字符串;<code>msg.buffer</code>为原始Buffer;<code>msg.meta</code>包含来源与连接信息;若开启则包含 <code>msg.timestamp</code>。</li>
93
55
  <li><b>显示最大字节数</b>:为避免控制台过载,可设置最大显示长度,超过则截断并在 meta.truncated 标记。</li>
56
+ <li><b>重要</b>:不再支持独立连接到Modbus服务器(会导致串口冲突),请使用共享的 serial-port-config 配置节点。</li>
94
57
  </ul>
95
58
  </script>
@@ -100,68 +100,11 @@ module.exports = function(RED) {
100
100
  node.status({ fill: "red", shape: "ring", text: "监听失败" });
101
101
  }
102
102
  } else if (node.sourceType === "modbus") {
103
- if (!node.modbusServer) {
104
- node.status({ fill: "red", shape: "ring", text: "未选择Modbus服务器" });
105
- node.error("未配置Modbus服务器,请在节点中选择 modbus-server-config 配置节点");
106
- return;
107
- }
108
-
109
- // 独立建立原始连接以抓取数据
110
- if (node.modbusServer.connectionType === "tcp") {
111
- try {
112
- node.localConnType = "tcp";
113
- node.localConnection = new net.Socket();
114
- node.localConnection.setKeepAlive(true, 30000);
115
- node.localConnection.connect(node.modbusServer.tcpPort, node.modbusServer.tcpHost, () => {
116
- node.log(`modbus-debug 已连接TCP:${node.modbusServer.tcpHost}:${node.modbusServer.tcpPort}`);
117
- node.status({ fill: "blue", shape: "dot", text: "TCP监听中" });
118
- });
119
- node.localConnection.on("data", sendHexMsg);
120
- node.localConnection.on("error", (err) => {
121
- node.warn(`TCP监听错误: ${err.message}`);
122
- node.status({ fill: "red", shape: "ring", text: "TCP错误" });
123
- });
124
- node.localConnection.on("close", () => {
125
- node.status({ fill: "grey", shape: "ring", text: "TCP已关闭" });
126
- });
127
- } catch (e) {
128
- node.error(`TCP初始化失败: ${e.message}`);
129
- node.status({ fill: "red", shape: "ring", text: "TCP初始化失败" });
130
- }
131
- } else {
132
- // 串口
133
- if (!SerialPort) {
134
- node.error("未找到 serialport 模块,无法打开串口");
135
- node.status({ fill: "red", shape: "ring", text: "serialport缺失" });
136
- return;
137
- }
138
- try {
139
- node.localConnType = "serial";
140
- node.localConnection = new SerialPort({
141
- path: node.modbusServer.serialPort,
142
- baudRate: node.modbusServer.serialBaudRate || 9600,
143
- dataBits: node.modbusServer.serialDataBits || 8,
144
- stopBits: node.modbusServer.serialStopBits || 1,
145
- parity: node.modbusServer.serialParity || "none",
146
- autoOpen: true,
147
- });
148
- node.localConnection.on("open", () => {
149
- node.log(`modbus-debug 已打开串口:${node.modbusServer.serialPort}`);
150
- node.status({ fill: "blue", shape: "dot", text: "串口监听中" });
151
- });
152
- node.localConnection.on("data", sendHexMsg);
153
- node.localConnection.on("error", (err) => {
154
- node.warn(`串口监听错误: ${err.message}`);
155
- node.status({ fill: "red", shape: "ring", text: "串口错误" });
156
- });
157
- node.localConnection.on("close", () => {
158
- node.status({ fill: "grey", shape: "ring", text: "串口已关闭" });
159
- });
160
- } catch (e) {
161
- node.error(`串口初始化失败: ${e.message}`);
162
- node.status({ fill: "red", shape: "ring", text: "串口初始化失败" });
163
- }
164
- }
103
+ // 已废弃:不再支持独立连接到Modbus服务器(会导致串口冲突)
104
+ // 请使用 "serial" 源类型并选择 serial-port-config 配置节点
105
+ node.status({ fill: "red", shape: "ring", text: "请使用serial源类型" });
106
+ node.error("不再支持独立连接到Modbus服务器(会导致串口冲突),请将源类型改为 'serial' 并选择 serial-port-config 配置节点");
107
+ return;
165
108
  }
166
109
  };
167
110