homebridge-melcloud-control 4.9.2-beta.2 → 4.9.2-beta.3
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/index.js +1 -0
- package/package.json +1 -1
- package/src/melcloudhome.js +124 -1
package/index.js
CHANGED
|
@@ -112,6 +112,7 @@ class MelCloudPlatform {
|
|
|
112
112
|
}
|
|
113
113
|
if (logLevel.debug) log.info(melCloudDevicesData.Status);
|
|
114
114
|
|
|
115
|
+
|
|
115
116
|
//filter configured devices
|
|
116
117
|
const devicesIds = (melCloudDevicesData.Devices ?? []).map(d => String(d.DeviceID));
|
|
117
118
|
const ataDevices = (account.ataDevices || []).filter(d => (d.displayType ?? 0) > 0 && devicesIds.includes(d.id));
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"displayName": "MELCloud Control",
|
|
3
3
|
"name": "homebridge-melcloud-control",
|
|
4
|
-
"version": "4.9.2-beta.
|
|
4
|
+
"version": "4.9.2-beta.3",
|
|
5
5
|
"description": "Homebridge plugin to control Mitsubishi Air Conditioner, Heat Pump and Energy Recovery Ventilation.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "grzegorz914",
|
package/src/melcloudhome.js
CHANGED
|
@@ -40,6 +40,15 @@ class MelCloudHome extends EventEmitter {
|
|
|
40
40
|
// Flaga zapobiegająca wielokrotnemu dodaniu interceptorów
|
|
41
41
|
this.interceptorsAttached = false;
|
|
42
42
|
|
|
43
|
+
// WebSocket state
|
|
44
|
+
this.socket = null;
|
|
45
|
+
this.socketConnected = false;
|
|
46
|
+
this.connecting = false;
|
|
47
|
+
this.heartbeat = null;
|
|
48
|
+
this.reconnectTimer = null;
|
|
49
|
+
this.reconnectDelay = 5_000; // ms, rośnie wykładniczo do reconnectDelayMax
|
|
50
|
+
this.reconnectDelayMax = 300_000; // 5 minut
|
|
51
|
+
|
|
43
52
|
if (pluginStart) {
|
|
44
53
|
this.impulseGenerator = new ImpulseGenerator()
|
|
45
54
|
.on('checkDevicesList', async () => {
|
|
@@ -51,6 +60,116 @@ class MelCloudHome extends EventEmitter {
|
|
|
51
60
|
}
|
|
52
61
|
}
|
|
53
62
|
|
|
63
|
+
// ── WebSocket ─────────────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
cleanupSocket() {
|
|
66
|
+
if (this.heartbeat) {
|
|
67
|
+
clearInterval(this.heartbeat);
|
|
68
|
+
this.heartbeat = null;
|
|
69
|
+
}
|
|
70
|
+
this.socketConnected = false;
|
|
71
|
+
this.connecting = false;
|
|
72
|
+
this.socket = null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Łączy się z WebSocket. Wywoływane po udanym connect() lub po reconnect.
|
|
76
|
+
async connectSocket() {
|
|
77
|
+
if (this.connecting || this.socketConnected) return;
|
|
78
|
+
this.connecting = true;
|
|
79
|
+
|
|
80
|
+
let hash;
|
|
81
|
+
try {
|
|
82
|
+
const resp = await this.client.get(ApiUrls.Home.Get.Context);
|
|
83
|
+
hash = resp.data.id ?? null;
|
|
84
|
+
} catch (err) {
|
|
85
|
+
if (this.logError) this.emit('error', `connectSocket: cannot get WS hash: ${err.message}`);
|
|
86
|
+
this.connecting = false;
|
|
87
|
+
this.scheduleReconnect();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const url = `${ApiUrls.Home.WebSocket}${hash}`;
|
|
92
|
+
const headers = {
|
|
93
|
+
Origin: ApiUrls.Home.Base,
|
|
94
|
+
Pragma: 'no-cache',
|
|
95
|
+
'Cache-Control': 'no-cache',
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
if (this.logDebug) this.emit('debug', `Connecting WebSocket: ${url.slice(0, 60)}...`);
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const ws = new WebSocket(url, { headers });
|
|
102
|
+
this.socket = ws;
|
|
103
|
+
|
|
104
|
+
ws.on('error', (error) => {
|
|
105
|
+
if (this.logError) this.emit('error', `Web socket error: ${error.message}`);
|
|
106
|
+
try { ws.close(); } catch { /* ignoruj */ }
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
ws.on('close', () => {
|
|
110
|
+
if (this.logDebug) this.emit('debug', 'Web socket closed');
|
|
111
|
+
this.cleanupSocket();
|
|
112
|
+
this.scheduleReconnect();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
ws.on('open', () => {
|
|
116
|
+
this.socketConnected = true;
|
|
117
|
+
this.connecting = false;
|
|
118
|
+
this.reconnectDelay = 5_000; // reset backoff po udanym połączeniu
|
|
119
|
+
if (this.reconnectTimer) {
|
|
120
|
+
clearTimeout(this.reconnectTimer);
|
|
121
|
+
this.reconnectTimer = null;
|
|
122
|
+
}
|
|
123
|
+
if (this.logDebug) this.emit('debug', 'Web Socket Connected');
|
|
124
|
+
|
|
125
|
+
// Heartbeat co 30s
|
|
126
|
+
this.heartbeat = setInterval(() => {
|
|
127
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
128
|
+
if (this.logDebug) this.emit('debug', 'Web socket send heartbeat');
|
|
129
|
+
ws.ping();
|
|
130
|
+
}
|
|
131
|
+
}, 30_000);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
ws.on('pong', () => {
|
|
135
|
+
if (this.logDebug) this.emit('debug', 'Web socket received heartbeat');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
ws.on('message', (message) => {
|
|
139
|
+
try {
|
|
140
|
+
const parsedMessage = JSON.parse(message);
|
|
141
|
+
if (this.logDebug) this.emit('debug', `Web socket incoming message: ${JSON.stringify(parsedMessage, null, 2)}`);
|
|
142
|
+
|
|
143
|
+
// Format: array, pierwszy element ma Data.id
|
|
144
|
+
const messageData = parsedMessage?.[0]?.Data;
|
|
145
|
+
if (!messageData || parsedMessage.message === 'Forbidden') return;
|
|
146
|
+
|
|
147
|
+
this.emit(messageData.id, 'ws', parsedMessage[0]);
|
|
148
|
+
} catch (err) {
|
|
149
|
+
if (this.logError) this.emit('error', `Web socket message parse error: ${err.message}`);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
} catch (error) {
|
|
153
|
+
if (this.logError) this.emit('error', `Web socket connection failed: ${error.message}`);
|
|
154
|
+
this.cleanupSocket();
|
|
155
|
+
this.scheduleReconnect();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Wykładniczy backoff: 5s → 10s → 20s → ... → max 5 minut
|
|
160
|
+
scheduleReconnect() {
|
|
161
|
+
if (this.reconnectTimer) return; // już zaplanowany
|
|
162
|
+
|
|
163
|
+
if (this.logDebug) this.emit('debug', `Web socket reconnecting in ${this.reconnectDelay / 1000}s...`);
|
|
164
|
+
|
|
165
|
+
this.reconnectTimer = setTimeout(async () => {
|
|
166
|
+
this.reconnectTimer = null;
|
|
167
|
+
await this.connectSocket();
|
|
168
|
+
}, this.reconnectDelay);
|
|
169
|
+
|
|
170
|
+
this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.reconnectDelayMax);
|
|
171
|
+
}
|
|
172
|
+
|
|
54
173
|
// ── Utils ─────────────────────────────────────────────────────────────────
|
|
55
174
|
|
|
56
175
|
capitalizeKeysDeep(obj) {
|
|
@@ -310,8 +429,12 @@ class MelCloudHome extends EventEmitter {
|
|
|
310
429
|
this.emit('client', this.client);
|
|
311
430
|
}
|
|
312
431
|
|
|
432
|
+
this.connectSocket().catch(err => {
|
|
433
|
+
if (this.logError) this.emit('error', `Initial WebSocket connect failed: ${err.message}`);
|
|
434
|
+
});
|
|
435
|
+
|
|
313
436
|
connectInfo.State = exchangeRes;
|
|
314
|
-
connectInfo.Status =
|
|
437
|
+
connectInfo.Status = `Connect Success${this.socketConnected ? ', Web Socket Connected' : ''}`;
|
|
315
438
|
|
|
316
439
|
return connectInfo;
|
|
317
440
|
}
|