node-red-contrib-symi-mesh 1.7.0 → 1.7.2
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 +270 -48
- package/examples/knx-sync-example.json +122 -58
- package/examples/rs485-sync-example.json +76 -0
- package/lib/device-manager.js +114 -18
- package/nodes/rs485-debug.html +2 -1
- package/nodes/symi-485-bridge.html +233 -32
- package/nodes/symi-485-bridge.js +970 -104
- package/nodes/symi-485-config.js +49 -11
- package/nodes/symi-cloud-sync.html +2 -0
- package/nodes/symi-device.html +5 -3
- package/nodes/symi-gateway.js +10 -3
- package/nodes/symi-knx-bridge.html +3 -2
- package/nodes/symi-knx-bridge.js +3 -3
- package/nodes/symi-knx-ha-bridge.html +4 -3
- package/nodes/symi-knx-ha-bridge.js +2 -2
- package/nodes/symi-mqtt.js +17 -1
- package/nodes/symi-rs485-sync.html +361 -0
- package/nodes/symi-rs485-sync.js +765 -0
- package/package.json +3 -2
package/lib/device-manager.js
CHANGED
|
@@ -13,7 +13,7 @@ const DEVICE_TYPE_NAMES = {
|
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
class DeviceInfo {
|
|
16
|
-
constructor(data) {
|
|
16
|
+
constructor(data, manager) {
|
|
17
17
|
this.macAddress = data.macAddress;
|
|
18
18
|
this.networkAddress = data.networkAddress;
|
|
19
19
|
this.deviceType = data.deviceType;
|
|
@@ -25,6 +25,7 @@ class DeviceInfo {
|
|
|
25
25
|
this.state = {};
|
|
26
26
|
this.lastSeen = Date.now();
|
|
27
27
|
this.isThreeInOne = false;
|
|
28
|
+
this.manager = manager; // 保存DeviceManager引用
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
generateName() {
|
|
@@ -75,10 +76,41 @@ class DeviceInfo {
|
|
|
75
76
|
this.lastSeen = Date.now();
|
|
76
77
|
|
|
77
78
|
switch (attrType) {
|
|
79
|
+
case 0x01:
|
|
78
80
|
case 0x02:
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
case 0x03:
|
|
82
|
+
case 0x04:
|
|
83
|
+
// 特殊协议:attrType直接表示风速值(无参数)
|
|
84
|
+
// 53 80 06 03 E3 01 01 35 - 高风(01)
|
|
85
|
+
// 53 80 06 03 E3 01 02 36 - 中风(02)
|
|
86
|
+
// 53 80 06 03 E3 01 03 37 - 低风(03)
|
|
87
|
+
// 53 80 06 03 E3 01 04 30 - 自动(04)
|
|
88
|
+
if (this.deviceType === 10 && parameters.length === 0) {
|
|
89
|
+
const oldFanMode = this.state.fanMode;
|
|
90
|
+
this.state.fanMode = attrType;
|
|
91
|
+
if (oldFanMode !== this.state.fanMode && this.manager) {
|
|
92
|
+
this.manager.emit('device-state-changed', {
|
|
93
|
+
device: this,
|
|
94
|
+
state: { fanMode: this.state.fanMode, acFanSpeed: this.state.fanMode },
|
|
95
|
+
attrType: 0x1C,
|
|
96
|
+
parameters: [],
|
|
97
|
+
subOpcode: 0x06,
|
|
98
|
+
isUserControl: true,
|
|
99
|
+
isSceneExecution: false
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return; // 已处理,直接返回
|
|
103
|
+
}
|
|
104
|
+
// 非温控器设备,继续处理其他逻辑
|
|
105
|
+
if (attrType === 0x03 && parameters.length > 0) {
|
|
106
|
+
this.state.brightness = parameters[0];
|
|
107
|
+
this.state.switch = parameters[0] > 0;
|
|
108
|
+
}
|
|
109
|
+
if (attrType === 0x04 && parameters.length > 0) {
|
|
110
|
+
this.state.colorTemp = parameters[0];
|
|
111
|
+
}
|
|
112
|
+
// 0x02用于开关状态,如果是温控器且无参数已在上面处理为风速
|
|
113
|
+
if (attrType === 0x02 && parameters.length > 0 && this.deviceType !== 5) {
|
|
82
114
|
this.handleSwitchState(parameters);
|
|
83
115
|
}
|
|
84
116
|
break;
|
|
@@ -91,15 +123,6 @@ class DeviceInfo {
|
|
|
91
123
|
this.handleSwitchState(parameters);
|
|
92
124
|
}
|
|
93
125
|
break;
|
|
94
|
-
case 0x03:
|
|
95
|
-
if (parameters.length > 0) {
|
|
96
|
-
this.state.brightness = parameters[0];
|
|
97
|
-
this.state.switch = parameters[0] > 0;
|
|
98
|
-
}
|
|
99
|
-
break;
|
|
100
|
-
case 0x04:
|
|
101
|
-
if (parameters.length > 0) this.state.colorTemp = parameters[0];
|
|
102
|
-
break;
|
|
103
126
|
case 0x05:
|
|
104
127
|
// CURT_RUN_STATUS - 窗帘运行状态
|
|
105
128
|
// 【兼容两种协议】:
|
|
@@ -108,6 +131,8 @@ class DeviceInfo {
|
|
|
108
131
|
// 通过status=0(仅米家)和status=3(仅小程序)来区分协议类型
|
|
109
132
|
if (parameters.length > 0) {
|
|
110
133
|
const status = parameters[0];
|
|
134
|
+
const oldStatus = this.state.curtainStatus;
|
|
135
|
+
const oldAction = this.state.curtainAction;
|
|
111
136
|
this.state.curtainStatus = status;
|
|
112
137
|
|
|
113
138
|
// 检测协议类型:status=0只在米家出现,status=3只在小程序出现
|
|
@@ -132,6 +157,19 @@ class DeviceInfo {
|
|
|
132
157
|
this.state.curtainAction = 'closing';
|
|
133
158
|
}
|
|
134
159
|
}
|
|
160
|
+
|
|
161
|
+
// 触发状态变化事件
|
|
162
|
+
if ((oldStatus !== this.state.curtainStatus || oldAction !== this.state.curtainAction) && this.manager) {
|
|
163
|
+
this.manager.emit('device-state-changed', {
|
|
164
|
+
device: this,
|
|
165
|
+
state: { curtainStatus: this.state.curtainStatus, curtainAction: this.state.curtainAction },
|
|
166
|
+
attrType: attrType,
|
|
167
|
+
parameters: parameters,
|
|
168
|
+
subOpcode: 0x06,
|
|
169
|
+
isUserControl: true,
|
|
170
|
+
isSceneExecution: false
|
|
171
|
+
});
|
|
172
|
+
}
|
|
135
173
|
}
|
|
136
174
|
break;
|
|
137
175
|
case 0x06:
|
|
@@ -199,7 +237,20 @@ class DeviceInfo {
|
|
|
199
237
|
case 0x1C:
|
|
200
238
|
// 风速设置(温控器和三合一空调都使用)
|
|
201
239
|
if (parameters.length > 0) {
|
|
240
|
+
const oldFanMode = this.state.fanMode;
|
|
202
241
|
this.state.fanMode = parameters[0];
|
|
242
|
+
// 触发状态变化事件(通过DeviceManager)
|
|
243
|
+
if (oldFanMode !== this.state.fanMode && this.manager) {
|
|
244
|
+
this.manager.emit('device-state-changed', {
|
|
245
|
+
device: this,
|
|
246
|
+
state: { fanMode: this.state.fanMode, acFanSpeed: this.state.fanMode },
|
|
247
|
+
attrType: attrType,
|
|
248
|
+
parameters: [],
|
|
249
|
+
subOpcode: 0x06,
|
|
250
|
+
isUserControl: true,
|
|
251
|
+
isSceneExecution: false
|
|
252
|
+
});
|
|
253
|
+
}
|
|
203
254
|
}
|
|
204
255
|
break;
|
|
205
256
|
case 0x1D:
|
|
@@ -316,8 +367,10 @@ class DeviceInfo {
|
|
|
316
367
|
}
|
|
317
368
|
|
|
318
369
|
// byte2: bit[0-2]=空调风速 (协议值: 0=自动, 1=低, 2=中, 4=高)
|
|
370
|
+
// 统一转换为标准Mesh值: 1=高, 2=中, 3=低, 4=自动
|
|
319
371
|
const fanLevel = byte2 & 0x07;
|
|
320
|
-
|
|
372
|
+
const fanMap = { 0: 4, 1: 3, 2: 2, 4: 1 };
|
|
373
|
+
this.state.fanMode = fanMap[fanLevel] || 3; // 默认低风
|
|
321
374
|
|
|
322
375
|
// byte3: 空调设定温度 (16-30°C)
|
|
323
376
|
if (byte3 >= 16 && byte3 <= 30) {
|
|
@@ -353,8 +406,10 @@ class DeviceInfo {
|
|
|
353
406
|
this.state.freshAirMode = freshMode;
|
|
354
407
|
|
|
355
408
|
// byte7: bit[0-2]=新风风速 (协议值: 0=自动, 1=低, 2=中, 4=高)
|
|
409
|
+
// 统一转换为标准Mesh值: 1=高, 2=中, 3=低, 4=自动
|
|
356
410
|
const freshFan = byte7 & 0x07;
|
|
357
|
-
|
|
411
|
+
const freshFanMap = { 0: 4, 1: 3, 2: 2, 4: 1 };
|
|
412
|
+
this.state.freshAirSpeed = freshFanMap[freshFan] || 4; // 默认自动
|
|
358
413
|
|
|
359
414
|
}
|
|
360
415
|
|
|
@@ -385,7 +440,29 @@ class DeviceInfo {
|
|
|
385
440
|
if (this.channels === 1) {
|
|
386
441
|
// 单路开关
|
|
387
442
|
const value = parameters[0];
|
|
388
|
-
|
|
443
|
+
// 温控器设备:0x02消息的值含义
|
|
444
|
+
// 0x01=关, 0x02=开 (开关控制)
|
|
445
|
+
// 但某些协议中 1-4 表示风速(需要通过 attrType=0x1C 来区分)
|
|
446
|
+
// 这里只处理开关控制,风速控制由 0x1C 消息处理
|
|
447
|
+
if (this.deviceType === 10) {
|
|
448
|
+
// 温控器开关:0x01=关, 0x02=开
|
|
449
|
+
const oldSwitch = this.state.switch;
|
|
450
|
+
this.state.switch = value === 0x02;
|
|
451
|
+
// 触发开关状态变化事件
|
|
452
|
+
if (oldSwitch !== this.state.switch && this.manager) {
|
|
453
|
+
this.manager.emit('device-state-changed', {
|
|
454
|
+
device: this,
|
|
455
|
+
state: { switch: this.state.switch, acSwitch: this.state.switch },
|
|
456
|
+
attrType: 0x02,
|
|
457
|
+
parameters: [],
|
|
458
|
+
subOpcode: 0x06,
|
|
459
|
+
isUserControl: true,
|
|
460
|
+
isSceneExecution: false
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
} else {
|
|
464
|
+
this.state.switch = value === 0x02;
|
|
465
|
+
}
|
|
389
466
|
} else if (this.channels <= 4) {
|
|
390
467
|
// 1-4路开关:通常1字节,但米家/面板操作可能发送2字节(0x45类型)
|
|
391
468
|
let value;
|
|
@@ -449,6 +526,25 @@ class DeviceInfo {
|
|
|
449
526
|
// 存储变化的按键信息,供外部使用
|
|
450
527
|
this.lastChangedButtons = changedButtons;
|
|
451
528
|
|
|
529
|
+
// 触发状态变化事件(多路开关)
|
|
530
|
+
// 注意:这个事件会被symi-gateway.js转发给symi-485-bridge.js
|
|
531
|
+
if (changedButtons.length > 0 && this.manager) {
|
|
532
|
+
const changedState = {};
|
|
533
|
+
for (const btn of changedButtons) {
|
|
534
|
+
changedState[`switch_${btn.button}`] = btn.newState;
|
|
535
|
+
}
|
|
536
|
+
// 使用与symi-gateway.js相同的事件格式
|
|
537
|
+
this.manager.emit('device-state-changed', {
|
|
538
|
+
device: this,
|
|
539
|
+
state: changedState,
|
|
540
|
+
attrType: 0x02,
|
|
541
|
+
parameters: [],
|
|
542
|
+
subOpcode: 0x06,
|
|
543
|
+
isUserControl: true,
|
|
544
|
+
isSceneExecution: false
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
|
|
452
548
|
return changedButtons;
|
|
453
549
|
}
|
|
454
550
|
|
|
@@ -533,7 +629,7 @@ class DeviceManager extends EventEmitter {
|
|
|
533
629
|
const addrMap = this.context.get(`symi_addr_map_${this.gatewayId}`) || {};
|
|
534
630
|
|
|
535
631
|
Object.entries(saved).forEach(([mac, data]) => {
|
|
536
|
-
const device = new DeviceInfo(data);
|
|
632
|
+
const device = new DeviceInfo(data, this); // 传递manager引用
|
|
537
633
|
// 恢复三合一标记
|
|
538
634
|
if (data.isThreeInOne) {
|
|
539
635
|
device.isThreeInOne = true;
|
|
@@ -601,7 +697,7 @@ class DeviceManager extends EventEmitter {
|
|
|
601
697
|
let nameChanged = false;
|
|
602
698
|
|
|
603
699
|
if (!device) {
|
|
604
|
-
device = new DeviceInfo(deviceData);
|
|
700
|
+
device = new DeviceInfo(deviceData, this);
|
|
605
701
|
this.devices.set(mac, device);
|
|
606
702
|
isNew = true;
|
|
607
703
|
} else {
|