iobroker.al-ko 0.3.0 → 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/LICENSE +1 -1
- package/README.md +25 -13
- package/docs/de/README.md +13 -1
- package/docs/en/README.md +13 -1
- package/io-package.json +14 -10
- package/main.js +184 -13
- package/package.json +7 -7
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -51,29 +51,41 @@ Do **not** contact AL-KO customer service regarding this project.
|
|
|
51
51
|
|
|
52
52
|
---
|
|
53
53
|
|
|
54
|
-
##
|
|
54
|
+
## Changelog
|
|
55
55
|
|
|
56
|
-
###
|
|
56
|
+
### 0.3.2 (2026-03-12)
|
|
57
57
|
|
|
58
|
-
- Improved
|
|
59
|
-
-
|
|
60
|
-
-
|
|
61
|
-
-
|
|
62
|
-
- Many internal improvements and preparations for future patches
|
|
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
|
|
63
62
|
|
|
64
|
-
###
|
|
63
|
+
### 0.3.1 (2026-03-09)
|
|
65
64
|
|
|
66
|
-
-
|
|
67
|
-
-
|
|
68
|
-
-
|
|
65
|
+
- Documentation improvements
|
|
66
|
+
- Corrected LICENSE information
|
|
67
|
+
- Updated development dependencies
|
|
68
|
+
- Minor CI / workflow cleanup
|
|
69
69
|
- No functional changes
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
### 0.3.0 (2026-03-09)
|
|
72
|
+
|
|
73
|
+
- Major maintenance release
|
|
74
|
+
- Updated ESLint 9, Prettier 3 and TypeScript tooling
|
|
75
|
+
- Updated development dependencies
|
|
76
|
+
- Improved CI pipeline and adapter structure
|
|
77
|
+
- No functional changes
|
|
78
|
+
|
|
79
|
+
➡ Full changelog here:
|
|
80
|
+
[CHANGELOG.md](./CHANGELOG.md)
|
|
73
81
|
|
|
74
82
|
---
|
|
75
83
|
|
|
76
84
|
## License
|
|
77
85
|
|
|
86
|
+
MIT License
|
|
87
|
+
|
|
88
|
+
Copyright (c) 2026 Hubert Zechner <hubertiob@posteo.at>
|
|
89
|
+
|
|
78
90
|
This project is released under the **MIT License**.
|
|
79
91
|
See the included **LICENSE** file for full details.
|
package/docs/de/README.md
CHANGED
|
@@ -48,7 +48,19 @@ AL-KO bietet **keinen offiziellen Support** hierfür.
|
|
|
48
48
|
|
|
49
49
|
## Änderungen (Auszug)
|
|
50
50
|
|
|
51
|
-
###
|
|
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
|
+
|
|
58
|
+
### 0.3.1 (2026-03-09)
|
|
59
|
+
|
|
60
|
+
- Verbesserte Dokumentation
|
|
61
|
+
- Lizenzangaben korrigiert
|
|
62
|
+
- Entwicklungsabhängigkeiten aktualisiert
|
|
63
|
+
- Keine funktionalen Änderungen
|
|
52
64
|
|
|
53
65
|
Alle Änderungen siehe vollständigen Changelog:
|
|
54
66
|
➡ [CHANGELOG.md](../../CHANGELOG.md)
|
package/docs/en/README.md
CHANGED
|
@@ -48,7 +48,19 @@ It is a **community-developed project**.
|
|
|
48
48
|
|
|
49
49
|
## Changes (Summary)
|
|
50
50
|
|
|
51
|
-
###
|
|
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
|
+
|
|
58
|
+
### 0.3.1 (2026-03-09)
|
|
59
|
+
|
|
60
|
+
- Documentation improvements
|
|
61
|
+
- Corrected LICENSE information
|
|
62
|
+
- Updated development dependencies
|
|
63
|
+
- No functional changes
|
|
52
64
|
|
|
53
65
|
See full changelog here:
|
|
54
66
|
➡ [CHANGELOG.md](../../CHANGELOG.md)
|
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,18 @@
|
|
|
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
|
+
},
|
|
47
|
+
"0.3.1": {
|
|
48
|
+
"en": "Documentation improvements, corrected license information and updated development dependencies. No functional changes.",
|
|
49
|
+
"de": "Dokumentation verbessert, Lizenzangaben korrigiert und Entwicklungsabhängigkeiten aktualisiert. Keine funktionalen Änderungen."
|
|
50
|
+
},
|
|
51
|
+
"0.3.0": {
|
|
52
|
+
"en": "Major maintenance release. Updated ESLint 9, Prettier 3, TypeScript tooling and development dependencies. Improved adapter structure and CI pipeline. No functional changes.",
|
|
53
|
+
"de": "Größeres Wartungsrelease. Aktualisierung von ESLint 9, Prettier 3, TypeScript-Tooling und Entwicklungsabhängigkeiten. Verbesserte Adapterstruktur und CI-Pipeline. Keine funktionalen Änderungen."
|
|
54
|
+
},
|
|
43
55
|
"0.2.15": {
|
|
44
56
|
"en": "Admin config cleanup for adapter-check: removed $schema, corrected structure, added missing size attributes.",
|
|
45
57
|
"de": "Admin-Konfiguration für adapter-check bereinigt: $schema entfernt, Struktur korrigiert, fehlende size-Attribute ergänzt."
|
|
@@ -51,14 +63,6 @@
|
|
|
51
63
|
"0.2.13": {
|
|
52
64
|
"en": "Fixed JSON syntax error in io-package.json. No functional changes.",
|
|
53
65
|
"de": "JSON-Syntaxfehler in io-package.json behoben. Keine funktionalen Änderungen."
|
|
54
|
-
},
|
|
55
|
-
"0.2.12": {
|
|
56
|
-
"en": "Fixed jsonConfig schema URL, added missing size attributes, increased minimum requirements.",
|
|
57
|
-
"de": "Schema-URL korrigiert, fehlende Size-Attribute ergänzt, Mindestanforderungen erhöht."
|
|
58
|
-
},
|
|
59
|
-
"0.2.11": {
|
|
60
|
-
"en": "Minor code corrections.",
|
|
61
|
-
"de": "Kleine Code-Korrekturen."
|
|
62
66
|
}
|
|
63
67
|
},
|
|
64
68
|
"keywords": ["al-ko", "Robolinho", "mower", "garden", "smart-garden"],
|
|
@@ -81,7 +85,7 @@
|
|
|
81
85
|
],
|
|
82
86
|
"globalDependencies": [
|
|
83
87
|
{
|
|
84
|
-
"admin": ">=7.6.
|
|
88
|
+
"admin": ">=7.6.20"
|
|
85
89
|
}
|
|
86
90
|
]
|
|
87
91
|
},
|
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.al-ko",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Adapter for communication with AL-KO smart garden devices (Robolinho, mowing windows, operationState, etc.)",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Hubert Zechner",
|
|
@@ -56,13 +56,13 @@
|
|
|
56
56
|
"ws": "^8.19.0"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
|
-
"@alcalzone/release-script": "^5.
|
|
60
|
-
"@alcalzone/release-script-plugin-iobroker": "^
|
|
61
|
-
"@alcalzone/release-script-plugin-license": "^
|
|
62
|
-
"@alcalzone/release-script-plugin-manual-review": "^
|
|
63
|
-
"@iobroker/adapter-dev": "^1.
|
|
59
|
+
"@alcalzone/release-script": "^5.1.1",
|
|
60
|
+
"@alcalzone/release-script-plugin-iobroker": "^5.1.2",
|
|
61
|
+
"@alcalzone/release-script-plugin-license": "^5.1.1",
|
|
62
|
+
"@alcalzone/release-script-plugin-manual-review": "^5.1.1",
|
|
63
|
+
"@iobroker/adapter-dev": "^1.5.0",
|
|
64
64
|
"@iobroker/eslint-config": "^2.2.0",
|
|
65
|
-
"@iobroker/testing": "^5.
|
|
65
|
+
"@iobroker/testing": "^5.2.2",
|
|
66
66
|
"@tsconfig/node20": "^20.1.8",
|
|
67
67
|
"@types/node": "^25.1.0",
|
|
68
68
|
"@typescript-eslint/eslint-plugin": "^8.54.0",
|