iobroker.anthbot-genie 0.1.2 → 0.1.4

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 CHANGED
@@ -30,13 +30,13 @@ An example ioBroker Blockly with conditions for mower automation is available in
30
30
  - Region and IoT endpoint lookup per mower
31
31
  - Polling of property and service shadows
32
32
  - Detailed status states for connection, online state, battery, mower status, charging state, mowing time, mowing area, map status, errors, active mowing mode, point mowing, and zone counts
33
- - Diagnostic states for RTK fix, RTK base station, moved antenna warnings, firmware versions, OTA progress, WiFi, cellular, SIM, Bluetooth, camera/map flags, obstacle avoidance, security flags, system timestamps, and mower error data
33
+ - Diagnostic states for RTK fix, RTK base station, moved antenna warnings, firmware versions, OTA progress, WiFi, cellular, SIM, Bluetooth, camera/map flags, obstacle avoidance, security flags, system timestamps, and cloud-backed mower error data
34
34
  - Location states for anti-loss GPS coordinates and local mower pose
35
35
  - Consumable lifetime states and reset buttons for charging port, cameras, and blades
36
36
  - Writable control states for full-map mowing, zone mowing, cutting height, voice volume, custom mowing direction, obstacle avoidance, rain settings, and mowing near the charging pile
37
37
  - Command states for full mowing, stop, return to dock, pause return to dock, grass dump, disk maintenance mode, edge mowing, mowing near the charging pile, point mowing, refresh, manual zone mowing, and automatic zone mowing
38
38
  - Manual and automatic zone metadata as JSON states, including active manual zone IDs
39
- - Raw property shadow, service shadow, and area definition payloads for troubleshooting and automation debugging
39
+ - Raw property shadow, service shadow, Anthbot event-code translations, and area definition payloads for troubleshooting and automation debugging
40
40
 
41
41
  ## Requirements
42
42
 
@@ -56,7 +56,8 @@ Open the adapter instance configuration in ioBroker Admin and set:
56
56
  | Anthbot account password | Anthbot account password, stored encrypted by ioBroker | empty |
57
57
  | Area code | Phone or account area code, for example `49` for Germany | `49` |
58
58
  | API host | Anthbot cloud API host | `api.anthbot.com` |
59
- | Poll interval in seconds | Polling interval for mower data. The adapter enforces at least 10 seconds. | `30` |
59
+ | Poll interval in seconds | Polling interval for mower data. The adapter enforces at least 10 seconds. | `60` |
60
+ | Error description language | Language used for Anthbot cloud error descriptions | `English` |
60
61
 
61
62
  After saving the configuration, start or restart the adapter instance.
62
63
 
@@ -102,7 +103,7 @@ anthbot-genie.<instance>.<serial>.*
102
103
  | `<serial>.metrics.map.totalArea` | number | `m2` | Total mapped area |
103
104
  | `<serial>.metrics.map.status` | string | | Raw map status |
104
105
  | `<serial>.metrics.error.code` | number | | Last mower error code |
105
- | `<serial>.metrics.error.description` | string | | Human-readable error description when known |
106
+ | `<serial>.metrics.error.description` | string | | Human-readable error description from the cached Anthbot event-code list when known |
106
107
  | `<serial>.metrics.error.active` | boolean | | Whether a non-zero mower error is active |
107
108
 
108
109
  ### Location
@@ -198,6 +199,7 @@ Availability of `commands.maintenance.startDiskMaintenance`, `commands.maintenan
198
199
  | --- | --- | --- |
199
200
  | `<serial>.raw.shadow.property` | JSON string | Raw property shadow payload |
200
201
  | `<serial>.raw.shadow.service` | JSON string | Raw service shadow payload |
202
+ | `<serial>.raw.shadow.event-code` | JSON string | Cached Anthbot event-code translation payload used for error descriptions |
201
203
  | `<serial>.raw.areaDefinition` | JSON string | Raw area definition payload |
202
204
 
203
205
  ## Zone Mowing
@@ -272,8 +274,14 @@ Special credit to the Home Assistant Anthbot Genie projects, which made the Anth
272
274
  This ioBroker adapter is an independent project, but it builds on public API research and implementation ideas from that Home Assistant integration.
273
275
 
274
276
  ## Changelog
277
+ ### 0.1.4 (2026-05-08)
275
278
 
276
- ### **WORK IN PROGRESS**
279
+ - Use Anthbot cloud event-code translations for mower error descriptions and add a configurable description language.
280
+ - Store the fetched event-code translation cache in `raw.shadow.event-code` for troubleshooting.
281
+
282
+ ### 0.1.3 (2026-05-08)
283
+
284
+ - Fix AWS IoT shadow access by using temporary Anthbot IoT credentials instead of the expired bundled AWS credentials.
277
285
 
278
286
  ### 0.1.2
279
287
 
@@ -296,49 +304,10 @@ This ioBroker adapter is an independent project, but it builds on public API res
296
304
  - Fix near-charger mowing enable control to use the mower shadow setting.
297
305
  - Remove unsupported camera-enabled and docking resume-return controls.
298
306
 
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
307
  ### 0.0.8
325
308
 
326
309
  - Add consumable channels and values ​​to the adapter definition.
327
310
 
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
311
  ## License
343
312
 
344
313
  MIT License
@@ -4,5 +4,7 @@
4
4
  "Area code": "Ländervorwahl",
5
5
  "Examples: 49 for Germany, 32 for Belgium, 1 for US/Canada": "Beispiele: 49 für Deutschland, 32 für Belgien, 1 für USA/Kanada",
6
6
  "API host": "API-Host",
7
- "Poll interval in seconds": "Abfrageintervall in Sekunden"
7
+ "Poll interval in seconds": "Abfrageintervall in Sekunden",
8
+ "Error description language": "Sprache der Fehlerbeschreibung",
9
+ "Language used for Anthbot cloud error descriptions": "Sprache für Anthbot-Cloud-Fehlerbeschreibungen"
8
10
  }
@@ -4,5 +4,7 @@
4
4
  "Area code": "Area code",
5
5
  "Examples: 49 for Germany, 32 for Belgium, 1 for US/Canada": "Examples: 49 for Germany, 32 for Belgium, 1 for US/Canada",
6
6
  "API host": "API host",
7
- "Poll interval in seconds": "Poll interval in seconds"
7
+ "Poll interval in seconds": "Poll interval in seconds",
8
+ "Error description language": "Error description language",
9
+ "Language used for Anthbot cloud error descriptions": "Language used for Anthbot cloud error descriptions"
8
10
  }
@@ -4,5 +4,7 @@
4
4
  "Area code": "Código de país",
5
5
  "Examples: 49 for Germany, 32 for Belgium, 1 for US/Canada": "Ejemplos: 49 para Alemania, 32 para Bélgica, 1 para EE. UU./Canadá",
6
6
  "API host": "Host de la API",
7
- "Poll interval in seconds": "Intervalo de consulta en segundos"
7
+ "Poll interval in seconds": "Intervalo de consulta en segundos",
8
+ "Error description language": "Idioma de la descripción del error",
9
+ "Language used for Anthbot cloud error descriptions": "Idioma utilizado para las descripciones de errores de la nube Anthbot"
8
10
  }
@@ -4,5 +4,7 @@
4
4
  "Area code": "Indicatif pays",
5
5
  "Examples: 49 for Germany, 32 for Belgium, 1 for US/Canada": "Exemples : 49 pour l'Allemagne, 32 pour la Belgique, 1 pour les États-Unis/Canada",
6
6
  "API host": "Hôte de l'API",
7
- "Poll interval in seconds": "Intervalle d'interrogation en secondes"
7
+ "Poll interval in seconds": "Intervalle d'interrogation en secondes",
8
+ "Error description language": "Langue de description des erreurs",
9
+ "Language used for Anthbot cloud error descriptions": "Langue utilisée pour les descriptions d'erreur du cloud Anthbot"
8
10
  }
@@ -4,5 +4,7 @@
4
4
  "Area code": "Prefisso internazionale",
5
5
  "Examples: 49 for Germany, 32 for Belgium, 1 for US/Canada": "Esempi: 49 per Germania, 32 per Belgio, 1 per USA/Canada",
6
6
  "API host": "Host API",
7
- "Poll interval in seconds": "Intervallo di interrogazione in secondi"
7
+ "Poll interval in seconds": "Intervallo di interrogazione in secondi",
8
+ "Error description language": "Lingua della descrizione errore",
9
+ "Language used for Anthbot cloud error descriptions": "Lingua usata per le descrizioni degli errori cloud Anthbot"
8
10
  }
@@ -4,5 +4,7 @@
4
4
  "Area code": "Landcode",
5
5
  "Examples: 49 for Germany, 32 for Belgium, 1 for US/Canada": "Voorbeelden: 49 voor Duitsland, 32 voor België, 1 voor VS/Canada",
6
6
  "API host": "API-host",
7
- "Poll interval in seconds": "Pollinginterval in seconden"
7
+ "Poll interval in seconds": "Pollinginterval in seconden",
8
+ "Error description language": "Taal voor foutbeschrijving",
9
+ "Language used for Anthbot cloud error descriptions": "Taal voor Anthbot-cloudfoutbeschrijvingen"
8
10
  }
@@ -4,5 +4,7 @@
4
4
  "Area code": "Kod kraju",
5
5
  "Examples: 49 for Germany, 32 for Belgium, 1 for US/Canada": "Przykłady: 49 dla Niemiec, 32 dla Belgii, 1 dla USA/Kanady",
6
6
  "API host": "Host API",
7
- "Poll interval in seconds": "Interwał odpytywania w sekundach"
7
+ "Poll interval in seconds": "Interwał odpytywania w sekundach",
8
+ "Error description language": "Język opisu błędów",
9
+ "Language used for Anthbot cloud error descriptions": "Język używany dla opisów błędów chmury Anthbot"
8
10
  }
@@ -4,5 +4,7 @@
4
4
  "Area code": "Código do país",
5
5
  "Examples: 49 for Germany, 32 for Belgium, 1 for US/Canada": "Exemplos: 49 para Alemanha, 32 para Bélgica, 1 para EUA/Canadá",
6
6
  "API host": "Host da API",
7
- "Poll interval in seconds": "Intervalo de consulta em segundos"
7
+ "Poll interval in seconds": "Intervalo de consulta em segundos",
8
+ "Error description language": "Idioma da descrição do erro",
9
+ "Language used for Anthbot cloud error descriptions": "Idioma usado para descrições de erro da nuvem Anthbot"
8
10
  }
@@ -4,5 +4,7 @@
4
4
  "Area code": "Код страны",
5
5
  "Examples: 49 for Germany, 32 for Belgium, 1 for US/Canada": "Примеры: 49 для Германии, 32 для Бельгии, 1 для США/Канады",
6
6
  "API host": "Хост API",
7
- "Poll interval in seconds": "Интервал опроса в секундах"
7
+ "Poll interval in seconds": "Интервал опроса в секундах",
8
+ "Error description language": "Язык описания ошибок",
9
+ "Language used for Anthbot cloud error descriptions": "Язык для описаний ошибок облака Anthbot"
8
10
  }
@@ -4,5 +4,7 @@
4
4
  "Area code": "Код країни",
5
5
  "Examples: 49 for Germany, 32 for Belgium, 1 for US/Canada": "Приклади: 49 для Німеччини, 32 для Бельгії, 1 для США/Канади",
6
6
  "API host": "Хост API",
7
- "Poll interval in seconds": "Інтервал опитування в секундах"
7
+ "Poll interval in seconds": "Інтервал опитування в секундах",
8
+ "Error description language": "Мова опису помилок",
9
+ "Language used for Anthbot cloud error descriptions": "Мова для описів помилок хмари Anthbot"
8
10
  }
@@ -4,5 +4,7 @@
4
4
  "Area code": "国家/地区代码",
5
5
  "Examples: 49 for Germany, 32 for Belgium, 1 for US/Canada": "示例:德国为 49,比利时为 32,美国/加拿大为 1",
6
6
  "API host": "API 主机",
7
- "Poll interval in seconds": "轮询间隔(秒)"
7
+ "Poll interval in seconds": "轮询间隔(秒)",
8
+ "Error description language": "错误说明语言",
9
+ "Language used for Anthbot cloud error descriptions": "用于 Anthbot 云端错误说明的语言"
8
10
  }
@@ -50,6 +50,70 @@
50
50
  "md": 4,
51
51
  "lg": 4,
52
52
  "xl": 4
53
+ },
54
+ "errorDescriptionLanguage": {
55
+ "type": "select",
56
+ "label": "Error description language",
57
+ "help": "Language used for Anthbot cloud error descriptions",
58
+ "options": [
59
+ {
60
+ "label": "English",
61
+ "value": "English"
62
+ },
63
+ {
64
+ "label": "German",
65
+ "value": "German"
66
+ },
67
+ {
68
+ "label": "French",
69
+ "value": "French"
70
+ },
71
+ {
72
+ "label": "Spain",
73
+ "value": "Spain"
74
+ },
75
+ {
76
+ "label": "Italy",
77
+ "value": "Italy"
78
+ },
79
+ {
80
+ "label": "Polish",
81
+ "value": "Polish"
82
+ },
83
+ {
84
+ "label": "Russian",
85
+ "value": "Russian"
86
+ },
87
+ {
88
+ "label": "Czech",
89
+ "value": "Czech"
90
+ },
91
+ {
92
+ "label": "Estonian",
93
+ "value": "Estonian"
94
+ },
95
+ {
96
+ "label": "Latvian",
97
+ "value": "Latvian"
98
+ },
99
+ {
100
+ "label": "Lithuanian",
101
+ "value": "Lithuanian"
102
+ },
103
+ {
104
+ "label": "Romanian",
105
+ "value": "Romanian"
106
+ },
107
+ {
108
+ "label": "Slovakia",
109
+ "value": "Slovakia"
110
+ }
111
+ ],
112
+ "xs": 12,
113
+ "sm": 12,
114
+ "md": 4,
115
+ "lg": 4,
116
+ "xl": 4
53
117
  }
54
118
  }
55
119
  }
package/io-package.json CHANGED
@@ -1,8 +1,34 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "anthbot-genie",
4
- "version": "0.1.2",
4
+ "version": "0.1.4",
5
5
  "news": {
6
+ "0.1.4": {
7
+ "en": "Use Anthbot cloud event-code translations for mower error descriptions.",
8
+ "de": "Anthbot-Cloud-Ereigniscode-Übersetzungen für Mäher-Fehlerbeschreibungen verwendet.",
9
+ "ru": "Использованы облачные переводы кодов событий Anthbot для описаний ошибок косилки.",
10
+ "pt": "Usadas traduções de códigos de evento da nuvem Anthbot para descrições de erro do cortador.",
11
+ "nl": "Anthbot-cloudvertalingen van gebeurteniscodes gebruikt voor foutbeschrijvingen van de maaier.",
12
+ "fr": "Utilisation des traductions cloud Anthbot des codes d'événement pour les descriptions d'erreur de la tondeuse.",
13
+ "it": "Usate le traduzioni cloud Anthbot dei codici evento per le descrizioni degli errori del rasaerba.",
14
+ "es": "Se usan traducciones de códigos de evento de la nube Anthbot para descripciones de errores del cortacésped.",
15
+ "pl": "Użyto chmurowych tłumaczeń kodów zdarzeń Anthbot dla opisów błędów kosiarki.",
16
+ "uk": "Використано хмарні переклади кодів подій Anthbot для описів помилок косарки.",
17
+ "zh-cn": "使用 Anthbot 云端事件代码翻译生成割草机错误说明。"
18
+ },
19
+ "0.1.3": {
20
+ "en": "Fix AWS IoT shadow access.",
21
+ "de": "AWS IoT-Shadow-Zugriff korrigiert.",
22
+ "ru": "Исправлен доступ к AWS IoT shadow.",
23
+ "pt": "Corrigido o acesso ao shadow AWS IoT.",
24
+ "nl": "AWS IoT-shadowtoegang opgelost.",
25
+ "fr": "Correction de l'accès au shadow AWS IoT.",
26
+ "it": "Corretto l'accesso allo shadow AWS IoT.",
27
+ "es": "Corregido el acceso al shadow de AWS IoT.",
28
+ "pl": "Naprawiono dostęp do shadow AWS IoT.",
29
+ "uk": "Виправлено доступ до AWS IoT shadow.",
30
+ "zh-cn": "修复 AWS IoT shadow 访问。"
31
+ },
6
32
  "0.1.2": {
7
33
  "en": "Limit io-package news entries for the ioBroker repository builder.",
8
34
  "de": "Anzahl der io-package-News-Einträge für den ioBroker-Repository-Builder begrenzt.",
@@ -42,45 +68,6 @@
42
68
  "uk": "Додано розширену діагностику, згруповану структуру станів, скидання витратних матеріалів, команди дій косарки та записувані елементи керування косінням.\nДодано керування обрізанням країв для повного косіння карти та виправлено керування біля зарядної станції.\nВилучено непідтримувані елементи керування камерою та продовженням повернення до бази.",
43
69
  "zh-cn": "添加扩展诊断、分组状态结构、耗材重置、割草机动作命令和可写割草控制。\n添加全地图割草的边缘修剪控制,并修正充电站附近割草启用控制。\n移除不支持的摄像头和继续回充控制。"
44
70
  },
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
71
  "0.0.8": {
85
72
  "en": "Add consumable channels and values to the adapter definition.",
86
73
  "de": "Verbrauchskanäle und Werte zur Adapterdefinition hinzugefügt.",
@@ -109,7 +96,7 @@
109
96
  "zh-cn": "Anthbot Genie"
110
97
  },
111
98
  "desc": {
112
- "en": "Unofficial cloud adapter for Anthbot Genie mower control and status polling",
99
+ "en": "Unofficial cloud adapter for Anthbot Genie robotic lawn mowers with telemetry, diagnostics, consumables, zones, and controls",
113
100
  "de": "Inoffizieller Cloud-Adapter für Statusabfrage und Steuerung von Anthbot Genie",
114
101
  "ru": "Неофициальный облачный адаптер для управления газонокосилками Anthbot Genie и опроса состояния",
115
102
  "pt": "Adaptador de nuvem não oficial para controlar cortadores Anthbot Genie e consultar o estado",
@@ -133,15 +120,19 @@
133
120
  "readme": "https://github.com/reloxx13/ioBroker.anthbot-genie/blob/main/README.md",
134
121
  "keywords": [
135
122
  "anthbot",
136
- "genie",
123
+ "anthbot-genie",
124
+ "robotic-lawn-mower",
125
+ "lawn-mower",
137
126
  "mower",
138
- "robot",
139
- "lawn"
127
+ "home-automation",
128
+ "rtk",
129
+ "cloud-api",
130
+ "iot"
140
131
  ],
141
132
  "platform": "Javascript/Node.js",
142
133
  "mode": "daemon",
143
134
  "tier": 2,
144
- "type": "household",
135
+ "type": "garden",
145
136
  "compact": true,
146
137
  "connectionType": "cloud",
147
138
  "dataSource": "poll",
@@ -172,7 +163,8 @@
172
163
  "password": "",
173
164
  "areaCode": "49",
174
165
  "apiHost": "api.anthbot.com",
175
- "pollInterval": 30
166
+ "pollInterval": 60,
167
+ "errorDescriptionLanguage": "English"
176
168
  },
177
169
  "objects": [
178
170
  {
package/lib/anthbot.js CHANGED
@@ -20,63 +20,6 @@ const MODEL_NAME_BY_CATEGORY = {
20
20
  "Genie 5000": "Anthbot Genie 5000",
21
21
  };
22
22
 
23
- const ERROR_CODE_DESCRIPTIONS = {
24
- 0: "No error",
25
- 1: "Battery low",
26
- 2: "Battery critically low",
27
- 3: "Battery over-temperature",
28
- 4: "Battery under-temperature",
29
- 5: "Battery overvoltage",
30
- 10: "Left wheel stuck",
31
- 11: "Right wheel stuck",
32
- 12: "Left wheel motor overload",
33
- 13: "Right wheel motor overload",
34
- 14: "Left wheel motor overheat",
35
- 15: "Right wheel motor overheat",
36
- 20: "Blade motor stuck",
37
- 21: "Blade motor overload",
38
- 22: "Blade motor overheat",
39
- 30: "Device lifted",
40
- 31: "Device tilted over limit",
41
- 32: "Device rollover",
42
- 33: "Device stuck",
43
- 40: "Bumper sensor triggered",
44
- 41: "Collision sensor jammed",
45
- 42: "ToF sensor fault",
46
- 43: "Structured-light sensor fault",
47
- 50: "GPS not ready",
48
- 51: "RTK not ready",
49
- 52: "IMU bias error",
50
- 53: "IMU data error",
51
- 60: "Boundary wire break",
52
- 61: "Boundary wire too long",
53
- 62: "Charging pile communication error",
54
- 63: "Charging pile overcurrent protection",
55
- 64: "Charging pile wire error",
56
- 65: "Recharge failure",
57
- 66: "Docking station return failure",
58
- 70: "WiFi config error",
59
- 71: "WiFi connection error",
60
- 80: "Firmware download error",
61
- 81: "Firmware upgrade error",
62
- 100: "Out of bounds",
63
- 101: "Unreachable mowing area",
64
- 102: "Started from forbidden zone",
65
- 103: "Started from virtual wall",
66
- 226: "Functional safety error",
67
- 227: "Ground app error",
68
- 228: "Ground base error",
69
- 229: "React error",
70
- 230: "Battery communication error",
71
- 231: "Drive motor stall",
72
- 232: "Drive motor overtemperature",
73
- 233: "Cutting motor stall protection",
74
- 234: "Mowing motor undervoltage",
75
- 235: "Mowing motor stuck",
76
- 236: "Lift up detected",
77
- 237: "Lift motor fault",
78
- };
79
-
80
23
  const RTK_STATE_OPTIONS = {
81
24
  0: "not_ready",
82
25
  1: "single",
@@ -194,12 +137,31 @@ function modelNameByCategory(categoryId) {
194
137
  return MODEL_NAME_BY_CATEGORY[raw] || (raw ? `Anthbot ${raw}` : "Anthbot mower");
195
138
  }
196
139
 
197
- function errorDescription(data) {
140
+ function eventCodeTranslationsFromCache(cacheOrPayload) {
141
+ const payload = cacheOrPayload?.payload && typeof cacheOrPayload.payload === "object"
142
+ ? cacheOrPayload.payload
143
+ : cacheOrPayload;
144
+ return payload?.data && typeof payload.data === "object" && !Array.isArray(payload.data)
145
+ ? payload.data
146
+ : {};
147
+ }
148
+
149
+ function errorDescription(data, cacheOrPayload = null, language = "English") {
198
150
  const code = asInteger(data?.err_code);
199
151
  if (code == null) {
200
152
  return null;
201
153
  }
202
- return ERROR_CODE_DESCRIPTIONS[code] || `Unknown error (${code})`;
154
+ const translations = eventCodeTranslationsFromCache(cacheOrPayload);
155
+ const byLanguage = translations[String(code)];
156
+ if (byLanguage && typeof byLanguage === "object" && !Array.isArray(byLanguage)) {
157
+ for (const candidateLanguage of [language, "English"]) {
158
+ const eventMessage = byLanguage[candidateLanguage]?.event_message;
159
+ if (typeof eventMessage === "string" && eventMessage.trim()) {
160
+ return eventMessage;
161
+ }
162
+ }
163
+ }
164
+ return `Unknown error (${code})`;
203
165
  }
204
166
 
205
167
  function rtkStateLabel(data) {
@@ -335,6 +297,51 @@ class AnthbotCloudApiClient {
335
297
  };
336
298
  }
337
299
 
300
+ async getDeviceIotCredentials(serialNumber) {
301
+ this.requireToken();
302
+ const response = await this.http.post(`https://${this.host}/api/v1/device/v2/iot/sts/arn`, {
303
+ sn: serialNumber,
304
+ verification_token: AnthbotCloudApiClient.buildVerificationToken(serialNumber),
305
+ }, {
306
+ headers: {
307
+ ...this.authHeaders,
308
+ "content-type": "application/json",
309
+ },
310
+ });
311
+ const payload = response.data;
312
+ if (response.status !== 200) {
313
+ throw new AnthbotGenieError(`IoT STS failed (${response.status}): ${String(payload).slice(0, 300)}`);
314
+ }
315
+ if (!payload || typeof payload !== "object") {
316
+ throw new AnthbotGenieError("Invalid IoT STS payload type");
317
+ }
318
+ if (payload.code !== 0) {
319
+ throw new AnthbotGenieError(`IoT STS returned code=${JSON.stringify(payload.code)}`);
320
+ }
321
+ const data = payload.data;
322
+ if (!data || typeof data !== "object") {
323
+ throw new AnthbotGenieError("IoT STS payload missing data object");
324
+ }
325
+ const requiredFields = ["access_key_id", "secret_access_key", "session_token", "region_name", "endpoint"];
326
+ if (requiredFields.some(field => typeof data[field] !== "string" || !data[field])) {
327
+ throw new AnthbotGenieError("IoT STS payload missing required fields");
328
+ }
329
+ const expiration = asInteger(data.expiration);
330
+ const expiresAt = expiration == null
331
+ ? null
332
+ : expiration > 2000000000
333
+ ? expiration * 1000
334
+ : Date.now() + expiration * 1000;
335
+ return {
336
+ accessKeyId: data.access_key_id,
337
+ secretAccessKey: data.secret_access_key,
338
+ sessionToken: data.session_token,
339
+ regionName: data.region_name,
340
+ endpoint: data.endpoint,
341
+ expiresAt,
342
+ };
343
+ }
344
+
338
345
  async getDeviceAreaDefinition(serialNumber) {
339
346
  this.requireToken();
340
347
  const response = await this.http.get(`https://${this.host}/api/v1/device/v2/presigned_url`, {
@@ -378,6 +385,58 @@ class AnthbotCloudApiClient {
378
385
  return areaDefinition;
379
386
  }
380
387
 
388
+ async getEventCodeVersion() {
389
+ this.requireToken();
390
+ const response = await this.http.get(`https://${this.host}/api/v1/message/code/version`, {
391
+ headers: this.authHeaders,
392
+ });
393
+ const payload = response.data;
394
+ if (response.status !== 200) {
395
+ throw new AnthbotGenieError(`Event code version failed (${response.status}): ${String(payload).slice(0, 300)}`);
396
+ }
397
+ if (!payload || typeof payload !== "object") {
398
+ throw new AnthbotGenieError("Invalid event code version payload type");
399
+ }
400
+ if (payload.code !== 0) {
401
+ throw new AnthbotGenieError(`Event code version returned code=${JSON.stringify(payload.code)}`);
402
+ }
403
+
404
+ const data = payload.data;
405
+ const version = typeof data === "object" && data !== null
406
+ ? asInteger(data.version ?? data.event_code_version ?? data.code_version)
407
+ : asInteger(data);
408
+ if (version == null) {
409
+ throw new AnthbotGenieError("Event code version payload missing version");
410
+ }
411
+ return version;
412
+ }
413
+
414
+ async getEventCodeTranslations(version) {
415
+ this.requireToken();
416
+ const response = await this.http.post(`https://${this.host}/api/v1/message/code/translate`, {
417
+ version,
418
+ }, {
419
+ headers: {
420
+ ...this.authHeaders,
421
+ "content-type": "application/json",
422
+ },
423
+ });
424
+ const payload = response.data;
425
+ if (response.status !== 200) {
426
+ throw new AnthbotGenieError(`Event code translations failed (${response.status}): ${String(payload).slice(0, 300)}`);
427
+ }
428
+ if (!payload || typeof payload !== "object") {
429
+ throw new AnthbotGenieError("Invalid event code translations payload type");
430
+ }
431
+ if (payload.code !== 0) {
432
+ throw new AnthbotGenieError(`Event code translations returned code=${JSON.stringify(payload.code)}`);
433
+ }
434
+ if (!payload.data || typeof payload.data !== "object" || Array.isArray(payload.data)) {
435
+ throw new AnthbotGenieError("Event code translations payload missing data object");
436
+ }
437
+ return payload;
438
+ }
439
+
381
440
  async getDevicePresignedRegion(serialNumber) {
382
441
  this.requireToken();
383
442
  const response = await this.http.get(`https://${this.host}/api/v1/device/v2/presigned_url`, {
@@ -427,11 +486,12 @@ class AnthbotCloudApiClient {
427
486
  }
428
487
 
429
488
  class AnthbotShadowApiClient {
430
- constructor({ http, serialNumber, regionName, iotEndpoint }) {
489
+ constructor({ http, serialNumber, regionName, iotEndpoint, iotCredentials = null }) {
431
490
  this.http = http;
432
491
  this.serialNumber = serialNumber;
433
492
  this.regionName = typeof regionName === "string" && regionName ? regionName : null;
434
493
  this.iotEndpoint = AnthbotShadowApiClient.normalizeEndpoint(iotEndpoint);
494
+ this.iotCredentials = iotCredentials && typeof iotCredentials === "object" ? iotCredentials : null;
435
495
  }
436
496
 
437
497
  static normalizeEndpoint(iotEndpoint) {
@@ -459,6 +519,9 @@ class AnthbotShadowApiClient {
459
519
  }
460
520
 
461
521
  accessKeyId() {
522
+ if (typeof this.iotCredentials?.accessKeyId === "string" && this.iotCredentials.accessKeyId) {
523
+ return this.iotCredentials.accessKeyId;
524
+ }
462
525
  if (this.iotEndpoint === CN_NORTHWEST_IOT_ENDPOINT) {
463
526
  return AWS_ACCESS_KEY_CN_NORTHWEST;
464
527
  }
@@ -469,6 +532,9 @@ class AnthbotShadowApiClient {
469
532
  }
470
533
 
471
534
  secretAccessKey() {
535
+ if (typeof this.iotCredentials?.secretAccessKey === "string" && this.iotCredentials.secretAccessKey) {
536
+ return this.iotCredentials.secretAccessKey;
537
+ }
472
538
  if (this.iotEndpoint === CN_NORTHWEST_IOT_ENDPOINT) {
473
539
  return AWS_SECRET_KEY_CN_NORTHWEST;
474
540
  }
@@ -478,6 +544,12 @@ class AnthbotShadowApiClient {
478
544
  return AWS_SECRET_KEY_DEFAULT;
479
545
  }
480
546
 
547
+ sessionToken() {
548
+ return typeof this.iotCredentials?.sessionToken === "string" && this.iotCredentials.sessionToken
549
+ ? this.iotCredentials.sessionToken
550
+ : null;
551
+ }
552
+
481
553
  sign(key, msg) {
482
554
  return crypto.createHmac("sha256", key).update(msg, "utf8").digest();
483
555
  }
@@ -557,6 +629,10 @@ class AnthbotShadowApiClient {
557
629
  "x-amz-content-sha256": payloadHash,
558
630
  "x-amz-date": amzDate,
559
631
  };
632
+ const sessionToken = this.sessionToken();
633
+ if (sessionToken) {
634
+ signedHeaderValues["x-amz-security-token"] = sessionToken;
635
+ }
560
636
  const { canonical, signedHeaders } = AnthbotShadowApiClient.canonicalHeaders(signedHeaderValues);
561
637
  const canonicalRequest = [
562
638
  "GET",
@@ -573,6 +649,7 @@ class AnthbotShadowApiClient {
573
649
  Host: this.iotEndpoint,
574
650
  "x-amz-date": amzDate,
575
651
  "x-amz-content-sha256": payloadHash,
652
+ ...(sessionToken ? { "x-amz-security-token": sessionToken } : {}),
576
653
  Authorization: authorization,
577
654
  "User-Agent": "LdMower/1581 CFNetwork/3860.400.51 Darwin/25.3.0",
578
655
  },
@@ -617,6 +694,11 @@ class AnthbotShadowApiClient {
617
694
  "x-amz-content-sha256": payloadHash,
618
695
  "x-amz-date": amzDate,
619
696
  };
697
+ const sessionToken = this.sessionToken();
698
+ if (sessionToken) {
699
+ signedHeaderValues["x-amz-security-token"] = sessionToken;
700
+ headers["x-amz-security-token"] = sessionToken;
701
+ }
620
702
  if (signContentLength) {
621
703
  signedHeaderValues["content-length"] = String(payloadBytes.length);
622
704
  headers["Content-Length"] = String(payloadBytes.length);
package/main.js CHANGED
@@ -198,6 +198,7 @@ const DEVICE_STATE_DEFINITIONS = {
198
198
  "zones.autoList": { type: "string", role: "json", read: true, write: false, name: t("Auto zones", "Automatische Zonen") },
199
199
  "raw.shadow.property": { type: "string", role: "json", read: true, write: false, name: t("Raw property shadow", "Rohdaten Property Shadow") },
200
200
  "raw.shadow.service": { type: "string", role: "json", read: true, write: false, name: t("Raw service shadow", "Rohdaten Service Shadow") },
201
+ "raw.shadow.event-code": { type: "string", role: "json", read: true, write: false, name: t("Raw event code translations", "Rohdaten Ereigniscode-Übersetzungen") },
201
202
  "raw.areaDefinition": { type: "string", role: "json", read: true, write: false, name: t("Raw area definition", "Rohdaten Flächendefinition") },
202
203
  };
203
204
 
@@ -246,6 +247,8 @@ class AnthbotGenieAdapter extends utils.Adapter {
246
247
  this.cloudClient = null;
247
248
  this.authToken = null;
248
249
  this.deviceContexts = new Map();
250
+ this.eventCodeCache = null;
251
+ this.eventCodeCacheInitialized = false;
249
252
  this.pollTimer = null;
250
253
  this.refreshInFlight = null;
251
254
  this.unloaded = false;
@@ -309,7 +312,7 @@ class AnthbotGenieAdapter extends utils.Adapter {
309
312
  if (this.pollTimer) {
310
313
  this.clearTimeout(this.pollTimer);
311
314
  }
312
- const intervalSeconds = Math.max(10, Number(this.config.pollInterval) || 30);
315
+ const intervalSeconds = Math.max(10, Number(this.config.pollInterval) || 60);
313
316
  this.pollTimer = this.setTimeout(async () => {
314
317
  this.pollTimer = null;
315
318
  try {
@@ -342,6 +345,7 @@ class AnthbotGenieAdapter extends utils.Adapter {
342
345
  try {
343
346
  await this.ensureSession(forceLogin);
344
347
  await this.discoverDevices(forceLogin);
348
+ await this.ensureEventCodeCache();
345
349
  for (const context of this.deviceContexts.values()) {
346
350
  try {
347
351
  await this.refreshDevice(context);
@@ -361,6 +365,92 @@ class AnthbotGenieAdapter extends utils.Adapter {
361
365
  await this.setStateAsync("info.connection", successful > 0, true);
362
366
  }
363
367
 
368
+ async ensureEventCodeCache() {
369
+ if (this.eventCodeCacheInitialized) {
370
+ return;
371
+ }
372
+ this.eventCodeCacheInitialized = true;
373
+
374
+ const cached = await this.readStoredEventCodeCache();
375
+ this.eventCodeCache = cached;
376
+
377
+ let cloudVersion = null;
378
+ try {
379
+ cloudVersion = await this.cloudClient.getEventCodeVersion();
380
+ } catch (error) {
381
+ if (cached) {
382
+ this.log.warn(`Failed to fetch event code version, using cached translations: ${error.message}`);
383
+ await this.writeEventCodeCacheToDevices(cached);
384
+ } else {
385
+ this.log.warn(`Failed to fetch event code version and no cached translations are available: ${error.message}`);
386
+ }
387
+ return;
388
+ }
389
+
390
+ if (cached && asInteger(cached.version) === cloudVersion) {
391
+ this.eventCodeCache = cached;
392
+ await this.writeEventCodeCacheToDevices(cached);
393
+ return;
394
+ }
395
+
396
+ try {
397
+ const payload = await this.cloudClient.getEventCodeTranslations(cloudVersion);
398
+ this.eventCodeCache = {
399
+ version: cloudVersion,
400
+ fetchedAt: new Date().toISOString(),
401
+ payload,
402
+ };
403
+ await this.writeEventCodeCacheToDevices(this.eventCodeCache);
404
+ } catch (error) {
405
+ if (cached) {
406
+ this.log.warn(`Failed to fetch event code translations, using cached translations: ${error.message}`);
407
+ this.eventCodeCache = cached;
408
+ await this.writeEventCodeCacheToDevices(cached);
409
+ } else {
410
+ this.log.warn(`Failed to fetch event code translations and no cached translations are available: ${error.message}`);
411
+ }
412
+ }
413
+ }
414
+
415
+ async readStoredEventCodeCache() {
416
+ for (const context of this.deviceContexts.values()) {
417
+ const serial = context.device.serialNumber;
418
+ try {
419
+ const state = await this.getStateAsync(`${serial}.raw.shadow.event-code`);
420
+ const raw = typeof state?.val === "string" ? state.val : "";
421
+ if (!raw) {
422
+ continue;
423
+ }
424
+ const parsed = JSON.parse(raw);
425
+ if (this.isValidEventCodeCache(parsed)) {
426
+ return parsed;
427
+ }
428
+ } catch (error) {
429
+ this.log.debug(`Stored event code cache could not be read for ${serial}: ${error.message}`);
430
+ }
431
+ }
432
+ return null;
433
+ }
434
+
435
+ isValidEventCodeCache(cache) {
436
+ return Boolean(cache
437
+ && typeof cache === "object"
438
+ && asInteger(cache.version) != null
439
+ && cache.payload
440
+ && typeof cache.payload === "object"
441
+ && !Array.isArray(cache.payload));
442
+ }
443
+
444
+ async writeEventCodeCacheToDevices(cache) {
445
+ if (!cache) {
446
+ return;
447
+ }
448
+ const value = JSON.stringify(cache);
449
+ for (const context of this.deviceContexts.values()) {
450
+ await this.setStateAsync(`${context.device.serialNumber}.raw.shadow.event-code`, { val: value, ack: true });
451
+ }
452
+ }
453
+
364
454
  async ensureSession(force = false) {
365
455
  if (!this.cloudClient || force) {
366
456
  this.cloudClient = new AnthbotCloudApiClient({
@@ -402,7 +492,9 @@ class AnthbotGenieAdapter extends utils.Adapter {
402
492
  serialNumber: device.serialNumber,
403
493
  regionName: region.regionName,
404
494
  iotEndpoint: region.iotEndpoint,
495
+ iotCredentials: region.iotCredentials,
405
496
  }),
497
+ iotCredentials: region.iotCredentials,
406
498
  areaDefinition: existing?.areaDefinition || {},
407
499
  lastAreaTime: existing?.lastAreaTime || null,
408
500
  lastReported: existing?.lastReported || {},
@@ -416,6 +508,7 @@ class AnthbotGenieAdapter extends utils.Adapter {
416
508
  async resolveDeviceRegion(device) {
417
509
  let regionName = null;
418
510
  let iotEndpoint = null;
511
+ let iotCredentials = null;
419
512
 
420
513
  try {
421
514
  const deviceRegion = await this.cloudClient.getDeviceRegion(device.serialNumber);
@@ -446,10 +539,19 @@ class AnthbotGenieAdapter extends utils.Adapter {
446
539
  this.log.debug(`Presigned region fallback failed for ${device.serialNumber}: ${error.message}`);
447
540
  }
448
541
 
542
+ try {
543
+ iotCredentials = await this.cloudClient.getDeviceIotCredentials(device.serialNumber);
544
+ regionName = iotCredentials.regionName || regionName;
545
+ iotEndpoint = iotCredentials.endpoint || iotEndpoint;
546
+ } catch (error) {
547
+ this.log.warn(`Failed to fetch temporary IoT credentials for ${device.serialNumber}, using bundled fallback credentials: ${error.message}`);
548
+ }
549
+
449
550
  return {
450
551
  serialNumber: device.serialNumber,
451
552
  regionName: regionName || AnthbotShadowApiClient.guessRegionFromEndpoint(iotEndpoint) || "unknown",
452
553
  iotEndpoint,
554
+ iotCredentials,
453
555
  };
454
556
  }
455
557
 
@@ -500,6 +602,7 @@ class AnthbotGenieAdapter extends utils.Adapter {
500
602
  }
501
603
 
502
604
  async refreshDevice(context) {
605
+ await this.ensureDeviceIotCredentials(context);
503
606
  const propertyState = await context.shadowClient.getShadowReportedState();
504
607
  let serviceState = {};
505
608
  try {
@@ -536,6 +639,27 @@ class AnthbotGenieAdapter extends utils.Adapter {
536
639
  await this.updateStates(context, merged);
537
640
  }
538
641
 
642
+ async ensureDeviceIotCredentials(context) {
643
+ if (context.iotCredentials && (!context.iotCredentials.expiresAt || context.iotCredentials.expiresAt - Date.now() > 60000)) {
644
+ return;
645
+ }
646
+ const iotCredentials = await this.cloudClient.getDeviceIotCredentials(context.device.serialNumber);
647
+ context.iotCredentials = iotCredentials;
648
+ context.region = {
649
+ ...context.region,
650
+ regionName: iotCredentials.regionName || context.region.regionName,
651
+ iotEndpoint: iotCredentials.endpoint || context.region.iotEndpoint,
652
+ iotCredentials,
653
+ };
654
+ context.shadowClient = new AnthbotShadowApiClient({
655
+ http: this.http,
656
+ serialNumber: context.device.serialNumber,
657
+ regionName: context.region.regionName,
658
+ iotEndpoint: context.region.iotEndpoint,
659
+ iotCredentials,
660
+ });
661
+ }
662
+
539
663
  async updateStates(context, data) {
540
664
  const serial = context.device.serialNumber;
541
665
  const manualZoneList = manualZones(data);
@@ -589,7 +713,7 @@ class AnthbotGenieAdapter extends utils.Adapter {
589
713
  "metrics.map.totalArea": typeof data.map_area === "number" ? data.map_area : null,
590
714
  "metrics.map.status": asText(safeGet(data, "map_sta", "value")),
591
715
  "metrics.error.code": asInteger(data.err_code),
592
- "metrics.error.description": asText(errorDescription(data)),
716
+ "metrics.error.description": asText(errorDescription(data, this.eventCodeCache, this.config.errorDescriptionLanguage || "English")),
593
717
  "metrics.error.active": isNonZero(data.err_code),
594
718
 
595
719
  "location.gps.latitude": typeof safeGet(data, "anti_loss_pose", "posegps", "lat") === "number" ? safeGet(data, "anti_loss_pose", "posegps", "lat") : null,
@@ -667,6 +791,7 @@ class AnthbotGenieAdapter extends utils.Adapter {
667
791
 
668
792
  "raw.shadow.property": JSON.stringify(context.lastReported || {}),
669
793
  "raw.shadow.service": JSON.stringify(context.lastService || {}),
794
+ "raw.shadow.event-code": JSON.stringify(this.eventCodeCache || {}),
670
795
  "raw.areaDefinition": JSON.stringify(context.areaDefinition || {}),
671
796
  };
672
797
 
@@ -797,6 +922,7 @@ class AnthbotGenieAdapter extends utils.Adapter {
797
922
  return;
798
923
  }
799
924
 
925
+ await this.ensureDeviceIotCredentials(context);
800
926
  const shouldRequestProperties = await this.executeCommand(context, command, value);
801
927
  if (shouldRequestProperties) {
802
928
  await context.shadowClient.requestAllProperties();
@@ -809,6 +935,7 @@ class AnthbotGenieAdapter extends utils.Adapter {
809
935
  return;
810
936
  }
811
937
 
938
+ await this.ensureDeviceIotCredentials(context);
812
939
  await this.executeControl(context, control, value);
813
940
  await context.shadowClient.requestAllProperties();
814
941
  await this.delay(1000);
@@ -820,6 +947,7 @@ class AnthbotGenieAdapter extends utils.Adapter {
820
947
  return;
821
948
  }
822
949
 
950
+ await this.ensureDeviceIotCredentials(context);
823
951
  await this.executeConsumableCommand(context, command);
824
952
  await this.delay(1000);
825
953
  }
package/package.json CHANGED
@@ -1,17 +1,24 @@
1
1
  {
2
2
  "name": "iobroker.anthbot-genie",
3
- "version": "0.1.2",
4
- "description": "Unofficial ioBroker adapter for Anthbot Genie mowers",
3
+ "version": "0.1.4",
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
- "adapter"
17
+ "smart-home",
18
+ "home-automation",
19
+ "rtk",
20
+ "cloud-api",
21
+ "iot"
15
22
  ],
16
23
  "repository": {
17
24
  "type": "git",