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

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
@@ -246,6 +246,34 @@ node-red-restart
246
246
  - 从站上报的数据得到优先处理
247
247
  - 系统响应迅速,状态同步及时
248
248
 
249
+ ## 调试节点(modbus-debug)
250
+
251
+ 用于抓取并显示原始RS485字节流数据(HEX),帮助定位串口或TCP网关下的Modbus通信问题。
252
+
253
+ - 数据来源:
254
+ - 共享连接:`serial-port-config`(推荐,复用主站/从站的同一连接)
255
+ - 独立连接:`modbus-server-config`(直接连接TCP或串口)
256
+ - 输出内容:
257
+ - `msg.payload`:格式化的HEX字符串(可选大写)
258
+ - `msg.buffer`:原始Buffer数据
259
+ - `msg.meta`:来源信息与连接参数(串口或TCP详情)
260
+ - `msg.timestamp`:时间戳(可选)
261
+ - 显示控制:
262
+ - `maxBytes`:限制显示字节数,超过会截断并在`meta.truncated`标记
263
+
264
+ 使用步骤:
265
+ 1. 将 `modbus-debug` 节点拖入画布。
266
+ 2. 选择“数据来源”:
267
+ - 选“共享串口配置(serial-port-config)”以复用主站/从站连接;或
268
+ - 选“Modbus服务器(modbus-server-config)”进行独立直连。
269
+ 3. 可选:勾选“大写HEX”、勾选“时间戳”、设置“最大字节数”。
270
+ 4. 部署后,节点自动输出捕获到的原始数据。
271
+
272
+ 典型用法:
273
+ - 联调TCP转RS485网关时,观察上行/下行原始数据帧是否完整。
274
+ - 排查波特率/数据位/校验位配置是否正确(串口模式)。
275
+ - 与 `modbus-master` 或 `modbus-slave-switch` 同时使用,定位现场设备通信异常。
276
+
249
277
  ### MQTT自动发现
250
278
 
251
279
  启用MQTT后,自动生成Home Assistant兼容的Discovery配置:
@@ -479,7 +507,7 @@ msg.payload = 1; // 或 0
479
507
 
480
508
  ## 项目信息
481
509
 
482
- **版本**: v2.6.4
510
+ **版本**: v2.6.6
483
511
 
484
512
  **核心功能**:
485
513
  - 支持多种Modbus协议(Telnet ASCII、RTU over TCP、Modbus TCP、Modbus RTU串口)
@@ -499,24 +527,34 @@ msg.payload = 1; // 或 0
499
527
  - Node.js: >=14.0.0
500
528
  - Node-RED: >=2.0.0
501
529
 
502
- **最新更新(v2.6.4)**:
530
+ **最新更新(v2.6.6)**:
531
+ - **🔥 彻底解决MQTT日志刷屏问题**:
532
+ - 局域网IP检测优化:配置192.168.x.x等IP后不再尝试fallback地址
533
+ - 高频MQTT日志改为debug级别:broker候选、认证、重连等
534
+ - 默认不输出到日志文件和调试窗口
535
+ - 配置局域网IP后立即连接,不产生多余日志
536
+ - **日志输出优化**:
537
+ - 连接成功/失败:仍使用log(重要信息)
538
+ - 重连尝试、fallback地址、认证信息:改为debug(调试信息)
539
+ - 仅启用debug模式时才在调试窗口显示
540
+ - 彻底解决日志刷屏和硬盘占用问题
541
+
542
+ **v2.6.5更新**:
543
+ - **🔥 修复MQTT报错问题**:从站开关节点新增"启用MQTT"勾选框
544
+ - 默认不启用MQTT,不会尝试连接
545
+ - 本地模式和MQTT模式自由切换
546
+
547
+ **v2.6.4更新**:
503
548
  - **🔥 日志优化**:大幅减少日志输出,保证长期稳定运行
504
- - 高频操作日志改为debug级别(按键、轮询、写入等)
505
- - 默认不输出到日志文件,不占用硬盘空间
506
- - 保留错误日志的10分钟限流机制
507
- - 完善的内存清理机制,防止内存泄漏
508
- - **适合工控机长期运行**:
509
- - 无debug节点时不产生日志
510
- - 不会因日志增加硬盘占用
511
- - 不会因日志增加内存占用
512
- - 断网/MQTT断开也不会持续报错
549
+ - 高频操作日志改为debug级别
550
+ - 默认不输出到日志文件
551
+ - 完善的内存清理机制
513
552
 
514
553
  **v2.6.3更新**:
515
554
  - **🔥 MQTT可选配置**:完全兼容无MQTT环境
516
- - 本地模式:纯串口通信,断网也能稳定运行
517
- - MQTT模式:可选接入Home Assistant等第三方平台
518
- - 智能切换:MQTT断开时自动切换到本地模式
519
- - 状态显示:清晰区分当前运行模式
555
+ - 本地模式:纯串口通信
556
+ - MQTT模式:可选接入HA
557
+ - 智能切换和状态显示
520
558
 
521
559
  **性能优化**:
522
560
  - 轮询间隔优化:修复间隔计算逻辑,确保每个从站使用正确的轮询间隔
@@ -535,3 +573,284 @@ msg.payload = 1; // 或 0
535
573
  **支持**:
536
574
  - Issues: https://github.com/symi-daguo/node-red-contrib-symi-modbus/issues
537
575
  - NPM: https://www.npmjs.com/package/node-red-contrib-symi-modbus
576
+
577
+ ### 节点与分类(Palette)
578
+
579
+ - 侧边栏分类名:`SYMI-MODBUS`
580
+ - 包含节点:`modbus-master`(主站)、`modbus-slave-switch`(从站开关)、`modbus-debug`(调试)
581
+ - 如果未显示该分类或节点:
582
+ - 刷新浏览器缓存(Shift+刷新)
583
+ - 重启 Node-RED(如:`node-red-restart` 或系统服务方式)
584
+ - 在“节点管理(Manage Palette)”确认安装版本为 `v2.6.6`
585
+
586
+ ### 调试节点(modbus-debug)使用要点
587
+
588
+ - 数据来源选择:`sourceType = serial`(共享串口)或 `modbus`(独立服务器)
589
+ - 共享串口:需要选择并关联一个 `serial-port-config` 配置节点
590
+ - 独立服务器:需要选择并关联一个 `modbus-server-config` 配置节点
591
+ - HEX显示:可选大写、可选时间戳、`maxBytes` 控制显示长度
592
+ - 输出:`msg.payload`(格式化HEX)、`msg.buffer`(原始Buffer)、`msg.meta`(来源信息)
593
+
594
+ ### v2.6.6 重要更新
595
+
596
+ - 调试节点配置验证修复:根据所选数据来源类型(`serial`/`modbus`)动态校验,避免误报为“配置不正确”
597
+ - 统一侧边栏分类:所有节点统一归类到 `SYMI-MODBUS`
598
+ - 文档更新:补充节点分类、调试节点要点与常见问题处理
599
+
600
+ 典型用法:
601
+ - 联调TCP转RS485网关时,观察上行/下行原始数据帧是否完整。
602
+ - 排查波特率/数据位/校验位配置是否正确(串口模式)。
603
+ - 与 `modbus-master` 或 `modbus-slave-switch` 同时使用,定位现场设备通信异常。
604
+
605
+ ### MQTT自动发现
606
+
607
+ 启用MQTT后,自动生成Home Assistant兼容的Discovery配置:
608
+ - **唯一性保证**:每个实体使用稳定的`unique_id`,避免重复生成
609
+ - **设备分组**:同一从站的所有继电器自动分组到一个设备下
610
+ - **状态持久化**:使用`retain=true`确保状态持久化
611
+ - **在线状态**:自动发布设备可用性状态
612
+
613
+ ### 配置持久化
614
+
615
+ 所有节点配置自动保存到Node-RED的flows文件中:
616
+ - 从站地址、线圈范围、轮询间隔
617
+ - MQTT服务器配置
618
+ - 开关面板映射关系
619
+
620
+ 部署后配置永久生效,重启Node-RED后自动恢复。
621
+
622
+ ### 长期稳定运行
623
+
624
+ 针对工控机24/7长期运行优化:
625
+ - **内存管理**:自动清理缓存,释放无用对象
626
+ - **事件监听器清理**:关闭时移除所有监听器,防止内存泄漏
627
+ - **智能日志限流**:错误日志10分钟输出一次,避免日志刷屏
628
+ - **智能重连机制**:
629
+ - Modbus连接断开自动重连(指数退避:5秒→10秒→20秒...最大60秒)
630
+ - MQTT连接断开自动重连(支持多地址fallback)
631
+ - 串口拔插自动检测并重连
632
+ - TCP网络故障自动恢复
633
+ - 连接前彻底清理旧实例,避免资源泄漏
634
+ - **互斥锁机制**:防止读写冲突导致的数据异常
635
+ - **Keep-Alive心跳**:TCP连接启用30秒心跳检测
636
+
637
+ ## 技术规格
638
+
639
+ ### Modbus协议
640
+
641
+ - **协议类型**:Modbus TCP / Modbus RTU
642
+ - **底层库**:modbus-serial ^8.0.23(内部封装serialport,支持TCP和串口)
643
+ - **功能码支持**:0x01(读线圈)、0x05(写单个线圈)、0x0F(写多个线圈)
644
+ - **从站地址范围**:1-247(建议从10开始)
645
+ - **线圈数量**:每台设备32个(0-31)
646
+ - **最大设备数**:10台同时轮询
647
+ - **轮询间隔**:默认200ms(建议300-500ms,支持100-10000ms)
648
+ - **串口配置**:9600 8-N-1(波特率9600,8数据位,无校验,1停止位)
649
+ - **超时设置**:5000ms(TCP和串口通用)
650
+
651
+ ### 兼容性
652
+
653
+ - **Node.js**: >= 14.0.0
654
+ - **Node-RED**: >= 2.0.0
655
+ - **MQTT Broker**: Mosquitto / EMQX / Any MQTT 3.1.1/5.0
656
+ - **Home Assistant**: 2024.x+(MQTT Discovery标准)
657
+ - **操作系统**: Windows / Linux / macOS / HassOS
658
+
659
+ ## Home Assistant集成
660
+
661
+ ### 自动发现
662
+
663
+ 启用MQTT后,设备自动出现在Home Assistant中:
664
+ - 实体ID: `switch.relay_{从站地址}_{线圈编号}`
665
+ - 设备名称: `Modbus继电器-{从站地址}`
666
+ - 自动分组: 同一从站的所有继电器分组到一个设备
667
+
668
+ ### MQTT主题结构
669
+
670
+ ```
671
+ 状态主题: modbus/relay/{从站}/{线圈}/state
672
+ 命令主题: modbus/relay/{从站}/{线圈}/set
673
+ 可用性主题: modbus/relay/{从站}/availability
674
+ 发现主题: homeassistant/switch/modbus_relay_{从站}_{线圈}/config
675
+ ```
676
+
677
+ ## 故障排除
678
+
679
+ ### 串口连接失败
680
+
681
+ **Linux**:
682
+ ```bash
683
+ # 查看串口设备
684
+ ls -l /dev/ttyUSB* /dev/ttyS*
685
+
686
+ # 添加用户到dialout组(需要重新登录)
687
+ sudo usermod -a -G dialout $USER
688
+ ```
689
+
690
+ **macOS**:
691
+ ```bash
692
+ # 查看串口设备(注意macOS使用cu.*而不是tty.*)
693
+ ls -l /dev/cu.*
694
+ ```
695
+
696
+ **Docker/HassOS**:
697
+ ```yaml
698
+ # 在docker-compose.yml或HassOS插件配置中添加设备映射
699
+ devices:
700
+ - /dev/ttyUSB0:/dev/ttyUSB0
701
+ ```
702
+
703
+ ### MQTT连接失败
704
+
705
+ 1. 确认MQTT broker正在运行:
706
+ ```bash
707
+ # Linux
708
+ sudo systemctl status mosquitto
709
+
710
+ # macOS
711
+ brew services list | grep mosquitto
712
+ ```
713
+
714
+ 2. 测试MQTT连接:
715
+ ```bash
716
+ mosquitto_sub -h localhost -t test
717
+ ```
718
+
719
+ 3. 检查Node-RED日志中的MQTT连接信息
720
+
721
+ ### 主站轮询不工作
722
+
723
+ 1. **检查从站配置**:确认已添加所有从站设备(如10、11、12、13)
724
+ 2. **检查轮询间隔**:默认200ms,建议300-500ms(多台从站时避免总线拥堵)
725
+ 3. **查看Node-RED调试日志**:部署后查看日志中的轮询信息
726
+ 4. **检查串口波特率**:确认波特率为9600(与从站设备一致)
727
+ 5. **检查从站地址**:确认从站地址正确(1-247)
728
+ 6. **确认从站设备在线**:使用Modbus调试工具测试从站是否响应
729
+ 7. **检查MQTT连接**:确保MQTT broker地址正确,轮询不依赖MQTT但状态发布需要MQTT
730
+ 8. **测试连接**:
731
+ - TCP连接问题:先用 `modbus-serial` 单独测试TCP连接
732
+ - 串口问题:先用 `serialport` 单独测试串口通信
733
+
734
+ ### 从站开关无响应
735
+
736
+ 1. 检查RS-485连接是否正常
737
+ 2. 确认开关面板地址和按钮编号正确
738
+ 3. 检查MQTT连接状态
739
+ 4. 查看Node-RED日志中的协议解析信息
740
+
741
+ ## 输入消息格式
742
+
743
+ ### 主站节点
744
+
745
+ ```javascript
746
+ // 启动轮询
747
+ msg.payload = {cmd: "start"};
748
+
749
+ // 停止轮询
750
+ msg.payload = {cmd: "stop"};
751
+
752
+ // 写单个线圈
753
+ msg.payload = {
754
+ cmd: "writeCoil",
755
+ slave: 10, // 从站地址
756
+ coil: 0, // 线圈编号
757
+ value: true // true=开, false=关
758
+ };
759
+
760
+ // 批量写多个线圈
761
+ msg.payload = {
762
+ cmd: "writeCoils",
763
+ slave: 10, // 从站地址
764
+ startCoil: 0, // 起始线圈
765
+ values: [true, false, true, false] // 线圈值数组
766
+ };
767
+ ```
768
+
769
+ ### 从站开关节点
770
+
771
+ ```javascript
772
+ // 发送开关命令
773
+ msg.payload = true; // 或 false
774
+ msg.payload = "ON"; // 或 "OFF"
775
+ msg.payload = 1; // 或 0
776
+ ```
777
+
778
+ ## 输出消息格式
779
+
780
+ ### 主站节点
781
+
782
+ ```javascript
783
+ {
784
+ payload: {
785
+ slave: 10, // 从站地址
786
+ coils: [true, false, ...], // 线圈状态数组
787
+ timestamp: 1234567890 // 时间戳
788
+ }
789
+ }
790
+ ```
791
+
792
+ ### 从站开关节点
793
+
794
+ ```javascript
795
+ {
796
+ payload: true, // 开关状态
797
+ topic: "switch_0_btn1", // 主题
798
+ switchId: 0, // 开关面板ID
799
+ button: 1, // 按钮编号
800
+ targetSlave: 10, // 目标从站地址
801
+ targetCoil: 0 // 目标线圈编号
802
+ }
803
+ ```
804
+
805
+ ## 性能指标
806
+
807
+ - **内存占用**:< 50MB(单个主站节点,轮询10个设备)
808
+ - **CPU占用**:< 5%(正常轮询状态)
809
+ - **连接延迟**:Modbus响应 < 100ms,MQTT发布 < 50ms
810
+ - **稳定运行**:经过工控机7x24小时长期运行验证
811
+ - **容错能力**:Modbus从站离线不影响其他从站,MQTT断线自动重连
812
+
813
+ ## 示例Flow
814
+
815
+ ```json
816
+ [
817
+ {
818
+ "id": "modbus-master-1",
819
+ "type": "modbus-master",
820
+ "name": "主站",
821
+ "connectionType": "serial",
822
+ "serialPort": "/dev/ttyUSB0",
823
+ "serialBaudRate": 9600,
824
+ "slaves": [
825
+ {"address": 10, "coilStart": 0, "coilEnd": 31, "pollInterval": 200},
826
+ {"address": 11, "coilStart": 0, "coilEnd": 31, "pollInterval": 200},
827
+ {"address": 12, "coilStart": 0, "coilEnd": 31, "pollInterval": 200},
828
+ {"address": 13, "coilStart": 0, "coilEnd": 31, "pollInterval": 200}
829
+ ],
830
+ "enableMqtt": true,
831
+ "mqttServer": "mqtt-config-1"
832
+ }
833
+ ]
834
+ ```
835
+
836
+ ## 项目信息
837
+
838
+ **版本**: v2.6.6
839
+
840
+ **核心功能**:
841
+ - 支持多种Modbus协议(Telnet ASCII、RTU over TCP、Modbus TCP、Modbus RTU串口)
842
+ - 多设备轮询(最多10台从站,每台32路继电器,轮询间隔100-10000ms可调)
843
+ - Symi私有协议自动识别(支持两种485开关控制方式)
844
+ - 智能轮询暂停机制(从站上报时自动暂停,处理完成后恢复)
845
+ - 🔥 **双模式支持**(本地模式和MQTT模式可选切换,断网也能稳定运行)
846
+ - MQTT集成(可选启用,Home Assistant自动发现,实体唯一性保证,QoS=0高性能发布)
847
+ - 物理开关面板双向同步(亖米协议支持,LED反馈同步)
848
+ - 共享连接架构(多个从站开关节点共享同一个串口/TCP连接,支持500+节点)
849
+ - 长期稳定运行(内存管理、智能重连、错误日志限流、异步MQTT发布)
850
+
851
+ **技术栈**:
852
+ - modbus-serial: ^8.0.23(内部封装serialport,支持TCP和串口)
853
+ - serialport: ^12.0.0(原生串口通信)
854
+ - mqtt: ^5.14.1(最新稳定版,可选依赖)
855
+ - Node.js: >=14.0.0
856
+ - Node-RED: >=2.0.0
@@ -0,0 +1,95 @@
1
+ <script type="text/javascript">
2
+ RED.nodes.registerType('modbus-debug', {
3
+ category: 'SYMI-MODBUS',
4
+ color: '#C0DEED',
5
+ defaults: {
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
+ }
27
+ },
28
+ uppercase: { value: true },
29
+ includeTimestamp: { value: true },
30
+ maxBytes: { value: 0 }
31
+ },
32
+ inputs: 1,
33
+ outputs: 1,
34
+ 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() {}
51
+ });
52
+ </script>
53
+
54
+ <script type="text/x-red" data-template-name="modbus-debug">
55
+ <div class="form-row">
56
+ <label for="node-input-name"><i class="fa fa-tag"></i> 名称</label>
57
+ <input type="text" id="node-input-name" placeholder="原始485调试">
58
+ </div>
59
+ <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
+ <label for="node-input-serialPortConfig"><i class="fa fa-plug"></i> 串口配置</label>
68
+ <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">
73
+ </div>
74
+ <div class="form-row">
75
+ <label for="node-input-uppercase"><i class="fa fa-text-height"></i> 大写HEX</label>
76
+ <input type="checkbox" id="node-input-uppercase" style="width:auto" checked>
77
+ </div>
78
+ <div class="form-row">
79
+ <label for="node-input-includeTimestamp"><i class="fa fa-clock-o"></i> 包含时间戳</label>
80
+ <input type="checkbox" id="node-input-includeTimestamp" style="width:auto" checked>
81
+ </div>
82
+ <div class="form-row">
83
+ <label for="node-input-maxBytes"><i class="fa fa-filter"></i> 显示最大字节数 (0为不限制)</label>
84
+ <input type="number" id="node-input-maxBytes" min="0" step="1" placeholder="0">
85
+ </div>
86
+ </script>
87
+
88
+ <script type="text/x-red" data-help-name="modbus-debug">
89
+ <p>modbus-debug 节点用于抓取并显示原始485字节流,以十六进制格式输出,帮助调试串口或TCP网关下的Modbus通信。</p>
90
+ <ul>
91
+ <li><b>数据来源</b>:选择共享的 <code>serial-port-config</code>(推荐,复用主站/从站的同一连接),或选择 <code>modbus-server-config</code>(独立直连TCP/串口)。</li>
92
+ <li><b>输出</b>:<code>msg.payload</code> 为格式化HEX字符串;<code>msg.buffer</code>为原始Buffer;<code>msg.meta</code>包含来源与连接信息;若开启则包含 <code>msg.timestamp</code>。</li>
93
+ <li><b>显示最大字节数</b>:为避免控制台过载,可设置最大显示长度,超过则截断并在 meta.truncated 标记。</li>
94
+ </ul>
95
+ </script>
@@ -0,0 +1,199 @@
1
+ module.exports = function(RED) {
2
+ "use strict";
3
+
4
+ const net = require("net");
5
+ let SerialPort;
6
+ try { SerialPort = require("serialport").SerialPort || require("serialport"); } catch (e) { SerialPort = null; }
7
+
8
+ function bufferToHex(buffer, uppercase) {
9
+ const hex = buffer.toString("hex");
10
+ const grouped = hex.match(/.{1,2}/g) || [];
11
+ const str = grouped.join(" ");
12
+ return uppercase ? str.toUpperCase() : str;
13
+ }
14
+
15
+ function ModbusDebugNode(config) {
16
+ RED.nodes.createNode(this, config);
17
+ const node = this;
18
+
19
+ node.name = config.name || "原始485调试";
20
+ node.sourceType = config.sourceType || "serial"; // serial | modbus
21
+ node.serialPortConfig = RED.nodes.getNode(config.serialPortConfig);
22
+ node.modbusServer = RED.nodes.getNode(config.modbusServer);
23
+ node.uppercase = config.uppercase !== false; // 默认大写显示
24
+ node.includeTimestamp = config.includeTimestamp !== false; // 默认包含时间戳
25
+ node.maxBytes = parseInt(config.maxBytes) || 0; // 0 表示不截断
26
+
27
+ // 本地独立连接(当来源选择 modbus-server-config 时使用)
28
+ node.localConnection = null;
29
+ node.localConnType = null; // tcp | serial
30
+
31
+ const sendHexMsg = (data) => {
32
+ if (!data || !Buffer.isBuffer(data) || data.length === 0) return;
33
+
34
+ let buf = data;
35
+ if (node.maxBytes > 0 && buf.length > node.maxBytes) {
36
+ buf = buf.subarray(0, node.maxBytes);
37
+ }
38
+
39
+ const hex = bufferToHex(buf, node.uppercase);
40
+ const meta = {
41
+ length: data.length,
42
+ displayedLength: buf.length,
43
+ truncated: node.maxBytes > 0 && data.length > node.maxBytes,
44
+ };
45
+
46
+ // 来源信息
47
+ if (node.sourceType === "serial" && node.serialPortConfig) {
48
+ meta.source = "serial-port-config";
49
+ meta.connectionType = node.serialPortConfig.connectionType;
50
+ if (node.serialPortConfig.connectionType === "tcp") {
51
+ meta.tcpHost = node.serialPortConfig.tcpHost;
52
+ meta.tcpPort = node.serialPortConfig.tcpPort;
53
+ } else {
54
+ meta.serialPort = node.serialPortConfig.serialPort;
55
+ meta.baudRate = node.serialPortConfig.baudRate;
56
+ meta.dataBits = node.serialPortConfig.dataBits;
57
+ meta.stopBits = node.serialPortConfig.stopBits;
58
+ meta.parity = node.serialPortConfig.parity;
59
+ }
60
+ } else if (node.sourceType === "modbus" && node.modbusServer) {
61
+ meta.source = "modbus-server-config";
62
+ meta.connectionType = node.modbusServer.connectionType;
63
+ if (node.modbusServer.connectionType === "tcp") {
64
+ meta.tcpHost = node.modbusServer.tcpHost;
65
+ meta.tcpPort = node.modbusServer.tcpPort;
66
+ } else {
67
+ meta.serialPort = node.modbusServer.serialPort;
68
+ meta.baudRate = node.modbusServer.serialBaudRate;
69
+ meta.dataBits = node.modbusServer.serialDataBits;
70
+ meta.stopBits = node.modbusServer.serialStopBits;
71
+ meta.parity = node.modbusServer.serialParity;
72
+ }
73
+ }
74
+
75
+ const msg = { payload: hex, buffer: data, meta };
76
+ if (node.includeTimestamp) msg.timestamp = Date.now();
77
+
78
+ node.send(msg);
79
+ node.status({ fill: "green", shape: "dot", text: `RX ${data.length}B` });
80
+ };
81
+
82
+ // 选择来源:共享串口配置 或 独立连接到 Modbus 服务器配置
83
+ const startListening = () => {
84
+ if (node.sourceType === "serial") {
85
+ if (!node.serialPortConfig) {
86
+ node.status({ fill: "red", shape: "ring", text: "未选择串口配置" });
87
+ node.error("未配置RS-485连接,请在节点中选择 serial-port-config 配置节点");
88
+ return;
89
+ }
90
+ // 使用共享连接
91
+ try {
92
+ node.serialPortConfig.registerDataListener(sendHexMsg);
93
+ const desc = node.serialPortConfig.connectionType === "tcp"
94
+ ? `TCP ${node.serialPortConfig.tcpHost}:${node.serialPortConfig.tcpPort}`
95
+ : `串口 ${node.serialPortConfig.serialPort} @ ${node.serialPortConfig.baudRate}bps`;
96
+ node.log(`modbus-debug 监听共享连接:${desc}`);
97
+ node.status({ fill: "blue", shape: "ring", text: "监听中" });
98
+ } catch (err) {
99
+ node.error(`注册数据监听器失败: ${err.message}`);
100
+ node.status({ fill: "red", shape: "ring", text: "监听失败" });
101
+ }
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
+ }
165
+ }
166
+ };
167
+
168
+ startListening();
169
+
170
+ // 输入不做处理(预留写入功能),当前为纯监听输出节点
171
+ node.on("input", function(msg, send, done) { done(); });
172
+
173
+ // 关闭时清理
174
+ node.on("close", function(done) {
175
+ try {
176
+ if (node.sourceType === "serial" && node.serialPortConfig && typeof node.serialPortConfig.unregisterDataListener === "function") {
177
+ node.serialPortConfig.unregisterDataListener(sendHexMsg);
178
+ }
179
+ } catch (e) {
180
+ node.warn(`注销监听器时出错: ${e.message}`);
181
+ }
182
+ try {
183
+ if (node.localConnection) {
184
+ if (node.localConnType === "tcp") {
185
+ try { node.localConnection.destroy(); } catch (e) { /* ignore */ }
186
+ } else if (node.localConnType === "serial") {
187
+ try { node.localConnection.close(); } catch (e) { /* ignore */ }
188
+ }
189
+ node.localConnection = null;
190
+ }
191
+ } catch (e) {
192
+ node.warn(`关闭本地连接时出错: ${e.message}`);
193
+ }
194
+ done();
195
+ });
196
+ }
197
+
198
+ RED.nodes.registerType("modbus-debug", ModbusDebugNode);
199
+ };
@@ -1,6 +1,6 @@
1
1
  <script type="text/javascript">
2
2
  RED.nodes.registerType('modbus-master', {
3
- category: 'modbus',
3
+ category: 'SYMI-MODBUS',
4
4
  color: '#3FADB5',
5
5
  defaults: {
6
6
  name: {value: "Modbus主站"},
@@ -35,8 +35,6 @@
35
35
  }];
36
36
  }
37
37
 
38
- // 移除旧的连接类型切换逻辑(现在使用服务器配置节点)
39
-
40
38
  // 渲染从站列表
41
39
  function renderSlaveList() {
42
40
  var container = $("#slave-list-container");
@@ -79,7 +77,6 @@
79
77
  </div>
80
78
  `);
81
79
 
82
- // 添加hover效果
83
80
  slaveRow.hover(
84
81
  function() { $(this).css("box-shadow", "0 4px 12px rgba(0,0,0,0.15)"); },
85
82
  function() { $(this).css("box-shadow", "0 2px 6px rgba(0,0,0,0.08)"); }
@@ -88,33 +85,28 @@
88
85
  container.append(slaveRow);
89
86
  });
90
87
 
91
- // 更新删除按钮状态(至少保留1个从站)
92
88
  if (node.slaves.length === 1) {
93
89
  $(".btn-delete-slave").prop("disabled", true).css("opacity", "0.4").css("cursor", "not-allowed");
94
90
  }
95
91
 
96
- // 更新添加按钮状态(最多10个从站)
97
92
  if (node.slaves.length >= 10) {
98
93
  $("#btn-add-slave").prop("disabled", true).css("opacity", "0.5").css("cursor", "not-allowed");
99
94
  } else {
100
95
  $("#btn-add-slave").prop("disabled", false).css("opacity", "1").css("cursor", "pointer");
101
96
  }
102
97
 
103
- // 添加按钮hover效果
104
98
  $("#btn-add-slave").hover(
105
99
  function() { if (!$(this).prop("disabled")) $(this).css("background", "linear-gradient(135deg, #66bb6a 0%, #5cb85c 100%)"); },
106
100
  function() { if (!$(this).prop("disabled")) $(this).css("background", "linear-gradient(135deg, #5cb85c 0%, #4cae4c 100%)"); }
107
101
  );
108
102
  }
109
103
 
110
- // 添加从站
111
104
  $("#btn-add-slave").on("click", function() {
112
105
  if (node.slaves.length >= 10) {
113
106
  RED.notify("最多只能添加10个从站", "warning");
114
107
  return;
115
108
  }
116
109
 
117
- // 计算下一个从站地址(递增)
118
110
  var lastAddress = node.slaves[node.slaves.length - 1].address;
119
111
  var nextAddress = lastAddress + 1;
120
112
  if (nextAddress > 247) nextAddress = 1;
@@ -129,7 +121,6 @@
129
121
  renderSlaveList();
130
122
  });
131
123
 
132
- // 删除从站
133
124
  $(document).on("click", ".btn-delete-slave", function() {
134
125
  var index = parseInt($(this).data("index"));
135
126
  if (node.slaves.length > 1) {
@@ -140,7 +131,6 @@
140
131
  }
141
132
  });
142
133
 
143
- // 更新从站数据
144
134
  $(document).on("change", ".slave-address, .slave-coil-start, .slave-coil-end, .slave-poll-interval", function() {
145
135
  var index = parseInt($(this).data("index"));
146
136
  var className = $(this).attr("class").split(" ")[0];
@@ -162,10 +152,8 @@
162
152
  }
163
153
  });
164
154
 
165
- // 初始化渲染
166
155
  renderSlaveList();
167
156
 
168
- // MQTT开关
169
157
  $("#node-input-enableMqtt").on("change", function() {
170
158
  if ($(this).is(":checked")) {
171
159
  $(".form-row-mqtt").show();
@@ -174,24 +162,20 @@
174
162
  }
175
163
  });
176
164
 
177
- // 初始化MQTT显示
178
165
  $("#node-input-enableMqtt").trigger("change");
179
166
  },
180
167
  oneditsave: function() {
181
- // 保存从站配置到节点
182
168
  this.slaves = this.slaves || [];
183
169
  }
184
170
  });
185
171
  </script>
186
172
 
187
173
  <script type="text/html" data-template-name="modbus-master">
188
- <!-- 基本配置 -->
189
174
  <div class="form-row">
190
175
  <label for="node-input-name"><i class="fa fa-tag"></i> 名称</label>
191
176
  <input type="text" id="node-input-name" placeholder="Modbus主站">
192
177
  </div>
193
178
 
194
- <!-- Modbus服务器配置 -->
195
179
  <div class="form-row">
196
180
  <label for="node-input-modbusServer"><i class="fa fa-server"></i> Modbus服务器</label>
197
181
  <input type="text" id="node-input-modbusServer" placeholder="选择或添加Modbus服务器">
@@ -389,8 +389,8 @@ module.exports = function(RED) {
389
389
  let currentCandidateIndex = 0;
390
390
  let lastConnectAttempt = 0;
391
391
 
392
- node.log(`MQTT broker候选地址: ${brokerCandidates.join(', ')}`);
393
- node.log(`正在连接MQTT broker: ${brokerCandidates[0]}`);
392
+ node.debug(`MQTT broker候选地址: ${brokerCandidates.join(', ')}`);
393
+ node.debug(`正在连接MQTT broker: ${brokerCandidates[0]}`);
394
394
 
395
395
  const options = {
396
396
  clientId: `modbus_master_${Math.random().toString(16).substring(2, 10)}`,
@@ -403,7 +403,7 @@ module.exports = function(RED) {
403
403
  if (node.config.mqttUsername) {
404
404
  options.username = node.config.mqttUsername;
405
405
  options.password = node.config.mqttPassword;
406
- node.log(`MQTT认证: 用户名=${node.config.mqttUsername}`);
406
+ node.debug(`MQTT认证: 用户名=${node.config.mqttUsername}`);
407
407
  }
408
408
 
409
409
  // 尝试连接函数
@@ -427,7 +427,7 @@ module.exports = function(RED) {
427
427
 
428
428
  // 成功连接后,更新配置的broker地址(下次优先使用成功的地址)
429
429
  if (brokerUrl !== brokerCandidates[0]) {
430
- node.log(`使用fallback地址成功: ${brokerUrl}(原配置: ${brokerCandidates[0]})`);
430
+ node.debug(`使用fallback地址成功: ${brokerUrl}(原配置: ${brokerCandidates[0]})`);
431
431
  }
432
432
 
433
433
  // 异步发送设备发现消息(避免阻塞事件循环)
@@ -491,7 +491,7 @@ module.exports = function(RED) {
491
491
 
492
492
  // 5秒后重试第一个地址
493
493
  setTimeout(() => {
494
- node.log('重试连接MQTT broker...');
494
+ node.debug('重试连接MQTT broker...');
495
495
  tryConnect(brokerCandidates[0]);
496
496
  }, 5000);
497
497
  } else {
@@ -1,12 +1,13 @@
1
1
  <script type="text/javascript">
2
2
  RED.nodes.registerType('modbus-slave-switch', {
3
- category: 'modbus',
3
+ category: 'SYMI-MODBUS',
4
4
  color: '#E9967A',
5
5
  defaults: {
6
6
  name: {value: "从站开关"},
7
7
  // RS-485连接配置(共享配置节点)
8
8
  serialPortConfig: {value: "", type: "serial-port-config", required: true},
9
9
  // MQTT配置(可选)
10
+ enableMqtt: {value: false}, // 默认不启用MQTT
10
11
  mqttServer: {value: "", type: "mqtt-server-config", required: false},
11
12
  // 开关面板配置
12
13
  switchBrand: {value: "symi"}, // 品牌选择
@@ -24,6 +25,19 @@
24
25
  // 显示时直接使用用户输入的路数(1-32)
25
26
  const coilDisplay = this.targetCoilNumber || 1;
26
27
  return this.name || `开关${this.switchId}-按钮${this.buttonNumber} → 继电器${this.targetSlaveAddress}-${coilDisplay}路`;
28
+ },
29
+ oneditprepare: function() {
30
+ // MQTT开关控制显示/隐藏
31
+ $("#node-input-enableMqtt").on("change", function() {
32
+ if ($(this).is(":checked")) {
33
+ $(".form-row-mqtt-slave").show();
34
+ } else {
35
+ $(".form-row-mqtt-slave").hide();
36
+ }
37
+ });
38
+
39
+ // 初始化显示状态
40
+ $("#node-input-enableMqtt").trigger("change");
27
41
  }
28
42
  });
29
43
  </script>
@@ -64,13 +78,19 @@
64
78
  <span style="margin-left: 8px; padding: 2px 8px; background: #e3f2fd; color: #1976d2; border-radius: 3px; font-size: 11px; font-weight: 500;">可选</span>
65
79
  </label>
66
80
  <div style="font-size: 11px; color: #555; padding: 10px 12px; background: linear-gradient(135deg, #e3f2fd 0%, #f0f7ff 100%); border-left: 4px solid #2196f3; border-radius: 4px; box-shadow: 0 1px 3px rgba(0,0,0,0.08);">
67
- <strong>💡 提示:</strong>不配置MQTT则使用<strong>本地模式</strong>(纯串口通信),配置MQTT则使用<strong>MQTT模式</strong>(可接入Home Assistant等平台)
81
+ <strong>💡 提示:</strong>不启用MQTT则使用<strong>本地模式</strong>(通过连线控制),启用MQTT则使用<strong>MQTT模式</strong>(可接入Home Assistant
68
82
  </div>
69
83
  </div>
70
84
 
71
85
  <div class="form-row">
86
+ <label for="node-input-enableMqtt" style="width: 110px;"><i class="fa fa-toggle-on"></i> 启用MQTT</label>
87
+ <input type="checkbox" id="node-input-enableMqtt" style="width: auto; margin-left: 5px; transform: scale(1.3); cursor: pointer;">
88
+ <span style="margin-left: 15px; font-size: 11px; color: #666; font-style: italic;">勾选后切换到MQTT模式</span>
89
+ </div>
90
+
91
+ <div class="form-row form-row-mqtt-slave">
72
92
  <label for="node-input-mqttServer" style="width: 110px;"><i class="fa fa-server"></i> MQTT服务器</label>
73
- <input type="text" id="node-input-mqttServer" placeholder="" style="width: calc(70% - 110px);">
93
+ <input type="text" id="node-input-mqttServer" placeholder="选择MQTT服务器配置" style="width: calc(70% - 110px);">
74
94
  <div style="font-size: 11px; color: #888; margin-left: 110px; margin-top: 3px;">
75
95
  选择已配置的MQTT服务器(需与主站节点使用同一配置)
76
96
  </div>
@@ -79,8 +79,9 @@ module.exports = function(RED) {
79
79
 
80
80
  // 配置参数
81
81
  node.config = {
82
- // MQTT配置节点读取MQTT配置
83
- mqttBroker: node.mqttServerConfig ? node.mqttServerConfig.broker : "mqtt://localhost:1883",
82
+ // MQTT配置
83
+ enableMqtt: config.enableMqtt !== undefined ? config.enableMqtt : false, // 是否启用MQTT(默认false)
84
+ mqttBroker: node.mqttServerConfig ? node.mqttServerConfig.broker : "",
84
85
  mqttUsername: node.mqttServerConfig ? node.mqttServerConfig.username : "",
85
86
  mqttPassword: node.mqttServerConfig ? (node.mqttServerConfig.credentials ? node.mqttServerConfig.credentials.password : "") : "",
86
87
  mqttBaseTopic: node.mqttServerConfig ? node.mqttServerConfig.baseTopic : "modbus/relay",
@@ -164,7 +165,7 @@ module.exports = function(RED) {
164
165
 
165
166
  // 如果是局域网IP,不启用fallback(用户明确指定了IP)
166
167
  if (isLanIp) {
167
- node.log(`检测到局域网IP配置,将直接连接到 ${configuredBroker},不启用fallback`);
168
+ node.debug(`检测到局域网IP配置 ${host},只使用此地址`);
168
169
  return candidates;
169
170
  }
170
171
 
@@ -509,27 +510,27 @@ module.exports = function(RED) {
509
510
  node.updateStatus = function() {
510
511
  const rs485Status = node.isRs485Connected ? 'OK' : 'ERR';
511
512
  const state = node.currentState ? 'ON' : 'OFF';
512
- const hasMqttConfig = node.config.mqttBroker && node.config.mqttBroker.trim() !== '';
513
+ const mqttEnabled = node.config.enableMqtt === true;
513
514
  const mqttConnected = node.mqttClient && node.mqttClient.connected;
514
515
 
515
516
  // RS485连接正常
516
517
  if (node.isRs485Connected) {
517
- if (!hasMqttConfig) {
518
- // 本地模式(未配置MQTT)
518
+ if (!mqttEnabled) {
519
+ // 本地模式(未启用MQTT)
519
520
  node.status({
520
521
  fill: node.currentState ? "green" : "blue",
521
522
  shape: "dot",
522
- text: `本地模式 RS485-${rs485Status} ${state}`
523
+ text: `本地模式 ${state}`
523
524
  });
524
525
  } else if (mqttConnected) {
525
- // MQTT模式(已配置且已连接)
526
+ // MQTT模式(已启用且已连接)
526
527
  node.status({
527
528
  fill: node.currentState ? "green" : "grey",
528
529
  shape: "dot",
529
530
  text: `MQTT模式 ${state}`
530
531
  });
531
532
  } else {
532
- // MQTT配置但未连接(使用本地模式)
533
+ // MQTT已启用但未连接(使用本地模式)
533
534
  node.status({
534
535
  fill: node.currentState ? "green" : "blue",
535
536
  shape: "ring",
@@ -548,10 +549,17 @@ module.exports = function(RED) {
548
549
 
549
550
  // 连接MQTT(带智能重试和fallback)
550
551
  node.connectMqtt = function() {
552
+ // 检查是否启用MQTT
553
+ if (!node.config.enableMqtt) {
554
+ node.log('MQTT未启用 - 使用本地模式(通过Node-RED连线控制)');
555
+ node.log('提示:将此节点连线到主站节点,即可实现本地控制');
556
+ return;
557
+ }
558
+
551
559
  // 验证MQTT broker配置
552
560
  if (!node.config.mqttBroker || node.config.mqttBroker.trim() === '') {
553
- node.log('MQTT未配置 - 使用本地模式(通过Node-RED连线控制)');
554
- node.log('提示:将此节点连线到主站节点,即可实现本地控制');
561
+ node.warn('MQTT已启用但broker地址未配置 - 使用本地模式');
562
+ node.warn('提示:请在MQTT服务器配置节点中设置broker地址,或禁用MQTT功能');
555
563
  return;
556
564
  }
557
565
 
@@ -560,8 +568,8 @@ module.exports = function(RED) {
560
568
  let currentCandidateIndex = 0;
561
569
  let lastConnectAttempt = 0;
562
570
 
563
- node.log(`MQTT broker候选地址: ${brokerCandidates.join(', ')}`);
564
- node.log(`正在连接MQTT broker: ${brokerCandidates[0]}`);
571
+ node.debug(`MQTT broker候选地址: ${brokerCandidates.join(', ')}`);
572
+ node.debug(`正在连接MQTT broker: ${brokerCandidates[0]}`);
565
573
 
566
574
  const options = {
567
575
  clientId: `modbus_switch_${node.id}`,
@@ -574,7 +582,7 @@ module.exports = function(RED) {
574
582
  if (node.config.mqttUsername) {
575
583
  options.username = node.config.mqttUsername;
576
584
  options.password = node.config.mqttPassword;
577
- node.log(`MQTT认证: 用户名=${node.config.mqttUsername}`);
585
+ node.debug(`MQTT认证: 用户名=${node.config.mqttUsername}`);
578
586
  }
579
587
 
580
588
  // 尝试连接函数
@@ -598,7 +606,7 @@ module.exports = function(RED) {
598
606
 
599
607
  // 成功连接后,更新配置的broker地址(下次优先使用成功的地址)
600
608
  if (brokerUrl !== brokerCandidates[0]) {
601
- node.log(`使用fallback地址成功: ${brokerUrl}(原配置: ${brokerCandidates[0]})`);
609
+ node.debug(`使用fallback地址成功: ${brokerUrl}(原配置: ${brokerCandidates[0]})`);
602
610
  }
603
611
 
604
612
  node.updateStatus();
@@ -662,7 +670,7 @@ module.exports = function(RED) {
662
670
 
663
671
  // 5秒后重试第一个地址
664
672
  setTimeout(() => {
665
- node.log('重试连接MQTT broker...');
673
+ node.debug('重试连接MQTT broker...');
666
674
  tryConnect(brokerCandidates[0]);
667
675
  }, 5000);
668
676
  } else {
package/package.json CHANGED
@@ -1,55 +1,59 @@
1
- {
2
- "name": "node-red-contrib-symi-modbus",
3
- "version": "2.6.4",
4
- "description": "Node-RED Modbus节点,支持TCP/串口通信、串口自动搜索、多设备轮询、可选MQTT集成(支持纯本地模式和MQTT模式)、Home Assistant自动发现和物理开关面板双向同步,工控机长期稳定运行",
5
- "main": "nodes/modbus-master.js",
6
- "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1"
8
- },
9
- "keywords": [
10
- "node-red",
11
- "modbus",
12
- "modbus-tcp",
13
- "modbus-rtu",
14
- "relay",
15
- "mqtt",
16
- "automation",
17
- "home-assistant",
18
- "smart-home",
19
- "iot",
20
- "industrial",
21
- "plc"
22
- ],
23
- "author": {
24
- "name": "symi-daguo",
25
- "email": "symi@example.com"
26
- },
27
- "license": "MIT",
28
- "engines": {
29
- "node": ">=14.0.0",
30
- "npm": ">=6.0.0"
31
- },
32
- "node-red": {
33
- "version": ">=2.0.0",
34
- "nodes": {
35
- "modbus-master": "nodes/modbus-master.js",
36
- "modbus-slave-switch": "nodes/modbus-slave-switch.js",
37
- "modbus-server-config": "nodes/modbus-server-config.js",
38
- "mqtt-server-config": "nodes/mqtt-server-config.js",
39
- "serial-port-config": "nodes/serial-port-config.js"
40
- }
41
- },
42
- "dependencies": {
43
- "modbus-serial": "^8.0.23",
44
- "mqtt": "^5.14.1",
45
- "serialport": "^12.0.0"
46
- },
47
- "repository": {
48
- "type": "git",
49
- "url": "git+https://github.com/symi-daguo/node-red-contrib-symi-modbus.git"
50
- },
51
- "bugs": {
52
- "url": "https://github.com/symi-daguo/node-red-contrib-symi-modbus/issues"
53
- },
54
- "homepage": "https://github.com/symi-daguo/node-red-contrib-symi-modbus#readme"
55
- }
1
+ {
2
+ "name": "node-red-contrib-symi-modbus",
3
+ "version": "2.6.6",
4
+ "description": "Node-RED Modbus节点,支持TCP/串口通信、串口自动搜索、多设备轮询、可选MQTT集成(支持纯本地模式和MQTT模式)、Home Assistant自动发现和物理开关面板双向同步,工控机长期稳定运行",
5
+ "main": "nodes/modbus-master.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "keywords": [
10
+ "node-red",
11
+ "modbus",
12
+ "modbus-tcp",
13
+ "modbus-rtu",
14
+ "relay",
15
+ "mqtt",
16
+ "automation",
17
+ "home-assistant",
18
+ "smart-home",
19
+ "iot",
20
+ "industrial",
21
+ "plc"
22
+ ],
23
+ "author": {
24
+ "name": "symi-daguo",
25
+ "email": "symi@example.com"
26
+ },
27
+ "license": "MIT",
28
+ "engines": {
29
+ "node": ">=14.0.0",
30
+ "npm": ">=6.0.0"
31
+ },
32
+ "node-red": {
33
+ "version": ">=2.0.0",
34
+ "nodes": {
35
+ "modbus-master": "nodes/modbus-master.js",
36
+ "modbus-slave-switch": "nodes/modbus-slave-switch.js",
37
+ "modbus-server-config": "nodes/modbus-server-config.js",
38
+ "mqtt-server-config": "nodes/mqtt-server-config.js",
39
+ "serial-port-config": "nodes/serial-port-config.js",
40
+ "modbus-debug": "nodes/modbus-debug.js"
41
+ }
42
+ },
43
+ "dependencies": {
44
+ "modbus-serial": "^8.0.23",
45
+ "mqtt": "^5.14.1",
46
+ "serialport": "^12.0.0"
47
+ },
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "git+https://github.com/symi-daguo/node-red-contrib-symi-modbus.git"
51
+ },
52
+ "bugs": {
53
+ "url": "https://github.com/symi-daguo/node-red-contrib-symi-modbus/issues"
54
+ },
55
+ "homepage": "https://github.com/symi-daguo/node-red-contrib-symi-modbus#readme",
56
+ "devDependencies": {
57
+ "node-red": "^4.1.1"
58
+ }
59
+ }