iobroker.anthbot-genie 0.1.2 → 0.1.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/README.md +2 -40
- package/io-package.json +22 -44
- package/lib/anthbot.js +69 -1
- package/main.js +37 -0
- package/package.json +11 -4
package/README.md
CHANGED
|
@@ -272,8 +272,9 @@ Special credit to the Home Assistant Anthbot Genie projects, which made the Anth
|
|
|
272
272
|
This ioBroker adapter is an independent project, but it builds on public API research and implementation ideas from that Home Assistant integration.
|
|
273
273
|
|
|
274
274
|
## Changelog
|
|
275
|
+
### 0.1.3 (2026-05-08)
|
|
275
276
|
|
|
276
|
-
|
|
277
|
+
- Fix AWS IoT shadow access by using temporary Anthbot IoT credentials instead of the expired bundled AWS credentials.
|
|
277
278
|
|
|
278
279
|
### 0.1.2
|
|
279
280
|
|
|
@@ -296,49 +297,10 @@ This ioBroker adapter is an independent project, but it builds on public API res
|
|
|
296
297
|
- Fix near-charger mowing enable control to use the mower shadow setting.
|
|
297
298
|
- Remove unsupported camera-enabled and docking resume-return controls.
|
|
298
299
|
|
|
299
|
-
### 0.1.0-beta.2
|
|
300
|
-
|
|
301
|
-
- Add full-map mowing control to include edge trimming.
|
|
302
|
-
- Remove the unsupported camera-enabled control.
|
|
303
|
-
- Fix near-charger mowing enable control to use the mower shadow setting.
|
|
304
|
-
- Remove the docking resume-return command because the cloud command is not working reliably.
|
|
305
|
-
|
|
306
|
-
### 0.1.0-beta.1
|
|
307
|
-
|
|
308
|
-
- Add expanded diagnostics for model names, region fallback, errors, RTK, map, firmware, OTA, network, and GPS/location data.
|
|
309
|
-
- Correct consumable maintenance mapping to blades, cameras, and charging port.
|
|
310
|
-
- Add consumable reset buttons for charging port, cameras, and blades.
|
|
311
|
-
- Remove metric states duplicated by writable controls and group mowing controls by full-map, zone, and near-charger mowing.
|
|
312
|
-
- Group command states by device, docking, maintenance, and mowing with consistent action names.
|
|
313
|
-
- Refactor state layout into grouped metrics, diagnostics, consumables, zones, raw shadows, and rain controls while keeping single-entry controls flat.
|
|
314
|
-
|
|
315
|
-
### 0.1.0-beta.0
|
|
316
|
-
|
|
317
|
-
- Add mower action commands: find robot, grass dump, disk maintenance mode, edge mowing, near-charger mowing, and point mowing.
|
|
318
|
-
- Add task control commands: pause/continue mowing, pause/continue return-to-dock, and end mowing.
|
|
319
|
-
- Add RTK antenna moved warning cancel command.
|
|
320
|
-
- Add status and control states for mowing near the charging pile, including its mowing parameters.
|
|
321
|
-
- Add camera switch status and control.
|
|
322
|
-
- Add RTK antenna moved warning status.
|
|
323
|
-
|
|
324
300
|
### 0.0.8
|
|
325
301
|
|
|
326
302
|
- Add consumable channels and values to the adapter definition.
|
|
327
303
|
|
|
328
|
-
### 0.0.7
|
|
329
|
-
|
|
330
|
-
- Add Dependabot automerge configuration.
|
|
331
|
-
- Update repository metadata for ioBroker checks.
|
|
332
|
-
|
|
333
|
-
### 0.0.6
|
|
334
|
-
|
|
335
|
-
- Fix ioBroker repository checker issues.
|
|
336
|
-
- Move admin configuration translations to i18n files.
|
|
337
|
-
|
|
338
|
-
### 0.0.5
|
|
339
|
-
|
|
340
|
-
- Prepare adapter metadata for ioBroker repository checks.
|
|
341
|
-
|
|
342
304
|
## License
|
|
343
305
|
|
|
344
306
|
MIT License
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "anthbot-genie",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.3",
|
|
5
5
|
"news": {
|
|
6
|
+
"0.1.3": {
|
|
7
|
+
"en": "Fix AWS IoT shadow access.",
|
|
8
|
+
"de": "AWS IoT-Shadow-Zugriff korrigiert.",
|
|
9
|
+
"ru": "Исправлен доступ к AWS IoT shadow.",
|
|
10
|
+
"pt": "Corrigido o acesso ao shadow AWS IoT.",
|
|
11
|
+
"nl": "AWS IoT-shadowtoegang opgelost.",
|
|
12
|
+
"fr": "Correction de l'accès au shadow AWS IoT.",
|
|
13
|
+
"it": "Corretto l'accesso allo shadow AWS IoT.",
|
|
14
|
+
"es": "Corregido el acceso al shadow de AWS IoT.",
|
|
15
|
+
"pl": "Naprawiono dostęp do shadow AWS IoT.",
|
|
16
|
+
"uk": "Виправлено доступ до AWS IoT shadow.",
|
|
17
|
+
"zh-cn": "修复 AWS IoT shadow 访问。"
|
|
18
|
+
},
|
|
6
19
|
"0.1.2": {
|
|
7
20
|
"en": "Limit io-package news entries for the ioBroker repository builder.",
|
|
8
21
|
"de": "Anzahl der io-package-News-Einträge für den ioBroker-Repository-Builder begrenzt.",
|
|
@@ -42,45 +55,6 @@
|
|
|
42
55
|
"uk": "Додано розширену діагностику, згруповану структуру станів, скидання витратних матеріалів, команди дій косарки та записувані елементи керування косінням.\nДодано керування обрізанням країв для повного косіння карти та виправлено керування біля зарядної станції.\nВилучено непідтримувані елементи керування камерою та продовженням повернення до бази.",
|
|
43
56
|
"zh-cn": "添加扩展诊断、分组状态结构、耗材重置、割草机动作命令和可写割草控制。\n添加全地图割草的边缘修剪控制,并修正充电站附近割草启用控制。\n移除不支持的摄像头和继续回充控制。"
|
|
44
57
|
},
|
|
45
|
-
"0.1.0-beta.2": {
|
|
46
|
-
"en": "Add full-map edge trimming control.\nRemove unsupported camera and docking resume controls.\nFix near-charger mowing enable control to use the mower shadow setting.",
|
|
47
|
-
"de": "Vollflächen-Kantenschnitt-Steuerung hinzugefügt.\nNicht unterstützte Kamera- und Docking-Fortsetzen-Steuerungen entfernt.\nSteuerung zum Aktivieren des Mähens nahe der Ladestation auf die Mäher-Shadow-Einstellung korrigiert.",
|
|
48
|
-
"ru": "Добавлено управление обрезкой краев для полного кошения карты.\nУдалены неподдерживаемые элементы управления камерой и продолжением возврата на базу.\nИсправлено включение кошения возле зарядной станции через настройку shadow косилки.",
|
|
49
|
-
"pt": "Adicionado controle de corte de borda para corte de mapa completo.\nRemovidos controles não suportados de câmera e retomada do retorno à base.\nCorrigido o controle de ativação do corte perto da estação para usar a configuração shadow do cortador.",
|
|
50
|
-
"nl": "Besturing voor randmaaien bij volledig kaartmaaien toegevoegd.\nNiet-ondersteunde camera- en docking-hervatbesturing verwijderd.\nBesturing voor maaien nabij het laadstation gebruikt nu de shadow-instelling van de maaier.",
|
|
51
|
-
"fr": "Ajout du contrôle de coupe des bordures pour la tonte de toute la carte.\nSuppression des contrôles non pris en charge pour la caméra et la reprise du retour à la station.\nCorrection du contrôle d'activation de la tonte près de la station avec le réglage shadow de la tondeuse.",
|
|
52
|
-
"it": "Aggiunto il controllo del taglio dei bordi per il taglio completo della mappa.\nRimossi i controlli non supportati per fotocamera e ripresa del ritorno alla base.\nCorretto il controllo di attivazione del taglio vicino alla stazione usando l'impostazione shadow del rasaerba.",
|
|
53
|
-
"es": "Añadido control de corte de bordes para el corte de mapa completo.\nEliminados los controles no compatibles de cámara y reanudación del retorno a la base.\nCorregido el control para activar el corte cerca de la estación usando la configuración shadow del cortacésped.",
|
|
54
|
-
"pl": "Dodano sterowanie koszeniem krawędzi dla pełnego koszenia mapy.\nUsunięto nieobsługiwane sterowanie kamerą i wznawianiem powrotu do stacji.\nPoprawiono włączanie koszenia przy stacji, aby używało ustawienia shadow kosiarki.",
|
|
55
|
-
"uk": "Додано керування обрізанням країв для повного косіння карти.\nВилучено непідтримувані елементи керування камерою та продовженням повернення до бази.\nВиправлено вмикання косіння біля зарядної станції через налаштування shadow косарки.",
|
|
56
|
-
"zh-cn": "添加全地图割草的边缘修剪控制。\n移除不支持的摄像头和继续回充控制。\n修正充电站附近割草启用控制,改为使用割草机 shadow 设置。"
|
|
57
|
-
},
|
|
58
|
-
"0.1.0-beta.0": {
|
|
59
|
-
"en": "Add consumable reset buttons for station, cameras, and blades.\nAdd mower action commands: find robot, grass dump, disk maintenance mode, edge mowing, near-charger mowing, and point mowing.\nAdd task control commands: pause/continue mowing, pause/continue return-to-dock, and end mowing.\nAdd RTK antenna moved warning cancel command.\nAdd status and control states for mowing near the charging pile, including its mowing parameters.\nAdd camera switch status and control.\nAdd RTK antenna moved warning status.",
|
|
60
|
-
"de": "Hinzufügen von Verbrauchs-Reset-Tasten für Station, Kameras und Klingen.\nFügen Sie Mäher-Action-Befehle hinzu: Finden Sie Roboter, Gras-Dump, Festplatten-Wartungsmodus, Rand Mähen, Nah-Ladegerät Mähen und Punkt Mähen.\nBefehle zur Task-Steuerung hinzufügen: Pause/Kontinue-Mähen, Pause/Fortsetzen-Rückkehr-zu-Dock und Ende-Mähen.\nFügen Sie RTK-Antenne bewegte Warnung Löschbefehl.\nFügen Sie Status- und Steuerzustände zum Mähen in der Nähe des Ladestapels hinzu, einschließlich seiner Mähparameter.\nFügen Sie Kameraschalter Status und Steuerung hinzu.\nFügen Sie RTK-Antenne bewegten Warnstatus hinzu.",
|
|
61
|
-
"ru": "Добавьте расходные кнопки сброса для станции, камер и лопастей.\nДобавьте команды действия косилки: найдите робота, свалку травы, режим обслуживания диска, косилку ребра, косилку около зарядного устройства и точечную косилку.\nДобавьте команды управления задачами: пауза / продолжение скашивания, пауза / продолжение возврата в док и конец скашивания.\nДобавить антенну RTK с перемещенным предупреждением отменить команду.\nДобавить состояния состояния и контроля для скашивания вблизи зарядной кучи, включая ее параметры скашивания.\nДобавьте статус переключателя камеры и управление.\nДобавьте антенну RTK с измененным статусом предупреждения.",
|
|
62
|
-
"pt": "Adicione botões de reset consumíveis para estações, câmeras e lâminas.\nAdicionar comandos de ação do cortador de grama: encontrar robô, despejo de grama, modo de manutenção de disco, corte de borda, corte perto do carregador, e corte ponto.\nAdicionar comandos de controle de tarefa: pausar/continuar o corte, pausar/continuar o retorno à doca e terminar o corte.\nAdicionar o comando de cancelamento de aviso movido da antena RTK.\nAdicione status e estados de controle para cortar perto da pilha de carregamento, incluindo seus parâmetros de corte.\nAdicione status e controle do interruptor de câmera.\nAdicionar o estado de aviso movido da antena RTK.",
|
|
63
|
-
"nl": "Voeg verbruiksresetknoppen voor station, camera's en messen toe.\nVoeg maaier actie commando's: vind robot, gras dump, schijf onderhoud modus, rand maaien, bijna-charger maaien, en punt maaien.\nVoeg taakbeheeropdrachten toe: pauzeer/ continu maaien, pauzeer/continue return-to-dock en eind maaien.\nVoeg RTK antenne verplaatste waarschuwing annuleren commando.\nVoeg status en controle toestanden voor maaien in de buurt van de laadstapel, inclusief de maaiparameters.\nVoeg camera switch status en controle.\nVoeg RTK antenne verplaatste waarschuwingsstatus toe.",
|
|
64
|
-
"fr": "Ajoutez des boutons de réinitialisation consommables pour la station, les caméras et les lames.\nAjouter des commandes d'action de tondeuse : trouver le robot, la décharge d'herbe, le mode d'entretien du disque, le tondage des bords, le tondage du chargeur et le tondage des points.\nAjoutez les commandes de contrôle des tâches : pause/continuer le tondage, pause/continuer le retour à l'aiguillage et fin du tondage.\nAjouter une antenne RTK déplacée avertissement annuler la commande.\nAjouter l'état et les états de contrôle pour tondre près du tas de charge, y compris ses paramètres de tonte.\nAjouter l'état et le contrôle du commutateur de caméra.\nAjouter l'état d'avertissement déplacé de l'antenne RTK.",
|
|
65
|
-
"it": "Aggiungi pulsanti di ripristino dei materiali di consumo per stazioni, telecamere e lame.\nAggiungi comandi di azione del falciatore: trovare robot, discarica di erba, modalità di manutenzione del disco, mowing del bordo, mowing del quasi-charger e mowing del punto.\nAggiungi comandi di controllo dell'attività: pausa/continua la cucitura, pausa/continua il rientro al dock e la falciatura finale.\nAggiungere RTK antenna spostato avviso disdetto comando.\nAggiungere stati di stato e di controllo per il cucito vicino alla pila di ricarica, compresi i suoi parametri di cucito.\nAggiungi lo stato e il controllo dell'interruttore della fotocamera.\nAggiungere RTK antenna spostato stato di avvertimento.",
|
|
66
|
-
"es": "Agregue botones de reinicio consumibles para estaciones, cámaras y cuchillas.\nAñadir comandos de acción de la cortadora: encontrar robot, vertedero de césped, modo de mantenimiento de disco, mowing de borde, mowing de carga cercana, y mowing de puntos.\nAgregue comandos de control de tareas: pausa/continua mowing, pausa/continua retorno-a-dock, y mowing final.\nAñadir la antena RTK movió el comando de cancelación de advertencia.\nAgregue estados de estado y control para moverse cerca de la pila de carga, incluyendo sus parámetros de movimiento.\nAgregue el estado y el control del interruptor de cámara.\nAñadir la antena RTK movió el estado de advertencia.",
|
|
67
|
-
"pl": "Dodaj zużywalne przyciski resetujące dla stacji, kamer i ostrzy.\nDodaj polecenia działania kosiarki: znajdź robota, trawę, tryb konserwacji dysku, koszenie krawędzi, koszenie w pobliżu ładowarki i koszenie punktowe.\nDodaj polecenia kontroli zadania: pauza / kontynuuj koszenie, pauza / kontynuuj return-to- dock i koszenie końcowe.\nDodać RTK antena przeniesione ostrzeżenie anulować polecenie.\nDodać stany stanu i kontroli koszenia w pobliżu stosu ładowania, w tym jego parametry koszenia.\nDodaj status i sterowanie przełącznika kamery.\nDodaj status ostrzegawczy anteny RTK.",
|
|
68
|
-
"uk": "Додайте примітні кнопки скидання для станції, камер і леза.\nДодайте команди косарки: знайти роботу, травне скидання, режим технічного обслуговування диска, кромочні ковтки, приблизний ковток, а також точку ковтки.\nДодайте команди керування завданнями: пауза / безперервне скошування, пауза/переконайте зворотний дзвінок, а також кінцеве скошування.\nДодати RTK антену перемістив команду скасування попередження.\nДодайте статус і контрольні стани для скошування під час зарядки, в тому числі його параметри скошування.\nДодайте статус вимикача камери та контроль.\nДодати RTK антена перемістив статус попередження.",
|
|
69
|
-
"zh-cn": "增加空间站、摄像头和刀片的消耗性重置按钮.\n添加割草机动作命令:找到机器人,草堆,磁盘维护模式,剪边,近充机割草,点割.\n添加任务控制命令: 暂停/ 继续剪切, 暂停/ 继续折返到.\n添加 RTK 天线移动警告取消命令 .\n增加电荷堆附近割草的状态和控制状态,包括其割草参数.\n添加相机开关状态及控制.\n添加 RTK 天线移动警告状态 ."
|
|
70
|
-
},
|
|
71
|
-
"0.0.9-beta.0": {
|
|
72
|
-
"en": "Add mower service commands and controls.",
|
|
73
|
-
"de": "Mäher-Servicebefehle und Steuerungen hinzugefügt.",
|
|
74
|
-
"ru": "Добавлены сервисные команды и элементы управления косилкой.",
|
|
75
|
-
"pt": "Adicionados comandos de serviço e controles do cortador.",
|
|
76
|
-
"nl": "Maaier-serviceopdrachten en bedieningselementen toegevoegd.",
|
|
77
|
-
"fr": "Ajout de commandes de service et de contrôles pour la tondeuse.",
|
|
78
|
-
"it": "Aggiunti comandi di servizio e controlli del rasaerba.",
|
|
79
|
-
"es": "Se añadieron comandos de servicio y controles del cortacésped.",
|
|
80
|
-
"pl": "Dodano polecenia serwisowe i sterowanie kosiarką.",
|
|
81
|
-
"uk": "Додано сервісні команди та елементи керування косаркою.",
|
|
82
|
-
"zh-cn": "添加割草机服务命令和控制。"
|
|
83
|
-
},
|
|
84
58
|
"0.0.8": {
|
|
85
59
|
"en": "Add consumable channels and values to the adapter definition.",
|
|
86
60
|
"de": "Verbrauchskanäle und Werte zur Adapterdefinition hinzugefügt.",
|
|
@@ -109,7 +83,7 @@
|
|
|
109
83
|
"zh-cn": "Anthbot Genie"
|
|
110
84
|
},
|
|
111
85
|
"desc": {
|
|
112
|
-
"en": "Unofficial cloud adapter for Anthbot Genie
|
|
86
|
+
"en": "Unofficial cloud adapter for Anthbot Genie robotic lawn mowers with telemetry, diagnostics, consumables, zones, and controls",
|
|
113
87
|
"de": "Inoffizieller Cloud-Adapter für Statusabfrage und Steuerung von Anthbot Genie",
|
|
114
88
|
"ru": "Неофициальный облачный адаптер для управления газонокосилками Anthbot Genie и опроса состояния",
|
|
115
89
|
"pt": "Adaptador de nuvem não oficial para controlar cortadores Anthbot Genie e consultar o estado",
|
|
@@ -133,10 +107,14 @@
|
|
|
133
107
|
"readme": "https://github.com/reloxx13/ioBroker.anthbot-genie/blob/main/README.md",
|
|
134
108
|
"keywords": [
|
|
135
109
|
"anthbot",
|
|
136
|
-
"genie",
|
|
110
|
+
"anthbot-genie",
|
|
111
|
+
"robotic-lawn-mower",
|
|
112
|
+
"lawn-mower",
|
|
137
113
|
"mower",
|
|
138
|
-
"
|
|
139
|
-
"
|
|
114
|
+
"home-automation",
|
|
115
|
+
"rtk",
|
|
116
|
+
"cloud-api",
|
|
117
|
+
"iot"
|
|
140
118
|
],
|
|
141
119
|
"platform": "Javascript/Node.js",
|
|
142
120
|
"mode": "daemon",
|
package/lib/anthbot.js
CHANGED
|
@@ -335,6 +335,51 @@ class AnthbotCloudApiClient {
|
|
|
335
335
|
};
|
|
336
336
|
}
|
|
337
337
|
|
|
338
|
+
async getDeviceIotCredentials(serialNumber) {
|
|
339
|
+
this.requireToken();
|
|
340
|
+
const response = await this.http.post(`https://${this.host}/api/v1/device/v2/iot/sts/arn`, {
|
|
341
|
+
sn: serialNumber,
|
|
342
|
+
verification_token: AnthbotCloudApiClient.buildVerificationToken(serialNumber),
|
|
343
|
+
}, {
|
|
344
|
+
headers: {
|
|
345
|
+
...this.authHeaders,
|
|
346
|
+
"content-type": "application/json",
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
const payload = response.data;
|
|
350
|
+
if (response.status !== 200) {
|
|
351
|
+
throw new AnthbotGenieError(`IoT STS failed (${response.status}): ${String(payload).slice(0, 300)}`);
|
|
352
|
+
}
|
|
353
|
+
if (!payload || typeof payload !== "object") {
|
|
354
|
+
throw new AnthbotGenieError("Invalid IoT STS payload type");
|
|
355
|
+
}
|
|
356
|
+
if (payload.code !== 0) {
|
|
357
|
+
throw new AnthbotGenieError(`IoT STS returned code=${JSON.stringify(payload.code)}`);
|
|
358
|
+
}
|
|
359
|
+
const data = payload.data;
|
|
360
|
+
if (!data || typeof data !== "object") {
|
|
361
|
+
throw new AnthbotGenieError("IoT STS payload missing data object");
|
|
362
|
+
}
|
|
363
|
+
const requiredFields = ["access_key_id", "secret_access_key", "session_token", "region_name", "endpoint"];
|
|
364
|
+
if (requiredFields.some(field => typeof data[field] !== "string" || !data[field])) {
|
|
365
|
+
throw new AnthbotGenieError("IoT STS payload missing required fields");
|
|
366
|
+
}
|
|
367
|
+
const expiration = asInteger(data.expiration);
|
|
368
|
+
const expiresAt = expiration == null
|
|
369
|
+
? null
|
|
370
|
+
: expiration > 2000000000
|
|
371
|
+
? expiration * 1000
|
|
372
|
+
: Date.now() + expiration * 1000;
|
|
373
|
+
return {
|
|
374
|
+
accessKeyId: data.access_key_id,
|
|
375
|
+
secretAccessKey: data.secret_access_key,
|
|
376
|
+
sessionToken: data.session_token,
|
|
377
|
+
regionName: data.region_name,
|
|
378
|
+
endpoint: data.endpoint,
|
|
379
|
+
expiresAt,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
338
383
|
async getDeviceAreaDefinition(serialNumber) {
|
|
339
384
|
this.requireToken();
|
|
340
385
|
const response = await this.http.get(`https://${this.host}/api/v1/device/v2/presigned_url`, {
|
|
@@ -427,11 +472,12 @@ class AnthbotCloudApiClient {
|
|
|
427
472
|
}
|
|
428
473
|
|
|
429
474
|
class AnthbotShadowApiClient {
|
|
430
|
-
constructor({ http, serialNumber, regionName, iotEndpoint }) {
|
|
475
|
+
constructor({ http, serialNumber, regionName, iotEndpoint, iotCredentials = null }) {
|
|
431
476
|
this.http = http;
|
|
432
477
|
this.serialNumber = serialNumber;
|
|
433
478
|
this.regionName = typeof regionName === "string" && regionName ? regionName : null;
|
|
434
479
|
this.iotEndpoint = AnthbotShadowApiClient.normalizeEndpoint(iotEndpoint);
|
|
480
|
+
this.iotCredentials = iotCredentials && typeof iotCredentials === "object" ? iotCredentials : null;
|
|
435
481
|
}
|
|
436
482
|
|
|
437
483
|
static normalizeEndpoint(iotEndpoint) {
|
|
@@ -459,6 +505,9 @@ class AnthbotShadowApiClient {
|
|
|
459
505
|
}
|
|
460
506
|
|
|
461
507
|
accessKeyId() {
|
|
508
|
+
if (typeof this.iotCredentials?.accessKeyId === "string" && this.iotCredentials.accessKeyId) {
|
|
509
|
+
return this.iotCredentials.accessKeyId;
|
|
510
|
+
}
|
|
462
511
|
if (this.iotEndpoint === CN_NORTHWEST_IOT_ENDPOINT) {
|
|
463
512
|
return AWS_ACCESS_KEY_CN_NORTHWEST;
|
|
464
513
|
}
|
|
@@ -469,6 +518,9 @@ class AnthbotShadowApiClient {
|
|
|
469
518
|
}
|
|
470
519
|
|
|
471
520
|
secretAccessKey() {
|
|
521
|
+
if (typeof this.iotCredentials?.secretAccessKey === "string" && this.iotCredentials.secretAccessKey) {
|
|
522
|
+
return this.iotCredentials.secretAccessKey;
|
|
523
|
+
}
|
|
472
524
|
if (this.iotEndpoint === CN_NORTHWEST_IOT_ENDPOINT) {
|
|
473
525
|
return AWS_SECRET_KEY_CN_NORTHWEST;
|
|
474
526
|
}
|
|
@@ -478,6 +530,12 @@ class AnthbotShadowApiClient {
|
|
|
478
530
|
return AWS_SECRET_KEY_DEFAULT;
|
|
479
531
|
}
|
|
480
532
|
|
|
533
|
+
sessionToken() {
|
|
534
|
+
return typeof this.iotCredentials?.sessionToken === "string" && this.iotCredentials.sessionToken
|
|
535
|
+
? this.iotCredentials.sessionToken
|
|
536
|
+
: null;
|
|
537
|
+
}
|
|
538
|
+
|
|
481
539
|
sign(key, msg) {
|
|
482
540
|
return crypto.createHmac("sha256", key).update(msg, "utf8").digest();
|
|
483
541
|
}
|
|
@@ -557,6 +615,10 @@ class AnthbotShadowApiClient {
|
|
|
557
615
|
"x-amz-content-sha256": payloadHash,
|
|
558
616
|
"x-amz-date": amzDate,
|
|
559
617
|
};
|
|
618
|
+
const sessionToken = this.sessionToken();
|
|
619
|
+
if (sessionToken) {
|
|
620
|
+
signedHeaderValues["x-amz-security-token"] = sessionToken;
|
|
621
|
+
}
|
|
560
622
|
const { canonical, signedHeaders } = AnthbotShadowApiClient.canonicalHeaders(signedHeaderValues);
|
|
561
623
|
const canonicalRequest = [
|
|
562
624
|
"GET",
|
|
@@ -573,6 +635,7 @@ class AnthbotShadowApiClient {
|
|
|
573
635
|
Host: this.iotEndpoint,
|
|
574
636
|
"x-amz-date": amzDate,
|
|
575
637
|
"x-amz-content-sha256": payloadHash,
|
|
638
|
+
...(sessionToken ? { "x-amz-security-token": sessionToken } : {}),
|
|
576
639
|
Authorization: authorization,
|
|
577
640
|
"User-Agent": "LdMower/1581 CFNetwork/3860.400.51 Darwin/25.3.0",
|
|
578
641
|
},
|
|
@@ -617,6 +680,11 @@ class AnthbotShadowApiClient {
|
|
|
617
680
|
"x-amz-content-sha256": payloadHash,
|
|
618
681
|
"x-amz-date": amzDate,
|
|
619
682
|
};
|
|
683
|
+
const sessionToken = this.sessionToken();
|
|
684
|
+
if (sessionToken) {
|
|
685
|
+
signedHeaderValues["x-amz-security-token"] = sessionToken;
|
|
686
|
+
headers["x-amz-security-token"] = sessionToken;
|
|
687
|
+
}
|
|
620
688
|
if (signContentLength) {
|
|
621
689
|
signedHeaderValues["content-length"] = String(payloadBytes.length);
|
|
622
690
|
headers["Content-Length"] = String(payloadBytes.length);
|
package/main.js
CHANGED
|
@@ -402,7 +402,9 @@ class AnthbotGenieAdapter extends utils.Adapter {
|
|
|
402
402
|
serialNumber: device.serialNumber,
|
|
403
403
|
regionName: region.regionName,
|
|
404
404
|
iotEndpoint: region.iotEndpoint,
|
|
405
|
+
iotCredentials: region.iotCredentials,
|
|
405
406
|
}),
|
|
407
|
+
iotCredentials: region.iotCredentials,
|
|
406
408
|
areaDefinition: existing?.areaDefinition || {},
|
|
407
409
|
lastAreaTime: existing?.lastAreaTime || null,
|
|
408
410
|
lastReported: existing?.lastReported || {},
|
|
@@ -416,6 +418,7 @@ class AnthbotGenieAdapter extends utils.Adapter {
|
|
|
416
418
|
async resolveDeviceRegion(device) {
|
|
417
419
|
let regionName = null;
|
|
418
420
|
let iotEndpoint = null;
|
|
421
|
+
let iotCredentials = null;
|
|
419
422
|
|
|
420
423
|
try {
|
|
421
424
|
const deviceRegion = await this.cloudClient.getDeviceRegion(device.serialNumber);
|
|
@@ -446,10 +449,19 @@ class AnthbotGenieAdapter extends utils.Adapter {
|
|
|
446
449
|
this.log.debug(`Presigned region fallback failed for ${device.serialNumber}: ${error.message}`);
|
|
447
450
|
}
|
|
448
451
|
|
|
452
|
+
try {
|
|
453
|
+
iotCredentials = await this.cloudClient.getDeviceIotCredentials(device.serialNumber);
|
|
454
|
+
regionName = iotCredentials.regionName || regionName;
|
|
455
|
+
iotEndpoint = iotCredentials.endpoint || iotEndpoint;
|
|
456
|
+
} catch (error) {
|
|
457
|
+
this.log.warn(`Failed to fetch temporary IoT credentials for ${device.serialNumber}, using bundled fallback credentials: ${error.message}`);
|
|
458
|
+
}
|
|
459
|
+
|
|
449
460
|
return {
|
|
450
461
|
serialNumber: device.serialNumber,
|
|
451
462
|
regionName: regionName || AnthbotShadowApiClient.guessRegionFromEndpoint(iotEndpoint) || "unknown",
|
|
452
463
|
iotEndpoint,
|
|
464
|
+
iotCredentials,
|
|
453
465
|
};
|
|
454
466
|
}
|
|
455
467
|
|
|
@@ -500,6 +512,7 @@ class AnthbotGenieAdapter extends utils.Adapter {
|
|
|
500
512
|
}
|
|
501
513
|
|
|
502
514
|
async refreshDevice(context) {
|
|
515
|
+
await this.ensureDeviceIotCredentials(context);
|
|
503
516
|
const propertyState = await context.shadowClient.getShadowReportedState();
|
|
504
517
|
let serviceState = {};
|
|
505
518
|
try {
|
|
@@ -536,6 +549,27 @@ class AnthbotGenieAdapter extends utils.Adapter {
|
|
|
536
549
|
await this.updateStates(context, merged);
|
|
537
550
|
}
|
|
538
551
|
|
|
552
|
+
async ensureDeviceIotCredentials(context) {
|
|
553
|
+
if (context.iotCredentials && (!context.iotCredentials.expiresAt || context.iotCredentials.expiresAt - Date.now() > 60000)) {
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
const iotCredentials = await this.cloudClient.getDeviceIotCredentials(context.device.serialNumber);
|
|
557
|
+
context.iotCredentials = iotCredentials;
|
|
558
|
+
context.region = {
|
|
559
|
+
...context.region,
|
|
560
|
+
regionName: iotCredentials.regionName || context.region.regionName,
|
|
561
|
+
iotEndpoint: iotCredentials.endpoint || context.region.iotEndpoint,
|
|
562
|
+
iotCredentials,
|
|
563
|
+
};
|
|
564
|
+
context.shadowClient = new AnthbotShadowApiClient({
|
|
565
|
+
http: this.http,
|
|
566
|
+
serialNumber: context.device.serialNumber,
|
|
567
|
+
regionName: context.region.regionName,
|
|
568
|
+
iotEndpoint: context.region.iotEndpoint,
|
|
569
|
+
iotCredentials,
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
|
|
539
573
|
async updateStates(context, data) {
|
|
540
574
|
const serial = context.device.serialNumber;
|
|
541
575
|
const manualZoneList = manualZones(data);
|
|
@@ -797,6 +831,7 @@ class AnthbotGenieAdapter extends utils.Adapter {
|
|
|
797
831
|
return;
|
|
798
832
|
}
|
|
799
833
|
|
|
834
|
+
await this.ensureDeviceIotCredentials(context);
|
|
800
835
|
const shouldRequestProperties = await this.executeCommand(context, command, value);
|
|
801
836
|
if (shouldRequestProperties) {
|
|
802
837
|
await context.shadowClient.requestAllProperties();
|
|
@@ -809,6 +844,7 @@ class AnthbotGenieAdapter extends utils.Adapter {
|
|
|
809
844
|
return;
|
|
810
845
|
}
|
|
811
846
|
|
|
847
|
+
await this.ensureDeviceIotCredentials(context);
|
|
812
848
|
await this.executeControl(context, control, value);
|
|
813
849
|
await context.shadowClient.requestAllProperties();
|
|
814
850
|
await this.delay(1000);
|
|
@@ -820,6 +856,7 @@ class AnthbotGenieAdapter extends utils.Adapter {
|
|
|
820
856
|
return;
|
|
821
857
|
}
|
|
822
858
|
|
|
859
|
+
await this.ensureDeviceIotCredentials(context);
|
|
823
860
|
await this.executeConsumableCommand(context, command);
|
|
824
861
|
await this.delay(1000);
|
|
825
862
|
}
|
package/package.json
CHANGED
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.anthbot-genie",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Unofficial ioBroker adapter for Anthbot Genie mowers",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "Unofficial ioBroker adapter for Anthbot Genie robotic lawn mowers with cloud telemetry, diagnostics, consumables, zones, and controls.",
|
|
5
5
|
"author": "reloxx13",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"main": "main.js",
|
|
8
8
|
"homepage": "https://github.com/reloxx13/ioBroker.anthbot-genie",
|
|
9
9
|
"keywords": [
|
|
10
10
|
"ioBroker",
|
|
11
|
+
"iobroker-adapter",
|
|
11
12
|
"anthbot",
|
|
12
|
-
"genie",
|
|
13
|
+
"anthbot-genie",
|
|
14
|
+
"robotic-lawn-mower",
|
|
15
|
+
"lawn-mower",
|
|
13
16
|
"mower",
|
|
14
|
-
"
|
|
17
|
+
"smart-home",
|
|
18
|
+
"home-automation",
|
|
19
|
+
"rtk",
|
|
20
|
+
"cloud-api",
|
|
21
|
+
"iot"
|
|
15
22
|
],
|
|
16
23
|
"repository": {
|
|
17
24
|
"type": "git",
|