node-red-contrib-symi-modbus 1.3.0 → 1.5.4

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.
@@ -16,6 +16,7 @@
16
16
  // MQTT配置
17
17
  mqttServer: {value: "", type: "mqtt-server-config"},
18
18
  // 开关面板配置
19
+ switchBrand: {value: "symi"}, // 品牌选择
19
20
  switchId: {value: 0, validate: RED.validators.number()},
20
21
  buttonNumber: {value: 1, validate: RED.validators.number()},
21
22
  // 映射到继电器
@@ -41,6 +42,54 @@
41
42
  }
42
43
  });
43
44
 
45
+ // 搜索串口按钮
46
+ $("#btn-search-ports").on("click", function() {
47
+ var btn = $(this);
48
+ var inputBox = $("#node-input-serialPort");
49
+ var selectBox = $("#port-list");
50
+
51
+ btn.prop("disabled", true).html('<i class="fa fa-spinner fa-spin"></i> 搜索中');
52
+
53
+ $.ajax({
54
+ url: 'modbus-slave-switch/serialports',
55
+ type: 'GET',
56
+ success: function(ports) {
57
+ selectBox.empty().append('<option value="">-- 选择检测到的串口 --</option>');
58
+
59
+ if (ports.length === 0) {
60
+ selectBox.append('<option disabled>未找到可用串口</option>');
61
+ RED.notify("未找到可用串口,请手动输入串口路径", "warning");
62
+ } else {
63
+ ports.forEach(function(port) {
64
+ var label = port.comName;
65
+ if (port.manufacturer && port.manufacturer !== '未知设备') {
66
+ label += ' - ' + port.manufacturer;
67
+ }
68
+ selectBox.append('<option value="' + port.comName + '">' + label + '</option>');
69
+ });
70
+ // 显示下拉框,隐藏输入框
71
+ inputBox.hide();
72
+ selectBox.show();
73
+ }
74
+
75
+ btn.prop("disabled", false).html('<i class="fa fa-search"></i> 搜索');
76
+ },
77
+ error: function() {
78
+ RED.notify("搜索串口失败", "error");
79
+ btn.prop("disabled", false).html('<i class="fa fa-search"></i> 搜索');
80
+ }
81
+ });
82
+ });
83
+
84
+ // 选择串口(下拉选择)
85
+ $("#port-list").on("change", function() {
86
+ var selectedPort = $(this).val();
87
+ if (selectedPort) {
88
+ $("#node-input-serialPort").val(selectedPort).show();
89
+ $(this).hide();
90
+ }
91
+ });
92
+
44
93
  // 初始化显示
45
94
  $("#node-input-connectionType").trigger("change");
46
95
  }
@@ -48,25 +97,27 @@
48
97
  </script>
49
98
 
50
99
  <script type="text/html" data-template-name="modbus-slave-switch">
100
+ <!-- 基本配置 -->
51
101
  <div class="form-row">
52
102
  <label for="node-input-name"><i class="fa fa-tag"></i> 名称</label>
53
103
  <input type="text" id="node-input-name" placeholder="从站开关">
54
104
  </div>
55
105
 
56
106
  <!-- RS-485总线连接配置 -->
57
- <hr style="margin: 15px 0;">
107
+ <hr style="margin: 15px 0; border: 0; border-top: 2px solid #e0e0e0;">
58
108
  <div class="form-row">
59
- <label style="width: 100%; margin-bottom: 5px;">
60
- <i class="fa fa-plug"></i> RS-485总线连接配置
109
+ <label style="width: 100%; margin-bottom: 8px;">
110
+ <i class="fa fa-plug" style="color: #ff9800;"></i>
111
+ <span style="font-size: 14px; font-weight: 600; color: #333;">RS-485总线连接配置</span>
61
112
  </label>
62
- <div style="font-size: 11px; color: #999; margin-left: 110px;">
63
- 连接物理开关面板的RS-485总线(监听按键事件,发送控制指令)
113
+ <div style="font-size: 11px; color: #555; padding: 10px 12px; background: linear-gradient(135deg, #fff3cd 0%, #fffbe6 100%); border-left: 4px solid #ffc107; border-radius: 4px; box-shadow: 0 1px 3px rgba(0,0,0,0.08);">
114
+ <strong>总线说明:</strong>连接物理开关面板的RS-485总线,监听按键事件并发送控制指令
64
115
  </div>
65
116
  </div>
66
117
 
67
118
  <div class="form-row">
68
- <label for="node-input-connectionType"><i class="fa fa-plug"></i> 连接类型</label>
69
- <select id="node-input-connectionType">
119
+ <label for="node-input-connectionType" style="width: 110px;"><i class="fa fa-exchange"></i> 连接类型</label>
120
+ <select id="node-input-connectionType" style="width: calc(70% - 110px);">
70
121
  <option value="tcp">TCP/IP</option>
71
122
  <option value="serial">串口</option>
72
123
  </select>
@@ -74,95 +125,122 @@
74
125
 
75
126
  <!-- TCP配置 -->
76
127
  <div class="form-row form-row-tcp">
77
- <label for="node-input-tcpHost"><i class="fa fa-server"></i> TCP主机</label>
78
- <input type="text" id="node-input-tcpHost" placeholder="127.0.0.1">
79
- <div style="font-size: 11px; color: #999; margin-top: 3px; margin-left: 110px;">
80
- RS-485转TCP网关地址
128
+ <label for="node-input-tcpHost" style="width: 110px;"><i class="fa fa-server"></i> TCP主机</label>
129
+ <input type="text" id="node-input-tcpHost" placeholder="192.168.1.200" style="width: calc(70% - 110px);">
130
+ <div style="font-size: 11px; color: #888; margin-left: 110px; margin-top: 3px;">
131
+ RS-485转TCP网关IP地址
81
132
  </div>
82
133
  </div>
83
134
 
84
135
  <div class="form-row form-row-tcp">
85
- <label for="node-input-tcpPort"><i class="fa fa-plug"></i> TCP端口</label>
86
- <input type="number" id="node-input-tcpPort" placeholder="8888">
136
+ <label for="node-input-tcpPort" style="width: 110px;"><i class="fa fa-plug"></i> TCP端口</label>
137
+ <input type="number" id="node-input-tcpPort" placeholder="8888" style="width: 100px;">
138
+ <span style="margin-left: 10px; font-size: 12px; color: #666;">默认 8888</span>
87
139
  </div>
88
140
 
89
141
  <!-- 串口配置 -->
90
142
  <div class="form-row form-row-serial">
91
- <label for="node-input-serialPort"><i class="fa fa-terminal"></i> 串口</label>
92
- <input type="text" id="node-input-serialPort" placeholder="COM1 /dev/ttyUSB0">
143
+ <label for="node-input-serialPort" style="width: 110px;"><i class="fa fa-terminal"></i> 串口</label>
144
+ <div style="display: inline-block; width: calc(70% - 110px);">
145
+ <div style="display: flex; gap: 5px; align-items: center;">
146
+ <input type="text" id="node-input-serialPort" placeholder="COM1, /dev/ttyUSB0, /dev/ttyS1" style="flex: 1; padding: 5px 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 13px;">
147
+ <select id="port-list" style="flex: 1; padding: 5px; font-family: monospace; font-size: 12px; border: 1px solid #ccc; border-radius: 4px; display: none;">
148
+ <option value="">-- 选择检测到的串口 --</option>
149
+ </select>
150
+ <button type="button" id="btn-search-ports" style="padding: 6px 12px; background: #2196f3; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px; white-space: nowrap; transition: background 0.3s;">
151
+ <i class="fa fa-search"></i> 搜索
152
+ </button>
153
+ </div>
154
+ <div style="font-size: 11px; color: #888; margin-top: 3px;">
155
+ 支持COM1、/dev/ttyUSB0、/dev/ttyS1等RS-485串口设备
156
+ </div>
157
+ </div>
93
158
  </div>
94
159
 
95
160
  <div class="form-row form-row-serial">
96
- <label for="node-input-serialBaudRate"><i class="fa fa-tachometer"></i> 波特率</label>
97
- <select id="node-input-serialBaudRate">
161
+ <label for="node-input-serialBaudRate" style="width: 110px;"><i class="fa fa-tachometer"></i> 波特率</label>
162
+ <select id="node-input-serialBaudRate" style="width: 150px;">
98
163
  <option value="9600">9600</option>
99
164
  <option value="19200">19200</option>
100
165
  <option value="38400">38400</option>
101
166
  <option value="57600">57600</option>
102
167
  <option value="115200">115200</option>
103
168
  </select>
169
+ <span style="margin-left: 10px; font-size: 11px; color: #888;">8-N-1固定配置(亖米协议)</span>
104
170
  </div>
105
171
 
106
- <div class="form-row form-row-serial">
107
- <label for="node-input-serialDataBits"><i class="fa fa-database"></i> 数据位</label>
108
- <select id="node-input-serialDataBits">
172
+ <div class="form-row form-row-serial" style="display: none;">
173
+ <label for="node-input-serialDataBits" style="width: 110px;"><i class="fa fa-database"></i> 数据位</label>
174
+ <select id="node-input-serialDataBits" style="width: 100px;">
109
175
  <option value="7">7</option>
110
- <option value="8">8</option>
176
+ <option value="8" selected>8</option>
111
177
  </select>
112
178
  </div>
113
179
 
114
- <div class="form-row form-row-serial">
115
- <label for="node-input-serialStopBits"><i class="fa fa-stop"></i> 停止位</label>
116
- <select id="node-input-serialStopBits">
117
- <option value="1">1</option>
180
+ <div class="form-row form-row-serial" style="display: none;">
181
+ <label for="node-input-serialStopBits" style="width: 110px;"><i class="fa fa-stop"></i> 停止位</label>
182
+ <select id="node-input-serialStopBits" style="width: 100px;">
183
+ <option value="1" selected>1</option>
118
184
  <option value="2">2</option>
119
185
  </select>
120
186
  </div>
121
187
 
122
- <div class="form-row form-row-serial">
123
- <label for="node-input-serialParity"><i class="fa fa-check"></i> 校验位</label>
124
- <select id="node-input-serialParity">
125
- <option value="none">无</option>
188
+ <div class="form-row form-row-serial" style="display: none;">
189
+ <label for="node-input-serialParity" style="width: 110px;"><i class="fa fa-check"></i> 校验位</label>
190
+ <select id="node-input-serialParity" style="width: 100px;">
191
+ <option value="none" selected>无</option>
126
192
  <option value="even">偶校验</option>
127
193
  <option value="odd">奇校验</option>
128
194
  </select>
129
195
  </div>
130
196
 
131
197
  <!-- MQTT配置 -->
132
- <hr style="margin: 15px 0;">
198
+ <hr style="margin: 15px 0; border: 0; border-top: 2px solid #e0e0e0;">
133
199
  <div class="form-row">
134
- <label style="width: 100%; margin-bottom: 5px;">
135
- <i class="fa fa-server"></i> MQTT服务器配置
200
+ <label style="width: 100%; margin-bottom: 8px;">
201
+ <i class="fa fa-cloud" style="color: #9c27b0;"></i>
202
+ <span style="font-size: 14px; font-weight: 600; color: #333;">MQTT服务器配置</span>
136
203
  </label>
137
204
  </div>
138
205
 
139
206
  <div class="form-row">
140
- <label for="node-input-mqttServer"><i class="fa fa-server"></i> MQTT服务器</label>
141
- <input type="text" id="node-input-mqttServer" placeholder="选择或添加MQTT服务器配置">
142
- <div style="font-size: 11px; color: #999; margin-top: 3px; margin-left: 110px;">
207
+ <label for="node-input-mqttServer" style="width: 110px;"><i class="fa fa-server"></i> MQTT服务器</label>
208
+ <input type="text" id="node-input-mqttServer" placeholder="选择或添加MQTT服务器配置" style="width: calc(70% - 110px);">
209
+ <div style="font-size: 11px; color: #888; margin-left: 110px; margin-top: 3px;">
143
210
  选择已配置的MQTT服务器(需与主站节点使用同一配置)
144
211
  </div>
145
212
  </div>
146
213
 
147
214
  <!-- 物理开关面板配置 -->
148
- <hr style="margin: 15px 0;">
215
+ <hr style="margin: 15px 0; border: 0; border-top: 2px solid #e0e0e0;">
149
216
  <div class="form-row">
150
- <label style="width: 100%; margin-bottom: 5px;">
151
- <i class="fa fa-toggle-on"></i> 物理开关面板配置
217
+ <label style="width: 100%; margin-bottom: 8px;">
218
+ <i class="fa fa-toggle-on" style="color: #3f51b5;"></i>
219
+ <span style="font-size: 14px; font-weight: 600; color: #333;">物理开关面板配置</span>
152
220
  </label>
153
221
  </div>
154
222
 
155
223
  <div class="form-row">
156
- <label for="node-input-switchId"><i class="fa fa-id-card"></i> 开关ID</label>
157
- <input type="number" id="node-input-switchId" placeholder="0" min="0" max="255" style="width: 80px;">
158
- <span style="margin-left: 10px; color: #666;">(物理面板地址:0-255)</span>
159
- <div style="font-size: 11px; color: #999; margin-top: 3px; margin-left: 110px;">
160
- 物理开关面板的设备地址(RS-485地址)
224
+ <label for="node-input-switchBrand" style="width: 110px;"><i class="fa fa-trademark"></i> 面板品牌</label>
225
+ <select id="node-input-switchBrand" style="width: 200px;">
226
+ <option value="symi">亖米(Symi)</option>
227
+ <option value="other1" disabled style="color: #999;">其他品牌1(待开发)</option>
228
+ <option value="other2" disabled style="color: #999;">其他品牌2(待开发)</option>
229
+ </select>
230
+ <span style="margin-left: 10px; font-size: 11px; color: #666; font-style: italic;">支持1-8键开关</span>
231
+ </div>
232
+
233
+ <div class="form-row">
234
+ <label for="node-input-switchId" style="width: 110px;"><i class="fa fa-id-card"></i> 开关ID</label>
235
+ <input type="number" id="node-input-switchId" placeholder="0" min="0" max="255" style="width: 90px; padding: 5px 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 13px;">
236
+ <span style="margin-left: 10px; color: #666; font-size: 12px;">物理面板地址:<strong>0-255</strong></span>
237
+ <div style="font-size: 11px; color: #888; margin-left: 110px; margin-top: 3px;">
238
+ RS-485总线上的设备地址标识
161
239
  </div>
162
240
  </div>
163
241
 
164
242
  <div class="form-row">
165
- <label for="node-input-buttonNumber"><i class="fa fa-hand-pointer-o"></i> 按钮编号</label>
243
+ <label for="node-input-buttonNumber" style="width: 110px;"><i class="fa fa-hand-pointer-o"></i> 按钮编号</label>
166
244
  <select id="node-input-buttonNumber" style="width: 150px;">
167
245
  <option value="1">按钮 1</option>
168
246
  <option value="2">按钮 2</option>
@@ -173,47 +251,57 @@
173
251
  <option value="7">按钮 7</option>
174
252
  <option value="8">按钮 8</option>
175
253
  </select>
176
- <div style="font-size: 11px; color: #999; margin-top: 3px; margin-left: 110px;">
177
- 物理面板上的按键编号(1-8,对应面板按钮)
178
- </div>
254
+ <span style="margin-left: 10px; font-size: 11px; color: #666; font-style: italic;">面板物理按键</span>
179
255
  </div>
180
256
 
181
257
  <!-- 映射到继电器配置 -->
182
- <hr style="margin: 15px 0;">
258
+ <hr style="margin: 15px 0; border: 0; border-top: 2px solid #e0e0e0;">
183
259
  <div class="form-row">
184
- <label style="width: 100%; margin-bottom: 5px;">
185
- <i class="fa fa-arrow-right"></i> 映射到继电器设备
260
+ <label style="width: 100%; margin-bottom: 8px;">
261
+ <i class="fa fa-arrow-right" style="color: #4caf50;"></i>
262
+ <span style="font-size: 14px; font-weight: 600; color: #333;">映射到继电器设备</span>
186
263
  </label>
187
264
  </div>
188
265
 
189
266
  <div class="form-row">
190
- <label for="node-input-targetSlaveAddress"><i class="fa fa-map-marker"></i> 目标从站地址</label>
191
- <input type="number" id="node-input-targetSlaveAddress" placeholder="10" min="1" max="247" style="width: 80px;">
192
- <span style="margin-left: 10px; color: #666;">(Modbus继电器设备:10-19)</span>
193
- <div style="font-size: 11px; color: #999; margin-top: 3px; margin-left: 110px;">
194
- 要控制的继电器设备地址(主站节点中配置的从站)
267
+ <label for="node-input-targetSlaveAddress" style="width: 110px;"><i class="fa fa-map-marker"></i> 从站地址</label>
268
+ <input type="number" id="node-input-targetSlaveAddress" placeholder="10" min="1" max="247" style="width: 90px; padding: 5px 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 13px;">
269
+ <span style="margin-left: 10px; color: #666; font-size: 12px;">Modbus继电器:<strong>10-19</strong></span>
270
+ <div style="font-size: 11px; color: #888; margin-left: 110px; margin-top: 3px;">
271
+ 主站节点中配置的从站设备地址
195
272
  </div>
196
273
  </div>
197
274
 
198
275
  <div class="form-row">
199
- <label for="node-input-targetCoilNumber"><i class="fa fa-plug"></i> 目标线圈编号</label>
200
- <input type="number" id="node-input-targetCoilNumber" placeholder="0" min="0" max="31" style="width: 80px;">
201
- <span style="margin-left: 10px; color: #666;">(继电器通道:0-31)</span>
202
- <div style="font-size: 11px; color: #999; margin-top: 3px; margin-left: 110px;">
203
- 要控制的继电器线圈编号(32路继电器的具体通道)
276
+ <label for="node-input-targetCoilNumber" style="width: 110px;"><i class="fa fa-plug"></i> 线圈编号</label>
277
+ <input type="number" id="node-input-targetCoilNumber" placeholder="0" min="0" max="31" style="width: 90px; padding: 5px 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 13px;">
278
+ <span style="margin-left: 10px; color: #666; font-size: 12px;">继电器通道:<strong>0-31</strong></span>
279
+ <div style="font-size: 11px; color: #888; margin-left: 110px; margin-top: 3px;">
280
+ 32路继电器的具体通道编号
204
281
  </div>
205
282
  </div>
206
283
 
207
- <div class="form-row" style="background: #fff3cd; padding: 10px; border-left: 3px solid #ffc107; margin-top: 10px;">
208
- <div style="font-size: 12px; color: #333;">
209
- <strong>📝 配置说明:</strong><br>
210
- • <strong>开关ID</strong>:物理开关面板的设备地址(0-255)<br>
211
- • <strong>按钮编号</strong>:物理面板上的按键(1-8)<br>
212
- <strong>目标从站</strong>:要控制的Modbus继电器设备地址(10-19)<br>
213
- <strong>目标线圈</strong>:继电器的具体通道(0-31)<br>
214
- <br>
215
- <strong>示例:</strong>开关ID=0,按钮1 → 控制继电器10的线圈0<br>
216
- <strong>MQTT主题:</strong>modbus/relay/10/0/set
284
+ <div class="form-row" style="margin-top: 20px; padding: 14px; background: linear-gradient(135deg, #e8f5e9 0%, #f1f8f4 100%); border-left: 4px solid #4caf50; border-radius: 6px; box-shadow: 0 2px 4px rgba(0,0,0,0.08);">
285
+ <div style="font-size: 12px; color: #333; line-height: 1.8;">
286
+ <div style="font-weight: 600; color: #2e7d32; margin-bottom: 10px; font-size: 13px;">
287
+ 配置说明
288
+ </div>
289
+ <div style="margin-bottom: 8px; padding-left: 10px;">
290
+ <strong>面板品牌:</strong><span style="color: #555;">亖米协议,支持1-8键开关</span><br>
291
+ <strong>开关ID:</strong><span style="color: #555;">物理面板RS-485地址(0-255)</span><br>
292
+ <strong>按钮编号:</strong><span style="color: #555;">面板按键序号(1-8)</span><br>
293
+ <strong>从站地址:</strong><span style="color: #555;">Modbus继电器地址(10-19)</span><br>
294
+ <strong>线圈编号:</strong><span style="color: #555;">继电器通道号(0-31)</span>
295
+ </div>
296
+ <div style="margin-top: 12px; padding-top: 12px; border-top: 1px solid #a5d6a7;">
297
+ <div style="font-size: 11px; color: #2e7d32; font-weight: 500; margin-bottom: 5px;">
298
+ 配置示例
299
+ </div>
300
+ <div style="background: white; padding: 8px; border-radius: 4px; border: 1px solid #c8e6c9; font-size: 11px; color: #555;">
301
+ <strong>场景:</strong>亖米开关ID=0,按钮1 → 控制继电器10的线圈0<br>
302
+ <strong>MQTT:</strong><code style="background: #f5f5f5; padding: 2px 6px; border-radius: 3px; color: #e91e63; font-family: monospace;">modbus/relay/10/0/set</code>
303
+ </div>
304
+ </div>
217
305
  </div>
218
306
  </div>
219
307
  </script>
@@ -4,6 +4,58 @@ module.exports = function(RED) {
4
4
  const ModbusRTU = require("modbus-serial");
5
5
  const net = require("net");
6
6
  const protocol = require("./lightweight-protocol");
7
+
8
+ // 串口列表API - 支持Windows、Linux、macOS所有串口设备
9
+ RED.httpAdmin.get('/modbus-slave-switch/serialports', async function(req, res) {
10
+ try {
11
+ // 尝试从多个可能的位置获取serialport模块
12
+ let SerialPort;
13
+ try {
14
+ // 优先尝试使用node_modules中的serialport
15
+ SerialPort = require('serialport');
16
+ } catch (e) {
17
+ try {
18
+ // 如果失败,尝试从modbus-serial的依赖中获取
19
+ const ModbusRTU = require('modbus-serial');
20
+ SerialPort = ModbusRTU.SerialPort || require('serialport');
21
+ } catch (e2) {
22
+ // 两种方式都失败,返回空列表
23
+ return res.json([]);
24
+ }
25
+ }
26
+
27
+ // serialport v10+ (使用SerialPort.SerialPort.list)
28
+ if (SerialPort && SerialPort.SerialPort && SerialPort.SerialPort.list) {
29
+ const ports = await SerialPort.SerialPort.list();
30
+ const portList = ports.map(port => ({
31
+ comName: port.path || port.comName,
32
+ manufacturer: port.manufacturer || '未知设备',
33
+ vendorId: port.vendorId || '',
34
+ productId: port.productId || ''
35
+ }));
36
+ return res.json(portList);
37
+ }
38
+
39
+ // serialport v9 (使用SerialPort.list)
40
+ if (SerialPort && SerialPort.list) {
41
+ const ports = await SerialPort.list();
42
+ const portList = ports.map(port => ({
43
+ comName: port.path || port.comName,
44
+ manufacturer: port.manufacturer || '未知设备',
45
+ vendorId: port.vendorId || '',
46
+ productId: port.productId || ''
47
+ }));
48
+ return res.json(portList);
49
+ }
50
+
51
+ // 如果以上方法都不可用,返回空列表
52
+ res.json([]);
53
+ } catch (err) {
54
+ // 发生错误时记录日志并返回空列表
55
+ RED.log.warn(`串口列表获取失败: ${err.message}`);
56
+ res.json([]);
57
+ }
58
+ });
7
59
 
8
60
  function ModbusSlaveSwitchNode(config) {
9
61
  RED.nodes.createNode(this, config);
@@ -28,6 +80,7 @@ module.exports = function(RED) {
28
80
  mqttUsername: node.mqttServerConfig ? node.mqttServerConfig.username : "",
29
81
  mqttPassword: node.mqttServerConfig ? (node.mqttServerConfig.credentials ? node.mqttServerConfig.credentials.password : "") : "",
30
82
  mqttBaseTopic: node.mqttServerConfig ? node.mqttServerConfig.baseTopic : "modbus/relay",
83
+ switchBrand: config.switchBrand || "symi", // 面板品牌(默认亖米)
31
84
  switchId: parseInt(config.switchId) || 0, // 开关ID(0-255,物理面板地址)
32
85
  buttonNumber: parseInt(config.buttonNumber) || 1, // 按钮编号(1-8)
33
86
  targetSlaveAddress: parseInt(config.targetSlaveAddress) || 10, // 目标继电器从站地址
@@ -40,6 +93,8 @@ module.exports = function(RED) {
40
93
  node.isRs485Connected = false;
41
94
  node.reconnectTimer = null;
42
95
  node.isClosing = false;
96
+ node.lastMqttErrorLog = 0; // MQTT错误日志时间
97
+ node.errorLogInterval = 10 * 60 * 1000; // 错误日志间隔:10分钟
43
98
 
44
99
  // MQTT主题(映射到继电器设备)
45
100
  node.stateTopic = `${node.config.mqttBaseTopic}/${node.config.targetSlaveAddress}/${node.config.targetCoilNumber}/state`;
@@ -49,16 +104,24 @@ module.exports = function(RED) {
49
104
  node.connectRs485 = async function() {
50
105
  try {
51
106
  if (node.config.connectionType === "tcp") {
107
+ // TCP连接验证
108
+ if (!node.config.tcpHost) {
109
+ throw new Error("TCP主机地址未配置");
110
+ }
52
111
  await node.rs485Client.connectTCP(node.config.tcpHost, {
53
112
  port: node.config.tcpPort
54
113
  });
55
114
  node.log(`已连接到RS-485 TCP: ${node.config.tcpHost}:${node.config.tcpPort}`);
56
115
  } else {
116
+ // 串口连接验证
117
+ if (!node.config.serialPort) {
118
+ throw new Error("串口未配置,请配置串口路径(如:COM1、/dev/ttyUSB0、/dev/ttyS1)");
119
+ }
57
120
  await node.rs485Client.connectRTUBuffered(node.config.serialPort, {
58
- baudRate: node.config.serialBaudRate,
59
- dataBits: node.config.serialDataBits,
60
- stopBits: node.config.serialStopBits,
61
- parity: node.config.serialParity
121
+ baudRate: node.config.serialBaudRate || 9600,
122
+ dataBits: node.config.serialDataBits || 8,
123
+ stopBits: node.config.serialStopBits || 1,
124
+ parity: node.config.serialParity || 'none'
62
125
  });
63
126
  node.log(`已连接到RS-485串口: ${node.config.serialPort}`);
64
127
  }
@@ -75,8 +138,13 @@ module.exports = function(RED) {
75
138
  node.isRs485Connected = false;
76
139
  node.updateStatus();
77
140
 
78
- // 5秒后重试连接
141
+ // 5秒后重试连接(仅在配置有效时)
79
142
  if (!node.isClosing && !node.reconnectTimer) {
143
+ // 如果是配置错误,不重试
144
+ if (err.message.includes("未配置")) {
145
+ node.warn("RS-485连接配置无效,请检查配置");
146
+ return;
147
+ }
80
148
  node.reconnectTimer = setTimeout(() => {
81
149
  node.reconnectTimer = null;
82
150
  node.connectRs485();
@@ -256,7 +324,14 @@ module.exports = function(RED) {
256
324
  });
257
325
 
258
326
  node.mqttClient.on('error', (err) => {
259
- node.error(`MQTT错误: ${err.message}`);
327
+ // 日志限流:MQTT错误最多每10分钟输出一次
328
+ const now = Date.now();
329
+ const shouldLog = (now - node.lastMqttErrorLog) > node.errorLogInterval;
330
+
331
+ if (shouldLog) {
332
+ node.error(`MQTT错误: ${err.message} [此错误将在10分钟后再次显示]`);
333
+ node.lastMqttErrorLog = now;
334
+ }
260
335
  node.updateStatus();
261
336
  });
262
337
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "node-red-contrib-symi-modbus",
3
- "version": "1.3.0",
4
- "description": "Node-RED Modbus节点,支持TCP/串口通信、多设备轮询、MQTT集成和Home Assistant自动发现",
3
+ "version": "1.5.4",
4
+ "description": "Node-RED Modbus节点,支持TCP/串口通信、串口自动搜索(包括/dev/ttyS1等)、多设备轮询、智能日志限流、MQTT集成、Home Assistant自动发现和多品牌开关面板,现代化美观配置界面",
5
5
  "main": "nodes/modbus-master.js",
6
6
  "scripts": {
7
7
  "test": "echo \"Error: no test specified\" && exit 1"