node-red-contrib-symi-mesh 1.2.3 → 1.2.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 +8 -11
- package/lib/tcp-client.js +2 -2
- package/nodes/symi-gateway.js +10 -10
- package/nodes/symi-mqtt.js +42 -42
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -563,15 +563,12 @@ node-red-contrib-symi-mesh/
|
|
|
563
563
|
|
|
564
564
|
## 更新日志
|
|
565
565
|
|
|
566
|
-
### v1.2.
|
|
567
|
-
-
|
|
568
|
-
-
|
|
569
|
-
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
- 完整支持三合一设备(空调+新风+地暖)
|
|
573
|
-
- 新增MQTT Discovery自动发现功能
|
|
574
|
-
- 优化设备识别和状态同步机制
|
|
566
|
+
### v1.2.4 (2025-11-04)
|
|
567
|
+
- **重大优化**:大幅减少日志输出,解决长期运行日志膨胀问题
|
|
568
|
+
- 将高频日志(Frame接收/发送、状态事件、MQTT消息等)改为debug级别
|
|
569
|
+
- 保留关键日志(连接/断开、设备发现、错误)用于问题排查
|
|
570
|
+
- 提升系统性能和稳定性,适合7x24小时长期运行
|
|
571
|
+
- 修复网关或MQTT服务器离线时的错误处理
|
|
575
572
|
|
|
576
573
|
|
|
577
574
|
## 许可证
|
|
@@ -585,8 +582,8 @@ Copyright (c) 2025 SYMI 亖米
|
|
|
585
582
|
## 关于
|
|
586
583
|
|
|
587
584
|
**作者**: SYMI 亖米
|
|
588
|
-
**版本**: 1.2.
|
|
585
|
+
**版本**: 1.2.4
|
|
589
586
|
**协议**: 蓝牙MESH网关(初级版)串口协议V1.0
|
|
590
|
-
**最后更新**: 2025-
|
|
587
|
+
**最后更新**: 2025-11-04
|
|
591
588
|
**仓库**: https://github.com/symi-daguo/node-red-contrib-symi-mesh
|
|
592
589
|
**npm包**: https://www.npmjs.com/package/node-red-contrib-symi-mesh
|
package/lib/tcp-client.js
CHANGED
|
@@ -236,9 +236,9 @@ class TCPClient extends EventEmitter {
|
|
|
236
236
|
return;
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
-
//
|
|
239
|
+
// 使用debug记录发送帧,避免日志泛滥
|
|
240
240
|
const frameHex = frame.toString('hex').toUpperCase();
|
|
241
|
-
this.logger.
|
|
241
|
+
this.logger.debug(`[TCP Frame] 发送: ${frameHex}`);
|
|
242
242
|
|
|
243
243
|
this.client.write(frame, (error) => {
|
|
244
244
|
if (error) {
|
package/nodes/symi-gateway.js
CHANGED
|
@@ -124,16 +124,16 @@ module.exports = function(RED) {
|
|
|
124
124
|
};
|
|
125
125
|
|
|
126
126
|
SymiGatewayNode.prototype.handleFrame = function(frame) {
|
|
127
|
-
//
|
|
127
|
+
// 使用debug记录详细frame信息,避免日志泛滥
|
|
128
128
|
const frameHex = Buffer.from([frame.header, frame.opcode, ...(frame.status !== null ? [frame.status] : []), frame.length, ...frame.payload, frame.checksum]).toString('hex').toUpperCase();
|
|
129
|
-
this.
|
|
129
|
+
this.debug(`[TCP Frame] 接收: ${frameHex} (opcode=0x${frame.opcode.toString(16).toUpperCase()})`);
|
|
130
130
|
|
|
131
131
|
if (frame.opcode === OP_RESP_DEVICE_LIST && frame.status === 0x00) {
|
|
132
132
|
this.parseDeviceListFrame(frame);
|
|
133
133
|
} else if (frame.isDeviceStatusEvent()) {
|
|
134
134
|
try {
|
|
135
135
|
const event = parseStatusEvent(frame);
|
|
136
|
-
this.
|
|
136
|
+
this.debug(`[状态事件] 地址=0x${event.networkAddress.toString(16).toUpperCase()}, 消息类型=0x${event.attrType.toString(16).toUpperCase()}, 参数=[${Array.from(event.parameters).map(p => '0x' + p.toString(16).toUpperCase()).join(', ')}]`);
|
|
137
137
|
|
|
138
138
|
const device = this.deviceManager.updateDeviceState(
|
|
139
139
|
event.networkAddress,
|
|
@@ -156,10 +156,10 @@ module.exports = function(RED) {
|
|
|
156
156
|
}
|
|
157
157
|
} else if (frame.opcode === 0xB0) {
|
|
158
158
|
// 控制命令响应
|
|
159
|
-
this.
|
|
159
|
+
this.debug(`[控制响应] 0xB0: ${frameHex} ${frame.status === 0 ? '(成功)' : '(失败)'}`)
|
|
160
160
|
} else {
|
|
161
161
|
// 其他frame类型
|
|
162
|
-
this.
|
|
162
|
+
this.debug(`[其他Frame] ${frameHex}`);
|
|
163
163
|
}
|
|
164
164
|
};
|
|
165
165
|
|
|
@@ -344,13 +344,13 @@ module.exports = function(RED) {
|
|
|
344
344
|
|
|
345
345
|
if (device && typeof device.getCurrentSwitchState === 'function') {
|
|
346
346
|
currentState = device.getCurrentSwitchState();
|
|
347
|
-
this.
|
|
347
|
+
this.debug(`使用缓存状态: 0x${currentState.toString(16).toUpperCase()}`);
|
|
348
348
|
} else if (device && device.state.switchState !== undefined) {
|
|
349
349
|
currentState = device.state.switchState;
|
|
350
|
-
this.
|
|
350
|
+
this.debug(`使用保存状态: 0x${currentState.toString(16).toUpperCase()}`);
|
|
351
351
|
} else {
|
|
352
352
|
// 如果没有当前状态,查询一次
|
|
353
|
-
this.
|
|
353
|
+
this.debug(`查询开关状态: 地址0x${networkAddr.toString(16).toUpperCase()}`);
|
|
354
354
|
const queryFrame = this.protocolHandler.buildDeviceStatusQueryFrame(networkAddr, 0x02);
|
|
355
355
|
await this.client.sendFrame(queryFrame, 2);
|
|
356
356
|
await this.sleep(200);
|
|
@@ -361,7 +361,7 @@ module.exports = function(RED) {
|
|
|
361
361
|
// 使用默认全关状态
|
|
362
362
|
const defaultStates = { 2: 0x05, 3: 0x15, 4: 0x55, 6: 0x5555 };
|
|
363
363
|
currentState = defaultStates[channels] || 0x55;
|
|
364
|
-
this.
|
|
364
|
+
this.debug(`使用默认状态: 0x${currentState.toString(16).toUpperCase()}`);
|
|
365
365
|
}
|
|
366
366
|
}
|
|
367
367
|
|
|
@@ -377,7 +377,7 @@ module.exports = function(RED) {
|
|
|
377
377
|
controlParam = Buffer.from([stateValue]);
|
|
378
378
|
}
|
|
379
379
|
|
|
380
|
-
this.
|
|
380
|
+
this.debug(`发送开关控制: 地址0x${networkAddr.toString(16).toUpperCase()}, 第${targetChannel}路${targetState ? '开' : '关'}, 状态值0x${stateValue.toString(16).toUpperCase()}`);
|
|
381
381
|
const frame = this.protocolHandler.buildDeviceControlFrame(networkAddr, attrType, controlParam);
|
|
382
382
|
return await this.client.sendFrame(frame, 1);
|
|
383
383
|
|
package/nodes/symi-mqtt.js
CHANGED
|
@@ -92,7 +92,7 @@ module.exports = function(RED) {
|
|
|
92
92
|
node.mqttClient = mqtt.connect(node.mqttBroker, options);
|
|
93
93
|
|
|
94
94
|
node.mqttClient.on('message', (topic, message) => {
|
|
95
|
-
node.
|
|
95
|
+
node.debug(`[MQTT消息] topic=${topic}, message=${message.toString()}`);
|
|
96
96
|
node.handleMQTTMessage(topic, message);
|
|
97
97
|
});
|
|
98
98
|
|
|
@@ -114,7 +114,7 @@ module.exports = function(RED) {
|
|
|
114
114
|
});
|
|
115
115
|
|
|
116
116
|
node.mqttClient.on('reconnect', () => {
|
|
117
|
-
node.
|
|
117
|
+
node.debug('MQTT正在重连...');
|
|
118
118
|
node.status({ fill: 'yellow', shape: 'ring', text: '重连中' });
|
|
119
119
|
});
|
|
120
120
|
|
|
@@ -192,7 +192,7 @@ module.exports = function(RED) {
|
|
|
192
192
|
node.mqttClient.subscribe(topic, (err) => {
|
|
193
193
|
if (!err) {
|
|
194
194
|
node.subscriptions.set(topic, device.macAddress);
|
|
195
|
-
node.
|
|
195
|
+
node.debug(`订阅topic: ${topic} → ${device.macAddress}`);
|
|
196
196
|
} else {
|
|
197
197
|
node.error(`订阅失败: ${topic}, ${err.message}`);
|
|
198
198
|
}
|
|
@@ -329,9 +329,9 @@ module.exports = function(RED) {
|
|
|
329
329
|
topic: `symi_mesh/${macClean}/climate/mode`,
|
|
330
330
|
payload: mode
|
|
331
331
|
});
|
|
332
|
-
node.
|
|
332
|
+
node.debug(`发布温控器模式: ${mode} (开关=ON, climateMode=${state.climateMode})`);
|
|
333
333
|
} else {
|
|
334
|
-
node.
|
|
334
|
+
node.debug(`温控器开启但模式未设置,等待0x1D消息`);
|
|
335
335
|
}
|
|
336
336
|
} else {
|
|
337
337
|
// 关闭状态:明确发布off
|
|
@@ -339,7 +339,7 @@ module.exports = function(RED) {
|
|
|
339
339
|
topic: `symi_mesh/${macClean}/climate/mode`,
|
|
340
340
|
payload: 'off'
|
|
341
341
|
});
|
|
342
|
-
node.
|
|
342
|
+
node.debug(`发布温控器模式: off`);
|
|
343
343
|
}
|
|
344
344
|
} else if (device.deviceType === 4 || device.deviceType === 0x18) {
|
|
345
345
|
// 灯光开关:发布JSON格式
|
|
@@ -368,7 +368,7 @@ module.exports = function(RED) {
|
|
|
368
368
|
topic: `symi_mesh/${macClean}/light/state`,
|
|
369
369
|
payload: JSON.stringify(lightState)
|
|
370
370
|
});
|
|
371
|
-
node.
|
|
371
|
+
node.debug(`发布灯光开关: ${lightState.state} (JSON)`);
|
|
372
372
|
} else if (device.deviceType === 9) {
|
|
373
373
|
// 插卡取电器
|
|
374
374
|
const switchState = convertStateValue('switch', 0x02, state.switch);
|
|
@@ -376,14 +376,14 @@ module.exports = function(RED) {
|
|
|
376
376
|
topic: `symi_mesh/${macClean}/switch/state`,
|
|
377
377
|
payload: switchState
|
|
378
378
|
});
|
|
379
|
-
node.
|
|
379
|
+
node.debug(`发布插卡取电器开关状态: ${switchState}`);
|
|
380
380
|
} else if (device.channels === 1) {
|
|
381
381
|
const switchState = convertStateValue('switch', 0x02, state.switch);
|
|
382
382
|
publishes.push({
|
|
383
383
|
topic: `symi_mesh/${macClean}/switch/state`,
|
|
384
384
|
payload: switchState
|
|
385
385
|
});
|
|
386
|
-
node.
|
|
386
|
+
node.debug(`发布开关状态: ${switchState}`);
|
|
387
387
|
} else {
|
|
388
388
|
// 发布每个继电器的状态
|
|
389
389
|
for (let i = 1; i <= device.channels; i++) {
|
|
@@ -394,7 +394,7 @@ module.exports = function(RED) {
|
|
|
394
394
|
topic: `symi_mesh/${macClean}/switch_${i}/state`,
|
|
395
395
|
payload: switchState
|
|
396
396
|
});
|
|
397
|
-
node.
|
|
397
|
+
node.debug(`发布开关路${i}: ${switchState}`);
|
|
398
398
|
}
|
|
399
399
|
}
|
|
400
400
|
}
|
|
@@ -416,9 +416,9 @@ module.exports = function(RED) {
|
|
|
416
416
|
topic: switchTopic,
|
|
417
417
|
payload: switchState
|
|
418
418
|
});
|
|
419
|
-
node.
|
|
420
|
-
node.
|
|
421
|
-
node.
|
|
419
|
+
node.debug(`发布插卡状态: ${cardState} → ${cardTopic}`);
|
|
420
|
+
node.debug(`发布开关状态: ${switchState} → ${switchTopic}`);
|
|
421
|
+
node.debug(`插卡原始值: cardInserted=${state.cardInserted}, switch=${state.switch}`);
|
|
422
422
|
}
|
|
423
423
|
break;
|
|
424
424
|
|
|
@@ -456,7 +456,7 @@ module.exports = function(RED) {
|
|
|
456
456
|
topic: `symi_mesh/${macClean}/light/state`,
|
|
457
457
|
payload: JSON.stringify(lightState)
|
|
458
458
|
});
|
|
459
|
-
node.
|
|
459
|
+
node.debug(`发布灯光亮度: ${state.brightness}% -> ${lightState.brightness}/255`);
|
|
460
460
|
}
|
|
461
461
|
break;
|
|
462
462
|
|
|
@@ -477,7 +477,7 @@ module.exports = function(RED) {
|
|
|
477
477
|
topic: `symi_mesh/${macClean}/light/state`,
|
|
478
478
|
payload: JSON.stringify(lightState)
|
|
479
479
|
});
|
|
480
|
-
node.
|
|
480
|
+
node.debug(`发布灯光色温: ${state.colorTemp}% -> ${lightState.color_temp}mireds, 亮度: ${lightState.brightness}/255`);
|
|
481
481
|
}
|
|
482
482
|
break;
|
|
483
483
|
|
|
@@ -491,7 +491,7 @@ module.exports = function(RED) {
|
|
|
491
491
|
topic: `symi_mesh/${macClean}/cover/state`,
|
|
492
492
|
payload: curtainState
|
|
493
493
|
});
|
|
494
|
-
node.
|
|
494
|
+
node.debug(`发布窗帘运行状态: ${curtainState} (原始值=${state.curtainStatus})`);
|
|
495
495
|
|
|
496
496
|
// 到头(status=0)时也发布position,确保HA显示正确位置
|
|
497
497
|
if (state.curtainStatus === 0 && state.curtainPosition !== undefined) {
|
|
@@ -499,7 +499,7 @@ module.exports = function(RED) {
|
|
|
499
499
|
topic: `symi_mesh/${macClean}/cover/position`,
|
|
500
500
|
payload: state.curtainPosition.toString()
|
|
501
501
|
});
|
|
502
|
-
node.
|
|
502
|
+
node.debug(`窗帘到头,同时发布位置: ${state.curtainPosition}%`);
|
|
503
503
|
}
|
|
504
504
|
}
|
|
505
505
|
break;
|
|
@@ -511,7 +511,7 @@ module.exports = function(RED) {
|
|
|
511
511
|
topic: `symi_mesh/${macClean}/cover/position`,
|
|
512
512
|
payload: state.curtainPosition.toString()
|
|
513
513
|
});
|
|
514
|
-
node.
|
|
514
|
+
node.debug(`发布窗帘位置: ${state.curtainPosition}%`);
|
|
515
515
|
|
|
516
516
|
// 同时发布运行状态(确保HA正确显示)
|
|
517
517
|
if (state.curtainStatus !== undefined) {
|
|
@@ -521,7 +521,7 @@ module.exports = function(RED) {
|
|
|
521
521
|
topic: `symi_mesh/${macClean}/cover/state`,
|
|
522
522
|
payload: curtainState
|
|
523
523
|
});
|
|
524
|
-
node.
|
|
524
|
+
node.debug(`同时发布窗帘状态: ${curtainState}`);
|
|
525
525
|
}
|
|
526
526
|
}
|
|
527
527
|
break;
|
|
@@ -535,8 +535,8 @@ module.exports = function(RED) {
|
|
|
535
535
|
topic: motionTopic,
|
|
536
536
|
payload: motionState
|
|
537
537
|
});
|
|
538
|
-
node.
|
|
539
|
-
node.
|
|
538
|
+
node.debug(`发布人体感应状态: ${motionState} → ${motionTopic}`);
|
|
539
|
+
node.debug(`人体感应原始值: motion=${state.motion}, MAC=${device.macAddress}`);
|
|
540
540
|
}
|
|
541
541
|
break;
|
|
542
542
|
|
|
@@ -557,7 +557,7 @@ module.exports = function(RED) {
|
|
|
557
557
|
topic: `symi_mesh/${macClean}/light/state`,
|
|
558
558
|
payload: JSON.stringify(lightState)
|
|
559
559
|
});
|
|
560
|
-
node.
|
|
560
|
+
node.debug(`发布RGB颜色: R=${state.rgb.r}, G=${state.rgb.g}, B=${state.rgb.b}`);
|
|
561
561
|
}
|
|
562
562
|
break;
|
|
563
563
|
|
|
@@ -596,7 +596,7 @@ module.exports = function(RED) {
|
|
|
596
596
|
topic: `symi_mesh/${macClean}/climate/fan_mode`,
|
|
597
597
|
payload: fanMode
|
|
598
598
|
});
|
|
599
|
-
node.
|
|
599
|
+
node.debug(`发布温控器风速: ${fanMode} (原始值=${state.fanMode})`);
|
|
600
600
|
}
|
|
601
601
|
break;
|
|
602
602
|
|
|
@@ -621,7 +621,7 @@ module.exports = function(RED) {
|
|
|
621
621
|
topic: `symi_mesh/${macClean}/binary_sensor/state`,
|
|
622
622
|
payload: doorState
|
|
623
623
|
});
|
|
624
|
-
node.
|
|
624
|
+
node.debug(`发布门磁状态: ${doorState} (${state.doorOpen})`);
|
|
625
625
|
}
|
|
626
626
|
break;
|
|
627
627
|
|
|
@@ -632,7 +632,7 @@ module.exports = function(RED) {
|
|
|
632
632
|
topic: `symi_mesh/${macClean}/temperature/state`,
|
|
633
633
|
payload: state.temperature.toFixed(1)
|
|
634
634
|
});
|
|
635
|
-
node.
|
|
635
|
+
node.debug(`发布温度: ${state.temperature.toFixed(1)}°C`);
|
|
636
636
|
}
|
|
637
637
|
// 温控器/三合一的当前温度也是0x16
|
|
638
638
|
if (state.currentTemp !== undefined && (device.deviceType === 10 || device.isThreeInOne)) {
|
|
@@ -657,7 +657,7 @@ module.exports = function(RED) {
|
|
|
657
657
|
topic: `symi_mesh/${macClean}/humidity/state`,
|
|
658
658
|
payload: state.humidity.toString()
|
|
659
659
|
});
|
|
660
|
-
node.
|
|
660
|
+
node.debug(`发布湿度: ${state.humidity}%`);
|
|
661
661
|
}
|
|
662
662
|
break;
|
|
663
663
|
|
|
@@ -866,24 +866,24 @@ module.exports = function(RED) {
|
|
|
866
866
|
}
|
|
867
867
|
|
|
868
868
|
const payload = message.toString();
|
|
869
|
-
node.
|
|
869
|
+
node.debug(`[MQTT收到] topic=${topic}, payload=${payload}, deviceType=${device.deviceType}, isThreeInOne=${device.isThreeInOne}`);
|
|
870
870
|
|
|
871
871
|
const commands = node.parseMQTTCommand(topic, payload, device);
|
|
872
872
|
|
|
873
873
|
if (commands && commands.length > 0) {
|
|
874
|
-
node.
|
|
874
|
+
node.debug(`[MQTT解析] 解析出${commands.length}个命令:`);
|
|
875
875
|
commands.forEach((cmd, idx) => {
|
|
876
|
-
node.
|
|
876
|
+
node.debug(` 命令${idx + 1}: attrType=0x${cmd.attrType.toString(16).toUpperCase()}, param=[${Array.from(cmd.param).map(p => '0x' + p.toString(16).toUpperCase()).join(', ')}]`);
|
|
877
877
|
});
|
|
878
878
|
|
|
879
879
|
// 使用for循环而不是forEach以正确处理async
|
|
880
880
|
(async () => {
|
|
881
881
|
for (const command of commands) {
|
|
882
882
|
const paramHex = Array.from(command.param).map(p => '0x' + p.toString(16).toUpperCase()).join(' ');
|
|
883
|
-
node.
|
|
883
|
+
node.debug(`[MQTT发送] → 网关: addr=0x${device.networkAddress.toString(16).toUpperCase()}, attr=0x${command.attrType.toString(16).toUpperCase()}, param=[${paramHex}]`);
|
|
884
884
|
try {
|
|
885
885
|
await node.gateway.sendControl(device.networkAddress, command.attrType, command.param);
|
|
886
|
-
node.
|
|
886
|
+
node.debug(`[MQTT发送] ✓ 成功: ${device.name}`);
|
|
887
887
|
|
|
888
888
|
// 立即发布状态更新(optimistic update)
|
|
889
889
|
node.publishCommandFeedback(device, command, payload, topic);
|
|
@@ -902,7 +902,7 @@ module.exports = function(RED) {
|
|
|
902
902
|
const node = this;
|
|
903
903
|
const macClean = device.macAddress.replace(/:/g, '').toLowerCase();
|
|
904
904
|
|
|
905
|
-
node.
|
|
905
|
+
node.debug(`反馈类型: attr=0x${command.attrType.toString(16)}, deviceType=${device.deviceType}, payload=${typeof payload === 'string' ? payload : JSON.stringify(payload)}`);
|
|
906
906
|
|
|
907
907
|
// 根据命令类型立即发布状态反馈(optimistic update)
|
|
908
908
|
if (command.attrType === 0x02 && device.deviceType !== 4 && device.deviceType !== 0x18 && device.deviceType !== 10 && !device.isThreeInOne) {
|
|
@@ -912,7 +912,7 @@ module.exports = function(RED) {
|
|
|
912
912
|
// 更新设备缓存状态
|
|
913
913
|
device.state.switch = (payload === 'ON');
|
|
914
914
|
node.mqttClient.publish(`symi_mesh/${macClean}/switch/state`, state, { retain: false });
|
|
915
|
-
node.
|
|
915
|
+
node.debug(`立即反馈开关状态: ${state}`);
|
|
916
916
|
} else {
|
|
917
917
|
// 多路开关
|
|
918
918
|
const match = topic.match(/switch_(\d+)\/set/);
|
|
@@ -921,7 +921,7 @@ module.exports = function(RED) {
|
|
|
921
921
|
// 更新设备缓存状态
|
|
922
922
|
device.state[`switch_${channel}`] = (payload === 'ON');
|
|
923
923
|
node.mqttClient.publish(`symi_mesh/${macClean}/switch_${channel}/state`, state, { retain: false });
|
|
924
|
-
node.
|
|
924
|
+
node.debug(`立即反馈开关路${channel}状态: ${state}`);
|
|
925
925
|
}
|
|
926
926
|
} else if (command.attrType === 0x02 && (device.deviceType === 4 || device.deviceType === 0x18)) {
|
|
927
927
|
// 灯光开关控制 - 不需要optimistic update,等待53 80反馈
|
|
@@ -943,15 +943,15 @@ module.exports = function(RED) {
|
|
|
943
943
|
} else if (command.attrType === 0x1B || command.attrType === 0x1C || command.attrType === 0x1D || (command.attrType === 0x02 && (device.deviceType === 10 || device.isThreeInOne))) {
|
|
944
944
|
// 温控器/三合一反馈 - 完全依赖53 80反馈,不做optimistic update
|
|
945
945
|
// 因为这些设备的状态会通过53 80事件准确上报,optimistic update会被53 80覆盖导致状态不一致
|
|
946
|
-
node.
|
|
946
|
+
node.debug(`温控器/三合一命令已发送(attr=0x${command.attrType.toString(16)}),等待53 80反馈`);
|
|
947
947
|
} else if (command.attrType === 0x68 || command.attrType === 0x6A || command.attrType === 0x6B || command.attrType === 0x6C) {
|
|
948
948
|
// 三合一专用消息 - 完全依赖53 80反馈
|
|
949
|
-
node.
|
|
949
|
+
node.debug(`三合一专用命令已发送(attr=0x${command.attrType.toString(16)}),等待53 80反馈`);
|
|
950
950
|
} else if (command.attrType === 0x03 && device.isThreeInOne && topic.includes('/fresh_air/')) {
|
|
951
951
|
// 三合一新风亮度/风速控制反馈
|
|
952
952
|
const speed = Math.round(parseFloat(payload));
|
|
953
953
|
node.mqttClient.publish(`symi_mesh/${macClean}/fresh_air/speed`, speed.toString(), { retain: false });
|
|
954
|
-
node.
|
|
954
|
+
node.debug(`立即反馈新风风速: ${speed}%`);
|
|
955
955
|
}
|
|
956
956
|
};
|
|
957
957
|
|
|
@@ -996,7 +996,7 @@ module.exports = function(RED) {
|
|
|
996
996
|
|
|
997
997
|
return commands;
|
|
998
998
|
} catch(e) {
|
|
999
|
-
this.
|
|
999
|
+
this.debug(`JSON解析失败: ${e.message}`);
|
|
1000
1000
|
return [];
|
|
1001
1001
|
}
|
|
1002
1002
|
}
|
|
@@ -1025,7 +1025,7 @@ module.exports = function(RED) {
|
|
|
1025
1025
|
}
|
|
1026
1026
|
} else if (topic.includes('/fresh_air/') && device.isThreeInOne) {
|
|
1027
1027
|
// 三合一 - 新风控制(使用专用消息类型)
|
|
1028
|
-
this.
|
|
1028
|
+
this.debug(`[MQTT解析] 新风控制 - topic=${topic}, payload=${payload}, isThreeInOne=${device.isThreeInOne}`);
|
|
1029
1029
|
if (topic.endsWith('/fresh_air/set')) {
|
|
1030
1030
|
const value = payload === 'ON';
|
|
1031
1031
|
commands.push({ attrType: 0x68, param: Buffer.from([value ? 0x02 : 0x01]) });
|
|
@@ -1075,12 +1075,12 @@ module.exports = function(RED) {
|
|
|
1075
1075
|
if (mode !== undefined) {
|
|
1076
1076
|
if (mode === 0) {
|
|
1077
1077
|
commands.push({ attrType: 0x02, param: Buffer.from([0x01]) });
|
|
1078
|
-
this.
|
|
1078
|
+
this.debug(`[MQTT解析] 空调模式命令: 0x02=0x01 (关闭)`);
|
|
1079
1079
|
} else {
|
|
1080
1080
|
// 先开启空调
|
|
1081
1081
|
commands.push({ attrType: 0x02, param: Buffer.from([0x02]) });
|
|
1082
1082
|
commands.push({ attrType: 0x1D, param: Buffer.from([mode]) });
|
|
1083
|
-
this.
|
|
1083
|
+
this.debug(`[MQTT解析] 空调模式命令: 先开启(0x02) + 设置模式(0x1D=0x${mode.toString(16).toUpperCase()})`);
|
|
1084
1084
|
}
|
|
1085
1085
|
}
|
|
1086
1086
|
|
|
@@ -1090,7 +1090,7 @@ module.exports = function(RED) {
|
|
|
1090
1090
|
const fan = fans[payload];
|
|
1091
1091
|
if (fan !== undefined) {
|
|
1092
1092
|
commands.push({ attrType: 0x1C, param: Buffer.from([fan]) });
|
|
1093
|
-
this.
|
|
1093
|
+
this.debug(`[MQTT解析] 空调风速命令: 0x1C=0x${fan.toString(16).toUpperCase()} (${payload})`);
|
|
1094
1094
|
}
|
|
1095
1095
|
|
|
1096
1096
|
} else if (topic.endsWith('/cover/set')) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-symi-mesh",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.4",
|
|
4
4
|
"description": "Node-RED节点集合,用于通过TCP/串口连接Symi蓝牙Mesh网关,支持Home Assistant MQTT Discovery自动发现",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
],
|
|
50
50
|
"repository": {
|
|
51
51
|
"type": "git",
|
|
52
|
-
"url": "https://github.com/symi-daguo/node-red-contrib-symi-mesh.git"
|
|
52
|
+
"url": "git+https://github.com/symi-daguo/node-red-contrib-symi-mesh.git"
|
|
53
53
|
},
|
|
54
54
|
"bugs": {
|
|
55
55
|
"url": "https://github.com/symi-daguo/node-red-contrib-symi-mesh/issues"
|