node-red-contrib-symi-mesh 1.6.6 → 1.6.8

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.
@@ -2,12 +2,12 @@
2
2
  {
3
3
  "id": "knx_sync_flow",
4
4
  "type": "tab",
5
- "label": "Symi Mesh KNX 双向同步",
5
+ "label": "Symi Mesh - KNX Sync",
6
6
  "disabled": false,
7
- "info": "展示Symi Mesh设备与KNX系统的完整双向同步,包括开关、调光灯、窗帘、温控器等所有设备类型"
7
+ "info": "Mesh设备与KNX系统双向同步示例"
8
8
  },
9
9
  {
10
- "id": "symi_gateway",
10
+ "id": "symi_gateway_knx",
11
11
  "type": "symi-gateway",
12
12
  "name": "Symi网关",
13
13
  "connectionType": "tcp",
@@ -17,449 +17,87 @@
17
17
  "baudRate": "115200"
18
18
  },
19
19
  {
20
- "id": "knx_gateway",
20
+ "id": "knx_gateway_config",
21
21
  "type": "knxUltimate-config",
22
22
  "host": "192.168.1.50",
23
23
  "port": "3671",
24
24
  "physAddr": "15.15.22",
25
25
  "hostProtocol": "TunnelUDP",
26
- "suppress_ack_ldatareq": false,
27
26
  "loglevel": "error",
28
- "name": "KNX网关",
29
- "localEchoInTunneling": true,
30
- "KNXEthInterface": "Auto",
31
- "KNXEthInterfaceManuallyInput": ""
27
+ "name": "KNX Gateway",
28
+ "delaybetweentelegrams": 25,
29
+ "autoReconnect": "yes"
32
30
  },
33
31
  {
34
- "id": "comment_switch_sync",
35
- "type": "comment",
36
- "z": "knx_sync_flow",
37
- "name": "开关双向同步 (Symi ↔ KNX)",
38
- "info": "Symi开关与KNX开关执行器双向同步",
39
- "x": 150,
40
- "y": 60,
41
- "wires": []
42
- },
43
- {
44
- "id": "symi_switch",
45
- "type": "symi-device",
46
- "z": "knx_sync_flow",
47
- "name": "三路开关",
48
- "gateway": "symi_gateway",
49
- "deviceMac": "14:27:c9:20:da:cc",
50
- "outputFormat": "full",
51
- "x": 130,
52
- "y": 120,
53
- "wires": [["switch_to_knx"]]
54
- },
55
- {
56
- "id": "switch_to_knx",
57
- "type": "function",
58
- "z": "knx_sync_flow",
59
- "name": "Symi→KNX转换",
60
- "func": "// Symi开关状态转换为KNX格式\nconst channel = msg.channel || 1;\nconst value = msg.payload ? 1 : 0;\n\n// 根据通道设置KNX组地址\nconst gaMap = {\n 1: '1/1/1',\n 2: '1/1/2',\n 3: '1/1/3'\n};\n\nreturn {\n payload: value,\n destination: gaMap[channel]\n};",
61
- "outputs": 1,
62
- "noerr": 0,
63
- "x": 330,
64
- "y": 120,
65
- "wires": [["knx_out_switch"]]
66
- },
67
- {
68
- "id": "knx_out_switch",
69
- "type": "knxUltimate-out",
32
+ "id": "knx_input_node",
33
+ "type": "knxUltimate",
70
34
  "z": "knx_sync_flow",
71
- "server": "knx_gateway",
72
- "name": "KNX开关输出",
35
+ "server": "knx_gateway_config",
73
36
  "topic": "",
74
37
  "dpt": "1.001",
75
- "initialread": false,
76
- "notifyreadrequest": false,
77
- "notifyresponse": false,
78
- "notifywrite": true,
79
- "notifyreadresponse": false,
80
- "notifyreadrequestalsorespondtobus": false,
81
- "notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized": "",
82
- "listenallga": false,
38
+ "name": "KNX Input",
83
39
  "outputtype": "write",
84
- "outputRBE": true,
85
- "inputRBE": true,
86
- "x": 560,
87
- "y": 120,
88
- "wires": []
89
- },
90
- {
91
- "id": "knx_in_switch",
92
- "type": "knxUltimate-in",
93
- "z": "knx_sync_flow",
94
- "server": "knx_gateway",
95
- "name": "KNX开关输入",
96
- "topic": "1/1/1",
97
- "dpt": "1.001",
98
- "initialread": false,
40
+ "outputRBE": "false",
41
+ "inputRBE": "false",
99
42
  "notifyreadrequest": false,
100
43
  "notifyresponse": true,
101
44
  "notifywrite": true,
102
- "notifyreadresponse": false,
103
- "notifyreadrequestalsorespondtobus": false,
104
- "notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized": "",
105
- "listenallga": false,
106
- "x": 140,
107
- "y": 180,
108
- "wires": [["knx_to_switch"]]
109
- },
110
- {
111
- "id": "knx_to_switch",
112
- "type": "function",
113
- "z": "knx_sync_flow",
114
- "name": "KNX→Symi转换",
115
- "func": "// KNX状态转换为Symi格式\n// 根据组地址确定通道\nconst gaChannelMap = {\n '1/1/1': 1,\n '1/1/2': 2,\n '1/1/3': 3\n};\n\nconst channel = gaChannelMap[msg.destination] || 1;\n\nreturn {\n payload: msg.payload === 1,\n channel: channel,\n deviceMac: '14:27:c9:20:da:cc'\n};",
116
- "outputs": 1,
117
- "noerr": 0,
118
- "x": 330,
119
- "y": 180,
120
- "wires": [["symi_switch_control"]]
121
- },
122
- {
123
- "id": "symi_switch_control",
124
- "type": "symi-device",
125
- "z": "knx_sync_flow",
126
- "name": "控制Symi开关",
127
- "gateway": "symi_gateway",
128
- "deviceMac": "",
129
- "outputFormat": "full",
130
- "x": 560,
131
- "y": 180,
132
- "wires": []
133
- },
134
- {
135
- "id": "comment_light_sync",
136
- "type": "comment",
137
- "z": "knx_sync_flow",
138
- "name": "调光灯双向同步 (Symi ↔ KNX)",
139
- "info": "Symi调光灯与KNX调光执行器双向同步",
140
- "x": 160,
141
- "y": 260,
142
- "wires": []
143
- },
144
- {
145
- "id": "symi_light",
146
- "type": "symi-device",
147
- "z": "knx_sync_flow",
148
- "name": "双色调光灯",
149
- "gateway": "symi_gateway",
150
- "deviceMac": "e4:53:4c:93:46:84",
151
- "outputFormat": "full",
152
- "x": 140,
153
- "y": 320,
154
- "wires": [["light_to_knx"]]
155
- },
156
- {
157
- "id": "light_to_knx",
158
- "type": "function",
159
- "z": "knx_sync_flow",
160
- "name": "Symi灯光→KNX",
161
- "func": "// Symi灯光状态转换为KNX DPT 1(开关) + DPT 5(亮度)\nconst msgs = [];\n\n// 开关状态\nif (msg.payload.switch !== undefined) {\n msgs.push({\n payload: msg.payload.switch ? 1 : 0,\n destination: '2/1/1', // KNX开关组地址\n dpt: '1.001'\n });\n}\n\n// 亮度\nif (msg.payload.brightness !== undefined) {\n msgs.push({\n payload: Math.round(msg.payload.brightness * 255 / 100),\n destination: '2/1/2', // KNX亮度组地址\n dpt: '5.001'\n });\n}\n\n// 色温(可选)\nif (msg.payload.colorTemp !== undefined) {\n msgs.push({\n payload: msg.payload.colorTemp,\n destination: '2/1/3', // KNX色温组地址\n dpt: '5.001'\n });\n}\n\nreturn [msgs];",
162
- "outputs": 1,
163
- "noerr": 0,
164
- "x": 340,
165
- "y": 320,
166
- "wires": [["knx_out_light"]]
167
- },
168
- {
169
- "id": "knx_out_light",
170
- "type": "knxUltimate-out",
171
- "z": "knx_sync_flow",
172
- "server": "knx_gateway",
173
- "name": "KNX调光输出",
174
- "topic": "",
175
- "dpt": "1.001",
176
- "initialread": false,
177
- "notifyreadrequest": false,
178
- "notifyresponse": false,
179
- "notifywrite": true,
180
- "notifyreadresponse": false,
181
- "notifyreadrequestalsorespondtobus": false,
182
- "notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized": "",
183
- "listenallga": false,
184
- "outputtype": "write",
185
- "outputRBE": true,
186
- "inputRBE": true,
187
- "x": 560,
188
- "y": 320,
189
- "wires": []
190
- },
191
- {
192
- "id": "knx_in_light_switch",
193
- "type": "knxUltimate-in",
194
- "z": "knx_sync_flow",
195
- "server": "knx_gateway",
196
- "name": "KNX灯光开关",
197
- "topic": "2/1/1",
198
- "dpt": "1.001",
199
- "initialread": false,
200
- "notifyreadrequest": false,
201
- "notifyresponse": true,
202
- "notifywrite": true,
203
- "notifyreadresponse": false,
204
- "notifyreadrequestalsorespondtobus": false,
205
- "notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized": "",
206
- "listenallga": false,
45
+ "listenallga": true,
207
46
  "x": 150,
208
- "y": 380,
209
- "wires": [["knx_light_to_symi"]]
47
+ "y": 100,
48
+ "wires": [["knx_bridge_node"]]
210
49
  },
211
50
  {
212
- "id": "knx_in_light_brightness",
213
- "type": "knxUltimate-in",
51
+ "id": "knx_bridge_node",
52
+ "type": "symi-knx-bridge",
214
53
  "z": "knx_sync_flow",
215
- "server": "knx_gateway",
216
- "name": "KNX亮度",
217
- "topic": "2/1/2",
218
- "dpt": "5.001",
219
- "initialread": false,
220
- "notifyreadrequest": false,
221
- "notifyresponse": true,
222
- "notifywrite": true,
223
- "notifyreadresponse": false,
224
- "notifyreadrequestalsorespondtobus": false,
225
- "notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized": "",
226
- "listenallga": false,
227
- "x": 130,
228
- "y": 420,
229
- "wires": [["knx_light_to_symi"]]
54
+ "name": "KNX Bridge",
55
+ "gateway": "symi_gateway_knx",
56
+ "mappings": "[]",
57
+ "knxEntities": "[]",
58
+ "x": 350,
59
+ "y": 100,
60
+ "wires": [["knx_output_node"], ["debug_node"]]
230
61
  },
231
62
  {
232
- "id": "knx_light_to_symi",
233
- "type": "function",
63
+ "id": "knx_output_node",
64
+ "type": "knxUltimate",
234
65
  "z": "knx_sync_flow",
235
- "name": "KNX→Symi灯光",
236
- "func": "// KNX状态转换为Symi MQTT格式\nconst mac = 'e4534c934684'; // 双色调光灯MAC(无冒号)\n\n// 根据DPT类型处理\nif (msg.dpt === '1.001') {\n // 开关控制\n return {\n topic: `symi_mesh/${mac}/light/set`,\n payload: JSON.stringify({\n state: msg.payload === 1 ? 'ON' : 'OFF'\n })\n };\n} else if (msg.dpt === '5.001') {\n // 亮度控制\n return {\n topic: `symi_mesh/${mac}/light/set`,\n payload: JSON.stringify({\n brightness: msg.payload // 0-255\n })\n };\n}\n\nreturn null;",
237
- "outputs": 1,
238
- "noerr": 0,
239
- "x": 340,
240
- "y": 400,
241
- "wires": [["mqtt_publish"]]
242
- },
243
- {
244
- "id": "comment_cover_sync",
245
- "type": "comment",
246
- "z": "knx_sync_flow",
247
- "name": "窗帘双向同步 (Symi ↔ KNX)",
248
- "info": "Symi窗帘与KNX窗帘执行器双向同步",
249
- "x": 150,
250
- "y": 480,
251
- "wires": []
252
- },
253
- {
254
- "id": "symi_cover",
255
- "type": "symi-device",
256
- "z": "knx_sync_flow",
257
- "name": "智能窗帘",
258
- "gateway": "symi_gateway",
259
- "deviceMac": "d4:86:c0:81:d8:14",
260
- "outputFormat": "full",
261
- "x": 130,
262
- "y": 540,
263
- "wires": [["cover_to_knx"]]
264
- },
265
- {
266
- "id": "cover_to_knx",
267
- "type": "function",
268
- "z": "knx_sync_flow",
269
- "name": "Symi窗帘→KNX",
270
- "func": "// Symi窗帘状态转换为KNX DPT 1(上下) + DPT 5(位置)\nconst msgs = [];\n\n// 运行状态转换\nif (msg.payload.curtainStatus !== undefined) {\n const status = msg.payload.curtainStatus;\n // 1=opening, 2=closing, 0/3=stopped\n if (status === 1) {\n msgs.push({\n payload: 1, // 上\n destination: '3/1/1',\n dpt: '1.008'\n });\n } else if (status === 2) {\n msgs.push({\n payload: 0, // 下\n destination: '3/1/1',\n dpt: '1.008'\n });\n }\n}\n\n// 位置\nif (msg.payload.curtainPosition !== undefined) {\n msgs.push({\n payload: Math.round(msg.payload.curtainPosition * 255 / 100),\n destination: '3/1/2', // KNX位置组地址\n dpt: '5.001'\n });\n}\n\nreturn [msgs];",
271
- "outputs": 1,
272
- "noerr": 0,
273
- "x": 330,
274
- "y": 540,
275
- "wires": [["knx_out_cover"]]
276
- },
277
- {
278
- "id": "knx_out_cover",
279
- "type": "knxUltimate-out",
280
- "z": "knx_sync_flow",
281
- "server": "knx_gateway",
282
- "name": "KNX窗帘输出",
66
+ "server": "knx_gateway_config",
283
67
  "topic": "",
284
- "dpt": "1.008",
285
- "initialread": false,
286
- "notifyreadrequest": false,
287
- "notifyresponse": false,
288
- "notifywrite": true,
289
- "notifyreadresponse": false,
290
- "notifyreadrequestalsorespondtobus": false,
291
- "notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized": "",
292
- "listenallga": false,
68
+ "dpt": "0",
69
+ "name": "KNX Output (Universal Mode)",
293
70
  "outputtype": "write",
294
- "outputRBE": true,
295
- "inputRBE": true,
296
- "x": 550,
297
- "y": 540,
298
- "wires": []
299
- },
300
- {
301
- "id": "knx_in_cover",
302
- "type": "knxUltimate-in",
303
- "z": "knx_sync_flow",
304
- "server": "knx_gateway",
305
- "name": "KNX窗帘控制",
306
- "topic": "3/1/1",
307
- "dpt": "1.008",
308
- "initialread": false,
309
- "notifyreadrequest": false,
310
- "notifyresponse": true,
311
- "notifywrite": true,
312
- "notifyreadresponse": false,
313
- "notifyreadrequestalsorespondtobus": false,
314
- "notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized": "",
315
- "listenallga": false,
316
- "x": 150,
317
- "y": 600,
318
- "wires": [["knx_cover_to_symi"]]
319
- },
320
- {
321
- "id": "knx_cover_to_symi",
322
- "type": "function",
323
- "z": "knx_sync_flow",
324
- "name": "KNX→Symi窗帘",
325
- "func": "// KNX窗帘控制转换为Symi MQTT格式\nconst mac = 'd486c081d814';\n\nif (msg.dpt === '1.008') {\n // 上下控制\n return {\n topic: `symi_mesh/${mac}/cover/set`,\n payload: msg.payload === 1 ? 'OPEN' : 'CLOSE'\n };\n} else if (msg.dpt === '5.001') {\n // 位置控制\n return {\n topic: `symi_mesh/${mac}/cover/position/set`,\n payload: Math.round(msg.payload * 100 / 255).toString()\n };\n}\n\nreturn null;",
326
- "outputs": 1,
327
- "noerr": 0,
328
- "x": 340,
329
- "y": 600,
330
- "wires": [["mqtt_publish"]]
331
- },
332
- {
333
- "id": "comment_climate_sync",
334
- "type": "comment",
335
- "z": "knx_sync_flow",
336
- "name": "温控器双向同步 (Symi ↔ KNX)",
337
- "info": "Symi温控器与KNX空调控制器双向同步",
338
- "x": 160,
339
- "y": 680,
340
- "wires": []
341
- },
342
- {
343
- "id": "symi_climate",
344
- "type": "symi-device",
345
- "z": "knx_sync_flow",
346
- "name": "温控器",
347
- "gateway": "symi_gateway",
348
- "deviceMac": "14:2e:68:46:8c:ac",
349
- "outputFormat": "full",
350
- "x": 120,
351
- "y": 740,
352
- "wires": [["climate_to_knx"]]
353
- },
354
- {
355
- "id": "climate_to_knx",
356
- "type": "function",
357
- "z": "knx_sync_flow",
358
- "name": "Symi温控→KNX",
359
- "func": "// Symi温控器状态转换为KNX DPT 9(温度) + DPT 20(模式)\nconst msgs = [];\n\n// 开关状态\nif (msg.payload.switch !== undefined) {\n msgs.push({\n payload: msg.payload.switch ? 1 : 0,\n destination: '4/1/1', // KNX空调开关\n dpt: '1.001'\n });\n}\n\n// 目标温度\nif (msg.payload.targetTemp !== undefined) {\n msgs.push({\n payload: msg.payload.targetTemp,\n destination: '4/1/2', // KNX温度设定\n dpt: '9.001'\n });\n}\n\n// 模式\nif (msg.payload.climateMode !== undefined) {\n // 1=cool, 2=heat, 3=fan_only, 4=dry\n msgs.push({\n payload: msg.payload.climateMode,\n destination: '4/1/3', // KNX模式\n dpt: '20.102'\n });\n}\n\n// 风速\nif (msg.payload.fanMode !== undefined) {\n msgs.push({\n payload: msg.payload.fanMode,\n destination: '4/1/4', // KNX风速\n dpt: '5.001'\n });\n}\n\nreturn [msgs];",
360
- "outputs": 1,
361
- "noerr": 0,
362
- "x": 320,
363
- "y": 740,
364
- "wires": [["knx_out_climate"]]
365
- },
366
- {
367
- "id": "knx_out_climate",
368
- "type": "knxUltimate-out",
369
- "z": "knx_sync_flow",
370
- "server": "knx_gateway",
371
- "name": "KNX温控输出",
372
- "topic": "",
373
- "dpt": "9.001",
374
- "initialread": false,
375
- "notifyreadrequest": false,
376
- "notifyresponse": false,
71
+ "outputRBE": "false",
72
+ "inputRBE": "false",
377
73
  "notifywrite": true,
378
- "notifyreadresponse": false,
379
- "notifyreadrequestalsorespondtobus": false,
380
- "notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized": "",
381
- "listenallga": false,
382
- "outputtype": "write",
383
- "outputRBE": true,
384
- "inputRBE": true,
385
- "x": 540,
386
- "y": 740,
74
+ "listenallga": true,
75
+ "x": 550,
76
+ "y": 100,
387
77
  "wires": []
388
78
  },
389
79
  {
390
- "id": "mqtt_publish",
391
- "type": "mqtt out",
80
+ "id": "debug_node",
81
+ "type": "debug",
392
82
  "z": "knx_sync_flow",
393
- "name": "MQTT发布",
394
- "topic": "",
395
- "qos": "0",
396
- "retain": "false",
397
- "respTopic": "",
398
- "contentType": "",
399
- "userProps": "",
400
- "correl": "",
401
- "expiry": "",
402
- "broker": "",
83
+ "name": "Sync Debug",
84
+ "active": true,
85
+ "tosidebar": true,
86
+ "console": false,
87
+ "tostatus": false,
88
+ "complete": "payload",
403
89
  "x": 550,
404
- "y": 600,
90
+ "y": 160,
405
91
  "wires": []
406
92
  },
407
93
  {
408
- "id": "comment_sensor_sync",
94
+ "id": "comment_usage",
409
95
  "type": "comment",
410
96
  "z": "knx_sync_flow",
411
- "name": "传感器同步 (Symi → KNX)",
412
- "info": "Symi传感器状态同步到KNX(单向)",
413
- "x": 150,
414
- "y": 820,
415
- "wires": []
416
- },
417
- {
418
- "id": "symi_motion",
419
- "type": "symi-device",
420
- "z": "knx_sync_flow",
421
- "name": "人体感应器",
422
- "gateway": "symi_gateway",
423
- "deviceMac": "f3:cf:7d:3d:44:9c",
424
- "outputFormat": "full",
97
+ "name": "使用说明",
98
+ "info": "1. 修改symi_gateway_knx的IP为网关实际地址\n2. 修改knx_gateway_config的IP为KNX网关地址\n3. 双击KNX Bridge节点配置映射:\n - 导入KNX实体(组地址)\n - 添加Mesh设备与KNX实体的映射\n4. 部署后等待20秒初始化完成\n5. 测试双向同步",
425
99
  "x": 140,
426
- "y": 880,
427
- "wires": [["motion_to_knx"]]
428
- },
429
- {
430
- "id": "motion_to_knx",
431
- "type": "function",
432
- "z": "knx_sync_flow",
433
- "name": "人体感应→KNX",
434
- "func": "// Symi人体感应转换为KNX DPT 1\nif (msg.payload.motion !== undefined) {\n return {\n payload: msg.payload.motion ? 1 : 0,\n destination: '5/1/1', // KNX人体感应组地址\n dpt: '1.002'\n };\n}\nreturn null;",
435
- "outputs": 1,
436
- "noerr": 0,
437
- "x": 330,
438
- "y": 880,
439
- "wires": [["knx_out_motion"]]
440
- },
441
- {
442
- "id": "knx_out_motion",
443
- "type": "knxUltimate-out",
444
- "z": "knx_sync_flow",
445
- "server": "knx_gateway",
446
- "name": "KNX人体感应",
447
- "topic": "",
448
- "dpt": "1.002",
449
- "initialread": false,
450
- "notifyreadrequest": false,
451
- "notifyresponse": false,
452
- "notifywrite": true,
453
- "notifyreadresponse": false,
454
- "notifyreadrequestalsorespondtobus": false,
455
- "notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized": "",
456
- "listenallga": false,
457
- "outputtype": "write",
458
- "outputRBE": true,
459
- "inputRBE": true,
460
- "x": 540,
461
- "y": 880,
100
+ "y": 40,
462
101
  "wires": []
463
102
  }
464
103
  ]
465
-
@@ -85,7 +85,9 @@ class DeviceInfo {
85
85
  case 0x45:
86
86
  // 6-8路开关状态上报(2字节,小端序)
87
87
  // 用于场景执行后的状态同步
88
- if (this.channels >= 6) {
88
+ // 注意:米家/面板操作会发送0x45类型,不管实际路数
89
+ // 窗帘(type=5)不使用0x45
90
+ if (this.deviceType !== 5) {
89
91
  this.handleSwitchState(parameters);
90
92
  }
91
93
  break;
@@ -385,8 +387,14 @@ class DeviceInfo {
385
387
  const value = parameters[0];
386
388
  this.state.switch = value === 0x02;
387
389
  } else if (this.channels <= 4) {
388
- // 1-4路开关:1字节,每2位表示1路
389
- const value = parameters[0];
390
+ // 1-4路开关:通常1字节,但米家/面板操作可能发送2字节(0x45类型)
391
+ let value;
392
+ if (parameters.length >= 2) {
393
+ // 2字节:小端序(米家/面板场景触发)
394
+ value = parameters[0] | (parameters[1] << 8);
395
+ } else {
396
+ value = parameters[0];
397
+ }
390
398
  // 保存原始状态值供控制时使用
391
399
  this.state.switchState = value;
392
400
  for (let i = 0; i < this.channels; i++) {
package/lib/tcp-client.js CHANGED
@@ -57,9 +57,13 @@ class TCPClient extends EventEmitter {
57
57
  }
58
58
  }, 10000);
59
59
 
60
- // 捕获connect过程中的同步错误(如AggregateError
60
+ // 使用family:4强制IPv4,避免Node.js 18+ Happy Eyeballs导致AggregateError
61
61
  try {
62
- this.client.connect(this.port, this.host, () => {
62
+ this.client.connect({
63
+ port: this.port,
64
+ host: this.host,
65
+ family: 4 // 强制IPv4
66
+ }, () => {
63
67
  if (!resolved && !rejected) {
64
68
  resolved = true;
65
69
  clearTimeout(timeout);
@@ -530,7 +530,7 @@ module.exports = function(RED) {
530
530
 
531
531
  // Mesh设备状态变化处理(事件驱动)
532
532
  const handleMeshStateChange = (eventData) => {
533
- if (node.syncLock) return;
533
+ // 只检查initializing,不检查syncLock以避免丢失事件
534
534
  if (node.initializing) return;
535
535
 
536
536
  const mac = eventData.device.macAddress;
@@ -972,7 +972,7 @@ module.exports = function(RED) {
972
972
 
973
973
  // RS485 device state change handler (event-driven)
974
974
  const handleModbusStateChange = (data) => {
975
- if (node.syncLock) return;
975
+ // 不检查syncLock以避免丢失事件,使用时间戳防回环
976
976
 
977
977
  const mapping = node.findRS485Mapping(data.device.modbusAddress);
978
978
  if (!mapping) return;
@@ -1025,36 +1025,36 @@ module.exports = function(RED) {
1025
1025
  if (node.processing || node.commandQueue.length === 0) return;
1026
1026
 
1027
1027
  node.processing = true;
1028
- node.syncLock = true;
1029
1028
  let multiChange = node.commandQueue.length > 1;
1030
1029
 
1031
- while (node.commandQueue.length > 0) {
1032
- const cmd = node.commandQueue.shift();
1033
- try {
1034
- if (cmd.direction === 'mesh-to-modbus') {
1035
- await node.syncMeshToModbus(cmd);
1036
- } else if (cmd.direction === 'modbus-to-mesh') {
1037
- await node.syncModbusToMesh(cmd);
1030
+ try {
1031
+ while (node.commandQueue.length > 0) {
1032
+ const cmd = node.commandQueue.shift();
1033
+ try {
1034
+ if (cmd.direction === 'mesh-to-modbus') {
1035
+ await node.syncMeshToModbus(cmd);
1036
+ } else if (cmd.direction === 'modbus-to-mesh') {
1037
+ await node.syncModbusToMesh(cmd);
1038
+ }
1039
+ // 命令之间延迟50ms
1040
+ await node.sleep(50);
1041
+ } catch (err) {
1042
+ node.error(`同步失败: ${err.message}`);
1038
1043
  }
1039
- // 命令之间延迟50ms
1040
- await node.sleep(50);
1041
- } catch (err) {
1042
- node.error(`同步失败: ${err.message}`);
1043
1044
  }
1044
- }
1045
1045
 
1046
- // 如果发生多次变化,安排验证
1047
- if (multiChange && !node.pendingVerify) {
1048
- node.pendingVerify = true;
1049
- setTimeout(() => {
1050
- node.verifySync();
1051
- node.pendingVerify = false;
1052
- }, 200);
1046
+ // 如果发生多次变化,安排验证
1047
+ if (multiChange && !node.pendingVerify) {
1048
+ node.pendingVerify = true;
1049
+ setTimeout(() => {
1050
+ node.verifySync();
1051
+ node.pendingVerify = false;
1052
+ }, 200);
1053
+ }
1054
+ } finally {
1055
+ node.processing = false;
1056
+ node.lastSyncTime = Date.now();
1053
1057
  }
1054
-
1055
- node.syncLock = false;
1056
- node.processing = false;
1057
- node.lastSyncTime = Date.now();
1058
1058
  };
1059
1059
 
1060
1060
  // 多实体变化后验证同步状态
@@ -2098,7 +2098,6 @@ module.exports = function(RED) {
2098
2098
  node.curtainCache = {};
2099
2099
  node.lastSentTime = {};
2100
2100
  node.processing = false;
2101
- node.syncLock = false;
2102
2101
 
2103
2102
  node.log('[RS485 Bridge] 节点已清理');
2104
2103
  done();