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.
@@ -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
- // TYPE_ON_OFF - 开关状态(仅用于开关、灯光、温控器)
80
- // 窗帘(type=5)不使用0x02,忽略此消息避免误操作
81
- if (this.deviceType !== 5) {
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
- this.state.fanMode = fanLevel;
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
- this.state.freshAirSpeed = freshFan;
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
- this.state.switch = value === 0x02;
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 {
@@ -11,7 +11,8 @@
11
11
  },
12
12
  inputs: 1,
13
13
  outputs: 1,
14
- icon: 'serial.svg',
14
+ icon: 'font-awesome/fa-bug',
15
+ align: 'left',
15
16
  paletteLabel: 'RS485调试',
16
17
  label: function() {
17
18
  return this.name || 'RS485调试';