node-red-contrib-symi-modbus 1.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.
@@ -0,0 +1,188 @@
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
+
@@ -0,0 +1,348 @@
1
+ <script type="text/javascript">
2
+ RED.nodes.registerType('modbus-master', {
3
+ category: 'modbus',
4
+ color: '#3FADB5',
5
+ defaults: {
6
+ name: {value: "Modbus主站"},
7
+ connectionType: {value: "tcp"},
8
+ // TCP配置
9
+ tcpHost: {value: "127.0.0.1"},
10
+ tcpPort: {value: 502},
11
+ // 串口配置
12
+ serialPort: {value: "COM1"},
13
+ serialBaudRate: {value: 9600},
14
+ serialDataBits: {value: 8},
15
+ serialStopBits: {value: 1},
16
+ serialParity: {value: "none"},
17
+ // 从站配置列表(数组)
18
+ slaves: {value: [{
19
+ address: 10,
20
+ coilStart: 0,
21
+ coilEnd: 31,
22
+ pollInterval: 200
23
+ }]},
24
+ // MQTT配置
25
+ enableMqtt: {value: false},
26
+ mqttBroker: {value: "mqtt://localhost:1883"},
27
+ mqttUsername: {value: ""},
28
+ mqttPassword: {value: ""},
29
+ mqttBaseTopic: {value: "modbus/relay"}
30
+ },
31
+ inputs: 1,
32
+ outputs: 1,
33
+ icon: "bridge.png",
34
+ label: function() {
35
+ return this.name || "Modbus主站";
36
+ },
37
+ oneditprepare: function() {
38
+ var node = this;
39
+
40
+ // 初始化从站列表
41
+ if (!node.slaves || node.slaves.length === 0) {
42
+ node.slaves = [{
43
+ address: 10,
44
+ coilStart: 0,
45
+ coilEnd: 31,
46
+ pollInterval: 200
47
+ }];
48
+ }
49
+
50
+ // 渲染从站列表
51
+ function renderSlaveList() {
52
+ var container = $("#slave-list-container");
53
+ container.empty();
54
+
55
+ node.slaves.forEach(function(slave, index) {
56
+ var slaveRow = $('<div class="slave-row" style="padding: 10px; margin: 5px 0; border: 1px solid #ddd; border-radius: 4px; background: #f9f9f9;">');
57
+
58
+ slaveRow.html(`
59
+ <div style="display: flex; align-items: center; gap: 10px; margin-bottom: 8px;">
60
+ <strong style="min-width: 80px;">从站 ${index + 1}:</strong>
61
+ <label style="margin: 0 5px;">地址:</label>
62
+ <input type="number" class="slave-address" data-index="${index}" value="${slave.address}" min="1" max="247" style="width: 60px;">
63
+ <label style="margin: 0 5px;">线圈:</label>
64
+ <input type="number" class="slave-coil-start" data-index="${index}" value="${slave.coilStart}" min="0" max="31" style="width: 50px;">
65
+ <span>-</span>
66
+ <input type="number" class="slave-coil-end" data-index="${index}" value="${slave.coilEnd}" min="0" max="31" style="width: 50px;">
67
+ <label style="margin: 0 5px;">间隔(ms):</label>
68
+ <input type="number" class="slave-poll-interval" data-index="${index}" value="${slave.pollInterval}" min="10" style="width: 60px;">
69
+ <button type="button" class="btn-delete-slave" data-index="${index}" style="background: #d9534f; color: white; border: none; padding: 4px 8px; border-radius: 3px; cursor: pointer;">删除</button>
70
+ </div>
71
+ `);
72
+
73
+ container.append(slaveRow);
74
+ });
75
+
76
+ // 更新删除按钮状态(至少保留1个从站)
77
+ if (node.slaves.length === 1) {
78
+ $(".btn-delete-slave").prop("disabled", true).css("opacity", "0.5");
79
+ }
80
+
81
+ // 更新添加按钮状态(最多10个从站)
82
+ if (node.slaves.length >= 10) {
83
+ $("#btn-add-slave").prop("disabled", true);
84
+ } else {
85
+ $("#btn-add-slave").prop("disabled", false);
86
+ }
87
+ }
88
+
89
+ // 添加从站
90
+ $("#btn-add-slave").on("click", function() {
91
+ if (node.slaves.length >= 10) {
92
+ RED.notify("最多只能添加10个从站", "warning");
93
+ return;
94
+ }
95
+
96
+ // 计算下一个从站地址(递增)
97
+ var lastAddress = node.slaves[node.slaves.length - 1].address;
98
+ var nextAddress = lastAddress + 1;
99
+ if (nextAddress > 247) nextAddress = 1;
100
+
101
+ node.slaves.push({
102
+ address: nextAddress,
103
+ coilStart: 0,
104
+ coilEnd: 31,
105
+ pollInterval: 200
106
+ });
107
+
108
+ renderSlaveList();
109
+ });
110
+
111
+ // 删除从站
112
+ $(document).on("click", ".btn-delete-slave", function() {
113
+ var index = parseInt($(this).data("index"));
114
+ if (node.slaves.length > 1) {
115
+ node.slaves.splice(index, 1);
116
+ renderSlaveList();
117
+ } else {
118
+ RED.notify("至少需要保留一个从站", "warning");
119
+ }
120
+ });
121
+
122
+ // 更新从站数据
123
+ $(document).on("change", ".slave-address, .slave-coil-start, .slave-coil-end, .slave-poll-interval", function() {
124
+ var index = parseInt($(this).data("index"));
125
+ var className = $(this).attr("class").split(" ")[0];
126
+ var value = parseInt($(this).val());
127
+
128
+ switch(className) {
129
+ case "slave-address":
130
+ node.slaves[index].address = value;
131
+ break;
132
+ case "slave-coil-start":
133
+ node.slaves[index].coilStart = value;
134
+ break;
135
+ case "slave-coil-end":
136
+ node.slaves[index].coilEnd = value;
137
+ break;
138
+ case "slave-poll-interval":
139
+ node.slaves[index].pollInterval = value;
140
+ break;
141
+ }
142
+ });
143
+
144
+ // 初始化渲染
145
+ renderSlaveList();
146
+
147
+ // 切换连接类型时显示/隐藏相关配置
148
+ $("#node-input-connectionType").on("change", function() {
149
+ var connType = $(this).val();
150
+ if (connType === "tcp") {
151
+ $(".form-row-tcp").show();
152
+ $(".form-row-serial").hide();
153
+ } else {
154
+ $(".form-row-tcp").hide();
155
+ $(".form-row-serial").show();
156
+ }
157
+ });
158
+
159
+ // 初始化显示
160
+ $("#node-input-connectionType").trigger("change");
161
+
162
+ // MQTT开关
163
+ $("#node-input-enableMqtt").on("change", function() {
164
+ if ($(this).is(":checked")) {
165
+ $(".form-row-mqtt").show();
166
+ } else {
167
+ $(".form-row-mqtt").hide();
168
+ }
169
+ });
170
+
171
+ // 初始化MQTT显示
172
+ $("#node-input-enableMqtt").trigger("change");
173
+ },
174
+ oneditsave: function() {
175
+ // 保存从站配置到节点
176
+ this.slaves = this.slaves || [];
177
+ }
178
+ });
179
+ </script>
180
+
181
+ <script type="text/html" data-template-name="modbus-master">
182
+ <div class="form-row">
183
+ <label for="node-input-name"><i class="fa fa-tag"></i> 名称</label>
184
+ <input type="text" id="node-input-name" placeholder="Modbus主站">
185
+ </div>
186
+
187
+ <div class="form-row">
188
+ <label for="node-input-connectionType"><i class="fa fa-plug"></i> 连接类型</label>
189
+ <select id="node-input-connectionType">
190
+ <option value="tcp">TCP/IP</option>
191
+ <option value="serial">串口</option>
192
+ </select>
193
+ </div>
194
+
195
+ <!-- TCP配置 -->
196
+ <div class="form-row form-row-tcp">
197
+ <label for="node-input-tcpHost"><i class="fa fa-server"></i> TCP主机</label>
198
+ <input type="text" id="node-input-tcpHost" placeholder="127.0.0.1">
199
+ </div>
200
+
201
+ <div class="form-row form-row-tcp">
202
+ <label for="node-input-tcpPort"><i class="fa fa-plug"></i> TCP端口</label>
203
+ <input type="number" id="node-input-tcpPort" placeholder="502">
204
+ </div>
205
+
206
+ <!-- 串口配置 -->
207
+ <div class="form-row form-row-serial">
208
+ <label for="node-input-serialPort"><i class="fa fa-terminal"></i> 串口</label>
209
+ <input type="text" id="node-input-serialPort" placeholder="COM1">
210
+ </div>
211
+
212
+ <div class="form-row form-row-serial">
213
+ <label for="node-input-serialBaudRate"><i class="fa fa-tachometer"></i> 波特率</label>
214
+ <select id="node-input-serialBaudRate">
215
+ <option value="9600">9600</option>
216
+ <option value="19200">19200</option>
217
+ <option value="38400">38400</option>
218
+ <option value="57600">57600</option>
219
+ <option value="115200">115200</option>
220
+ </select>
221
+ </div>
222
+
223
+ <div class="form-row form-row-serial">
224
+ <label for="node-input-serialDataBits"><i class="fa fa-database"></i> 数据位</label>
225
+ <select id="node-input-serialDataBits">
226
+ <option value="7">7</option>
227
+ <option value="8">8</option>
228
+ </select>
229
+ </div>
230
+
231
+ <div class="form-row form-row-serial">
232
+ <label for="node-input-serialStopBits"><i class="fa fa-stop"></i> 停止位</label>
233
+ <select id="node-input-serialStopBits">
234
+ <option value="1">1</option>
235
+ <option value="2">2</option>
236
+ </select>
237
+ </div>
238
+
239
+ <div class="form-row form-row-serial">
240
+ <label for="node-input-serialParity"><i class="fa fa-check"></i> 校验位</label>
241
+ <select id="node-input-serialParity">
242
+ <option value="none">无</option>
243
+ <option value="even">偶校验</option>
244
+ <option value="odd">奇校验</option>
245
+ </select>
246
+ </div>
247
+
248
+ <!-- 从站配置 -->
249
+ <hr>
250
+ <div class="form-row">
251
+ <label style="width: 100%; margin-bottom: 10px;">
252
+ <i class="fa fa-server"></i> 从站设备配置
253
+ <span style="font-size: 11px; color: #999; margin-left: 10px;">(最多10台,默认从地址10=0x0A开始)</span>
254
+ </label>
255
+ <div id="slave-list-container" style="width: 100%; margin-top: 10px;">
256
+ <!-- 从站列表将在这里动态生成 -->
257
+ </div>
258
+ <button type="button" id="btn-add-slave" style="margin-top: 10px; padding: 6px 12px; background: #5cb85c; color: white; border: none; border-radius: 4px; cursor: pointer;">
259
+ <i class="fa fa-plus"></i> 添加从站
260
+ </button>
261
+ <div style="font-size: 11px; color: #999; margin-top: 8px;">
262
+ 💡 提示:默认地址10(0x0A)、11(0x0B)、12(0x0C)...,轮询间隔推荐200ms
263
+ </div>
264
+ </div>
265
+
266
+ <!-- MQTT配置 -->
267
+ <hr>
268
+ <div class="form-row">
269
+ <label for="node-input-enableMqtt"><i class="fa fa-toggle-on"></i> 启用MQTT</label>
270
+ <input type="checkbox" id="node-input-enableMqtt" style="width: auto; margin-left: 10px;">
271
+ </div>
272
+
273
+ <div class="form-row form-row-mqtt">
274
+ <label for="node-input-mqttBroker"><i class="fa fa-server"></i> MQTT服务器</label>
275
+ <input type="text" id="node-input-mqttBroker" placeholder="mqtt://localhost:1883">
276
+ </div>
277
+
278
+ <div class="form-row form-row-mqtt">
279
+ <label for="node-input-mqttUsername"><i class="fa fa-user"></i> MQTT用户名</label>
280
+ <input type="text" id="node-input-mqttUsername" placeholder="可选">
281
+ </div>
282
+
283
+ <div class="form-row form-row-mqtt">
284
+ <label for="node-input-mqttPassword"><i class="fa fa-lock"></i> MQTT密码</label>
285
+ <input type="password" id="node-input-mqttPassword" placeholder="可选">
286
+ </div>
287
+
288
+ <div class="form-row form-row-mqtt">
289
+ <label for="node-input-mqttBaseTopic"><i class="fa fa-sitemap"></i> MQTT基础主题</label>
290
+ <input type="text" id="node-input-mqttBaseTopic" placeholder="modbus/relay">
291
+ <div style="font-size: 11px; color: #999; margin-top: 3px; margin-left: 110px;">
292
+ MQTT主题前缀(用于状态发布和命令订阅)<br>
293
+ 实体将自动注册到Home Assistant(MQTT Discovery)
294
+ </div>
295
+ </div>
296
+ </script>
297
+
298
+ <script type="text/html" data-help-name="modbus-master">
299
+ <p>Modbus主站节点,用于轮询Modbus继电器设备并生成MQTT实体。</p>
300
+
301
+ <h3>配置</h3>
302
+ <dl class="message-properties">
303
+ <dt>连接类型<span class="property-type">string</span></dt>
304
+ <dd>选择TCP/IP或串口连接方式</dd>
305
+
306
+ <dt>从站配置<span class="property-type">number</span></dt>
307
+ <dd>配置起始从站地址(默认10)、从站数量(最多10台)</dd>
308
+
309
+ <dt>线圈范围<span class="property-type">number</span></dt>
310
+ <dd>配置要轮询的线圈起始和结束位置(0-31)</dd>
311
+
312
+ <dt>轮询间隔<span class="property-type">number</span></dt>
313
+ <dd>每台设备的轮询间隔,单位毫秒(默认100ms)</dd>
314
+
315
+ <dt>MQTT配置<span class="property-type">object</span></dt>
316
+ <dd>可选启用MQTT,自动发布设备状态到MQTT服务器</dd>
317
+ </dl>
318
+
319
+ <h3>输入</h3>
320
+ <dl class="message-properties">
321
+ <dt>payload<span class="property-type">object</span></dt>
322
+ <dd>控制命令:
323
+ <ul>
324
+ <li><code>{"cmd": "writeCoil", "slave": 10, "coil": 0, "value": true}</code> - 写单个线圈</li>
325
+ <li><code>{"cmd": "writeCoils", "slave": 10, "coils": [true, false, ...]}</code> - 写多个线圈</li>
326
+ <li><code>{"cmd": "start"}</code> - 开始轮询</li>
327
+ <li><code>{"cmd": "stop"}</code> - 停止轮询</li>
328
+ </ul>
329
+ </dd>
330
+ </dl>
331
+
332
+ <h3>输出</h3>
333
+ <dl class="message-properties">
334
+ <dt>payload<span class="property-type">object</span></dt>
335
+ <dd>轮询结果:
336
+ <pre>{
337
+ "slave": 10,
338
+ "coils": [true, false, true, ...],
339
+ "timestamp": 1234567890
340
+ }</pre>
341
+ </dd>
342
+ </dl>
343
+
344
+ <h3>详细信息</h3>
345
+ <p>本节点支持通过TCP或串口连接Modbus继电器设备,可以轮询最多10台设备,每台设备32路继电器。</p>
346
+ <p>启用MQTT后,会自动生成Home Assistant兼容的MQTT发现消息和状态更新。</p>
347
+ </script>
348
+