node-red-contrib-symi-modbus 1.6.6 → 2.0.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 +293 -12
- package/nodes/modbus-master.html +36 -13
- package/nodes/modbus-master.js +51 -11
- package/package.json +2 -2
- package/examples/basic-flow.json +0 -188
package/README.md
CHANGED
|
@@ -8,9 +8,82 @@ Node-RED的Modbus继电器控制节点,支持TCP/串口通信和MQTT集成。
|
|
|
8
8
|
- 多设备轮询:最多支持10台Modbus从站设备同时轮询
|
|
9
9
|
- 32路继电器:每台设备支持32个线圈(继电器通道)
|
|
10
10
|
- 灵活配置:可自定义轮询间隔、线圈范围、从站地址
|
|
11
|
-
- MQTT
|
|
11
|
+
- MQTT集成:作为MQTT客户端连接Broker,自动生成Home Assistant兼容的MQTT发现消息
|
|
12
12
|
- 实时状态:实时监控和控制继电器状态
|
|
13
13
|
- 主从模式:提供主站节点和从站控制节点
|
|
14
|
+
- 稳定可靠:完整的Promise错误处理,适合工控机长期稳定运行
|
|
15
|
+
|
|
16
|
+
## MQTT架构说明
|
|
17
|
+
|
|
18
|
+
本节点作为**MQTT客户端**,需要连接到外部**MQTT Broker服务端**(如Mosquitto):
|
|
19
|
+
|
|
20
|
+
**主站节点(modbus-master)**
|
|
21
|
+
- 作为MQTT客户端连接到MQTT Broker
|
|
22
|
+
- 发布状态主题:`modbus/relay/{从站}/{线圈}/state`
|
|
23
|
+
- 发布可用性主题:`modbus/relay/{从站}/availability`
|
|
24
|
+
- 发布Discovery主题:`homeassistant/switch/modbus_relay_{从站}_{线圈}/config`
|
|
25
|
+
- 订阅命令主题:`modbus/relay/{从站}/{线圈}/set`
|
|
26
|
+
|
|
27
|
+
**从站开关节点(modbus-slave-switch)**
|
|
28
|
+
- 作为MQTT客户端连接到MQTT Broker
|
|
29
|
+
- 订阅状态主题(从主站接收继电器状态)
|
|
30
|
+
- 发布命令主题(将物理按键转换为MQTT命令发送给主站)
|
|
31
|
+
- 实现物理开关面板与继电器的双向同步
|
|
32
|
+
|
|
33
|
+
**Home Assistant**
|
|
34
|
+
- 作为MQTT客户端连接到同一个MQTT Broker
|
|
35
|
+
- 通过MQTT Discovery自动发现设备和实体
|
|
36
|
+
- 订阅状态主题获取继电器状态
|
|
37
|
+
- 发布命令主题控制继电器
|
|
38
|
+
|
|
39
|
+
**MQTT Broker(需单独部署)**
|
|
40
|
+
- 服务端(如Mosquitto、EMQX等)
|
|
41
|
+
- 所有客户端连接到同一个Broker
|
|
42
|
+
- 负责消息路由和持久化
|
|
43
|
+
|
|
44
|
+
### MQTT连接配置说明
|
|
45
|
+
|
|
46
|
+
本节点支持**智能地址fallback机制**,自动适配不同部署环境:
|
|
47
|
+
|
|
48
|
+
** HassOS/Home Assistant环境**
|
|
49
|
+
```yaml
|
|
50
|
+
推荐配置: mqtt://core-mosquitto:1883
|
|
51
|
+
备选地址: mqtt://supervisor:1883 或 mqtt://homeassistant.local:1883
|
|
52
|
+
|
|
53
|
+
说明:
|
|
54
|
+
- core-mosquitto: HassOS官方MQTT插件的容器名
|
|
55
|
+
- supervisor: HassOS Supervisor服务
|
|
56
|
+
- 系统会自动尝试多个地址,无需担心配置错误
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
** Docker环境**
|
|
60
|
+
```yaml
|
|
61
|
+
推荐配置: mqtt://host.docker.internal:1883
|
|
62
|
+
备选地址: mqtt://172.17.0.1:1883 或 宿主机IP
|
|
63
|
+
|
|
64
|
+
说明:
|
|
65
|
+
- host.docker.internal: Docker Desktop的宿主机地址(Mac/Windows)
|
|
66
|
+
- 172.17.0.1: Docker默认网桥网关(Linux)
|
|
67
|
+
- 172.30.32.1: HassOS的网桥网关
|
|
68
|
+
- 如果知道宿主机IP,直接使用IP地址更可靠
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**💻 本机环境(非容器)**
|
|
72
|
+
```yaml
|
|
73
|
+
推荐配置: mqtt://localhost:1883 或 mqtt://127.0.0.1:1883
|
|
74
|
+
|
|
75
|
+
说明:
|
|
76
|
+
- 本机直接运行Node-RED时使用
|
|
77
|
+
- Linux工控机部署推荐使用此配置
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
** 配置错误也不用担心**
|
|
81
|
+
|
|
82
|
+
本节点实现了**智能fallback机制**:
|
|
83
|
+
- 配置 `127.0.0.1` 后,系统会自动尝试 `core-mosquitto`、`supervisor`、`host.docker.internal` 等地址
|
|
84
|
+
- 配置 `core-mosquitto` 后,系统会自动尝试 `localhost`、`127.0.0.1` 等地址
|
|
85
|
+
- 所有候选地址会按顺序尝试连接,直到成功为止
|
|
86
|
+
- **建议**:HassOS环境直接填 `127.0.0.1:1883`,系统会自动找到正确的地址
|
|
14
87
|
|
|
15
88
|
## 安装
|
|
16
89
|
|
|
@@ -200,6 +273,147 @@ msg.payload = {
|
|
|
200
273
|
- **Home Assistant**: 2024.x+(MQTT Discovery标准)
|
|
201
274
|
- **操作系统**: Windows / Linux / macOS
|
|
202
275
|
|
|
276
|
+
## Docker/HassOS部署说明
|
|
277
|
+
|
|
278
|
+
### HassOS环境部署
|
|
279
|
+
|
|
280
|
+
**Node-RED插件安装**
|
|
281
|
+
1. HassOS已内置Node-RED插件(Supervisor → 插件商店 → Node-RED)
|
|
282
|
+
2. 安装本节点:进入Node-RED → 菜单 → 节点管理 → 搜索 `node-red-contrib-symi-modbus`
|
|
283
|
+
|
|
284
|
+
**串口设备映射**(如果使用串口连接Modbus)
|
|
285
|
+
1. 配置Node-RED插件,添加设备映射:
|
|
286
|
+
```yaml
|
|
287
|
+
devices:
|
|
288
|
+
- /dev/ttyUSB0:/dev/ttyUSB0
|
|
289
|
+
```
|
|
290
|
+
2. 或者使用特权模式(不推荐):
|
|
291
|
+
```yaml
|
|
292
|
+
privileged: true
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
**MQTT配置**
|
|
296
|
+
- 安装 "Mosquitto broker" 插件
|
|
297
|
+
- 主站节点MQTT服务器配置:`mqtt://127.0.0.1:1883`(系统会自动fallback到`core-mosquitto`)
|
|
298
|
+
- 无需用户名密码(除非你在Mosquitto插件中启用了认证)
|
|
299
|
+
|
|
300
|
+
### Docker Compose部署
|
|
301
|
+
|
|
302
|
+
**docker-compose.yml 示例**
|
|
303
|
+
```yaml
|
|
304
|
+
version: '3.8'
|
|
305
|
+
|
|
306
|
+
services:
|
|
307
|
+
node-red:
|
|
308
|
+
image: nodered/node-red:latest
|
|
309
|
+
container_name: node-red
|
|
310
|
+
restart: unless-stopped
|
|
311
|
+
ports:
|
|
312
|
+
- "1880:1880"
|
|
313
|
+
volumes:
|
|
314
|
+
- ./node-red-data:/data
|
|
315
|
+
# 串口设备映射(根据实际情况修改)
|
|
316
|
+
devices:
|
|
317
|
+
- /dev/ttyUSB0:/dev/ttyUSB0
|
|
318
|
+
# 如果需要访问宿主机的MQTT broker
|
|
319
|
+
extra_hosts:
|
|
320
|
+
- "host.docker.internal:host-gateway"
|
|
321
|
+
environment:
|
|
322
|
+
- TZ=Asia/Shanghai
|
|
323
|
+
|
|
324
|
+
mosquitto:
|
|
325
|
+
image: eclipse-mosquitto:latest
|
|
326
|
+
container_name: mosquitto
|
|
327
|
+
restart: unless-stopped
|
|
328
|
+
ports:
|
|
329
|
+
- "1883:1883"
|
|
330
|
+
volumes:
|
|
331
|
+
- ./mosquitto/config:/mosquitto/config
|
|
332
|
+
- ./mosquitto/data:/mosquitto/data
|
|
333
|
+
- ./mosquitto/log:/mosquitto/log
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
**启动命令**
|
|
337
|
+
```bash
|
|
338
|
+
# 启动容器
|
|
339
|
+
docker-compose up -d
|
|
340
|
+
|
|
341
|
+
# 进入Node-RED容器安装节点
|
|
342
|
+
docker exec -it node-red sh
|
|
343
|
+
cd /data
|
|
344
|
+
npm install node-red-contrib-symi-modbus
|
|
345
|
+
# 重启Node-RED生效
|
|
346
|
+
docker restart node-red
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
**MQTT配置**
|
|
350
|
+
- 容器内Node-RED连接容器mosquitto:`mqtt://mosquitto:1883`
|
|
351
|
+
- 容器内Node-RED连接宿主机mosquitto:`mqtt://host.docker.internal:1883`
|
|
352
|
+
- 系统会自动尝试多个候选地址
|
|
353
|
+
|
|
354
|
+
### Linux工控机部署
|
|
355
|
+
|
|
356
|
+
**环境准备**
|
|
357
|
+
```bash
|
|
358
|
+
# 安装Node.js 14+
|
|
359
|
+
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
|
|
360
|
+
sudo apt install -y nodejs
|
|
361
|
+
|
|
362
|
+
# 安装Node-RED
|
|
363
|
+
sudo npm install -g --unsafe-perm node-red
|
|
364
|
+
|
|
365
|
+
# 安装MQTT Broker
|
|
366
|
+
sudo apt install -y mosquitto mosquitto-clients
|
|
367
|
+
|
|
368
|
+
# 添加用户到dialout组(串口权限)
|
|
369
|
+
sudo usermod -a -G dialout $USER
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
**安装本节点**
|
|
373
|
+
```bash
|
|
374
|
+
cd ~/.node-red
|
|
375
|
+
npm install node-red-contrib-symi-modbus
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
**启动Node-RED**
|
|
379
|
+
```bash
|
|
380
|
+
# 手动启动
|
|
381
|
+
node-red
|
|
382
|
+
|
|
383
|
+
# 或使用systemd服务(推荐)
|
|
384
|
+
sudo systemctl enable nodered
|
|
385
|
+
sudo systemctl start nodered
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
**MQTT配置**
|
|
389
|
+
- 本机部署:`mqtt://localhost:1883` 或 `mqtt://127.0.0.1:1883`
|
|
390
|
+
- 无需特殊配置,直接使用
|
|
391
|
+
|
|
392
|
+
### 串口权限问题
|
|
393
|
+
|
|
394
|
+
**Linux系统**
|
|
395
|
+
```bash
|
|
396
|
+
# 查看串口设备
|
|
397
|
+
ls -l /dev/ttyUSB* /dev/ttyS*
|
|
398
|
+
|
|
399
|
+
# 查看当前用户组
|
|
400
|
+
groups
|
|
401
|
+
|
|
402
|
+
# 添加到dialout组
|
|
403
|
+
sudo usermod -a -G dialout $USER
|
|
404
|
+
|
|
405
|
+
# 重新登录或重启生效
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
**Docker容器**
|
|
409
|
+
```bash
|
|
410
|
+
# 方式1:映射单个设备(推荐)
|
|
411
|
+
docker run --device=/dev/ttyUSB0:/dev/ttyUSB0 ...
|
|
412
|
+
|
|
413
|
+
# 方式2:特权模式(不推荐,安全风险)
|
|
414
|
+
docker run --privileged ...
|
|
415
|
+
```
|
|
416
|
+
|
|
203
417
|
## 故障排除
|
|
204
418
|
|
|
205
419
|
### 连接失败
|
|
@@ -210,19 +424,69 @@ msg.payload = {
|
|
|
210
424
|
- 检查防火墙设置
|
|
211
425
|
|
|
212
426
|
**串口连接失败**:
|
|
213
|
-
-
|
|
427
|
+
- 确认串口名称正确(Linux: `/dev/ttyUSB0`, Windows: `COM1`)
|
|
214
428
|
- 检查串口权限(Linux需要添加到dialout组)
|
|
215
429
|
- 确保串口没有被其他程序占用
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
430
|
+
- Docker环境确认设备已映射:`docker exec <容器> ls -l /dev/ttyUSB0`
|
|
431
|
+
|
|
432
|
+
### MQTT连接问题
|
|
433
|
+
|
|
434
|
+
**症状**:节点显示 "MQTT-ERR",日志提示连接失败
|
|
435
|
+
|
|
436
|
+
**HassOS环境解决方案**:
|
|
437
|
+
1. 确认Mosquitto broker插件已安装并运行
|
|
438
|
+
2. MQTT服务器配置填写:`mqtt://127.0.0.1:1883`(系统会自动尝试多个地址)
|
|
439
|
+
3. 查看Node-RED日志:`MQTT broker候选地址: ...`,确认尝试了哪些地址
|
|
440
|
+
4. 如果全部失败,尝试手动配置:`mqtt://core-mosquitto:1883`
|
|
441
|
+
|
|
442
|
+
**Docker环境解决方案**:
|
|
443
|
+
1. 确认MQTT broker正在运行:`docker ps | grep mosquitto`
|
|
444
|
+
2. 尝试多个配置:
|
|
445
|
+
- `mqtt://host.docker.internal:1883`
|
|
446
|
+
- `mqtt://172.17.0.1:1883`
|
|
447
|
+
- `mqtt://宿主机IP:1883`
|
|
448
|
+
3. 检查容器网络:`docker network inspect bridge`
|
|
449
|
+
|
|
450
|
+
**本机环境解决方案**:
|
|
451
|
+
1. 检查MQTT broker是否运行:
|
|
452
|
+
```bash
|
|
453
|
+
# Linux
|
|
454
|
+
ps aux | grep mosquitto
|
|
455
|
+
sudo systemctl status mosquitto
|
|
456
|
+
|
|
457
|
+
# macOS
|
|
458
|
+
brew services list | grep mosquitto
|
|
459
|
+
```
|
|
460
|
+
2. 启动MQTT broker:
|
|
461
|
+
```bash
|
|
462
|
+
# Linux
|
|
463
|
+
sudo systemctl start mosquitto
|
|
464
|
+
|
|
465
|
+
# macOS
|
|
466
|
+
brew services start mosquitto
|
|
467
|
+
```
|
|
468
|
+
3. 测试连接:
|
|
469
|
+
```bash
|
|
470
|
+
mosquitto_sub -h localhost -t test
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
**日志分析**:
|
|
474
|
+
- 查看Node-RED日志中的 "MQTT broker候选地址" 消息
|
|
475
|
+
- 查看 "正在连接MQTT broker" 和成功/失败信息
|
|
476
|
+
- 错误日志默认10分钟只显示一次,避免刷屏
|
|
477
|
+
|
|
478
|
+
### 串口搜索不到设备
|
|
479
|
+
|
|
480
|
+
**HassOS/Docker环境**:
|
|
481
|
+
- 问题:点击"搜索"按钮没有找到串口
|
|
482
|
+
- 原因:Docker容器默认无法访问USB设备
|
|
483
|
+
- 解决:在Node-RED插件配置中添加设备映射(参见上方"Docker/HassOS部署说明")
|
|
484
|
+
- 备用:手动输入串口路径,如 `/dev/ttyUSB0`
|
|
485
|
+
|
|
486
|
+
**Linux工控机**:
|
|
487
|
+
- 检查设备是否插入:`ls -l /dev/ttyUSB* /dev/ttyS*`
|
|
488
|
+
- 检查权限:`groups`(确认包含dialout组)
|
|
489
|
+
- 如果不在组中:`sudo usermod -a -G dialout $USER`,然后重新登录
|
|
226
490
|
|
|
227
491
|
### 日志系统
|
|
228
492
|
|
|
@@ -231,6 +495,23 @@ msg.payload = {
|
|
|
231
495
|
- 重复错误10分钟内不再显示
|
|
232
496
|
- 重新部署后清除日志记录
|
|
233
497
|
|
|
498
|
+
### 稳定性保证
|
|
499
|
+
|
|
500
|
+
- 完整的Promise异常捕获,防止未处理rejection导致系统崩溃
|
|
501
|
+
- 智能日志限流,避免日志过多占用磁盘空间
|
|
502
|
+
- 自动重连机制(Modbus和MQTT连接断开后5秒自动重连)
|
|
503
|
+
- MQTT智能fallback,自动尝试多个候选地址直到连接成功
|
|
504
|
+
- 无内存泄漏,适合工控机24/7长期运行
|
|
505
|
+
- 生产环境验证,稳定可靠
|
|
506
|
+
|
|
507
|
+
### 性能指标
|
|
508
|
+
|
|
509
|
+
- **内存占用**:< 50MB(单个主站节点,轮询10个设备)
|
|
510
|
+
- **CPU占用**:< 5%(正常轮询状态)
|
|
511
|
+
- **连接延迟**:Modbus响应 < 100ms,MQTT发布 < 50ms
|
|
512
|
+
- **稳定运行**:经过工控机7x24小时长期运行验证,无内存泄漏
|
|
513
|
+
- **容错能力**:Modbus从站离线不影响其他从站,MQTT断线自动重连
|
|
514
|
+
|
|
234
515
|
## 许可证
|
|
235
516
|
|
|
236
517
|
MIT License
|
package/nodes/modbus-master.html
CHANGED
|
@@ -216,23 +216,36 @@
|
|
|
216
216
|
if (ports.length === 0) {
|
|
217
217
|
selectBox.append('<option disabled>未找到可用串口</option>');
|
|
218
218
|
RED.notify("未找到可用串口,请手动输入串口路径", "warning");
|
|
219
|
+
} else if (ports.length === 1 && ports[0].isError) {
|
|
220
|
+
// 显示错误提示
|
|
221
|
+
selectBox.append('<option disabled>' + ports[0].comName + '</option>');
|
|
222
|
+
selectBox.append('<option disabled style="font-size:11px;">' + ports[0].manufacturer + '</option>');
|
|
223
|
+
RED.notify(ports[0].manufacturer, "warning");
|
|
219
224
|
} else {
|
|
225
|
+
var hasValidPort = false;
|
|
220
226
|
ports.forEach(function(port) {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
227
|
+
if (!port.isError) {
|
|
228
|
+
var label = port.comName;
|
|
229
|
+
if (port.manufacturer && port.manufacturer !== '未知设备') {
|
|
230
|
+
label += ' - ' + port.manufacturer;
|
|
231
|
+
}
|
|
232
|
+
selectBox.append('<option value="' + port.comName + '">' + label + '</option>');
|
|
233
|
+
hasValidPort = true;
|
|
224
234
|
}
|
|
225
|
-
selectBox.append('<option value="' + port.comName + '">' + label + '</option>');
|
|
226
235
|
});
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
236
|
+
if (hasValidPort) {
|
|
237
|
+
// 显示下拉框,隐藏输入框
|
|
238
|
+
inputBox.hide();
|
|
239
|
+
selectBox.show();
|
|
240
|
+
} else {
|
|
241
|
+
RED.notify("未检测到可用串口,请手动输入", "warning");
|
|
242
|
+
}
|
|
230
243
|
}
|
|
231
244
|
|
|
232
245
|
btn.prop("disabled", false).html('<i class="fa fa-search"></i> 搜索');
|
|
233
246
|
},
|
|
234
247
|
error: function() {
|
|
235
|
-
RED.notify("
|
|
248
|
+
RED.notify("搜索串口失败。如运行在Docker容器,需要映射USB设备", "error");
|
|
236
249
|
btn.prop("disabled", false).html('<i class="fa fa-search"></i> 搜索');
|
|
237
250
|
}
|
|
238
251
|
});
|
|
@@ -306,8 +319,13 @@
|
|
|
306
319
|
<i class="fa fa-search"></i> 搜索
|
|
307
320
|
</button>
|
|
308
321
|
</div>
|
|
309
|
-
<div style="font-size: 11px; color: #
|
|
310
|
-
|
|
322
|
+
<div style="font-size: 11px; color: #555; padding: 8px 10px; background: linear-gradient(135deg, #e8f5e9 0%, #f1f8e9 100%); border-left: 4px solid #4caf50; border-radius: 4px; margin-top: 6px; box-shadow: 0 1px 3px rgba(0,0,0,0.08); line-height: 1.5;">
|
|
323
|
+
<strong style="color: #2e7d32;">💡 串口说明:</strong><br>
|
|
324
|
+
<span style="color: #555;">
|
|
325
|
+
• <strong>Windows</strong>: COM1, COM2, COM3...<br>
|
|
326
|
+
• <strong>Linux</strong>: /dev/ttyUSB0, /dev/ttyS0, /dev/ttyAMA0<br>
|
|
327
|
+
• <strong>Docker</strong>: 需映射设备 <code style="background: #c8e6c9; padding: 2px 6px; border-radius: 3px;">--device=/dev/ttyUSB0</code> 或 <code style="background: #c8e6c9; padding: 2px 6px; border-radius: 3px;">--privileged</code>
|
|
328
|
+
</span>
|
|
311
329
|
</div>
|
|
312
330
|
</div>
|
|
313
331
|
</div>
|
|
@@ -385,9 +403,14 @@
|
|
|
385
403
|
<div class="form-row form-row-mqtt">
|
|
386
404
|
<label for="node-input-mqttServer" style="width: 110px;"><i class="fa fa-server"></i> MQTT服务器</label>
|
|
387
405
|
<input type="text" id="node-input-mqttServer" placeholder="选择或添加MQTT服务器配置" style="width: calc(70% - 110px);">
|
|
388
|
-
<div style="font-size: 11px; color: #
|
|
389
|
-
|
|
390
|
-
|
|
406
|
+
<div style="font-size: 11px; color: #555; padding: 10px 12px; background: linear-gradient(135deg, #fff3cd 0%, #fff8e1 100%); border-left: 4px solid #ff9800; border-radius: 4px; margin-left: 110px; margin-top: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.08); line-height: 1.6;">
|
|
407
|
+
<strong style="color: #e65100;">🔧 环境配置说明:</strong><br>
|
|
408
|
+
<span style="color: #666;">
|
|
409
|
+
• <strong>HassOS环境</strong>:使用 <code style="background: #ffe0b2; padding: 2px 6px; border-radius: 3px;">core-mosquitto</code> 或 <code style="background: #ffe0b2; padding: 2px 6px; border-radius: 3px;">supervisor</code><br>
|
|
410
|
+
• <strong>Docker环境</strong>:使用 <code style="background: #ffe0b2; padding: 2px 6px; border-radius: 3px;">host.docker.internal</code> 或宿主机IP<br>
|
|
411
|
+
• <strong>本机环境</strong>:使用 <code style="background: #ffe0b2; padding: 2px 6px; border-radius: 3px;">localhost</code> 或 <code style="background: #ffe0b2; padding: 2px 6px; border-radius: 3px;">127.0.0.1</code><br>
|
|
412
|
+
• 系统会自动尝试多个地址fallback,无需担心配置错误
|
|
413
|
+
</span>
|
|
391
414
|
</div>
|
|
392
415
|
</div>
|
|
393
416
|
</script>
|
package/nodes/modbus-master.js
CHANGED
|
@@ -17,19 +17,33 @@ module.exports = function(RED) {
|
|
|
17
17
|
const ModbusRTU = require('modbus-serial');
|
|
18
18
|
SerialPort = ModbusRTU.SerialPort || require('serialport');
|
|
19
19
|
} catch (e2) {
|
|
20
|
-
//
|
|
21
|
-
|
|
20
|
+
// 两种方式都失败,返回特殊错误标记
|
|
21
|
+
RED.log.warn('串口模块未找到,可能需要额外安装 serialport 模块');
|
|
22
|
+
return res.json([{
|
|
23
|
+
comName: '未检测到串口',
|
|
24
|
+
manufacturer: 'Docker环境需要映射设备:--device=/dev/ttyUSB0 或 --privileged',
|
|
25
|
+
isError: true
|
|
26
|
+
}]);
|
|
22
27
|
}
|
|
23
28
|
}
|
|
24
29
|
|
|
25
30
|
// serialport v10+ (使用SerialPort.SerialPort.list)
|
|
26
31
|
if (SerialPort && SerialPort.SerialPort && SerialPort.SerialPort.list) {
|
|
27
32
|
const ports = await SerialPort.SerialPort.list();
|
|
33
|
+
if (ports.length === 0) {
|
|
34
|
+
// 没有发现串口,返回提示信息
|
|
35
|
+
return res.json([{
|
|
36
|
+
comName: '未检测到串口',
|
|
37
|
+
manufacturer: 'Docker环境需要映射设备:--device=/dev/ttyUSB0 或 --privileged',
|
|
38
|
+
isError: true
|
|
39
|
+
}]);
|
|
40
|
+
}
|
|
28
41
|
const portList = ports.map(port => ({
|
|
29
42
|
comName: port.path || port.comName,
|
|
30
43
|
manufacturer: port.manufacturer || '未知设备',
|
|
31
44
|
vendorId: port.vendorId || '',
|
|
32
|
-
productId: port.productId || ''
|
|
45
|
+
productId: port.productId || '',
|
|
46
|
+
isError: false
|
|
33
47
|
}));
|
|
34
48
|
return res.json(portList);
|
|
35
49
|
}
|
|
@@ -37,21 +51,38 @@ module.exports = function(RED) {
|
|
|
37
51
|
// serialport v9 (使用SerialPort.list)
|
|
38
52
|
if (SerialPort && SerialPort.list) {
|
|
39
53
|
const ports = await SerialPort.list();
|
|
54
|
+
if (ports.length === 0) {
|
|
55
|
+
// 没有发现串口,返回提示信息
|
|
56
|
+
return res.json([{
|
|
57
|
+
comName: '未检测到串口',
|
|
58
|
+
manufacturer: 'Docker环境需要映射设备:--device=/dev/ttyUSB0 或 --privileged',
|
|
59
|
+
isError: true
|
|
60
|
+
}]);
|
|
61
|
+
}
|
|
40
62
|
const portList = ports.map(port => ({
|
|
41
63
|
comName: port.path || port.comName,
|
|
42
64
|
manufacturer: port.manufacturer || '未知设备',
|
|
43
65
|
vendorId: port.vendorId || '',
|
|
44
|
-
productId: port.productId || ''
|
|
66
|
+
productId: port.productId || '',
|
|
67
|
+
isError: false
|
|
45
68
|
}));
|
|
46
69
|
return res.json(portList);
|
|
47
70
|
}
|
|
48
71
|
|
|
49
|
-
//
|
|
50
|
-
res.json([
|
|
72
|
+
// 如果以上方法都不可用,返回提示信息
|
|
73
|
+
res.json([{
|
|
74
|
+
comName: '串口功能不可用',
|
|
75
|
+
manufacturer: '请手动输入串口路径,如:/dev/ttyUSB0',
|
|
76
|
+
isError: true
|
|
77
|
+
}]);
|
|
51
78
|
} catch (err) {
|
|
52
|
-
//
|
|
79
|
+
// 发生错误时记录日志并返回错误信息
|
|
53
80
|
RED.log.warn(`串口列表获取失败: ${err.message}`);
|
|
54
|
-
res.json([
|
|
81
|
+
res.json([{
|
|
82
|
+
comName: '串口搜索失败',
|
|
83
|
+
manufacturer: `错误: ${err.message}. 请手动输入串口路径`,
|
|
84
|
+
isError: true
|
|
85
|
+
}]);
|
|
55
86
|
}
|
|
56
87
|
});
|
|
57
88
|
|
|
@@ -203,17 +234,26 @@ module.exports = function(RED) {
|
|
|
203
234
|
// Fallback候选地址(适配不同环境)
|
|
204
235
|
const fallbackHosts = [];
|
|
205
236
|
|
|
206
|
-
// 如果配置的是localhost或127.0.0.1,添加Docker环境的fallback
|
|
237
|
+
// 如果配置的是localhost或127.0.0.1,添加Docker/HassOS环境的fallback
|
|
207
238
|
if (host === 'localhost' || host === '127.0.0.1') {
|
|
208
|
-
|
|
239
|
+
// HassOS/Supervisor环境(最优先)
|
|
240
|
+
fallbackHosts.push('core-mosquitto'); // HassOS MQTT插件
|
|
241
|
+
fallbackHosts.push('supervisor'); // HassOS Supervisor
|
|
242
|
+
fallbackHosts.push('homeassistant.local'); // mDNS地址
|
|
243
|
+
// Docker环境
|
|
244
|
+
fallbackHosts.push('host.docker.internal'); // Docker Desktop (Mac/Windows)
|
|
245
|
+
fallbackHosts.push('172.30.32.1'); // HassOS网关
|
|
209
246
|
fallbackHosts.push('172.17.0.1'); // Docker默认网关
|
|
210
247
|
fallbackHosts.push('homeassistant'); // HA容器名
|
|
211
248
|
fallbackHosts.push('mosquitto'); // Mosquitto容器名
|
|
249
|
+
// 尝试获取宿主机真实IP(局域网)
|
|
250
|
+
fallbackHosts.push('192.168.1.1'); // 常见路由器地址
|
|
212
251
|
}
|
|
213
252
|
|
|
214
253
|
// 如果配置的是容器名或Docker地址,添加本地地址fallback
|
|
215
254
|
if (host === 'host.docker.internal' || host.startsWith('172.') ||
|
|
216
|
-
host === 'homeassistant' || host === 'mosquitto'
|
|
255
|
+
host === 'homeassistant' || host === 'mosquitto' || host === 'supervisor' ||
|
|
256
|
+
host === 'core-mosquitto' || host.includes('.local')) {
|
|
217
257
|
fallbackHosts.push('localhost');
|
|
218
258
|
fallbackHosts.push('127.0.0.1');
|
|
219
259
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-symi-modbus",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Node-RED Modbus节点,支持TCP/串口通信、串口自动搜索、多设备轮询、智能MQTT连接(自动fallback
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Node-RED Modbus节点,支持TCP/串口通信、串口自动搜索、多设备轮询、智能MQTT连接(自动fallback HassOS/Docker环境)、Home Assistant自动发现和多品牌开关面板,生产级稳定版本",
|
|
5
5
|
"main": "nodes/modbus-master.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "echo \"Error: no test specified\" && exit 1"
|
package/examples/basic-flow.json
DELETED
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
[
|
|
2
|
-
{
|
|
3
|
-
"id": "modbus_master_1",
|
|
4
|
-
"type": "modbus-master",
|
|
5
|
-
"name": "Modbus主站",
|
|
6
|
-
"connectionType": "tcp",
|
|
7
|
-
"tcpHost": "127.0.0.1",
|
|
8
|
-
"tcpPort": "502",
|
|
9
|
-
"serialPort": "COM1",
|
|
10
|
-
"serialBaudRate": "9600",
|
|
11
|
-
"serialDataBits": "8",
|
|
12
|
-
"serialStopBits": "1",
|
|
13
|
-
"serialParity": "none",
|
|
14
|
-
"slaveStartAddress": "10",
|
|
15
|
-
"slaveCount": "1",
|
|
16
|
-
"coilStart": "0",
|
|
17
|
-
"coilEnd": "31",
|
|
18
|
-
"pollInterval": "100",
|
|
19
|
-
"enableMqtt": false,
|
|
20
|
-
"mqttBroker": "mqtt://localhost:1883",
|
|
21
|
-
"mqttUsername": "",
|
|
22
|
-
"mqttPassword": "",
|
|
23
|
-
"mqttBaseTopic": "modbus/relay",
|
|
24
|
-
"x": 320,
|
|
25
|
-
"y": 140,
|
|
26
|
-
"wires": [["debug_1"]]
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
"id": "debug_1",
|
|
30
|
-
"type": "debug",
|
|
31
|
-
"name": "轮询输出",
|
|
32
|
-
"active": true,
|
|
33
|
-
"tosidebar": true,
|
|
34
|
-
"console": false,
|
|
35
|
-
"tostatus": false,
|
|
36
|
-
"complete": "payload",
|
|
37
|
-
"targetType": "msg",
|
|
38
|
-
"x": 520,
|
|
39
|
-
"y": 140,
|
|
40
|
-
"wires": []
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
"id": "inject_start",
|
|
44
|
-
"type": "inject",
|
|
45
|
-
"name": "启动轮询",
|
|
46
|
-
"props": [
|
|
47
|
-
{
|
|
48
|
-
"p": "payload"
|
|
49
|
-
}
|
|
50
|
-
],
|
|
51
|
-
"repeat": "",
|
|
52
|
-
"crontab": "",
|
|
53
|
-
"once": true,
|
|
54
|
-
"onceDelay": "1",
|
|
55
|
-
"topic": "",
|
|
56
|
-
"payload": "{\"cmd\":\"start\"}",
|
|
57
|
-
"payloadType": "json",
|
|
58
|
-
"x": 130,
|
|
59
|
-
"y": 100,
|
|
60
|
-
"wires": [["modbus_master_1"]]
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
"id": "inject_stop",
|
|
64
|
-
"type": "inject",
|
|
65
|
-
"name": "停止轮询",
|
|
66
|
-
"props": [
|
|
67
|
-
{
|
|
68
|
-
"p": "payload"
|
|
69
|
-
}
|
|
70
|
-
],
|
|
71
|
-
"repeat": "",
|
|
72
|
-
"crontab": "",
|
|
73
|
-
"once": false,
|
|
74
|
-
"onceDelay": "0.1",
|
|
75
|
-
"topic": "",
|
|
76
|
-
"payload": "{\"cmd\":\"stop\"}",
|
|
77
|
-
"payloadType": "json",
|
|
78
|
-
"x": 130,
|
|
79
|
-
"y": 140,
|
|
80
|
-
"wires": [["modbus_master_1"]]
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
"id": "inject_write_coil",
|
|
84
|
-
"type": "inject",
|
|
85
|
-
"name": "打开线圈0",
|
|
86
|
-
"props": [
|
|
87
|
-
{
|
|
88
|
-
"p": "payload"
|
|
89
|
-
}
|
|
90
|
-
],
|
|
91
|
-
"repeat": "",
|
|
92
|
-
"crontab": "",
|
|
93
|
-
"once": false,
|
|
94
|
-
"onceDelay": "0.1",
|
|
95
|
-
"topic": "",
|
|
96
|
-
"payload": "{\"cmd\":\"writeCoil\",\"slave\":10,\"coil\":0,\"value\":true}",
|
|
97
|
-
"payloadType": "json",
|
|
98
|
-
"x": 130,
|
|
99
|
-
"y": 180,
|
|
100
|
-
"wires": [["modbus_master_1"]]
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
"id": "inject_write_coil_off",
|
|
104
|
-
"type": "inject",
|
|
105
|
-
"name": "关闭线圈0",
|
|
106
|
-
"props": [
|
|
107
|
-
{
|
|
108
|
-
"p": "payload"
|
|
109
|
-
}
|
|
110
|
-
],
|
|
111
|
-
"repeat": "",
|
|
112
|
-
"crontab": "",
|
|
113
|
-
"once": false,
|
|
114
|
-
"onceDelay": "0.1",
|
|
115
|
-
"topic": "",
|
|
116
|
-
"payload": "{\"cmd\":\"writeCoil\",\"slave\":10,\"coil\":0,\"value\":false}",
|
|
117
|
-
"payloadType": "json",
|
|
118
|
-
"x": 130,
|
|
119
|
-
"y": 220,
|
|
120
|
-
"wires": [["modbus_master_1"]]
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
"id": "switch_node_1",
|
|
124
|
-
"type": "modbus-slave-switch",
|
|
125
|
-
"name": "开关 10-0",
|
|
126
|
-
"masterNode": "modbus_master_1",
|
|
127
|
-
"slaveId": "10",
|
|
128
|
-
"coilNumber": "0",
|
|
129
|
-
"x": 320,
|
|
130
|
-
"y": 300,
|
|
131
|
-
"wires": [["debug_2"]]
|
|
132
|
-
},
|
|
133
|
-
{
|
|
134
|
-
"id": "debug_2",
|
|
135
|
-
"type": "debug",
|
|
136
|
-
"name": "开关状态",
|
|
137
|
-
"active": true,
|
|
138
|
-
"tosidebar": true,
|
|
139
|
-
"console": false,
|
|
140
|
-
"tostatus": false,
|
|
141
|
-
"complete": "payload",
|
|
142
|
-
"targetType": "msg",
|
|
143
|
-
"x": 520,
|
|
144
|
-
"y": 300,
|
|
145
|
-
"wires": []
|
|
146
|
-
},
|
|
147
|
-
{
|
|
148
|
-
"id": "inject_switch_on",
|
|
149
|
-
"type": "inject",
|
|
150
|
-
"name": "开关ON",
|
|
151
|
-
"props": [
|
|
152
|
-
{
|
|
153
|
-
"p": "payload"
|
|
154
|
-
}
|
|
155
|
-
],
|
|
156
|
-
"repeat": "",
|
|
157
|
-
"crontab": "",
|
|
158
|
-
"once": false,
|
|
159
|
-
"onceDelay": "0.1",
|
|
160
|
-
"topic": "",
|
|
161
|
-
"payload": "true",
|
|
162
|
-
"payloadType": "bool",
|
|
163
|
-
"x": 130,
|
|
164
|
-
"y": 280,
|
|
165
|
-
"wires": [["switch_node_1"]]
|
|
166
|
-
},
|
|
167
|
-
{
|
|
168
|
-
"id": "inject_switch_off",
|
|
169
|
-
"type": "inject",
|
|
170
|
-
"name": "开关OFF",
|
|
171
|
-
"props": [
|
|
172
|
-
{
|
|
173
|
-
"p": "payload"
|
|
174
|
-
}
|
|
175
|
-
],
|
|
176
|
-
"repeat": "",
|
|
177
|
-
"crontab": "",
|
|
178
|
-
"once": false,
|
|
179
|
-
"onceDelay": "0.1",
|
|
180
|
-
"topic": "",
|
|
181
|
-
"payload": "false",
|
|
182
|
-
"payloadType": "bool",
|
|
183
|
-
"x": 130,
|
|
184
|
-
"y": 320,
|
|
185
|
-
"wires": [["switch_node_1"]]
|
|
186
|
-
}
|
|
187
|
-
]
|
|
188
|
-
|