node-red-contrib-symi-mesh 1.3.1 → 1.6.1
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 +219 -48
- package/examples/basic-example.json +151 -0
- package/lib/device-manager.js +109 -23
- package/lib/mqtt-helper.js +25 -4
- package/lib/protocol.js +22 -14
- package/lib/tcp-client.js +15 -13
- package/nodes/rs485-debug.html +238 -0
- package/nodes/rs485-debug.js +220 -0
- package/nodes/symi-485-bridge.html +376 -0
- package/nodes/symi-485-bridge.js +776 -0
- package/nodes/symi-485-config.html +125 -0
- package/nodes/symi-485-config.js +275 -0
- package/nodes/symi-cloud-sync.html +4 -4
- package/nodes/symi-cloud-sync.js +43 -10
- package/nodes/symi-device.html +1 -0
- package/nodes/symi-device.js +23 -14
- package/nodes/symi-gateway.js +121 -39
- package/nodes/symi-mqtt.js +233 -49
- package/package.json +7 -4
package/lib/device-manager.js
CHANGED
|
@@ -37,7 +37,7 @@ class DeviceInfo {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
getChannelCount() {
|
|
40
|
-
if ([1, 2, 3, 9, 12].includes(this.deviceType)) {
|
|
40
|
+
if ([1, 2, 3, 9, 12, 39].includes(this.deviceType)) {
|
|
41
41
|
return this.deviceSubType;
|
|
42
42
|
}
|
|
43
43
|
return 1;
|
|
@@ -48,12 +48,12 @@ class DeviceInfo {
|
|
|
48
48
|
if (this.isThreeInOne) {
|
|
49
49
|
return 'three_in_one';
|
|
50
50
|
}
|
|
51
|
-
|
|
51
|
+
|
|
52
52
|
const typeMap = {
|
|
53
53
|
1: 'switch', 2: 'switch', 3: 'switch', 4: 'light',
|
|
54
54
|
5: 'cover', 6: 'scene', 7: 'binary_sensor', 8: 'binary_sensor',
|
|
55
55
|
9: 'switch', 10: 'climate', 11: 'sensor', 12: 'switch',
|
|
56
|
-
0x14: 'scene', 0x18: 'light'
|
|
56
|
+
0x14: 'scene', 0x18: 'light', 39: 'switch'
|
|
57
57
|
};
|
|
58
58
|
return typeMap[this.deviceType] || 'switch';
|
|
59
59
|
}
|
|
@@ -83,9 +83,9 @@ class DeviceInfo {
|
|
|
83
83
|
}
|
|
84
84
|
break;
|
|
85
85
|
case 0x45:
|
|
86
|
-
// 6
|
|
86
|
+
// 6-8路开关状态上报(2字节,小端序)
|
|
87
87
|
// 用于场景执行后的状态同步
|
|
88
|
-
if (this.channels
|
|
88
|
+
if (this.channels >= 6) {
|
|
89
89
|
this.handleSwitchState(parameters);
|
|
90
90
|
}
|
|
91
91
|
break;
|
|
@@ -349,11 +349,17 @@ class DeviceInfo {
|
|
|
349
349
|
|
|
350
350
|
handleSwitchState(parameters) {
|
|
351
351
|
if (parameters.length === 0) return;
|
|
352
|
-
|
|
352
|
+
|
|
353
353
|
// 根据协议:TYPE_ON_OFF,每2位表示1路开关,b01=关,b10=开
|
|
354
354
|
// 1-4路开关:1字节
|
|
355
355
|
// 5-6路开关:2字节,小端序
|
|
356
|
-
|
|
356
|
+
|
|
357
|
+
// 保存旧状态用于检测变化
|
|
358
|
+
const oldStates = {};
|
|
359
|
+
for (let i = 1; i <= this.channels; i++) {
|
|
360
|
+
oldStates[i] = this.state[`switch_${i}`];
|
|
361
|
+
}
|
|
362
|
+
|
|
357
363
|
if (this.channels === 1) {
|
|
358
364
|
// 单路开关
|
|
359
365
|
const value = parameters[0];
|
|
@@ -372,8 +378,8 @@ class DeviceInfo {
|
|
|
372
378
|
this.state[`switch_${i + 1}`] = true;
|
|
373
379
|
}
|
|
374
380
|
}
|
|
375
|
-
} else if (this.channels === 6) {
|
|
376
|
-
// 6路开关:2字节,小端序
|
|
381
|
+
} else if (this.channels === 6 || this.channels === 8) {
|
|
382
|
+
// 6路或8路开关:2字节,小端序
|
|
377
383
|
let value;
|
|
378
384
|
if (parameters.length >= 2) {
|
|
379
385
|
// 小端序:低字节在前
|
|
@@ -382,12 +388,12 @@ class DeviceInfo {
|
|
|
382
388
|
// 兼容1字节情况,只处理前4路
|
|
383
389
|
value = parameters[0];
|
|
384
390
|
}
|
|
385
|
-
|
|
391
|
+
|
|
386
392
|
// 保存原始状态值供控制时使用
|
|
387
393
|
this.state.switchState = value;
|
|
388
|
-
|
|
389
|
-
//
|
|
390
|
-
for (let i = 0; i <
|
|
394
|
+
|
|
395
|
+
// 处理所有路数
|
|
396
|
+
for (let i = 0; i < this.channels; i++) {
|
|
391
397
|
const bitPos = i * 2;
|
|
392
398
|
const bitValue = (value >> bitPos) & 0x03;
|
|
393
399
|
if (bitValue === 0x01) {
|
|
@@ -397,6 +403,60 @@ class DeviceInfo {
|
|
|
397
403
|
}
|
|
398
404
|
}
|
|
399
405
|
}
|
|
406
|
+
|
|
407
|
+
// 检测哪些按键状态发生了变化
|
|
408
|
+
const changedButtons = [];
|
|
409
|
+
for (let i = 1; i <= this.channels; i++) {
|
|
410
|
+
const oldState = oldStates[i];
|
|
411
|
+
const newState = this.state[`switch_${i}`];
|
|
412
|
+
if (oldState !== undefined && oldState !== newState) {
|
|
413
|
+
changedButtons.push({
|
|
414
|
+
button: i,
|
|
415
|
+
oldState: oldState,
|
|
416
|
+
newState: newState
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// 存储变化的按键信息,供外部使用
|
|
422
|
+
this.lastChangedButtons = changedButtons;
|
|
423
|
+
|
|
424
|
+
return changedButtons;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// 获取按键绑定的场景ID
|
|
428
|
+
getButtonSceneId(buttonIndex) {
|
|
429
|
+
if (!this.subDeviceConfigs || buttonIndex < 1 || buttonIndex > this.subDeviceConfigs.length) {
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const config = this.subDeviceConfigs[buttonIndex - 1];
|
|
434
|
+
if (!config) return null;
|
|
435
|
+
|
|
436
|
+
// 场景按键:直接返回scene_id
|
|
437
|
+
if (config.sub_type === '场景' && config.scene_id) {
|
|
438
|
+
return {
|
|
439
|
+
type: 'scene',
|
|
440
|
+
sceneId: config.scene_id,
|
|
441
|
+
name: config.sub_name
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// 双控/总控按键:根据当前状态返回对应的场景ID
|
|
446
|
+
if ((config.sub_type === '双控' || config.sub_type === '总控')) {
|
|
447
|
+
const currentState = this.state[`switch_${buttonIndex}`];
|
|
448
|
+
const sceneId = currentState ? config.on_scene_id : config.off_scene_id;
|
|
449
|
+
if (sceneId) {
|
|
450
|
+
return {
|
|
451
|
+
type: config.sub_type === '双控' ? 'dual_control' : 'master_control',
|
|
452
|
+
sceneId: sceneId,
|
|
453
|
+
name: config.sub_name,
|
|
454
|
+
state: currentState ? 'on' : 'off'
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return null;
|
|
400
460
|
}
|
|
401
461
|
|
|
402
462
|
// 获取当前开关状态值(用于控制时组合状态)
|
|
@@ -412,9 +472,9 @@ class DeviceInfo {
|
|
|
412
472
|
stateValue |= (bitValue << bitPos);
|
|
413
473
|
}
|
|
414
474
|
return stateValue;
|
|
415
|
-
} else if (this.channels === 6) {
|
|
475
|
+
} else if (this.channels === 6 || this.channels === 8) {
|
|
416
476
|
let stateValue = 0;
|
|
417
|
-
for (let i = 0; i <
|
|
477
|
+
for (let i = 0; i < this.channels; i++) {
|
|
418
478
|
const bitPos = i * 2;
|
|
419
479
|
const channelState = this.state[`switch_${i + 1}`];
|
|
420
480
|
const bitValue = (channelState === true) ? 0x02 : 0x01;
|
|
@@ -427,10 +487,11 @@ class DeviceInfo {
|
|
|
427
487
|
}
|
|
428
488
|
|
|
429
489
|
class DeviceManager extends EventEmitter {
|
|
430
|
-
constructor(context, logger = console) {
|
|
490
|
+
constructor(context, logger = console, gatewayId = 'default') {
|
|
431
491
|
super();
|
|
432
492
|
this.context = context;
|
|
433
493
|
this.logger = logger;
|
|
494
|
+
this.gatewayId = gatewayId;
|
|
434
495
|
this.devices = new Map();
|
|
435
496
|
this.macToAddress = new Map();
|
|
436
497
|
this.addressToMac = new Map();
|
|
@@ -439,9 +500,9 @@ class DeviceManager extends EventEmitter {
|
|
|
439
500
|
|
|
440
501
|
loadDevices() {
|
|
441
502
|
try {
|
|
442
|
-
const saved = this.context.get(
|
|
443
|
-
const macMap = this.context.get(
|
|
444
|
-
const addrMap = this.context.get(
|
|
503
|
+
const saved = this.context.get(`symi_devices_${this.gatewayId}`) || {};
|
|
504
|
+
const macMap = this.context.get(`symi_mac_map_${this.gatewayId}`) || {};
|
|
505
|
+
const addrMap = this.context.get(`symi_addr_map_${this.gatewayId}`) || {};
|
|
445
506
|
|
|
446
507
|
Object.entries(saved).forEach(([mac, data]) => {
|
|
447
508
|
const device = new DeviceInfo(data);
|
|
@@ -449,6 +510,17 @@ class DeviceManager extends EventEmitter {
|
|
|
449
510
|
if (data.isThreeInOne) {
|
|
450
511
|
device.isThreeInOne = true;
|
|
451
512
|
}
|
|
513
|
+
// 恢复温控器确认标记
|
|
514
|
+
if (data.thermostatConfirmed) {
|
|
515
|
+
device.thermostatConfirmed = true;
|
|
516
|
+
}
|
|
517
|
+
// 恢复按键名称和配置
|
|
518
|
+
if (data.subDeviceNames) {
|
|
519
|
+
device.subDeviceNames = data.subDeviceNames;
|
|
520
|
+
}
|
|
521
|
+
if (data.subDeviceConfigs) {
|
|
522
|
+
device.subDeviceConfigs = data.subDeviceConfigs;
|
|
523
|
+
}
|
|
452
524
|
this.devices.set(mac, device);
|
|
453
525
|
});
|
|
454
526
|
|
|
@@ -474,7 +546,8 @@ class DeviceManager extends EventEmitter {
|
|
|
474
546
|
name: device.name,
|
|
475
547
|
channels: device.channels,
|
|
476
548
|
online: device.online,
|
|
477
|
-
isThreeInOne: device.isThreeInOne || false
|
|
549
|
+
isThreeInOne: device.isThreeInOne || false,
|
|
550
|
+
thermostatConfirmed: device.thermostatConfirmed || false
|
|
478
551
|
};
|
|
479
552
|
});
|
|
480
553
|
|
|
@@ -483,9 +556,9 @@ class DeviceManager extends EventEmitter {
|
|
|
483
556
|
Array.from(this.addressToMac.entries()).map(([k, v]) => [k.toString(), v])
|
|
484
557
|
);
|
|
485
558
|
|
|
486
|
-
this.context.set(
|
|
487
|
-
this.context.set(
|
|
488
|
-
this.context.set(
|
|
559
|
+
this.context.set(`symi_devices_${this.gatewayId}`, devicesObj);
|
|
560
|
+
this.context.set(`symi_mac_map_${this.gatewayId}`, macMapObj);
|
|
561
|
+
this.context.set(`symi_addr_map_${this.gatewayId}`, addrMapObj);
|
|
489
562
|
} catch (error) {
|
|
490
563
|
this.logger.error('Error saving devices:', error);
|
|
491
564
|
}
|
|
@@ -576,6 +649,19 @@ class DeviceManager extends EventEmitter {
|
|
|
576
649
|
this.context.set('symi_mac_map', {});
|
|
577
650
|
this.context.set('symi_addr_map', {});
|
|
578
651
|
}
|
|
652
|
+
|
|
653
|
+
// 清理资源,防止内存泄漏
|
|
654
|
+
cleanup() {
|
|
655
|
+
// 移除所有事件监听器
|
|
656
|
+
this.removeAllListeners();
|
|
657
|
+
|
|
658
|
+
// 清理设备对象中的定时器和监听器
|
|
659
|
+
for (const device of this.devices.values()) {
|
|
660
|
+
if (device.removeAllListeners) {
|
|
661
|
+
device.removeAllListeners();
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
579
665
|
}
|
|
580
666
|
|
|
581
667
|
module.exports = { DeviceManager, DeviceInfo, DEVICE_TYPE_NAMES };
|
package/lib/mqtt-helper.js
CHANGED
|
@@ -29,7 +29,7 @@ const DEVICE_CLASSES = {
|
|
|
29
29
|
11: 'temperature' // 温湿度传感器(主要传感器)
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
-
function generateDiscoveryConfig(device, mqttPrefix = 'homeassistant') {
|
|
32
|
+
function generateDiscoveryConfig(device, mqttPrefix = 'homeassistant', logger = console) {
|
|
33
33
|
const macClean = device.macAddress.replace(/:/g, '').toLowerCase();
|
|
34
34
|
const entityType = device.getEntityType();
|
|
35
35
|
const configs = [];
|
|
@@ -94,17 +94,22 @@ function generateDiscoveryConfig(device, mqttPrefix = 'homeassistant') {
|
|
|
94
94
|
let channelName = '';
|
|
95
95
|
if (device.subDeviceNames && device.subDeviceNames[i - 1]) {
|
|
96
96
|
channelName = ` ${device.subDeviceNames[i - 1]}`;
|
|
97
|
+
logger.debug(`[Discovery] ${device.name} 第${i}路使用云端名称: ${device.subDeviceNames[i - 1]}`);
|
|
97
98
|
} else if (device.channels > 1) {
|
|
98
99
|
channelName = ` 第${i}路`;
|
|
100
|
+
logger.debug(`[Discovery] ${device.name} 第${i}路使用默认名称`);
|
|
99
101
|
}
|
|
100
102
|
|
|
101
103
|
const stateTopic = device.channels === 1 ? `symi_mesh/${macClean}/switch/state` : `symi_mesh/${macClean}/switch_${i}/state`;
|
|
102
104
|
const commandTopic = device.channels === 1 ? `symi_mesh/${macClean}/switch/set` : `symi_mesh/${macClean}/switch_${i}/set`;
|
|
103
105
|
|
|
106
|
+
const entityName = `${device.name}${channelName}`;
|
|
107
|
+
logger.debug(`[Discovery配置] ${device.name} 第${i}路: name="${entityName}", state_topic="${stateTopic}"`);
|
|
108
|
+
|
|
104
109
|
configs.push({
|
|
105
110
|
topic: `${mqttPrefix}/switch/${objectId}/config`,
|
|
106
111
|
payload: JSON.stringify({
|
|
107
|
-
name:
|
|
112
|
+
name: entityName,
|
|
108
113
|
unique_id: objectId,
|
|
109
114
|
state_topic: stateTopic,
|
|
110
115
|
command_topic: commandTopic,
|
|
@@ -657,9 +662,25 @@ function convertStateValue(entityType, attrType, value, deviceType = null) {
|
|
|
657
662
|
}
|
|
658
663
|
}
|
|
659
664
|
|
|
665
|
+
/**
|
|
666
|
+
* 将字符串转换为安全的MQTT topic ID(仅保留ASCII字母、数字、下划线、连字符)
|
|
667
|
+
* 中文字符会被转换为URL编码格式
|
|
668
|
+
*/
|
|
669
|
+
function sanitizeTopicId(str) {
|
|
670
|
+
if (!str) return 'unknown';
|
|
671
|
+
|
|
672
|
+
// 将字符串转换为URL编码,然后替换%为_,确保只包含安全字符
|
|
673
|
+
return encodeURIComponent(String(str))
|
|
674
|
+
.replace(/%/g, '_')
|
|
675
|
+
.replace(/[^a-zA-Z0-9_-]/g, '_')
|
|
676
|
+
.toLowerCase();
|
|
677
|
+
}
|
|
678
|
+
|
|
660
679
|
function generateSceneButtonConfig(scene, roomNo, mqttPrefix = 'homeassistant') {
|
|
661
680
|
const sceneId = `scene_${scene.scene_id}`;
|
|
662
|
-
|
|
681
|
+
// 将roomNo转换为合法的MQTT Discovery object_id(只允许字母、数字、下划线、连字符)
|
|
682
|
+
const sanitizedRoomNo = sanitizeTopicId(roomNo);
|
|
683
|
+
const objectId = `symi_room_${sanitizedRoomNo}_${sceneId}`;
|
|
663
684
|
|
|
664
685
|
return {
|
|
665
686
|
topic: `${mqttPrefix}/button/${objectId}/config`,
|
|
@@ -670,7 +691,7 @@ function generateSceneButtonConfig(scene, roomNo, mqttPrefix = 'homeassistant')
|
|
|
670
691
|
payload_press: 'PRESS',
|
|
671
692
|
icon: 'mdi:play-circle',
|
|
672
693
|
device: {
|
|
673
|
-
identifiers: [`symi_room_${
|
|
694
|
+
identifiers: [`symi_room_${sanitizedRoomNo}`],
|
|
674
695
|
name: `房间${roomNo}场景控制`,
|
|
675
696
|
model: 'Symi 场景控制器',
|
|
676
697
|
manufacturer: 'SYMI 亖米'
|
package/lib/protocol.js
CHANGED
|
@@ -56,16 +56,16 @@ class ProtocolHandler {
|
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
58
|
* 构建开关状态值
|
|
59
|
-
* @param {number} channels - 开关路数 (1-
|
|
60
|
-
* @param {number} targetChannel - 目标路数 (1-
|
|
59
|
+
* @param {number} channels - 开关路数 (1-8)
|
|
60
|
+
* @param {number} targetChannel - 目标路数 (1-8)
|
|
61
61
|
* @param {boolean} targetState - 目标状态 (true=开, false=关)
|
|
62
62
|
* @param {number|null} currentState - 当前状态值,null时使用默认全关状态
|
|
63
|
-
* @returns {number|Buffer} - 1-4路返回number,6路返回Buffer(2字节)
|
|
63
|
+
* @returns {number|Buffer} - 1-4路返回number,6-8路返回Buffer(2字节)
|
|
64
64
|
*/
|
|
65
65
|
buildSwitchState(channels, targetChannel, targetState, currentState = null) {
|
|
66
66
|
// 验证参数
|
|
67
|
-
if (channels < 1 || channels >
|
|
68
|
-
throw new Error(`Invalid channels count: ${channels}, must be 1-
|
|
67
|
+
if (channels < 1 || channels > 8) {
|
|
68
|
+
throw new Error(`Invalid channels count: ${channels}, must be 1-8`);
|
|
69
69
|
}
|
|
70
70
|
if (targetChannel < 1 || targetChannel > channels) {
|
|
71
71
|
throw new Error(`Invalid target channel: ${targetChannel}, must be 1-${channels}`);
|
|
@@ -95,19 +95,27 @@ class ProtocolHandler {
|
|
|
95
95
|
|
|
96
96
|
stateValue = (stateValue & mask) | newBits;
|
|
97
97
|
return stateValue;
|
|
98
|
-
} else if (channels === 6) {
|
|
99
|
-
// 6路开关,2字节状态,小端序
|
|
98
|
+
} else if (channels === 6 || channels === 8) {
|
|
99
|
+
// 6-8路开关,2字节状态,小端序
|
|
100
|
+
// 协议说明:低2位开始表示第一路开关(与1-4路一致)
|
|
101
|
+
// 第1路=bits0-1, 第2路=bits2-3, ..., 第8路=bits14-15
|
|
100
102
|
const defaultState = 0x5555; // 全关状态
|
|
101
103
|
stateValue = currentState !== null ? currentState : defaultState;
|
|
102
|
-
|
|
103
|
-
//
|
|
104
|
+
|
|
105
|
+
// 确保currentState是16位值
|
|
106
|
+
if (typeof stateValue === 'number' && stateValue < 0x100) {
|
|
107
|
+
// 如果只有低8位,补充高8位为0x55(全关)
|
|
108
|
+
stateValue = stateValue | 0x5500;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 6-8路开关状态组合(从低位开始)
|
|
104
112
|
const bitPos = (targetChannel - 1) * 2;
|
|
105
113
|
const mask = ~(0x03 << bitPos);
|
|
106
114
|
const newBits = (targetState ? 0x02 : 0x01) << bitPos;
|
|
107
|
-
|
|
115
|
+
|
|
108
116
|
stateValue = (stateValue & mask) | newBits;
|
|
109
|
-
|
|
110
|
-
// 返回小端序Buffer
|
|
117
|
+
|
|
118
|
+
// 返回小端序Buffer(低字节在前)
|
|
111
119
|
return Buffer.from([stateValue & 0xFF, (stateValue >> 8) & 0xFF]);
|
|
112
120
|
}
|
|
113
121
|
}
|
|
@@ -445,8 +453,8 @@ class ProtocolHandler {
|
|
|
445
453
|
/**
|
|
446
454
|
* 构建开关控制帧(便捷方法)
|
|
447
455
|
* @param {number} networkAddr - 网络地址
|
|
448
|
-
* @param {number} channels - 开关路数 (1-
|
|
449
|
-
* @param {number} targetChannel - 目标路数 (1-
|
|
456
|
+
* @param {number} channels - 开关路数 (1-8)
|
|
457
|
+
* @param {number} targetChannel - 目标路数 (1-8)
|
|
450
458
|
* @param {boolean} targetState - 目标状态 (true=开, false=关)
|
|
451
459
|
* @param {number|null} currentState - 当前状态值,null时需要先查询
|
|
452
460
|
* @returns {Buffer} - 控制帧数据
|
package/lib/tcp-client.js
CHANGED
|
@@ -75,12 +75,7 @@ class TCPClient extends EventEmitter {
|
|
|
75
75
|
|
|
76
76
|
this.client.on('data', (data) => {
|
|
77
77
|
try {
|
|
78
|
-
// 添加原始数据日志
|
|
79
|
-
// const dataHex = data.toString('hex').toUpperCase();
|
|
80
|
-
// this.logger.log(`[TCP Raw Data] 收到${data.length}字节: ${dataHex}`);
|
|
81
|
-
|
|
82
78
|
const frames = this.protocolHandler.addData(data);
|
|
83
|
-
// this.logger.log(`[TCP Parse] 解析出${frames.length}个帧`);
|
|
84
79
|
|
|
85
80
|
frames.forEach(frame => {
|
|
86
81
|
this.emit('frame', frame);
|
|
@@ -93,11 +88,15 @@ class TCPClient extends EventEmitter {
|
|
|
93
88
|
|
|
94
89
|
this.client.on('error', (error) => {
|
|
95
90
|
clearTimeout(timeout);
|
|
96
|
-
|
|
97
|
-
|
|
91
|
+
// 只在首次连接或重要错误时记录,避免大量重复日志
|
|
92
|
+
// ECONNRESET通常是网络波动,不记录错误
|
|
93
|
+
if (!this.connected && error.code !== 'ECONNREFUSED' && error.code !== 'ECONNRESET') {
|
|
94
|
+
this.logger.error('TCP client error:', error.message);
|
|
95
|
+
}
|
|
96
|
+
|
|
98
97
|
// 确保错误不会导致uncaught exception
|
|
99
98
|
this.emit('error', error);
|
|
100
|
-
|
|
99
|
+
|
|
101
100
|
if (!resolved && !rejected) {
|
|
102
101
|
rejected = true;
|
|
103
102
|
this.handleDisconnect();
|
|
@@ -107,13 +106,16 @@ class TCPClient extends EventEmitter {
|
|
|
107
106
|
|
|
108
107
|
this.client.on('close', () => {
|
|
109
108
|
clearTimeout(timeout);
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
// 只在正常连接后断开时记录,避免重连时大量日志
|
|
110
|
+
if (this.connected) {
|
|
111
|
+
this.logger.log('Connection closed');
|
|
112
|
+
}
|
|
113
|
+
|
|
112
114
|
if (!resolved && !rejected) {
|
|
113
115
|
rejected = true;
|
|
114
116
|
reject(new Error('Connection closed during connect'));
|
|
115
117
|
}
|
|
116
|
-
|
|
118
|
+
|
|
117
119
|
this.handleDisconnect();
|
|
118
120
|
});
|
|
119
121
|
});
|
|
@@ -143,13 +145,13 @@ class TCPClient extends EventEmitter {
|
|
|
143
145
|
if (this.autoReconnect && !this.reconnectTimer) {
|
|
144
146
|
this.reconnectTimer = setTimeout(() => {
|
|
145
147
|
this.reconnectTimer = null;
|
|
146
|
-
|
|
148
|
+
// 减少重连日志,只在成功时记录
|
|
147
149
|
this.connect()
|
|
148
150
|
.then(() => {
|
|
149
151
|
this.logger.log('Reconnected successfully');
|
|
150
152
|
})
|
|
151
153
|
.catch((error) => {
|
|
152
|
-
|
|
154
|
+
// 静默失败,避免大量重连失败日志
|
|
153
155
|
// 失败后会自动继续尝试重连(通过handleDisconnect)
|
|
154
156
|
});
|
|
155
157
|
}, this.reconnectDelay);
|