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.
- package/README.md +189 -24
- package/nodes/modbus-master.html +168 -53
- package/nodes/modbus-master.js +86 -5
- package/nodes/modbus-slave-switch.html +158 -70
- package/nodes/modbus-slave-switch.js +81 -6
- package/package.json +2 -2
|
@@ -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:
|
|
60
|
-
<i class="fa fa-plug"></i>
|
|
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: #
|
|
63
|
-
|
|
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-
|
|
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="
|
|
79
|
-
<div style="font-size: 11px; color: #
|
|
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
|
-
<
|
|
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:
|
|
135
|
-
<i class="fa fa-
|
|
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: #
|
|
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:
|
|
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-
|
|
157
|
-
<
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
<
|
|
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:
|
|
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>
|
|
191
|
-
<input type="number" id="node-input-targetSlaveAddress" placeholder="10" min="1" max="247" style="width:
|
|
192
|
-
<span style="margin-left: 10px; color: #666;"
|
|
193
|
-
<div style="font-size: 11px; color: #
|
|
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>
|
|
200
|
-
<input type="number" id="node-input-targetCoilNumber" placeholder="0" min="0" max="31" style="width:
|
|
201
|
-
<span style="margin-left: 10px; color: #666;"
|
|
202
|
-
<div style="font-size: 11px; color: #
|
|
203
|
-
|
|
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="
|
|
208
|
-
<div style="font-size: 12px; color: #333;">
|
|
209
|
-
<
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
|
|
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.
|
|
4
|
-
"description": "Node-RED Modbus节点,支持TCP
|
|
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"
|