iobroker.al-ko 0.3.1 → 0.3.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 +7 -7
- package/docs/de/README.md +7 -0
- package/docs/en/README.md +7 -0
- package/io-package.json +5 -1
- package/main.js +184 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -53,6 +53,13 @@ Do **not** contact AL-KO customer service regarding this project.
|
|
|
53
53
|
|
|
54
54
|
## Changelog
|
|
55
55
|
|
|
56
|
+
### 0.3.2 (2026-03-12)
|
|
57
|
+
|
|
58
|
+
- Improved WebSocket reconnect handling after token refresh
|
|
59
|
+
- Prevented reconnect loops on intentional WebSocket closes
|
|
60
|
+
- Improved API error logging for push requests
|
|
61
|
+
- Added WebSocket close code and reason logging
|
|
62
|
+
|
|
56
63
|
### 0.3.1 (2026-03-09)
|
|
57
64
|
|
|
58
65
|
- Documentation improvements
|
|
@@ -69,13 +76,6 @@ Do **not** contact AL-KO customer service regarding this project.
|
|
|
69
76
|
- Improved CI pipeline and adapter structure
|
|
70
77
|
- No functional changes
|
|
71
78
|
|
|
72
|
-
### 0.2.15 (2025-11-02)
|
|
73
|
-
|
|
74
|
-
- Cleaned up admin/jsonConfig structure for adapter-check
|
|
75
|
-
- Added missing `size` attributes
|
|
76
|
-
- Added `.commitinfo` to `.gitignore`
|
|
77
|
-
- No functional changes
|
|
78
|
-
|
|
79
79
|
➡ Full changelog here:
|
|
80
80
|
[CHANGELOG.md](./CHANGELOG.md)
|
|
81
81
|
|
package/docs/de/README.md
CHANGED
|
@@ -48,6 +48,13 @@ AL-KO bietet **keinen offiziellen Support** hierfür.
|
|
|
48
48
|
|
|
49
49
|
## Änderungen (Auszug)
|
|
50
50
|
|
|
51
|
+
### 0.3.2 (2026-03-12)
|
|
52
|
+
|
|
53
|
+
- WebSocket-Reconnect nach Token-Aktualisierung verbessert
|
|
54
|
+
- Reconnect-Schleifen bei absichtlich geschlossenen WebSocket-Verbindungen verhindert
|
|
55
|
+
- API-Fehlerlogging für Push-Requests verbessert
|
|
56
|
+
- Logging für WebSocket-Close-Code und Reason ergänzt
|
|
57
|
+
|
|
51
58
|
### 0.3.1 (2026-03-09)
|
|
52
59
|
|
|
53
60
|
- Verbesserte Dokumentation
|
package/docs/en/README.md
CHANGED
|
@@ -48,6 +48,13 @@ It is a **community-developed project**.
|
|
|
48
48
|
|
|
49
49
|
## Changes (Summary)
|
|
50
50
|
|
|
51
|
+
### 0.3.2 (2026-03-12)
|
|
52
|
+
|
|
53
|
+
- Improved WebSocket reconnect handling after token refresh
|
|
54
|
+
- Prevented reconnect loops on intentional WebSocket closes
|
|
55
|
+
- Improved API error logging for push requests
|
|
56
|
+
- Added WebSocket close code and reason logging
|
|
57
|
+
|
|
51
58
|
### 0.3.1 (2026-03-09)
|
|
52
59
|
|
|
53
60
|
- Documentation improvements
|
package/io-package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "al-ko",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.2",
|
|
5
5
|
"tier": 3,
|
|
6
6
|
"titleLang": {
|
|
7
7
|
"en": "AL-KO",
|
|
@@ -40,6 +40,10 @@
|
|
|
40
40
|
"de": "docs/de/README.md"
|
|
41
41
|
},
|
|
42
42
|
"news": {
|
|
43
|
+
"0.3.2": {
|
|
44
|
+
"en": "Improved WebSocket reconnect handling, prevented reconnect loops on intentional closes, and improved API error logging for push requests.",
|
|
45
|
+
"de": "WebSocket-Reconnect-Verhalten verbessert, Reconnect-Schleifen bei absichtlich geschlossenen Verbindungen verhindert und API-Fehlerlogging für Push-Requests verbessert."
|
|
46
|
+
},
|
|
43
47
|
"0.3.1": {
|
|
44
48
|
"en": "Documentation improvements, corrected license information and updated development dependencies. No functional changes.",
|
|
45
49
|
"de": "Dokumentation verbessert, Lizenzangaben korrigiert und Entwicklungsabhängigkeiten aktualisiert. Keine funktionalen Änderungen."
|
package/main.js
CHANGED
|
@@ -83,7 +83,9 @@ class AlKoAdapter extends utils.Adapter {
|
|
|
83
83
|
this.log.info("Adapter is ready.");
|
|
84
84
|
} catch (err) {
|
|
85
85
|
this.log.error(
|
|
86
|
-
`Startup error: ${err.response?.
|
|
86
|
+
`Startup error: ${err.response?.status || ""} ${
|
|
87
|
+
JSON.stringify(err.response?.data, null, 2) || err.message
|
|
88
|
+
}`,
|
|
87
89
|
);
|
|
88
90
|
}
|
|
89
91
|
}
|
|
@@ -139,6 +141,34 @@ class AlKoAdapter extends utils.Adapter {
|
|
|
139
141
|
this.tokenExpiresAt = Date.now() + res.data.expires_in * 1000;
|
|
140
142
|
|
|
141
143
|
this.log.debug("Access token refreshed successfully.");
|
|
144
|
+
|
|
145
|
+
for (const deviceId of Object.keys(this.webSockets)) {
|
|
146
|
+
try {
|
|
147
|
+
this.log.debug(
|
|
148
|
+
`Reconnecting WebSocket for ${deviceId} after token refresh`,
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
// const ws = this.webSockets[deviceId];
|
|
152
|
+
// if (ws) {
|
|
153
|
+
// ws.terminate();
|
|
154
|
+
//}
|
|
155
|
+
|
|
156
|
+
const ws = this.webSockets[deviceId];
|
|
157
|
+
if (ws) {
|
|
158
|
+
ws._intentionalClose = true;
|
|
159
|
+
ws.terminate();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
this.clearInterval(this.pingIntervals[deviceId]);
|
|
163
|
+
this.clearTimeout(this.pongTimeouts[deviceId]);
|
|
164
|
+
|
|
165
|
+
this.connectWebSocket(deviceId);
|
|
166
|
+
} catch (err) {
|
|
167
|
+
this.log.warn(
|
|
168
|
+
`Failed to reconnect WebSocket for ${deviceId}: ${err.message}`,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
142
172
|
}
|
|
143
173
|
}
|
|
144
174
|
|
|
@@ -227,24 +257,137 @@ class AlKoAdapter extends utils.Adapter {
|
|
|
227
257
|
return res.data;
|
|
228
258
|
}
|
|
229
259
|
|
|
260
|
+
// ---------------- WebSocket Handling ----------------
|
|
261
|
+
// alte Version
|
|
262
|
+
|
|
263
|
+
//connectWebSocket(deviceId) {
|
|
264
|
+
// if (!this.accessToken) {
|
|
265
|
+
// return;
|
|
266
|
+
// }
|
|
267
|
+
//
|
|
268
|
+
// const url = `wss://socket.al-ko.com/v1?Authorization=${this.accessToken}&thingName=${deviceId}`;
|
|
269
|
+
// const ws = new WebSocket(url);
|
|
270
|
+
//
|
|
271
|
+
// ws.isAlive = true;
|
|
272
|
+
//
|
|
273
|
+
// ws.on("open", () => {
|
|
274
|
+
// this.log.debug(`WebSocket connected for device ${deviceId}`);
|
|
275
|
+
//
|
|
276
|
+
// this.pingIntervals[deviceId] = this.setInterval(() => {
|
|
277
|
+
// if (ws.readyState === WebSocket.OPEN) {
|
|
278
|
+
// ws.ping();
|
|
279
|
+
// ws.isAlive = false;
|
|
280
|
+
//
|
|
281
|
+
// this.pongTimeouts[deviceId] = this.setTimeout(() => {
|
|
282
|
+
// if (!ws.isAlive) {
|
|
283
|
+
// this.log.warn(
|
|
284
|
+
// `WebSocket ping timeout for ${deviceId}, closing connection.`,
|
|
285
|
+
// );
|
|
286
|
+
// ws.terminate();
|
|
287
|
+
// }
|
|
288
|
+
// }, 30000);
|
|
289
|
+
// }
|
|
290
|
+
// }, 120000);
|
|
291
|
+
// });
|
|
292
|
+
//
|
|
293
|
+
// ws.on("pong", () => {
|
|
294
|
+
// ws.isAlive = true;
|
|
295
|
+
// this.clearTimeout(this.pongTimeouts[deviceId]);
|
|
296
|
+
// });
|
|
297
|
+
//
|
|
298
|
+
// ws.on("message", async (msg) => {
|
|
299
|
+
// if (this.config.wsDebug) {
|
|
300
|
+
// this.log.debug(`WebSocket message (${deviceId}): ${msg}`);
|
|
301
|
+
// }
|
|
302
|
+
// try {
|
|
303
|
+
// const data = JSON.parse(msg.toString());
|
|
304
|
+
// if (data && data.state) {
|
|
305
|
+
// const newState = data.state.reported || data.state;
|
|
306
|
+
//
|
|
307
|
+
// this.deviceStates[deviceId] = this.deepMerge(
|
|
308
|
+
// this.deviceStates[deviceId] || {},
|
|
309
|
+
// newState,
|
|
310
|
+
// );
|
|
311
|
+
//
|
|
312
|
+
// await this.createStatesRecursive(
|
|
313
|
+
// `${this.namespace}.${deviceId}.state`,
|
|
314
|
+
// this.deviceStates[deviceId],
|
|
315
|
+
// "",
|
|
316
|
+
// );
|
|
317
|
+
// }
|
|
318
|
+
// } catch (e) {
|
|
319
|
+
// this.log.error(
|
|
320
|
+
// `Error processing WebSocket message for device ${deviceId}: ${e.message}`,
|
|
321
|
+
// );
|
|
322
|
+
// }
|
|
323
|
+
// });
|
|
324
|
+
//
|
|
325
|
+
// ws.on("close", () => {
|
|
326
|
+
// this.log.warn(
|
|
327
|
+
// `WebSocket closed for device ${deviceId}. Retrying in 10 seconds.`,
|
|
328
|
+
// );
|
|
329
|
+
// this.clearInterval(this.pingIntervals[deviceId]);
|
|
330
|
+
// this.clearTimeout(this.pongTimeouts[deviceId]);
|
|
331
|
+
//
|
|
332
|
+
// this.reconnectTimeouts[deviceId] = this.setTimeout(
|
|
333
|
+
// () => this.connectWebSocket(deviceId),
|
|
334
|
+
// 10000,
|
|
335
|
+
// );
|
|
336
|
+
// });
|
|
337
|
+
//
|
|
338
|
+
// ws.on("error", (err) => {
|
|
339
|
+
// this.log.error(`WebSocket error for ${deviceId}: ${err.message}`);
|
|
340
|
+
// try {
|
|
341
|
+
// ws.terminate();
|
|
342
|
+
// } catch {}
|
|
343
|
+
// });
|
|
344
|
+
//
|
|
345
|
+
// this.webSockets[deviceId] = ws;
|
|
346
|
+
//}
|
|
347
|
+
|
|
230
348
|
// ---------------- WebSocket Handling ----------------
|
|
231
349
|
connectWebSocket(deviceId) {
|
|
232
|
-
if (!this.accessToken) {
|
|
350
|
+
if (!this.accessToken || this._stopRequested) {
|
|
233
351
|
return;
|
|
234
352
|
}
|
|
235
353
|
|
|
354
|
+
if (this.reconnectTimeouts[deviceId]) {
|
|
355
|
+
this.clearTimeout(this.reconnectTimeouts[deviceId]);
|
|
356
|
+
delete this.reconnectTimeouts[deviceId];
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const existingWs = this.webSockets[deviceId];
|
|
360
|
+
if (existingWs) {
|
|
361
|
+
try {
|
|
362
|
+
existingWs._intentionalClose = true;
|
|
363
|
+
existingWs.terminate();
|
|
364
|
+
} catch {}
|
|
365
|
+
}
|
|
366
|
+
|
|
236
367
|
const url = `wss://socket.al-ko.com/v1?Authorization=${this.accessToken}&thingName=${deviceId}`;
|
|
237
368
|
const ws = new WebSocket(url);
|
|
238
369
|
|
|
239
370
|
ws.isAlive = true;
|
|
371
|
+
ws._intentionalClose = false;
|
|
240
372
|
|
|
241
373
|
ws.on("open", () => {
|
|
242
374
|
this.log.debug(`WebSocket connected for device ${deviceId}`);
|
|
243
375
|
|
|
376
|
+
if (this.pingIntervals[deviceId]) {
|
|
377
|
+
this.clearInterval(this.pingIntervals[deviceId]);
|
|
378
|
+
}
|
|
379
|
+
if (this.pongTimeouts[deviceId]) {
|
|
380
|
+
this.clearTimeout(this.pongTimeouts[deviceId]);
|
|
381
|
+
}
|
|
382
|
+
|
|
244
383
|
this.pingIntervals[deviceId] = this.setInterval(() => {
|
|
245
384
|
if (ws.readyState === WebSocket.OPEN) {
|
|
246
|
-
ws.ping();
|
|
247
385
|
ws.isAlive = false;
|
|
386
|
+
ws.ping();
|
|
387
|
+
|
|
388
|
+
if (this.pongTimeouts[deviceId]) {
|
|
389
|
+
this.clearTimeout(this.pongTimeouts[deviceId]);
|
|
390
|
+
}
|
|
248
391
|
|
|
249
392
|
this.pongTimeouts[deviceId] = this.setTimeout(() => {
|
|
250
393
|
if (!ws.isAlive) {
|
|
@@ -260,7 +403,10 @@ class AlKoAdapter extends utils.Adapter {
|
|
|
260
403
|
|
|
261
404
|
ws.on("pong", () => {
|
|
262
405
|
ws.isAlive = true;
|
|
263
|
-
|
|
406
|
+
if (this.pongTimeouts[deviceId]) {
|
|
407
|
+
this.clearTimeout(this.pongTimeouts[deviceId]);
|
|
408
|
+
delete this.pongTimeouts[deviceId];
|
|
409
|
+
}
|
|
264
410
|
});
|
|
265
411
|
|
|
266
412
|
ws.on("message", async (msg) => {
|
|
@@ -290,17 +436,36 @@ class AlKoAdapter extends utils.Adapter {
|
|
|
290
436
|
}
|
|
291
437
|
});
|
|
292
438
|
|
|
293
|
-
ws.on("close", () => {
|
|
439
|
+
ws.on("close", (code, reason) => {
|
|
440
|
+
if (this.pingIntervals[deviceId]) {
|
|
441
|
+
this.clearInterval(this.pingIntervals[deviceId]);
|
|
442
|
+
delete this.pingIntervals[deviceId];
|
|
443
|
+
}
|
|
444
|
+
if (this.pongTimeouts[deviceId]) {
|
|
445
|
+
this.clearTimeout(this.pongTimeouts[deviceId]);
|
|
446
|
+
delete this.pongTimeouts[deviceId];
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (this.webSockets[deviceId] === ws) {
|
|
450
|
+
delete this.webSockets[deviceId];
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (this._stopRequested || ws._intentionalClose) {
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
|
|
294
457
|
this.log.warn(
|
|
295
|
-
`WebSocket closed for device ${deviceId}. Retrying in 10 seconds.`,
|
|
458
|
+
`WebSocket closed for device ${deviceId}. Code: ${code}, Reason: ${reason?.toString() || "none"}. Retrying in 10 seconds.`,
|
|
296
459
|
);
|
|
297
|
-
this.clearInterval(this.pingIntervals[deviceId]);
|
|
298
|
-
this.clearTimeout(this.pongTimeouts[deviceId]);
|
|
299
460
|
|
|
300
|
-
this.reconnectTimeouts[deviceId]
|
|
301
|
-
(
|
|
302
|
-
|
|
303
|
-
|
|
461
|
+
if (this.reconnectTimeouts[deviceId]) {
|
|
462
|
+
this.clearTimeout(this.reconnectTimeouts[deviceId]);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
this.reconnectTimeouts[deviceId] = this.setTimeout(() => {
|
|
466
|
+
delete this.reconnectTimeouts[deviceId];
|
|
467
|
+
this.connectWebSocket(deviceId);
|
|
468
|
+
}, 10000);
|
|
304
469
|
});
|
|
305
470
|
|
|
306
471
|
ws.on("error", (err) => {
|
|
@@ -536,8 +701,14 @@ class AlKoAdapter extends utils.Adapter {
|
|
|
536
701
|
this.log.info(`Push successful: ${id}`);
|
|
537
702
|
this.updateDeviceStateCache(deviceId, relPathArr, state.val);
|
|
538
703
|
} catch (err) {
|
|
704
|
+
const status = err.response?.status;
|
|
705
|
+
const data =
|
|
706
|
+
typeof err.response?.data === "object"
|
|
707
|
+
? JSON.stringify(err.response.data, null, 2)
|
|
708
|
+
: err.response?.data;
|
|
709
|
+
|
|
539
710
|
this.log.error(
|
|
540
|
-
`Error pushing state ${id}: ${
|
|
711
|
+
`Error pushing state ${id}: ${status || ""} ${data || err.message}`,
|
|
541
712
|
);
|
|
542
713
|
} finally {
|
|
543
714
|
this.pendingPushes.delete(id);
|
package/package.json
CHANGED