iobroker.hydrawise 2.0.0 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -22
- package/admin/jsonConfig.json +32 -271
- package/build/lib/helpers.js +8 -0
- package/build/lib/helpers.js.map +2 -2
- package/build/lib/v2/auth.js +4 -12
- package/build/lib/v2/auth.js.map +2 -2
- package/build/main.js +26 -13
- package/build/main.js.map +2 -2
- package/io-package.json +27 -27
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -20,32 +20,37 @@
|
|
|
20
20
|

|
|
21
21
|

|
|
22
22
|
|
|
23
|
-
Integrate your Hydrawise controller into
|
|
24
|
-
You can see all controller information, schedules and sensors. It is also possible to suspend planned watering by x seconds.
|
|
23
|
+
Integrate your Hydrawise controller into ioBroker.
|
|
25
24
|
|
|
26
|
-
|
|
25
|
+
Both APIs provide zones and schedules. Use **v2 (GraphQL)** by default (same login as the Hydrawise app). **v1 (REST)** is a fallback via API key if GraphQL is unavailable. Enable one or both.
|
|
27
26
|
|
|
28
|
-
- **
|
|
29
|
-
- **
|
|
27
|
+
- **v2** (recommended): email/password like the app — `zones.*`, `sensors.*`, `weather.*`, `water.*`, `controller.*` (plus weather, measured sensors, leak indicator, GraphQL zone commands).
|
|
28
|
+
- **v1** (fallback): API key — `schedule.*` / `customer.*` (same zones and schedules, no weather or measured sensors).
|
|
30
29
|
|
|
31
30
|
## Documentation
|
|
32
31
|
|
|
32
|
+
### v2 API (recommended)
|
|
33
|
+
|
|
34
|
+
v2 is the unofficial GraphQL API used by the Hydrawise app (`app.hydrawise.com/api/v2/graph`). Enable **v2 API (GraphQL)** in instance settings and enter the same email/password as on hydrawise.com.
|
|
35
|
+
|
|
36
|
+
### v1 API (fallback)
|
|
37
|
+
|
|
38
|
+
Only needed if GraphQL is unavailable:
|
|
39
|
+
|
|
33
40
|
- log into https://app.hydrawise.com/config/account-details
|
|
34
41
|
- generate API Key by clicking "Generate API Key" under "Account Settings"
|
|
35
|
-
- paste key into
|
|
42
|
+
- paste the key into the v1 tab
|
|
36
43
|
- API documentation: https://support.hydrawise.com/hc/en-us/articles/360008965753-Hydrawise-API-Information
|
|
37
44
|
|
|
38
|
-
### v2 API (optional)
|
|
39
|
-
|
|
40
|
-
v2 is the unofficial GraphQL API used by the Hydrawise app (`app.hydrawise.com/api/v2/graph`). Enable it in instance settings and enter the same email/password as the app. v1 can be disabled independently if you only want GraphQL.
|
|
41
|
-
|
|
42
45
|
| Object tree | Source | Controls irrigation? |
|
|
43
46
|
| --- | --- | --- |
|
|
44
47
|
| `schedule.*` | v1 REST | yes (`setzone.php`) |
|
|
45
48
|
| `zones.*` | v2 GraphQL | yes (GraphQL mutations), only if v2 is enabled |
|
|
46
49
|
| `water.*`, `sensors.*`, `weather.*`, `controller.*` | v2 GraphQL | read-only |
|
|
47
|
-
| `info.connection` |
|
|
48
|
-
| `info.connectionV2` | v2 | — |
|
|
50
|
+
| `info.connection` | instance (all enabled APIs) | — |
|
|
51
|
+
| `info.connectionV2` | v2 GraphQL only | — |
|
|
52
|
+
|
|
53
|
+
The Admin traffic light (`info.connection`) is green only if **every enabled API** is online. v1 enabled but failing and v2 OK → yellow/red. v2-only and connected → green. `info.connectionV2` stays true whenever GraphQL works.
|
|
49
54
|
|
|
50
55
|
v1 `schedule.sensors.*` only contains sensor *configuration*. Measured flow, rainfall and leak suspicion come from v2 `sensors.*` / `water.leakSuspected`.
|
|
51
56
|
|
|
@@ -62,6 +67,16 @@ Default v2 poll interval is **300 seconds** (minimum 120). GraphQL is rate-limit
|
|
|
62
67
|
Placeholder for the next version (at the beginning of the line):
|
|
63
68
|
### **WORK IN PROGRESS**
|
|
64
69
|
-->
|
|
70
|
+
### 2.0.2 (2026-09-11)
|
|
71
|
+
|
|
72
|
+
* (SentiQ) **FIXED**: Admin jsonConfig responsive sizes for repository checker
|
|
73
|
+
* (SentiQ) **FIXED**: Admin jsonConfig uses `i18n: true` with translation files
|
|
74
|
+
* (SentiQ) **FIXED**: HTTP request timeouts via AbortSignal.timeout instead of global setTimeout
|
|
75
|
+
|
|
76
|
+
### 2.0.1 (2026-09-03)
|
|
77
|
+
|
|
78
|
+
* (SentiQ) **FIXED**: Instance `info.connection` follows every enabled API (v2-only no longer stays red)
|
|
79
|
+
|
|
65
80
|
### 2.0.0 (2026-09-02)
|
|
66
81
|
|
|
67
82
|
* (SentiQ) **NEW**: Optional Hydrawise v2 GraphQL API (water usage, live sensors, weather, leak indicator, zone commands)
|
|
@@ -80,16 +95,6 @@ Default v2 poll interval is **300 seconds** (minimum 120). GraphQL is rate-limit
|
|
|
80
95
|
- (SentiQ) updated dependencies
|
|
81
96
|
- (SentiQ) Adapter requires node.js >= 22 now
|
|
82
97
|
|
|
83
|
-
### 1.0.5 (2025-12-05)
|
|
84
|
-
|
|
85
|
-
- (SentiQ) updated js-controller dependency
|
|
86
|
-
- (SentiQ) updated @iobroker/adapter-dev dependency
|
|
87
|
-
|
|
88
|
-
### 1.0.4 (2025-12-05)
|
|
89
|
-
|
|
90
|
-
- (SentiQ) fixed dependencies
|
|
91
|
-
- (SentiQ) fixed schema URLs
|
|
92
|
-
|
|
93
98
|
[Older changelogs can be found there](CHANGELOG_OLD.md)
|
|
94
99
|
|
|
95
100
|
## License
|
package/admin/jsonConfig.json
CHANGED
|
@@ -1,266 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"i18n":
|
|
3
|
-
"Home": {
|
|
4
|
-
"en": "Home",
|
|
5
|
-
"de": "Home",
|
|
6
|
-
"ru": "Home",
|
|
7
|
-
"pt": "Home",
|
|
8
|
-
"nl": "Home",
|
|
9
|
-
"fr": "Home",
|
|
10
|
-
"it": "Home",
|
|
11
|
-
"es": "Home",
|
|
12
|
-
"pl": "Home",
|
|
13
|
-
"uk": "Home",
|
|
14
|
-
"zh-cn": "Home"
|
|
15
|
-
},
|
|
16
|
-
"v1 API": {
|
|
17
|
-
"en": "v1 API",
|
|
18
|
-
"de": "v1 API",
|
|
19
|
-
"ru": "v1 API",
|
|
20
|
-
"pt": "v1 API",
|
|
21
|
-
"nl": "v1 API",
|
|
22
|
-
"fr": "v1 API",
|
|
23
|
-
"it": "v1 API",
|
|
24
|
-
"es": "v1 API",
|
|
25
|
-
"pl": "v1 API",
|
|
26
|
-
"uk": "v1 API",
|
|
27
|
-
"zh-cn": "v1 API"
|
|
28
|
-
},
|
|
29
|
-
"v2 API": {
|
|
30
|
-
"en": "v2 API",
|
|
31
|
-
"de": "v2 API",
|
|
32
|
-
"ru": "v2 API",
|
|
33
|
-
"pt": "v2 API",
|
|
34
|
-
"nl": "v2 API",
|
|
35
|
-
"fr": "v2 API",
|
|
36
|
-
"it": "v2 API",
|
|
37
|
-
"es": "v2 API",
|
|
38
|
-
"pl": "v2 API",
|
|
39
|
-
"uk": "v2 API",
|
|
40
|
-
"zh-cn": "v2 API"
|
|
41
|
-
},
|
|
42
|
-
"Use GraphQL; REST is a fallback": {
|
|
43
|
-
"en": "Use GraphQL; REST is a fallback",
|
|
44
|
-
"de": "GraphQL nutzen, REST nur als Fallback",
|
|
45
|
-
"ru": "Используйте GraphQL; REST — запасной вариант",
|
|
46
|
-
"pt": "Usar GraphQL; REST é o recurso",
|
|
47
|
-
"nl": "GraphQL gebruiken; REST is fallback",
|
|
48
|
-
"fr": "Utiliser GraphQL ; REST en secours",
|
|
49
|
-
"it": "Usa GraphQL; REST è il ripiego",
|
|
50
|
-
"es": "Usar GraphQL; REST es el respaldo",
|
|
51
|
-
"pl": "Używaj GraphQL; REST to zapas",
|
|
52
|
-
"uk": "Використовуйте GraphQL; REST — запасний варіант",
|
|
53
|
-
"zh-cn": "使用 GraphQL;REST 仅作备用"
|
|
54
|
-
},
|
|
55
|
-
"Both APIs provide zones and schedules. v2 (GraphQL) is recommended: same login as the Hydrawise app, plus weather and sensors. v1 (REST) is a fallback via API key if GraphQL is unavailable. At least one API must be on.": {
|
|
56
|
-
"en": "Both APIs provide zones and schedules. v2 (GraphQL) is recommended: same login as the Hydrawise app, plus weather and sensors. v1 (REST) is a fallback via API key if GraphQL is unavailable. At least one API must be on.",
|
|
57
|
-
"de": "Beide APIs liefern Zonen und Zeitplan. v2 (GraphQL) ist empfohlen: Login wie in der Hydrawise-App, plus Wetter und Sensoren. v1 (REST) ist der Fallback per API-Schlüssel, falls GraphQL nicht geht. Mindestens eine API muss an sein.",
|
|
58
|
-
"ru": "Оба API дают зоны и расписание. v2 (GraphQL) рекомендуется: тот же вход, что в приложении Hydrawise, плюс погода и датчики. v1 (REST) — запасной вариант по API-ключу, если GraphQL недоступен. Должен быть включён хотя бы один API.",
|
|
59
|
-
"pt": "Ambas as APIs fornecem zonas e agenda. v2 (GraphQL) é recomendada: mesmo login da app Hydrawise, mais clima e sensores. v1 (REST) é o recurso via chave API se GraphQL não estiver disponível. Pelo menos uma API tem de estar ligada.",
|
|
60
|
-
"nl": "Beide API’s leveren zones en schema. v2 (GraphQL) is aanbevolen: dezelfde login als de Hydrawise-app, plus weer en sensoren. v1 (REST) is de fallback via API-sleutel als GraphQL niet beschikbaar is. Minstens één API moet aan staan.",
|
|
61
|
-
"fr": "Les deux API fournissent zones et planning. v2 (GraphQL) est recommandée : mêmes identifiants que l’app Hydrawise, plus météo et capteurs. v1 (REST) est le secours via clé API si GraphQL est indisponible. Au moins une API doit être activée.",
|
|
62
|
-
"it": "Entrambe le API forniscono zone e programmi. v2 (GraphQL) è consigliata: stesso login dell’app Hydrawise, più meteo e sensori. v1 (REST) è il ripiego con chiave API se GraphQL non è disponibile. Almeno un’API deve essere attiva.",
|
|
63
|
-
"es": "Ambas API ofrecen zonas y programación. v2 (GraphQL) es la recomendada: mismo inicio de sesión que la app Hydrawise, más clima y sensores. v1 (REST) es el respaldo con clave API si GraphQL no está disponible. Debe estar activa al menos una API.",
|
|
64
|
-
"pl": "Oba API dają strefy i harmonogram. v2 (GraphQL) jest zalecane: to samo logowanie co w aplikacji Hydrawise, plus pogoda i czujniki. v1 (REST) to zapas przez klucz API, gdy GraphQL jest niedostępne. Musi być włączone co najmniej jedno API.",
|
|
65
|
-
"uk": "Обидва API дають зони й розклад. v2 (GraphQL) рекомендовано: той самий вхід, що в програмі Hydrawise, плюс погода й датчики. v1 (REST) — запасний варіант за API-ключем, якщо GraphQL недоступний. Має бути ввімкнено принаймні один API.",
|
|
66
|
-
"zh-cn": "两个 API 都提供区域和计划。推荐 v2(GraphQL):与 Hydrawise 应用相同的登录,另加天气和传感器。v1(REST)是 GraphQL 不可用时通过 API 密钥的备用方案。至少启用一个 API。"
|
|
67
|
-
},
|
|
68
|
-
"open hydrawise app": {
|
|
69
|
-
"en": "Open Hydrawise dashboard",
|
|
70
|
-
"de": "Hydrawise-Dashboard öffnen",
|
|
71
|
-
"ru": "Открыть панель Hydrawise",
|
|
72
|
-
"pt": "Abrir painel Hydrawise",
|
|
73
|
-
"nl": "Hydrawise-dashboard openen",
|
|
74
|
-
"fr": "Ouvrir le tableau de bord Hydrawise",
|
|
75
|
-
"it": "Apri dashboard Hydrawise",
|
|
76
|
-
"es": "Abrir el panel de Hydrawise",
|
|
77
|
-
"pl": "Otwórz pulpit Hydrawise",
|
|
78
|
-
"uk": "Відкрити панель Hydrawise",
|
|
79
|
-
"zh-cn": "打开 Hydrawise 仪表板"
|
|
80
|
-
},
|
|
81
|
-
"open account details": {
|
|
82
|
-
"en": "Account / API key",
|
|
83
|
-
"de": "Konto / API-Schlüssel",
|
|
84
|
-
"ru": "Аккаунт / API-ключ",
|
|
85
|
-
"pt": "Conta / chave API",
|
|
86
|
-
"nl": "Account / API-sleutel",
|
|
87
|
-
"fr": "Compte / clé API",
|
|
88
|
-
"it": "Account / chiave API",
|
|
89
|
-
"es": "Cuenta / clave API",
|
|
90
|
-
"pl": "Konto / klucz API",
|
|
91
|
-
"uk": "Обліковий запис / API-ключ",
|
|
92
|
-
"zh-cn": "账户 / API 密钥"
|
|
93
|
-
},
|
|
94
|
-
"REST API (fallback)": {
|
|
95
|
-
"en": "REST API (fallback)",
|
|
96
|
-
"de": "REST-API (Fallback)",
|
|
97
|
-
"ru": "REST API (запасной)",
|
|
98
|
-
"pt": "API REST (recurso)",
|
|
99
|
-
"nl": "REST-API (fallback)",
|
|
100
|
-
"fr": "API REST (secours)",
|
|
101
|
-
"it": "API REST (ripiego)",
|
|
102
|
-
"es": "API REST (respaldo)",
|
|
103
|
-
"pl": "API REST (zapas)",
|
|
104
|
-
"uk": "REST API (запасний)",
|
|
105
|
-
"zh-cn": "REST API(备用)"
|
|
106
|
-
},
|
|
107
|
-
"GraphQL API (recommended)": {
|
|
108
|
-
"en": "GraphQL API (recommended)",
|
|
109
|
-
"de": "GraphQL-API (empfohlen)",
|
|
110
|
-
"ru": "GraphQL API (рекомендуется)",
|
|
111
|
-
"pt": "API GraphQL (recomendada)",
|
|
112
|
-
"nl": "GraphQL-API (aanbevolen)",
|
|
113
|
-
"fr": "API GraphQL (recommandée)",
|
|
114
|
-
"it": "API GraphQL (consigliata)",
|
|
115
|
-
"es": "API GraphQL (recomendada)",
|
|
116
|
-
"pl": "API GraphQL (zalecane)",
|
|
117
|
-
"uk": "GraphQL API (рекомендовано)",
|
|
118
|
-
"zh-cn": "GraphQL API(推荐)"
|
|
119
|
-
},
|
|
120
|
-
"api interval": {
|
|
121
|
-
"en": "Poll interval",
|
|
122
|
-
"de": "Abfrageintervall",
|
|
123
|
-
"ru": "Интервал опроса",
|
|
124
|
-
"pt": "Intervalo de consulta",
|
|
125
|
-
"nl": "Poll-interval",
|
|
126
|
-
"fr": "Intervalle d’interrogation",
|
|
127
|
-
"it": "Intervallo di polling",
|
|
128
|
-
"es": "Intervalo de consulta",
|
|
129
|
-
"pl": "Interwał odpytywania",
|
|
130
|
-
"uk": "Інтервал опитування",
|
|
131
|
-
"zh-cn": "轮询间隔"
|
|
132
|
-
},
|
|
133
|
-
"api key": {
|
|
134
|
-
"en": "API key",
|
|
135
|
-
"de": "API-Schlüssel",
|
|
136
|
-
"ru": "API-ключ",
|
|
137
|
-
"pt": "Chave API",
|
|
138
|
-
"nl": "API-sleutel",
|
|
139
|
-
"fr": "Clé API",
|
|
140
|
-
"it": "Chiave API",
|
|
141
|
-
"es": "Clave API",
|
|
142
|
-
"pl": "Klucz API",
|
|
143
|
-
"uk": "API-ключ",
|
|
144
|
-
"zh-cn": "API 密钥"
|
|
145
|
-
},
|
|
146
|
-
"how to get token": {
|
|
147
|
-
"en": "Generate the key under Account Settings on hydrawise.com",
|
|
148
|
-
"de": "Schlüssel unter Kontoeinstellungen auf hydrawise.com erzeugen",
|
|
149
|
-
"ru": "Создайте ключ в настройках аккаунта на hydrawise.com",
|
|
150
|
-
"pt": "Gere a chave em Definições da conta em hydrawise.com",
|
|
151
|
-
"nl": "Genereer de sleutel onder accountinstellingen op hydrawise.com",
|
|
152
|
-
"fr": "Générez la clé dans les paramètres du compte sur hydrawise.com",
|
|
153
|
-
"it": "Genera la chiave in Impostazioni account su hydrawise.com",
|
|
154
|
-
"es": "Genere la clave en Ajustes de cuenta en hydrawise.com",
|
|
155
|
-
"pl": "Wygeneruj klucz w ustawieniach konta na hydrawise.com",
|
|
156
|
-
"uk": "Створіть ключ у параметрах облікового запису на hydrawise.com",
|
|
157
|
-
"zh-cn": "在 hydrawise.com 的账户设置中生成密钥"
|
|
158
|
-
},
|
|
159
|
-
"Enable v1 API (REST)": {
|
|
160
|
-
"en": "Enable v1 API (REST)",
|
|
161
|
-
"de": "v1-API aktivieren (REST)",
|
|
162
|
-
"ru": "Включить API v1 (REST)",
|
|
163
|
-
"pt": "Ativar API v1 (REST)",
|
|
164
|
-
"nl": "v1-API inschakelen (REST)",
|
|
165
|
-
"fr": "Activer l’API v1 (REST)",
|
|
166
|
-
"it": "Abilita API v1 (REST)",
|
|
167
|
-
"es": "Activar API v1 (REST)",
|
|
168
|
-
"pl": "Włącz API v1 (REST)",
|
|
169
|
-
"uk": "Увімкнути API v1 (REST)",
|
|
170
|
-
"zh-cn": "启用 v1 API(REST)"
|
|
171
|
-
},
|
|
172
|
-
"Only needed if GraphQL is unavailable. Same zones and schedules as v2, without weather and sensors. customerdetails is limited to 5 requests / 5 minutes.": {
|
|
173
|
-
"en": "Only needed if GraphQL is unavailable. Same zones and schedules as v2, without weather and sensors. customerdetails is limited to 5 requests / 5 minutes.",
|
|
174
|
-
"de": "Nur nötig, wenn GraphQL nicht verfügbar ist. Dieselben Zonen und Zeitpläne wie v2, ohne Wetter und Sensoren. customerdetails: max. 5 Anfragen / 5 Minuten.",
|
|
175
|
-
"ru": "Только если GraphQL недоступен. Те же зоны и расписания, что у v2, без погоды и датчиков. customerdetails: 5 запросов / 5 минут.",
|
|
176
|
-
"pt": "Só necessário se GraphQL estiver indisponível. As mesmas zonas e agendas que v2, sem clima nem sensores. customerdetails: 5 pedidos / 5 minutos.",
|
|
177
|
-
"nl": "Alleen nodig als GraphQL niet beschikbaar is. Dezelfde zones en schema’s als v2, zonder weer en sensoren. customerdetails: 5 verzoeken / 5 minuten.",
|
|
178
|
-
"fr": "Uniquement si GraphQL est indisponible. Mêmes zones et plannings que v2, sans météo ni capteurs. customerdetails : 5 requêtes / 5 minutes.",
|
|
179
|
-
"it": "Solo se GraphQL non è disponibile. Stesse zone e programmi di v2, senza meteo né sensori. customerdetails: 5 richieste / 5 minuti.",
|
|
180
|
-
"es": "Solo si GraphQL no está disponible. Las mismas zonas y programaciones que v2, sin clima ni sensores. customerdetails: 5 solicitudes / 5 minutos.",
|
|
181
|
-
"pl": "Tylko gdy GraphQL jest niedostępne. Te same strefy i harmonogramy co v2, bez pogody i czujników. customerdetails: 5 żądań / 5 minut.",
|
|
182
|
-
"uk": "Лише якщо GraphQL недоступний. Ті самі зони й розклади, що у v2, без погоди й датчиків. customerdetails: 5 запитів / 5 хвилин.",
|
|
183
|
-
"zh-cn": "仅在 GraphQL 不可用时需要。与 v2 相同的区域和计划,不含天气和传感器。customerdetails:5 次 / 5 分钟。"
|
|
184
|
-
},
|
|
185
|
-
"Enable v2 API (GraphQL)": {
|
|
186
|
-
"en": "Enable v2 API (GraphQL)",
|
|
187
|
-
"de": "v2-API aktivieren (GraphQL)",
|
|
188
|
-
"ru": "Включить API v2 (GraphQL)",
|
|
189
|
-
"pt": "Ativar API v2 (GraphQL)",
|
|
190
|
-
"nl": "v2-API inschakelen (GraphQL)",
|
|
191
|
-
"fr": "Activer l’API v2 (GraphQL)",
|
|
192
|
-
"it": "Abilita API v2 (GraphQL)",
|
|
193
|
-
"es": "Activar API v2 (GraphQL)",
|
|
194
|
-
"pl": "Włącz API v2 (GraphQL)",
|
|
195
|
-
"uk": "Увімкнути API v2 (GraphQL)",
|
|
196
|
-
"zh-cn": "启用 v2 API(GraphQL)"
|
|
197
|
-
},
|
|
198
|
-
"Same email and password as the Hydrawise app. Zones, schedules, plus sensors, weather and water use. Do not poll faster than every 2 minutes.": {
|
|
199
|
-
"en": "Same email and password as the Hydrawise app. Zones, schedules, plus sensors, weather and water use. Do not poll faster than every 2 minutes.",
|
|
200
|
-
"de": "Dieselbe E-Mail und dasselbe Passwort wie in der Hydrawise-App. Zonen, Zeitplan, plus Sensoren, Wetter und Wasserverbrauch. Nicht öfter als alle 2 Minuten pollen.",
|
|
201
|
-
"ru": "Тот же email и пароль, что в приложении Hydrawise. Зоны, расписание, плюс датчики, погода и расход воды. Не опрашивать чаще чем раз в 2 минуты.",
|
|
202
|
-
"pt": "Mesmo e-mail e senha da app Hydrawise. Zonas, agenda, mais sensores, clima e consumo de água. Não consultar mais do que a cada 2 minutos.",
|
|
203
|
-
"nl": "Zelfde e-mail en wachtwoord als de Hydrawise-app. Zones, schema, plus sensoren, weer en waterverbruik. Niet vaker dan elke 2 minuten pollen.",
|
|
204
|
-
"fr": "Même e-mail et mot de passe que l’app Hydrawise. Zones, planning, plus capteurs, météo et consommation d’eau. Pas plus souvent que toutes les 2 minutes.",
|
|
205
|
-
"it": "Stessa email e password dell’app Hydrawise. Zone, programmi, più sensori, meteo e consumo idrico. Non interrogare più spesso di ogni 2 minuti.",
|
|
206
|
-
"es": "Mismo correo y contraseña que la app Hydrawise. Zonas, programación, más sensores, clima y consumo de agua. No consultar más a menudo que cada 2 minutos.",
|
|
207
|
-
"pl": "Ten sam e-mail i hasło co w aplikacji Hydrawise. Strefy, harmonogram, plus czujniki, pogoda i zużycie wody. Nie odpytywać częściej niż co 2 minuty.",
|
|
208
|
-
"uk": "Той самий email і пароль, що в програмі Hydrawise. Зони, розклад, плюс датчики, погода й витрата води. Не опитувати частіше ніж раз на 2 хвилини.",
|
|
209
|
-
"zh-cn": "与 Hydrawise 应用相同的邮箱和密码。区域、计划,另加传感器、天气和用水量。轮询间隔不要短于 2 分钟。"
|
|
210
|
-
},
|
|
211
|
-
"v2 username": {
|
|
212
|
-
"en": "Email",
|
|
213
|
-
"de": "E-Mail",
|
|
214
|
-
"ru": "Email",
|
|
215
|
-
"pt": "E-mail",
|
|
216
|
-
"nl": "E-mail",
|
|
217
|
-
"fr": "E-mail",
|
|
218
|
-
"it": "Email",
|
|
219
|
-
"es": "Correo",
|
|
220
|
-
"pl": "E-mail",
|
|
221
|
-
"uk": "Email",
|
|
222
|
-
"zh-cn": "邮箱"
|
|
223
|
-
},
|
|
224
|
-
"v2 password": {
|
|
225
|
-
"en": "Password",
|
|
226
|
-
"de": "Passwort",
|
|
227
|
-
"ru": "Пароль",
|
|
228
|
-
"pt": "Senha",
|
|
229
|
-
"nl": "Wachtwoord",
|
|
230
|
-
"fr": "Mot de passe",
|
|
231
|
-
"it": "Password",
|
|
232
|
-
"es": "Contraseña",
|
|
233
|
-
"pl": "Hasło",
|
|
234
|
-
"uk": "Пароль",
|
|
235
|
-
"zh-cn": "密码"
|
|
236
|
-
},
|
|
237
|
-
"v2 interval": {
|
|
238
|
-
"en": "Poll interval",
|
|
239
|
-
"de": "Abfrageintervall",
|
|
240
|
-
"ru": "Интервал опроса",
|
|
241
|
-
"pt": "Intervalo de consulta",
|
|
242
|
-
"nl": "Poll-interval",
|
|
243
|
-
"fr": "Intervalle d’interrogation",
|
|
244
|
-
"it": "Intervallo di polling",
|
|
245
|
-
"es": "Intervalo de consulta",
|
|
246
|
-
"pl": "Interwał odpytywania",
|
|
247
|
-
"uk": "Інтервал опитування",
|
|
248
|
-
"zh-cn": "轮询间隔"
|
|
249
|
-
},
|
|
250
|
-
"v2 interval help": {
|
|
251
|
-
"en": "Default 300 s, minimum 120 s (GraphQL rate limits, shared with the official app).",
|
|
252
|
-
"de": "Standard 300 s, Minimum 120 s (GraphQL-Limit, geteilt mit der offiziellen App).",
|
|
253
|
-
"ru": "По умолчанию 300 с, минимум 120 с (лимиты GraphQL, общие с официальным приложением).",
|
|
254
|
-
"pt": "Padrão 300 s, mínimo 120 s (limites GraphQL, partilhados com o app oficial).",
|
|
255
|
-
"nl": "Standaard 300 s, minimum 120 s (GraphQL-limieten, gedeeld met de officiële app).",
|
|
256
|
-
"fr": "300 s par défaut, minimum 120 s (limites GraphQL, partagées avec l’app officielle).",
|
|
257
|
-
"it": "Predefinito 300 s, minimo 120 s (limiti GraphQL, condivisi con l’app ufficiale).",
|
|
258
|
-
"es": "300 s por defecto, mínimo 120 s (límites GraphQL, compartidos con la app oficial).",
|
|
259
|
-
"pl": "Domyślnie 300 s, minimum 120 s (limity GraphQL, wspólne z oficjalną aplikacją).",
|
|
260
|
-
"uk": "За замовчуванням 300 с, мінімум 120 с (ліміти GraphQL, спільні з офіційною програмою).",
|
|
261
|
-
"zh-cn": "默认 300 秒,最小 120 秒(GraphQL 限速,与官方应用共享)。"
|
|
262
|
-
}
|
|
263
|
-
},
|
|
2
|
+
"i18n": true,
|
|
264
3
|
"type": "tabs",
|
|
265
4
|
"iconPosition": "start",
|
|
266
5
|
"items": {
|
|
@@ -273,7 +12,7 @@
|
|
|
273
12
|
"type": "staticImage",
|
|
274
13
|
"src": "hydrawise.jpg",
|
|
275
14
|
"xs": 12,
|
|
276
|
-
"sm":
|
|
15
|
+
"sm": 2,
|
|
277
16
|
"md": 2,
|
|
278
17
|
"lg": 2,
|
|
279
18
|
"xl": 2
|
|
@@ -283,7 +22,7 @@
|
|
|
283
22
|
"text": "Use GraphQL; REST is a fallback",
|
|
284
23
|
"size": 3,
|
|
285
24
|
"xs": 12,
|
|
286
|
-
"sm":
|
|
25
|
+
"sm": 10,
|
|
287
26
|
"md": 10,
|
|
288
27
|
"lg": 10,
|
|
289
28
|
"xl": 10
|
|
@@ -302,7 +41,11 @@
|
|
|
302
41
|
"type": "divider",
|
|
303
42
|
"newLine": true,
|
|
304
43
|
"color": "primary",
|
|
305
|
-
"xs": 12
|
|
44
|
+
"xs": 12,
|
|
45
|
+
"sm": 12,
|
|
46
|
+
"md": 12,
|
|
47
|
+
"lg": 12,
|
|
48
|
+
"xl": 12
|
|
306
49
|
},
|
|
307
50
|
"_openApp": {
|
|
308
51
|
"type": "staticLink",
|
|
@@ -316,7 +59,8 @@
|
|
|
316
59
|
"xs": 12,
|
|
317
60
|
"sm": 6,
|
|
318
61
|
"md": 4,
|
|
319
|
-
"lg": 3
|
|
62
|
+
"lg": 3,
|
|
63
|
+
"xl": 3
|
|
320
64
|
},
|
|
321
65
|
"_openAccount": {
|
|
322
66
|
"type": "staticLink",
|
|
@@ -329,7 +73,8 @@
|
|
|
329
73
|
"xs": 12,
|
|
330
74
|
"sm": 6,
|
|
331
75
|
"md": 4,
|
|
332
|
-
"lg": 3
|
|
76
|
+
"lg": 3,
|
|
77
|
+
"xl": 3
|
|
333
78
|
}
|
|
334
79
|
}
|
|
335
80
|
},
|
|
@@ -342,13 +87,21 @@
|
|
|
342
87
|
"type": "header",
|
|
343
88
|
"text": "REST API (fallback)",
|
|
344
89
|
"size": 4,
|
|
345
|
-
"xs": 12
|
|
90
|
+
"xs": 12,
|
|
91
|
+
"sm": 12,
|
|
92
|
+
"md": 12,
|
|
93
|
+
"lg": 12,
|
|
94
|
+
"xl": 12
|
|
346
95
|
},
|
|
347
96
|
"_v1Intro": {
|
|
348
97
|
"type": "staticText",
|
|
349
98
|
"text": "Only needed if GraphQL is unavailable. Same zones and schedules as v2, without weather and sensors. customerdetails is limited to 5 requests / 5 minutes.",
|
|
350
99
|
"newLine": true,
|
|
351
|
-
"xs": 12
|
|
100
|
+
"xs": 12,
|
|
101
|
+
"sm": 12,
|
|
102
|
+
"md": 12,
|
|
103
|
+
"lg": 12,
|
|
104
|
+
"xl": 12
|
|
352
105
|
},
|
|
353
106
|
"enableV1": {
|
|
354
107
|
"type": "checkbox",
|
|
@@ -399,13 +152,21 @@
|
|
|
399
152
|
"type": "header",
|
|
400
153
|
"text": "GraphQL API (recommended)",
|
|
401
154
|
"size": 4,
|
|
402
|
-
"xs": 12
|
|
155
|
+
"xs": 12,
|
|
156
|
+
"sm": 12,
|
|
157
|
+
"md": 12,
|
|
158
|
+
"lg": 12,
|
|
159
|
+
"xl": 12
|
|
403
160
|
},
|
|
404
161
|
"_v2Intro": {
|
|
405
162
|
"type": "staticText",
|
|
406
163
|
"text": "Same email and password as the Hydrawise app. Zones, schedules, plus sensors, weather and water use. Do not poll faster than every 2 minutes.",
|
|
407
164
|
"newLine": true,
|
|
408
|
-
"xs": 12
|
|
165
|
+
"xs": 12,
|
|
166
|
+
"sm": 12,
|
|
167
|
+
"md": 12,
|
|
168
|
+
"lg": 12,
|
|
169
|
+
"xl": 12
|
|
409
170
|
},
|
|
410
171
|
"enableV2": {
|
|
411
172
|
"type": "checkbox",
|
package/build/lib/helpers.js
CHANGED
|
@@ -27,6 +27,7 @@ __export(helpers_exports, {
|
|
|
27
27
|
SCHEDULE_SKIP_KEYS: () => SCHEDULE_SKIP_KEYS,
|
|
28
28
|
buildHydrawiseUrl: () => buildHydrawiseUrl,
|
|
29
29
|
getRetryAfterSec: () => getRetryAfterSec,
|
|
30
|
+
instanceConnected: () => instanceConnected,
|
|
30
31
|
isRateLimitError: () => isRateLimitError,
|
|
31
32
|
isRateLimited: () => isRateLimited,
|
|
32
33
|
isScalarKey: () => isScalarKey,
|
|
@@ -109,6 +110,12 @@ function nextBackoffMs(failCount, retryAfterSec, random = Math.random) {
|
|
|
109
110
|
const jitter = 1 + (random() * 0.2 - 0.1);
|
|
110
111
|
return Math.round(capped * jitter);
|
|
111
112
|
}
|
|
113
|
+
function instanceConnected(v1Enabled, v2Enabled, v1Online, v2Online) {
|
|
114
|
+
if (!v1Enabled && !v2Enabled) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
return (!v1Enabled || v1Online) && (!v2Enabled || v2Online);
|
|
118
|
+
}
|
|
112
119
|
// Annotate the CommonJS export names for ESM import in node:
|
|
113
120
|
0 && (module.exports = {
|
|
114
121
|
BACKOFF_CAP_MS,
|
|
@@ -120,6 +127,7 @@ function nextBackoffMs(failCount, retryAfterSec, random = Math.random) {
|
|
|
120
127
|
SCHEDULE_SKIP_KEYS,
|
|
121
128
|
buildHydrawiseUrl,
|
|
122
129
|
getRetryAfterSec,
|
|
130
|
+
instanceConnected,
|
|
123
131
|
isRateLimitError,
|
|
124
132
|
isRateLimited,
|
|
125
133
|
isScalarKey,
|
package/build/lib/helpers.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/helpers.ts"],
|
|
4
|
-
"sourcesContent": ["export const HYDRAWISE_BASE_URL = 'https://api.hydrawise.com';\n\nexport const SCHEDULE_SKIP_KEYS = ['relays', 'sensors', 'expanders'] as const;\nexport const CUSTOMER_SKIP_KEYS = ['controllers'] as const;\n\nexport const CUSTOMER_INTERVAL_MS = 5 * 60 * 1000;\nexport const CUSTOMER_STAGGER_MS = 30_000;\nexport const BACKOFF_CAP_MS = 30 * 60 * 1000;\nexport const RATE_LIMIT_MESSAGE = /exceeded maximum number of requests/i;\n\n/**\n * Replace characters forbidden in ioBroker object IDs.\n *\n * @param name raw name from API\n * @param forbiddenChars adapter FORBIDDEN_CHARS regex\n */\nexport function name2id(name: string, forbiddenChars: RegExp): string {\n return (name || '').replace(forbiddenChars, '_');\n}\n\n/**\n * True when the key is a scalar field (not a nested array/object list).\n *\n * @param key API response key\n * @param skipKeys keys to skip (arrays/objects)\n */\nexport function isScalarKey(key: string, skipKeys: readonly string[]): boolean {\n return !skipKeys.includes(key);\n}\n\n/**\n * Build a full Hydrawise API URL with query parameters.\n *\n * @param service endpoint file name (e.g. statusschedule.php)\n * @param params query parameters\n */\nexport function buildHydrawiseUrl(service: string, params: Record<string, string | number | boolean>): string {\n const url = new URL(`/api/v1/${service}`, HYDRAWISE_BASE_URL);\n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined && value !== null) {\n url.searchParams.set(key, String(value));\n }\n }\n return url.toString();\n}\n\n/**\n * Stable signature of schedule/customer structure for object-sync decisions.\n *\n * @param relayIds relay numbers from status API\n * @param sensorInputs sensor input numbers\n * @param controllerNames controller names from customer API\n */\nexport function structureSignature(\n relayIds: Array<string | number>,\n sensorInputs: Array<string | number>,\n controllerNames: string[] = [],\n): string {\n return JSON.stringify({\n relays: [...relayIds].map(String).sort(),\n sensors: [...sensorInputs].map(String).sort(),\n controllers: [...controllerNames].map(String).sort(),\n });\n}\n\n/**\n * Parse a Retry-After header (delta-seconds or HTTP date) to seconds.\n *\n * @param header Retry-After header value\n */\nexport function parseRetryAfter(header: string | null | undefined): number | undefined {\n if (!header) {\n return undefined;\n }\n const seconds = Number(header);\n if (Number.isFinite(seconds) && seconds >= 0) {\n return seconds;\n }\n const date = Date.parse(header);\n if (!Number.isNaN(date)) {\n return Math.max(0, (date - Date.now()) / 1000);\n }\n return undefined;\n}\n\n/**\n * True when HTTP status or body indicates a Hydrawise rate limit.\n *\n * @param status HTTP status code\n * @param body response body (string or parsed JSON)\n */\nexport function isRateLimited(status?: number, body?: unknown): boolean {\n if (status === 429) {\n return true;\n }\n if (typeof body === 'string') {\n return RATE_LIMIT_MESSAGE.test(body);\n }\n if (body && typeof body === 'object') {\n return RATE_LIMIT_MESSAGE.test(JSON.stringify(body));\n }\n return false;\n}\n\n/**\n * True when a thrown error is a Hydrawise rate-limit response.\n *\n * @param error caught error\n */\nexport function isRateLimitError(error: unknown): boolean {\n const err = error as {\n code?: number | string;\n data?: unknown;\n response?: { status?: number; data?: unknown };\n message?: string;\n };\n const status = typeof err?.code === 'number' ? err.code : err?.response?.status;\n const data = err?.response?.data ?? err?.data ?? err?.message;\n return isRateLimited(status, data);\n}\n\n/**\n * Retry-After seconds attached to a thrown HTTP error, if any.\n *\n * @param error caught error\n */\nexport function getRetryAfterSec(error: unknown): number | undefined {\n const retryAfter = (error as { retryAfter?: number })?.retryAfter;\n return typeof retryAfter === 'number' && Number.isFinite(retryAfter) ? retryAfter : undefined;\n}\n\n/**\n * Exponential backoff after a rate limit: max(300s, Retry-After) * 2^(failCount-1), \u00B110% jitter, cap 30 min.\n *\n * @param failCount consecutive rate-limit failures (1-based)\n * @param retryAfterSec optional Retry-After from the response\n * @param random RNG in [0, 1) \u2014 inject for tests\n */\nexport function nextBackoffMs(failCount: number, retryAfterSec?: number, random: () => number = Math.random): number {\n const n = Math.max(1, failCount);\n const base = Math.max(CUSTOMER_INTERVAL_MS, (retryAfterSec ?? 0) * 1000);\n const capped = Math.min(base * 2 ** (n - 1), BACKOFF_CAP_MS);\n const jitter = 1 + (random() * 0.2 - 0.1);\n return Math.round(capped * jitter);\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["export const HYDRAWISE_BASE_URL = 'https://api.hydrawise.com';\n\nexport const SCHEDULE_SKIP_KEYS = ['relays', 'sensors', 'expanders'] as const;\nexport const CUSTOMER_SKIP_KEYS = ['controllers'] as const;\n\nexport const CUSTOMER_INTERVAL_MS = 5 * 60 * 1000;\nexport const CUSTOMER_STAGGER_MS = 30_000;\nexport const BACKOFF_CAP_MS = 30 * 60 * 1000;\nexport const RATE_LIMIT_MESSAGE = /exceeded maximum number of requests/i;\n\n/**\n * Replace characters forbidden in ioBroker object IDs.\n *\n * @param name raw name from API\n * @param forbiddenChars adapter FORBIDDEN_CHARS regex\n */\nexport function name2id(name: string, forbiddenChars: RegExp): string {\n return (name || '').replace(forbiddenChars, '_');\n}\n\n/**\n * True when the key is a scalar field (not a nested array/object list).\n *\n * @param key API response key\n * @param skipKeys keys to skip (arrays/objects)\n */\nexport function isScalarKey(key: string, skipKeys: readonly string[]): boolean {\n return !skipKeys.includes(key);\n}\n\n/**\n * Build a full Hydrawise API URL with query parameters.\n *\n * @param service endpoint file name (e.g. statusschedule.php)\n * @param params query parameters\n */\nexport function buildHydrawiseUrl(service: string, params: Record<string, string | number | boolean>): string {\n const url = new URL(`/api/v1/${service}`, HYDRAWISE_BASE_URL);\n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined && value !== null) {\n url.searchParams.set(key, String(value));\n }\n }\n return url.toString();\n}\n\n/**\n * Stable signature of schedule/customer structure for object-sync decisions.\n *\n * @param relayIds relay numbers from status API\n * @param sensorInputs sensor input numbers\n * @param controllerNames controller names from customer API\n */\nexport function structureSignature(\n relayIds: Array<string | number>,\n sensorInputs: Array<string | number>,\n controllerNames: string[] = [],\n): string {\n return JSON.stringify({\n relays: [...relayIds].map(String).sort(),\n sensors: [...sensorInputs].map(String).sort(),\n controllers: [...controllerNames].map(String).sort(),\n });\n}\n\n/**\n * Parse a Retry-After header (delta-seconds or HTTP date) to seconds.\n *\n * @param header Retry-After header value\n */\nexport function parseRetryAfter(header: string | null | undefined): number | undefined {\n if (!header) {\n return undefined;\n }\n const seconds = Number(header);\n if (Number.isFinite(seconds) && seconds >= 0) {\n return seconds;\n }\n const date = Date.parse(header);\n if (!Number.isNaN(date)) {\n return Math.max(0, (date - Date.now()) / 1000);\n }\n return undefined;\n}\n\n/**\n * True when HTTP status or body indicates a Hydrawise rate limit.\n *\n * @param status HTTP status code\n * @param body response body (string or parsed JSON)\n */\nexport function isRateLimited(status?: number, body?: unknown): boolean {\n if (status === 429) {\n return true;\n }\n if (typeof body === 'string') {\n return RATE_LIMIT_MESSAGE.test(body);\n }\n if (body && typeof body === 'object') {\n return RATE_LIMIT_MESSAGE.test(JSON.stringify(body));\n }\n return false;\n}\n\n/**\n * True when a thrown error is a Hydrawise rate-limit response.\n *\n * @param error caught error\n */\nexport function isRateLimitError(error: unknown): boolean {\n const err = error as {\n code?: number | string;\n data?: unknown;\n response?: { status?: number; data?: unknown };\n message?: string;\n };\n const status = typeof err?.code === 'number' ? err.code : err?.response?.status;\n const data = err?.response?.data ?? err?.data ?? err?.message;\n return isRateLimited(status, data);\n}\n\n/**\n * Retry-After seconds attached to a thrown HTTP error, if any.\n *\n * @param error caught error\n */\nexport function getRetryAfterSec(error: unknown): number | undefined {\n const retryAfter = (error as { retryAfter?: number })?.retryAfter;\n return typeof retryAfter === 'number' && Number.isFinite(retryAfter) ? retryAfter : undefined;\n}\n\n/**\n * Exponential backoff after a rate limit: max(300s, Retry-After) * 2^(failCount-1), \u00B110% jitter, cap 30 min.\n *\n * @param failCount consecutive rate-limit failures (1-based)\n * @param retryAfterSec optional Retry-After from the response\n * @param random RNG in [0, 1) \u2014 inject for tests\n */\nexport function nextBackoffMs(failCount: number, retryAfterSec?: number, random: () => number = Math.random): number {\n const n = Math.max(1, failCount);\n const base = Math.max(CUSTOMER_INTERVAL_MS, (retryAfterSec ?? 0) * 1000);\n const capped = Math.min(base * 2 ** (n - 1), BACKOFF_CAP_MS);\n const jitter = 1 + (random() * 0.2 - 0.1);\n return Math.round(capped * jitter);\n}\n\n/**\n * True when every enabled API is online. Admin uses this for `info.connection`.\n *\n * @param v1Enabled whether v1 REST is enabled\n * @param v2Enabled whether v2 GraphQL is enabled\n * @param v1Online whether v1 last poll succeeded\n * @param v2Online whether v2 last poll succeeded\n */\nexport function instanceConnected(\n v1Enabled: boolean,\n v2Enabled: boolean,\n v1Online: boolean,\n v2Online: boolean,\n): boolean {\n if (!v1Enabled && !v2Enabled) {\n return false;\n }\n return (!v1Enabled || v1Online) && (!v2Enabled || v2Online);\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/build/lib/v2/auth.js
CHANGED
|
@@ -77,12 +77,10 @@ function parseTokenResponse(json) {
|
|
|
77
77
|
};
|
|
78
78
|
}
|
|
79
79
|
async function postForm(url, params) {
|
|
80
|
-
const abort = new AbortController();
|
|
81
|
-
const timeout = setTimeout(() => abort.abort(), V2_REQUEST_TIMEOUT_MS);
|
|
82
80
|
try {
|
|
83
81
|
const response = await fetch(url, {
|
|
84
82
|
method: "POST",
|
|
85
|
-
signal:
|
|
83
|
+
signal: AbortSignal.timeout(V2_REQUEST_TIMEOUT_MS),
|
|
86
84
|
headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" },
|
|
87
85
|
body: new URLSearchParams(params)
|
|
88
86
|
});
|
|
@@ -100,12 +98,10 @@ async function postForm(url, params) {
|
|
|
100
98
|
if (error instanceof HydrawiseV2Error) {
|
|
101
99
|
throw error;
|
|
102
100
|
}
|
|
103
|
-
if ((error == null ? void 0 : error.name) === "AbortError") {
|
|
101
|
+
if ((error == null ? void 0 : error.name) === "AbortError" || (error == null ? void 0 : error.name) === "TimeoutError") {
|
|
104
102
|
throw new HydrawiseV2Error("request timed out", "ECONNABORTED");
|
|
105
103
|
}
|
|
106
104
|
throw new HydrawiseV2Error((error == null ? void 0 : error.message) || String(error), (error == null ? void 0 : error.code) || "ENOTFOUND");
|
|
107
|
-
} finally {
|
|
108
|
-
clearTimeout(timeout);
|
|
109
105
|
}
|
|
110
106
|
}
|
|
111
107
|
async function fetchAccessToken(username, password) {
|
|
@@ -128,12 +124,10 @@ async function refreshAccessToken(refreshToken) {
|
|
|
128
124
|
}
|
|
129
125
|
async function graphqlRequest(token, request) {
|
|
130
126
|
var _a;
|
|
131
|
-
const abort = new AbortController();
|
|
132
|
-
const timeout = setTimeout(() => abort.abort(), V2_REQUEST_TIMEOUT_MS);
|
|
133
127
|
try {
|
|
134
128
|
const response = await fetch(V2_GRAPH_URL, {
|
|
135
129
|
method: "POST",
|
|
136
|
-
signal:
|
|
130
|
+
signal: AbortSignal.timeout(V2_REQUEST_TIMEOUT_MS),
|
|
137
131
|
headers: {
|
|
138
132
|
Authorization: `${token.tokenType} ${token.accessToken}`,
|
|
139
133
|
"Content-Type": "application/json",
|
|
@@ -170,12 +164,10 @@ async function graphqlRequest(token, request) {
|
|
|
170
164
|
if (error instanceof HydrawiseV2Error) {
|
|
171
165
|
throw error;
|
|
172
166
|
}
|
|
173
|
-
if ((error == null ? void 0 : error.name) === "AbortError") {
|
|
167
|
+
if ((error == null ? void 0 : error.name) === "AbortError" || (error == null ? void 0 : error.name) === "TimeoutError") {
|
|
174
168
|
throw new HydrawiseV2Error("request timed out", "ECONNABORTED");
|
|
175
169
|
}
|
|
176
170
|
throw new HydrawiseV2Error((error == null ? void 0 : error.message) || String(error), (error == null ? void 0 : error.code) || "ENOTFOUND");
|
|
177
|
-
} finally {
|
|
178
|
-
clearTimeout(timeout);
|
|
179
171
|
}
|
|
180
172
|
}
|
|
181
173
|
// Annotate the CommonJS export names for ESM import in node:
|
package/build/lib/v2/auth.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/v2/auth.ts"],
|
|
4
|
-
"sourcesContent": ["import { parseRetryAfter } from '../helpers';\n\nexport const V2_TOKEN_URL = 'https://app.hydrawise.com/api/v2/oauth/access-token';\nexport const V2_GRAPH_URL = 'https://app.hydrawise.com/api/v2/graph';\nexport const V2_CLIENT_ID = 'hydrawise_app';\nexport const V2_CLIENT_SECRET = 'zn3CrjglwNV1';\nexport const V2_TOKEN_SKEW_MS = 5 * 60 * 1000;\nexport const V2_REQUEST_TIMEOUT_MS = 30_000;\n\n/** OAuth access token for the Hydrawise v2 GraphQL API. */\nexport interface V2Token {\n /** Bearer access token. */\n accessToken: string;\n /** Refresh token. */\n refreshToken: string;\n /** Token type, usually Bearer. */\n tokenType: string;\n /** Unix epoch milliseconds when the access token expires. */\n expiresAt: number;\n}\n\n/** HTTP/GraphQL error from the Hydrawise v2 API. */\nexport class HydrawiseV2Error extends Error {\n readonly code: number | string;\n readonly data: unknown;\n readonly retryAfter?: number;\n\n /**\n * @param message human-readable error\n * @param code HTTP status or error code\n * @param data response body\n * @param retryAfter Retry-After in seconds\n */\n constructor(message: string, code: number | string, data?: unknown, retryAfter?: number) {\n super(message);\n this.name = 'HydrawiseV2Error';\n this.code = code;\n this.data = data;\n this.retryAfter = retryAfter;\n }\n}\n\n/**\n * True when the access token is missing or expires within the refresh skew.\n *\n * @param token current token\n * @param nowMs current time\n */\nexport function tokenNeedsRefresh(token: V2Token | null, nowMs: number = Date.now()): boolean {\n if (!token) {\n return true;\n }\n return token.expiresAt - nowMs < V2_TOKEN_SKEW_MS;\n}\n\nfunction parseTokenResponse(json: Record<string, any>): V2Token {\n if (json.error) {\n throw new HydrawiseV2Error(String(json.message || json.error), 'unauthorized', json);\n }\n if (!json.access_token || !json.token_type) {\n throw new HydrawiseV2Error('Invalid token response', 'unauthorized', json);\n }\n return {\n accessToken: json.access_token,\n refreshToken: json.refresh_token || '',\n tokenType: json.token_type,\n expiresAt: Date.now() + Number(json.expires_in || 3600) * 1000,\n };\n}\n\nasync function postForm(url: string, params: Record<string, string>): Promise<V2Token> {\n
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAAgC;AAEzB,MAAM,eAAe;AACrB,MAAM,eAAe;AACrB,MAAM,eAAe;AACrB,MAAM,mBAAmB;AACzB,MAAM,mBAAmB,IAAI,KAAK;AAClC,MAAM,wBAAwB;AAe9B,MAAM,yBAAyB,MAAM;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQT,YAAY,SAAiB,MAAuB,MAAgB,YAAqB;AACrF,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,SAAK,aAAa;AAAA,EACtB;AACJ;AAQO,SAAS,kBAAkB,OAAuB,QAAgB,KAAK,IAAI,GAAY;AAC1F,MAAI,CAAC,OAAO;AACR,WAAO;AAAA,EACX;AACA,SAAO,MAAM,YAAY,QAAQ;AACrC;AAEA,SAAS,mBAAmB,MAAoC;AAC5D,MAAI,KAAK,OAAO;AACZ,UAAM,IAAI,iBAAiB,OAAO,KAAK,WAAW,KAAK,KAAK,GAAG,gBAAgB,IAAI;AAAA,EACvF;AACA,MAAI,CAAC,KAAK,gBAAgB,CAAC,KAAK,YAAY;AACxC,UAAM,IAAI,iBAAiB,0BAA0B,gBAAgB,IAAI;AAAA,EAC7E;AACA,SAAO;AAAA,IACH,aAAa,KAAK;AAAA,IAClB,cAAc,KAAK,iBAAiB;AAAA,IACpC,WAAW,KAAK;AAAA,IAChB,WAAW,KAAK,IAAI,IAAI,OAAO,KAAK,cAAc,IAAI,IAAI;AAAA,EAC9D;AACJ;AAEA,eAAe,SAAS,KAAa,QAAkD;AACnF,
|
|
4
|
+
"sourcesContent": ["import { parseRetryAfter } from '../helpers';\n\nexport const V2_TOKEN_URL = 'https://app.hydrawise.com/api/v2/oauth/access-token';\nexport const V2_GRAPH_URL = 'https://app.hydrawise.com/api/v2/graph';\nexport const V2_CLIENT_ID = 'hydrawise_app';\nexport const V2_CLIENT_SECRET = 'zn3CrjglwNV1';\nexport const V2_TOKEN_SKEW_MS = 5 * 60 * 1000;\nexport const V2_REQUEST_TIMEOUT_MS = 30_000;\n\n/** OAuth access token for the Hydrawise v2 GraphQL API. */\nexport interface V2Token {\n /** Bearer access token. */\n accessToken: string;\n /** Refresh token. */\n refreshToken: string;\n /** Token type, usually Bearer. */\n tokenType: string;\n /** Unix epoch milliseconds when the access token expires. */\n expiresAt: number;\n}\n\n/** HTTP/GraphQL error from the Hydrawise v2 API. */\nexport class HydrawiseV2Error extends Error {\n readonly code: number | string;\n readonly data: unknown;\n readonly retryAfter?: number;\n\n /**\n * @param message human-readable error\n * @param code HTTP status or error code\n * @param data response body\n * @param retryAfter Retry-After in seconds\n */\n constructor(message: string, code: number | string, data?: unknown, retryAfter?: number) {\n super(message);\n this.name = 'HydrawiseV2Error';\n this.code = code;\n this.data = data;\n this.retryAfter = retryAfter;\n }\n}\n\n/**\n * True when the access token is missing or expires within the refresh skew.\n *\n * @param token current token\n * @param nowMs current time\n */\nexport function tokenNeedsRefresh(token: V2Token | null, nowMs: number = Date.now()): boolean {\n if (!token) {\n return true;\n }\n return token.expiresAt - nowMs < V2_TOKEN_SKEW_MS;\n}\n\nfunction parseTokenResponse(json: Record<string, any>): V2Token {\n if (json.error) {\n throw new HydrawiseV2Error(String(json.message || json.error), 'unauthorized', json);\n }\n if (!json.access_token || !json.token_type) {\n throw new HydrawiseV2Error('Invalid token response', 'unauthorized', json);\n }\n return {\n accessToken: json.access_token,\n refreshToken: json.refresh_token || '',\n tokenType: json.token_type,\n expiresAt: Date.now() + Number(json.expires_in || 3600) * 1000,\n };\n}\n\nasync function postForm(url: string, params: Record<string, string>): Promise<V2Token> {\n try {\n const response = await fetch(url, {\n method: 'POST',\n signal: AbortSignal.timeout(V2_REQUEST_TIMEOUT_MS),\n headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' },\n body: new URLSearchParams(params),\n });\n const json = (await response.json().catch(() => ({}))) as Record<string, any>;\n if (!response.ok && !json.error) {\n throw new HydrawiseV2Error(\n `HTTP ${response.status}`,\n response.status,\n json,\n parseRetryAfter(response.headers.get('Retry-After')),\n );\n }\n return parseTokenResponse(json);\n } catch (error: any) {\n if (error instanceof HydrawiseV2Error) {\n throw error;\n }\n if (error?.name === 'AbortError' || error?.name === 'TimeoutError') {\n throw new HydrawiseV2Error('request timed out', 'ECONNABORTED');\n }\n throw new HydrawiseV2Error(error?.message || String(error), error?.code || 'ENOTFOUND');\n }\n}\n\n/**\n * Fetch an OAuth access token with username/password (password grant).\n *\n * @param username Hydrawise account email\n * @param password Hydrawise account password\n */\nexport async function fetchAccessToken(username: string, password: string): Promise<V2Token> {\n return postForm(V2_TOKEN_URL, {\n client_id: V2_CLIENT_ID,\n client_secret: V2_CLIENT_SECRET,\n grant_type: 'password',\n scope: 'all',\n username,\n password,\n });\n}\n\n/**\n * Refresh an OAuth access token.\n *\n * @param refreshToken current refresh token\n */\nexport async function refreshAccessToken(refreshToken: string): Promise<V2Token> {\n return postForm(V2_TOKEN_URL, {\n client_id: V2_CLIENT_ID,\n client_secret: V2_CLIENT_SECRET,\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n });\n}\n\n/** GraphQL query or mutation payload. */\nexport interface GraphQlRequest {\n /** GraphQL document. */\n query: string;\n /** GraphQL variables. */\n variables?: Record<string, unknown>;\n}\n\n/**\n * POST a GraphQL operation to the Hydrawise v2 endpoint.\n *\n * @param token OAuth token\n * @param request query and optional variables\n */\nexport async function graphqlRequest(token: V2Token, request: GraphQlRequest): Promise<any> {\n try {\n const response = await fetch(V2_GRAPH_URL, {\n method: 'POST',\n signal: AbortSignal.timeout(V2_REQUEST_TIMEOUT_MS),\n headers: {\n Authorization: `${token.tokenType} ${token.accessToken}`,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify({\n query: request.query,\n variables: request.variables || {},\n }),\n });\n\n const text = await response.text();\n let json: any = null;\n if (text) {\n try {\n json = JSON.parse(text);\n } catch {\n json = text;\n }\n }\n\n if (!response.ok) {\n throw new HydrawiseV2Error(\n `HTTP ${response.status}`,\n response.status,\n json,\n parseRetryAfter(response.headers.get('Retry-After')),\n );\n }\n\n if (json?.errors?.length) {\n const message = json.errors.map((e: { message?: string }) => e.message || 'GraphQL error').join('; ');\n throw new HydrawiseV2Error(message, 'graphql', json);\n }\n\n return json?.data;\n } catch (error: any) {\n if (error instanceof HydrawiseV2Error) {\n throw error;\n }\n if (error?.name === 'AbortError' || error?.name === 'TimeoutError') {\n throw new HydrawiseV2Error('request timed out', 'ECONNABORTED');\n }\n throw new HydrawiseV2Error(error?.message || String(error), error?.code || 'ENOTFOUND');\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/build/main.js
CHANGED
|
@@ -51,6 +51,8 @@ class Hydrawise extends utils.Adapter {
|
|
|
51
51
|
v2LastRetryAfter;
|
|
52
52
|
v2StructureSig = "";
|
|
53
53
|
v2ObjectsReady = false;
|
|
54
|
+
v1Online = false;
|
|
55
|
+
v2Online = false;
|
|
54
56
|
constructor(options = {}) {
|
|
55
57
|
super({
|
|
56
58
|
...options,
|
|
@@ -63,6 +65,19 @@ class Hydrawise extends utils.Adapter {
|
|
|
63
65
|
isV1Enabled() {
|
|
64
66
|
return this.config.enableV1 !== false;
|
|
65
67
|
}
|
|
68
|
+
setV1Online(online) {
|
|
69
|
+
this.v1Online = online;
|
|
70
|
+
this.syncInstanceConnection();
|
|
71
|
+
}
|
|
72
|
+
setV2Online(online) {
|
|
73
|
+
this.v2Online = online;
|
|
74
|
+
void this.setStateChangedAsync("info.connectionV2", online, true);
|
|
75
|
+
this.syncInstanceConnection();
|
|
76
|
+
}
|
|
77
|
+
syncInstanceConnection() {
|
|
78
|
+
const connected = (0, import_helpers.instanceConnected)(this.isV1Enabled(), !!this.config.enableV2, this.v1Online, this.v2Online);
|
|
79
|
+
void this.setStateChangedAsync("info.connection", connected, true);
|
|
80
|
+
}
|
|
66
81
|
async onReady() {
|
|
67
82
|
const v1 = this.isV1Enabled();
|
|
68
83
|
const v2 = !!this.config.enableV2;
|
|
@@ -70,7 +85,9 @@ class Hydrawise extends utils.Adapter {
|
|
|
70
85
|
this.log.error("Neither v1 nor v2 API is enabled");
|
|
71
86
|
return;
|
|
72
87
|
}
|
|
73
|
-
|
|
88
|
+
this.v1Online = false;
|
|
89
|
+
this.v2Online = false;
|
|
90
|
+
this.syncInstanceConnection();
|
|
74
91
|
if (v1) {
|
|
75
92
|
if (!this.config.apiKey) {
|
|
76
93
|
this.log.error("No API-Key defined!");
|
|
@@ -88,7 +105,7 @@ class Hydrawise extends utils.Adapter {
|
|
|
88
105
|
this.log.error("v2 API enabled but username or password is missing");
|
|
89
106
|
} else {
|
|
90
107
|
await this.subscribeV2Commands();
|
|
91
|
-
|
|
108
|
+
this.setV2Online(false);
|
|
92
109
|
this.scheduleV2Poll(0);
|
|
93
110
|
}
|
|
94
111
|
}
|
|
@@ -105,7 +122,7 @@ class Hydrawise extends utils.Adapter {
|
|
|
105
122
|
return;
|
|
106
123
|
}
|
|
107
124
|
const content = response.data;
|
|
108
|
-
|
|
125
|
+
this.setV1Online(true);
|
|
109
126
|
const relayIds = (content.relays || []).map((r) => r.relay);
|
|
110
127
|
const sensorInputs = (content.sensors || []).map((s) => s.input);
|
|
111
128
|
const nextStructureKey = (0, import_helpers.structureSignature)(relayIds, sensorInputs);
|
|
@@ -118,7 +135,7 @@ class Hydrawise extends utils.Adapter {
|
|
|
118
135
|
this.updateScheduleStates(content);
|
|
119
136
|
} catch (error) {
|
|
120
137
|
this.log.debug(`(schedule) received error - API is now offline: ${(error == null ? void 0 : error.message) || error}`);
|
|
121
|
-
|
|
138
|
+
this.setV1Online(false);
|
|
122
139
|
} finally {
|
|
123
140
|
this.schedulePollRunning = false;
|
|
124
141
|
}
|
|
@@ -540,12 +557,10 @@ class Hydrawise extends utils.Adapter {
|
|
|
540
557
|
throw new Error("API key is not configured");
|
|
541
558
|
}
|
|
542
559
|
const url = (0, import_helpers.buildHydrawiseUrl)(service, params);
|
|
543
|
-
const abort = new AbortController();
|
|
544
|
-
const timeout = setTimeout(() => abort.abort(), 3e4);
|
|
545
560
|
try {
|
|
546
561
|
const response = await fetch(url, {
|
|
547
562
|
method: "GET",
|
|
548
|
-
signal:
|
|
563
|
+
signal: AbortSignal.timeout(3e4),
|
|
549
564
|
headers: { Accept: "application/json" }
|
|
550
565
|
});
|
|
551
566
|
this.lastErrorCode = 0;
|
|
@@ -571,7 +586,7 @@ class Hydrawise extends utils.Adapter {
|
|
|
571
586
|
return { status: response.status, data };
|
|
572
587
|
} catch (error) {
|
|
573
588
|
if (error == null ? void 0 : error.response) {
|
|
574
|
-
} else if ((error == null ? void 0 : error.name) === "AbortError") {
|
|
589
|
+
} else if ((error == null ? void 0 : error.name) === "AbortError" || (error == null ? void 0 : error.name) === "TimeoutError") {
|
|
575
590
|
const code = "ECONNABORTED";
|
|
576
591
|
if (code === this.lastErrorCode) {
|
|
577
592
|
this.log.debug(`timeout from ${import_helpers.HYDRAWISE_BASE_URL}/api/v1/${service}`);
|
|
@@ -592,8 +607,6 @@ class Hydrawise extends utils.Adapter {
|
|
|
592
607
|
this.log.error(String(error));
|
|
593
608
|
}
|
|
594
609
|
throw error instanceof Error ? error : new Error(String(error));
|
|
595
|
-
} finally {
|
|
596
|
-
clearTimeout(timeout);
|
|
597
610
|
}
|
|
598
611
|
}
|
|
599
612
|
/**
|
|
@@ -887,7 +900,7 @@ class Hydrawise extends utils.Adapter {
|
|
|
887
900
|
);
|
|
888
901
|
if (!controller) {
|
|
889
902
|
this.log.warn("v2 status: no controller in response");
|
|
890
|
-
|
|
903
|
+
this.setV2Online(false);
|
|
891
904
|
return "error";
|
|
892
905
|
}
|
|
893
906
|
this.v2ControllerId = Number(controller.id);
|
|
@@ -913,12 +926,12 @@ class Hydrawise extends utils.Adapter {
|
|
|
913
926
|
for (const sensor of controller.sensors || []) {
|
|
914
927
|
await this.applyMappedNodes((0, import_mapper.mapSensorStates)(sensor), needObjects);
|
|
915
928
|
}
|
|
916
|
-
|
|
929
|
+
this.setV2Online(true);
|
|
917
930
|
return "ok";
|
|
918
931
|
} catch (error) {
|
|
919
932
|
this.v2LastRetryAfter = (0, import_helpers.getRetryAfterSec)(error);
|
|
920
933
|
this.log.debug(`v2 status: ${(error == null ? void 0 : error.message) || error}`);
|
|
921
|
-
|
|
934
|
+
this.setV2Online(false);
|
|
922
935
|
return (0, import_helpers.isRateLimitError)(error) ? "rate-limit" : "error";
|
|
923
936
|
}
|
|
924
937
|
}
|
package/build/main.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/main.ts"],
|
|
4
|
-
"sourcesContent": ["/*\n * Created with @iobroker/create-adapter v2.3.0\n */\n\nimport * as utils from '@iobroker/adapter-core';\nimport {\n CUSTOMER_INTERVAL_MS,\n CUSTOMER_SKIP_KEYS,\n CUSTOMER_STAGGER_MS,\n HYDRAWISE_BASE_URL,\n SCHEDULE_SKIP_KEYS,\n buildHydrawiseUrl,\n getRetryAfterSec,\n isRateLimitError,\n isScalarKey,\n name2id as sanitizeId,\n nextBackoffMs,\n parseRetryAfter,\n structureSignature,\n} from './lib/helpers';\nimport {\n HydrawiseV2Error,\n type V2Token,\n fetchAccessToken,\n graphqlRequest,\n refreshAccessToken,\n tokenNeedsRefresh,\n} from './lib/v2/auth';\nimport {\n type MappedNode,\n computeWaterSummary,\n mapAllZoneCommandObjects,\n mapControllerStates,\n mapSensorStates,\n mapWaterStates,\n mapWeatherStates,\n mapZoneCommandObjects,\n mapZoneStates,\n pickController,\n v2StructureKey,\n zoneIdMap,\n} from './lib/v2/mapper';\nimport {\n STATUS_QUERY,\n type WaterPeriod,\n nextLongPeriod,\n startAllZonesMutation,\n mutationErrorSummary,\n startZoneMutation,\n stopAllZonesMutation,\n stopZoneMutation,\n suspendAllZonesMutation,\n suspendZoneMutation,\n waterRequest,\n weatherRequest,\n} from './lib/v2/queries';\n\ninterface ApiResponse {\n status: number;\n data: any;\n}\n\nclass Hydrawise extends utils.Adapter {\n private pollScheduleTimer?: ioBroker.Interval;\n private pollCustomerTimer?: ioBroker.Timeout;\n private pollV2Timer?: ioBroker.Timeout;\n private resetSwitchTimer?: ioBroker.Timeout;\n private relays: Record<string, number> = {};\n private lastErrorCode: string | number = 0;\n private schedulePollRunning = false;\n private customerPollRunning = false;\n private scheduleStructureKey = '';\n private customerStructureKey = '';\n private scheduleObjectsReady = false;\n private customerObjectsReady = false;\n private customerBackoffFails = 0;\n private lastCustomerCallAt = 0;\n private v1ControllerId?: number;\n private v2Token: V2Token | null = null;\n private v2PollRunning = false;\n private v2BackoffFails = 0;\n private v2LongPeriodIndex = 0;\n private v2ControllerId?: number;\n private v2ZoneIds: Record<string, number> = {};\n private v2LastRetryAfter?: number;\n private v2StructureSig = '';\n private v2ObjectsReady = false;\n\n public constructor(options: Partial<utils.AdapterOptions> = {}) {\n super({\n ...options,\n name: 'hydrawise',\n });\n\n this.on('ready', this.onReady.bind(this));\n this.on('stateChange', this.onStateChange.bind(this));\n this.on('unload', this.onUnload.bind(this));\n }\n\n private isV1Enabled(): boolean {\n return this.config.enableV1 !== false;\n }\n\n private async onReady(): Promise<void> {\n const v1 = this.isV1Enabled();\n const v2 = !!this.config.enableV2;\n\n if (!v1 && !v2) {\n this.log.error('Neither v1 nor v2 API is enabled');\n return;\n }\n\n void this.setStateChangedAsync('info.connection', false, true);\n\n if (v1) {\n if (!this.config.apiKey) {\n this.log.error('No API-Key defined!');\n } else {\n await this.GetStatusSchedule();\n this.pollScheduleTimer = this.setInterval(() => {\n void this.GetStatusSchedule();\n }, this.config.apiInterval * 1000);\n\n this.scheduleCustomerPoll(CUSTOMER_STAGGER_MS);\n await this.subscribeV1Commands();\n }\n }\n\n if (v2) {\n if (!this.config.v2Username || !this.config.v2Password) {\n this.log.error('v2 API enabled but username or password is missing');\n } else {\n await this.subscribeV2Commands();\n void this.setStateChangedAsync('info.connectionV2', false, true);\n this.scheduleV2Poll(0);\n }\n }\n }\n\n private async GetStatusSchedule(): Promise<void> {\n if (this.schedulePollRunning) {\n this.log.debug('Skipping overlapping status schedule poll');\n return;\n }\n\n this.schedulePollRunning = true;\n try {\n const response = await this.buildRequest('statusschedule.php', { api_key: this.config.apiKey });\n if (response.status !== 200) {\n return;\n }\n\n const content = response.data;\n void this.setStateChangedAsync('info.connection', true, true);\n\n const relayIds = (content.relays || []).map((r: any) => r.relay);\n const sensorInputs = (content.sensors || []).map((s: any) => s.input);\n const nextStructureKey = structureSignature(relayIds, sensorInputs);\n const needObjects = !this.scheduleObjectsReady || this.scheduleStructureKey !== nextStructureKey;\n\n if (needObjects) {\n await this.ensureScheduleObjects(content);\n this.scheduleStructureKey = nextStructureKey;\n this.scheduleObjectsReady = true;\n }\n\n this.updateScheduleStates(content);\n } catch (error: any) {\n this.log.debug(`(schedule) received error - API is now offline: ${error?.message || error}`);\n void this.setStateChangedAsync('info.connection', false, true);\n } finally {\n this.schedulePollRunning = false;\n }\n }\n\n private async ensureScheduleObjects(content: any): Promise<void> {\n await this.setObjectNotExistsAsync('schedule.stopall', {\n type: 'state',\n common: {\n name: {\n en: 'stop all zones',\n de: 'alle Zonen stoppen',\n ru: '\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C \u0432\u0441\u0435 \u0437\u043E\u043D\u044B',\n pt: 'parar todas as zonas',\n nl: 'stop alle zones',\n fr: 'arr\u00EAter toutes les zones',\n it: 'fermare tutte le zone',\n es: 'detener todas las zonas',\n pl: 'zatrzymuj\u0105 wszystkie strefy',\n uk: '\u0437\u0443\u043F\u0438\u043D\u0438\u0442\u0438 \u0432\u0441\u0456 \u0437\u043E\u043D\u0438',\n 'zh-cn': '\u505C\u6B62\u6240\u6709\u5730\u533A',\n },\n type: 'boolean',\n role: 'button.stop',\n read: false,\n write: true,\n },\n native: {},\n });\n\n await this.setObjectNotExistsAsync('schedule.runall', {\n type: 'state',\n common: {\n name: {\n en: 'run all zones for x seconds',\n de: 'alle Zonen f\u00FCr x Sekunden ausf\u00FChren',\n ru: '\u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u0432\u0441\u0435 \u0437\u043E\u043D\u044B \u0437\u0430 x \u0441\u0435\u043A\u0443\u043D\u0434\u044B',\n pt: 'executar todas as zonas por x segundos',\n nl: 'ren alle zones voor x seconden',\n fr: 'ex\u00E9cuter toutes les zones pendant x secondes',\n it: 'eseguire tutte le zone per x secondi',\n es: 'ejecutar todas las zonas durante x segundos',\n pl: 'wszystkie strefy startuj\u0105 dla x sekundy',\n uk: '\u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u0438 \u0432\u0441\u0456 \u0437\u043E\u043D\u0438 \u0434\u043B\u044F x \u0441\u0435\u043A\u0443\u043D\u0434',\n 'zh-cn': '\u8DD1\u9053\u533A',\n },\n type: 'number',\n role: 'level',\n unit: 'seconds',\n read: true,\n write: true,\n },\n native: {},\n });\n\n await this.setObjectNotExistsAsync('schedule.suspendall', {\n type: 'state',\n common: {\n name: {\n en: 'suspend all zones for x seconds',\n de: 'alle Zonen f\u00FCr x Sekunden aussetzen',\n ru: '\u043F\u0440\u0438\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C \u0432\u0441\u0435 \u0437\u043E\u043D\u044B \u0437\u0430 x \u0441\u0435\u043A\u0443\u043D\u0434\u044B',\n pt: 'suspender todas as zonas por x segundos',\n nl: 'vertaling:',\n fr: 'suspendre toutes les zones pendant x secondes',\n it: 'sospendere tutte le zone per x secondi',\n es: 'suspender todas las zonas durante x segundos',\n pl: 'wszystkie strefy zawieszenia dla x sekundy',\n uk: '\u043F\u0440\u0438\u0437\u0443\u043F\u0438\u043D\u0438\u0442\u0438 \u0432\u0441\u0456 \u0437\u043E\u043D\u0438 \u043D\u0430 x \u0441\u0435\u043A\u0443\u043D\u0434',\n 'zh-cn': '\u505C\u6B62\u6240\u6709\u00D7\u4E8C\u533A',\n },\n type: 'number',\n role: 'level',\n read: true,\n write: true,\n },\n native: {},\n });\n\n for (const rawKey of Object.keys(content)) {\n const key = this.name2id(rawKey);\n if (!isScalarKey(key, SCHEDULE_SKIP_KEYS)) {\n continue;\n }\n\n await this.setObjectNotExistsAsync(`schedule.${key}`, {\n type: 'state',\n common: {\n name: key,\n type: key === 'message' ? 'string' : 'number',\n role: key === 'message' ? 'text' : 'value',\n read: true,\n write: false,\n },\n native: {},\n });\n\n if (key === 'time') {\n await this.setObjectNotExistsAsync('schedule.timestr', {\n type: 'state',\n common: {\n name: 'last api call',\n type: 'string',\n role: 'text',\n read: true,\n write: false,\n },\n native: {},\n });\n }\n }\n\n for (const relay of content.relays || []) {\n await this.setObjectNotExistsAsync(`schedule.${relay.relay}`, {\n type: 'channel',\n common: {\n name: String(relay.relay),\n },\n native: {},\n });\n\n for (const rawKey of Object.keys(relay)) {\n const key = this.name2id(rawKey);\n await this.setObjectNotExistsAsync(`schedule.${relay.relay}.${key}`, {\n type: 'state',\n common: {\n name: key,\n type: key === 'name' || key === 'timestr' ? 'string' : 'number',\n role: key === 'name' || key === 'timestr' ? 'text' : 'value',\n read: true,\n write: false,\n },\n native: {},\n });\n }\n\n await this.setObjectNotExistsAsync(`schedule.${relay.relay}.stopZone`, {\n type: 'state',\n common: {\n name: {\n en: 'stop zone',\n de: 'Zone stoppen',\n ru: '\u0437\u043E\u043D\u0430 \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0438',\n pt: 'zona de paragem',\n nl: 'stop zone',\n fr: \"zone d ' arr\u00EAt\",\n it: 'zona di sosta',\n es: 'zona de parada',\n pl: 'strefa stopu',\n uk: '\u0437\u043E\u043D\u0430 \u0437\u0443\u043F\u0438\u043D\u043A\u0438',\n 'zh-cn': '\u505C\u6B62\u5730\u533A',\n },\n type: 'boolean',\n role: 'button.stop',\n read: false,\n write: true,\n },\n native: {},\n });\n\n await this.setObjectNotExistsAsync(`schedule.${relay.relay}.runZone`, {\n type: 'state',\n common: {\n name: {\n en: 'run zone for x seconds',\n de: 'Zone f\u00FCr x Sekunden starten',\n ru: '\u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u0437\u043E\u043D\u0443 \u0437\u0430 x \u0441\u0435\u043A\u0443\u043D\u0434\u044B',\n pt: 'zona de execu\u00E7\u00E3o por x segundos',\n nl: 'ren zone voor x seconden',\n fr: 'zone de course pour x secondes',\n it: 'zona di corsa per x secondi',\n es: 'zona de ejecuci\u00F3n por x segundos',\n pl: 'strefa x sekundy',\n uk: '\u0437\u043E\u043D\u0430 \u0437\u0430\u043F\u0443\u0441\u043A\u0443 \u0434\u043B\u044F x \u0441\u0435\u043A\u0443\u043D\u0434',\n 'zh-cn': '\u00D7\u4E8C\u533A',\n },\n type: 'number',\n role: 'level',\n read: true,\n write: true,\n },\n native: {},\n });\n\n await this.setObjectNotExistsAsync(`schedule.${relay.relay}.suspendZone`, {\n type: 'state',\n common: {\n name: {\n en: 'suspend zone for x seconds',\n de: 'Zone f\u00FCr x Sekunden aussetzen',\n ru: '\u043F\u0440\u0438\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C \u0437\u043E\u043D\u0443 \u043D\u0430 x \u0441\u0435\u043A\u0443\u043D\u0434\u044B',\n pt: 'zona de suspens\u00E3o por x segundos',\n nl: 'quality over quantity (qoq) releases vertaling:',\n fr: 'zone de suspension pour x secondes',\n it: 'zona di sospensione per x secondi',\n es: 'zona de suspensi\u00F3n por x segundos',\n pl: 'strefa zawies\u0142a na x sekundy',\n uk: '\u0437\u043E\u043D\u0430 \u043F\u0456\u0434\u0432\u0456\u0441\u043A\u0438 \u0434\u043B\u044F x \u0441\u0435\u043A\u0443\u043D\u0434',\n 'zh-cn': '\u505C\u6B62x\u4E8C\u533A',\n },\n type: 'number',\n role: 'level',\n read: true,\n write: true,\n },\n native: {},\n });\n\n await this.setObjectNotExistsAsync(`schedule.${relay.relay}.runDefault`, {\n type: 'state',\n common: {\n name: {\n en: 'run zone for default time',\n de: 'Zone mit Standardlaufzeit starten',\n ru: '\u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u0437\u043E\u043D\u0443 \u0434\u043B\u044F \u0432\u0440\u0435\u043C\u0435\u043D\u0438 \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E',\n pt: 'fuso de execu\u00E7\u00E3o para o tempo padr\u00E3o',\n nl: 'run zone for default time',\n fr: 'run zone for default time',\n it: 'run zone per il tempo predefinito',\n es: 'zona de ejecuci\u00F3n por tiempo predeterminado',\n pl: 'strefa czasu domy\u015Blnego',\n uk: '\u0437\u043E\u043D\u0430 \u0437\u0430\u043F\u0443\u0441\u043A\u0443 \u0437\u0430 \u0437\u0430\u043C\u043E\u0432\u0447\u0443\u0432\u0430\u043D\u043D\u044F\u043C',\n 'zh-cn': 'a. \u6682\u505C\u65F6\u95F4\u533A',\n },\n type: 'boolean',\n role: 'button.start',\n read: true,\n write: true,\n },\n native: {},\n });\n }\n\n for (const sensor of content.sensors || []) {\n await this.setObjectNotExistsAsync(`schedule.sensors.${sensor.input}`, {\n type: 'channel',\n common: {\n name: 'sensors',\n },\n native: {},\n });\n\n for (const rawKey of Object.keys(sensor)) {\n if (rawKey === 'relays') {\n continue;\n }\n const key = this.name2id(rawKey);\n await this.setObjectNotExistsAsync(`schedule.sensors.${sensor.input}.${key}`, {\n type: 'state',\n common: {\n name: key,\n type: 'number',\n role: 'value',\n read: true,\n write: false,\n },\n native: {},\n });\n }\n }\n }\n\n private updateScheduleStates(content: any): void {\n for (const rawKey of Object.keys(content)) {\n const key = this.name2id(rawKey);\n if (!isScalarKey(key, SCHEDULE_SKIP_KEYS)) {\n continue;\n }\n\n void this.setStateChangedAsync(`schedule.${key}`, content[rawKey], true);\n\n if (key === 'time') {\n const t = new Date(content[rawKey] * 1000);\n void this.setStateChangedAsync('schedule.timestr', t.toString(), true);\n }\n }\n\n for (const relay of content.relays || []) {\n this.relays[relay.relay] = relay.relay_id;\n\n for (const rawKey of Object.keys(relay)) {\n const key = this.name2id(rawKey);\n let value = relay[rawKey];\n\n if (key === 'timestr') {\n const t = new Date();\n t.setSeconds(t.getSeconds() + relay.time);\n value = t.toString();\n }\n\n void this.setStateChangedAsync(`schedule.${relay.relay}.${key}`, value, true);\n }\n }\n\n for (const sensor of content.sensors || []) {\n for (const rawKey of Object.keys(sensor)) {\n if (rawKey === 'relays') {\n continue;\n }\n const key = this.name2id(rawKey);\n void this.setStateChangedAsync(`schedule.sensors.${sensor.input}.${key}`, sensor[rawKey], true);\n }\n }\n }\n\n private scheduleCustomerPoll(delayMs: number): void {\n if (this.pollCustomerTimer) {\n this.clearTimeout(this.pollCustomerTimer);\n }\n this.pollCustomerTimer = this.setTimeout(\n () => {\n this.pollCustomerTimer = undefined;\n void this.runCustomerPoll();\n },\n Math.max(0, delayMs),\n );\n }\n\n private async runCustomerPoll(): Promise<void> {\n const delay = await this.GetCustomerDetails();\n this.scheduleCustomerPoll(delay);\n }\n\n private async GetCustomerDetails(): Promise<number> {\n if (this.customerPollRunning) {\n this.log.debug('Skipping overlapping customer details poll');\n return CUSTOMER_INTERVAL_MS;\n }\n\n const elapsed = this.lastCustomerCallAt ? Date.now() - this.lastCustomerCallAt : CUSTOMER_INTERVAL_MS;\n if (this.lastCustomerCallAt && elapsed < CUSTOMER_INTERVAL_MS) {\n this.log.debug('Skipping customer details poll (inside 5 minute window)');\n return CUSTOMER_INTERVAL_MS - elapsed;\n }\n\n this.customerPollRunning = true;\n this.lastCustomerCallAt = Date.now();\n try {\n const response = await this.buildRequest('customerdetails.php', { api_key: this.config.apiKey });\n if (response.status !== 200) {\n return CUSTOMER_INTERVAL_MS;\n }\n\n const content = response.data;\n this.customerBackoffFails = 0;\n if (content?.controller_id != null) {\n this.v1ControllerId = Number(content.controller_id);\n }\n\n const controllerNames = (content.controllers || []).map((c: any) => this.name2id(c.name));\n const nextStructureKey = structureSignature([], [], controllerNames);\n const needObjects = !this.customerObjectsReady || this.customerStructureKey !== nextStructureKey;\n\n if (needObjects) {\n await this.ensureCustomerObjects(content);\n this.customerStructureKey = nextStructureKey;\n this.customerObjectsReady = true;\n }\n\n this.updateCustomerStates(content);\n return CUSTOMER_INTERVAL_MS;\n } catch (error: any) {\n if (isRateLimitError(error)) {\n this.customerBackoffFails += 1;\n const delay = nextBackoffMs(this.customerBackoffFails, getRetryAfterSec(error));\n this.log.warn(\n `customerdetails rate limited, next poll in ${Math.round(delay / 1000)}s (attempt ${this.customerBackoffFails})`,\n );\n return delay;\n }\n\n this.log.debug(`(customer) received error: ${error?.message || error}`);\n return CUSTOMER_INTERVAL_MS;\n } finally {\n this.customerPollRunning = false;\n }\n }\n\n private async ensureCustomerObjects(content: any): Promise<void> {\n for (const rawKey of Object.keys(content)) {\n if (!isScalarKey(rawKey, CUSTOMER_SKIP_KEYS)) {\n continue;\n }\n const key = this.name2id(rawKey);\n\n await this.setObjectNotExistsAsync(`customer.${key}`, {\n type: 'state',\n common: {\n name: key,\n type: key === 'message' || key === 'current_controller' ? 'string' : 'number',\n role: key === 'message' || key === 'current_controller' ? 'text' : 'value',\n read: true,\n write: false,\n },\n native: {},\n });\n }\n\n for (const controller of content.controllers || []) {\n const controllerId = this.name2id(controller.name);\n await this.setObjectNotExistsAsync(`customer.controllers.${controllerId}`, {\n type: 'channel',\n common: {\n name: controller.name,\n },\n native: {},\n });\n\n for (const rawKey of Object.keys(controller)) {\n const key = this.name2id(rawKey);\n await this.setObjectNotExistsAsync(`customer.controllers.${controllerId}.${key}`, {\n type: 'state',\n common: {\n name: key,\n type: key !== 'controller_id' ? 'string' : 'number',\n role: key !== 'controller_id' ? 'text' : 'value',\n read: true,\n write: false,\n },\n native: {},\n });\n }\n }\n }\n\n private updateCustomerStates(content: any): void {\n for (const rawKey of Object.keys(content)) {\n if (!isScalarKey(rawKey, CUSTOMER_SKIP_KEYS)) {\n continue;\n }\n const key = this.name2id(rawKey);\n void this.setStateChangedAsync(`customer.${key}`, content[rawKey], true);\n }\n\n for (const controller of content.controllers || []) {\n const controllerId = this.name2id(controller.name);\n\n for (const rawKey of Object.keys(controller)) {\n const key = this.name2id(rawKey);\n let value = controller[rawKey];\n\n if (key === 'last_contact') {\n const t = new Date(controller[rawKey] * 1000);\n value = t.toString();\n }\n\n void this.setStateChangedAsync(`customer.controllers.${controllerId}.${key}`, value, true);\n }\n }\n }\n\n /**\n * Perform a GET request against the Hydrawise API.\n *\n * @param service endpoint file name\n * @param params query parameters\n */\n async buildRequest(service: string, params: Record<string, string | number | boolean>): Promise<ApiResponse> {\n if (!params.api_key) {\n throw new Error('API key is not configured');\n }\n\n const url = buildHydrawiseUrl(service, params);\n const abort = new AbortController();\n const timeout = setTimeout(() => abort.abort(), 30_000);\n\n try {\n const response = await fetch(url, {\n method: 'GET',\n signal: abort.signal,\n headers: { Accept: 'application/json' },\n });\n\n this.lastErrorCode = 0;\n\n let data: any = null;\n const text = await response.text();\n if (text) {\n try {\n data = JSON.parse(text);\n } catch {\n data = text;\n }\n }\n\n if (!response.ok) {\n this.log.warn(\n `received ${response.status} response from /api/v1/${service} with content: ${JSON.stringify(data)}`,\n );\n throw Object.assign(new Error(`HTTP ${response.status}`), {\n code: response.status,\n retryAfter: parseRetryAfter(response.headers.get('Retry-After')),\n response: { status: response.status, data },\n });\n }\n\n return { status: response.status, data };\n } catch (error: any) {\n if (error?.response) {\n // already logged above for HTTP errors\n } else if (error?.name === 'AbortError') {\n const code = 'ECONNABORTED';\n if (code === this.lastErrorCode) {\n this.log.debug(`timeout from ${HYDRAWISE_BASE_URL}/api/v1/${service}`);\n } else {\n this.log.info(`error ${code} from /api/v1/${service}: request timed out`);\n this.lastErrorCode = code;\n }\n throw Object.assign(new Error('request timed out'), { code });\n } else if (error?.message) {\n const code = error.code || error.cause?.code || 'ENOTFOUND';\n if (code === this.lastErrorCode) {\n this.log.debug(error.message);\n } else {\n this.log.info(`error ${code} from /api/v1/${service}: ${error.message}`);\n this.lastErrorCode = code;\n }\n } else {\n this.log.error(String(error));\n }\n\n throw error instanceof Error ? error : new Error(String(error));\n } finally {\n clearTimeout(timeout);\n }\n }\n\n /**\n * Is called when adapter shuts down - callback has to be called under any circumstances!\n *\n * @param callback callback function\n */\n private onUnload(callback: () => void): void {\n try {\n if (this.pollScheduleTimer) {\n this.clearInterval(this.pollScheduleTimer);\n this.pollScheduleTimer = undefined;\n }\n if (this.pollCustomerTimer) {\n this.clearTimeout(this.pollCustomerTimer);\n this.pollCustomerTimer = undefined;\n }\n if (this.pollV2Timer) {\n this.clearTimeout(this.pollV2Timer);\n this.pollV2Timer = undefined;\n }\n if (this.resetSwitchTimer) {\n this.clearTimeout(this.resetSwitchTimer);\n this.resetSwitchTimer = undefined;\n }\n\n callback();\n } catch {\n callback();\n }\n }\n\n /**\n * Is called if a subscribed state changes\n *\n * @param id id of the state\n * @param state state object\n */\n private onStateChange(id: string, state: ioBroker.State | null | undefined): void {\n if (!state) {\n return;\n }\n if (state.ack && state.from === `system.adapter.${this.namespace}`) {\n return;\n }\n void this.handleStateChange(id, state);\n }\n\n private async handleStateChange(id: string, state: ioBroker.State): Promise<void> {\n try {\n if (id.includes('.zones.')) {\n await this.handleV2Command(id, state);\n return;\n }\n if (!this.isV1Enabled() || !id.includes('.schedule.')) {\n return;\n }\n\n let commandSent = false;\n\n if (id.includes('stopall')) {\n await this.buildRequest('setzone.php', { api_key: this.config.apiKey, action: 'stopall' });\n commandSent = true;\n } else if (id.includes('stopZone')) {\n const relay = id.match(/.*schedule\\.(.*)\\.stopZone/);\n if (relay && relay.length > 1) {\n await this.buildRequest('setzone.php', {\n api_key: this.config.apiKey,\n action: 'stop',\n relay_id: this.relays[relay[1]],\n });\n commandSent = true;\n }\n }\n\n if (id.includes('runall') && (state.val || state.val === 0)) {\n await this.buildRequest('setzone.php', {\n api_key: this.config.apiKey,\n action: 'runall',\n period_id: 999,\n custom: state.val,\n });\n commandSent = true;\n } else if (id.includes('runZone') && (state.val || state.val === 0)) {\n const relay = id.match(/.*schedule\\.(.*)\\.runZone/);\n if (relay && relay.length > 1) {\n await this.buildRequest('setzone.php', {\n api_key: this.config.apiKey,\n action: 'run',\n period_id: 999,\n custom: state.val,\n relay_id: this.relays[relay[1]],\n });\n commandSent = true;\n }\n }\n\n if (id.includes('runDefault') && state.val !== null) {\n await this.initRunDefault(id, state.val as boolean);\n commandSent = true;\n }\n\n if (id.includes('suspendall') && (state.val || state.val === 0)) {\n const num = state.val as number;\n await this.buildRequest('setzone.php', {\n api_key: this.config.apiKey,\n action: 'suspendall',\n period_id: 999,\n custom: Math.trunc((state.ts + num) / 1000),\n });\n commandSent = true;\n } else if (id.includes('suspendZone') && (state.val || state.val === 0)) {\n const num = state.val as number;\n const relay = id.match(/.*schedule\\.(.*)\\.suspendZone/);\n if (relay && relay.length > 1) {\n await this.buildRequest('setzone.php', {\n api_key: this.config.apiKey,\n action: 'suspend',\n period_id: 999,\n custom: Math.trunc((state.ts + num) / 1000),\n relay_id: this.relays[relay[1]],\n });\n commandSent = true;\n }\n }\n\n if (commandSent && !id.includes('runDefault')) {\n await this.GetStatusSchedule();\n }\n } catch (error: any) {\n this.log.error(`Command failed for ${id}: ${error?.message || error}`);\n }\n }\n\n async initRunDefault(id: string, run: boolean): Promise<void> {\n const relay = id.match(/(.*schedule.*\\.)runDefault/);\n\n if (this.resetSwitchTimer) {\n this.clearTimeout(this.resetSwitchTimer);\n this.resetSwitchTimer = undefined;\n }\n\n if (relay) {\n if (run) {\n const defaultRunTime = await this.getStateAsync(`${relay[1]}run`);\n if (defaultRunTime && defaultRunTime.val) {\n void this.setState(`${relay[1]}runZone`, defaultRunTime.val, false);\n this.resetSwitchTimer = this.setTimeout(\n () => {\n void this.setState(id, false, true);\n this.resetSwitchTimer = undefined;\n },\n (defaultRunTime.val as number) * 1000,\n );\n }\n } else {\n void this.setState(`${relay[1]}stopZone`, true, false);\n }\n }\n }\n\n private async subscribeV1Commands(): Promise<void> {\n await this.subscribeStatesAsync('schedule.stopall');\n await this.subscribeStatesAsync('schedule.runall');\n await this.subscribeStatesAsync('schedule.suspendall');\n await this.subscribeStatesAsync('schedule.*.stopZone');\n await this.subscribeStatesAsync('schedule.*.runZone');\n await this.subscribeStatesAsync('schedule.*.suspendZone');\n await this.subscribeStatesAsync('schedule.*.runDefault');\n }\n\n private async subscribeV2Commands(): Promise<void> {\n await this.subscribeStatesAsync('zones.stopall');\n await this.subscribeStatesAsync('zones.runall');\n await this.subscribeStatesAsync('zones.suspendall');\n await this.subscribeStatesAsync('zones.*.stopZone');\n await this.subscribeStatesAsync('zones.*.runZone');\n await this.subscribeStatesAsync('zones.*.suspendZone');\n await this.subscribeStatesAsync('zones.*.runDefault');\n }\n\n private v2IntervalMs(): number {\n return Math.max(120, Number(this.config.apiIntervalV2) || 300) * 1000;\n }\n\n private scheduleV2Poll(delayMs: number): void {\n if (this.pollV2Timer) {\n this.clearTimeout(this.pollV2Timer);\n }\n this.pollV2Timer = this.setTimeout(\n () => {\n this.pollV2Timer = undefined;\n void this.runV2Poll();\n },\n Math.max(0, delayMs),\n );\n }\n\n private async runV2Poll(statusOnly = false): Promise<void> {\n if (this.v2PollRunning) {\n this.log.debug('Skipping overlapping v2 poll');\n return;\n }\n\n this.v2PollRunning = true;\n let delay = this.v2IntervalMs();\n try {\n const statusResult = await this.pollV2Status();\n if (statusResult === 'rate-limit') {\n this.v2BackoffFails += 1;\n delay = nextBackoffMs(this.v2BackoffFails, this.v2LastRetryAfter);\n this.log.warn(`v2 rate limited, next poll in ${Math.round(delay / 1000)}s`);\n return;\n }\n\n if (statusOnly || !this.v2ControllerId) {\n return;\n }\n\n const weatherResult = await this.pollV2Weather();\n if (weatherResult === 'rate-limit') {\n this.v2BackoffFails += 1;\n delay = nextBackoffMs(this.v2BackoffFails, this.v2LastRetryAfter);\n this.log.warn(`v2 rate limited, next poll in ${Math.round(delay / 1000)}s`);\n return;\n }\n\n const todayResult = await this.pollV2Water('today');\n if (todayResult === 'rate-limit') {\n this.v2BackoffFails += 1;\n delay = nextBackoffMs(this.v2BackoffFails, this.v2LastRetryAfter);\n this.log.warn(`v2 rate limited, next poll in ${Math.round(delay / 1000)}s`);\n return;\n }\n\n const { period, nextIndex } = nextLongPeriod(this.v2LongPeriodIndex);\n const longResult = await this.pollV2Water(period);\n if (longResult === 'rate-limit') {\n this.v2BackoffFails += 1;\n delay = nextBackoffMs(this.v2BackoffFails, this.v2LastRetryAfter);\n this.log.warn(`v2 rate limited, next poll in ${Math.round(delay / 1000)}s`);\n return;\n }\n this.v2LongPeriodIndex = nextIndex;\n this.v2BackoffFails = 0;\n } finally {\n this.v2PollRunning = false;\n if (!statusOnly) {\n this.scheduleV2Poll(delay);\n }\n }\n }\n\n private async ensureV2Token(): Promise<V2Token> {\n if (tokenNeedsRefresh(this.v2Token)) {\n if (this.v2Token?.refreshToken) {\n try {\n this.v2Token = await refreshAccessToken(this.v2Token.refreshToken);\n } catch {\n this.v2Token = await fetchAccessToken(this.config.v2Username, this.config.v2Password);\n }\n } else {\n this.v2Token = await fetchAccessToken(this.config.v2Username, this.config.v2Password);\n }\n }\n if (!this.v2Token) {\n throw new HydrawiseV2Error('No v2 token', 'unauthorized');\n }\n return this.v2Token;\n }\n\n private async v2Graphql(request: { query: string; variables?: Record<string, unknown> }): Promise<any> {\n const token = await this.ensureV2Token();\n try {\n return await graphqlRequest(token, request);\n } catch (error) {\n if (error instanceof HydrawiseV2Error && error.code === 401) {\n this.v2Token = await fetchAccessToken(this.config.v2Username, this.config.v2Password);\n return await graphqlRequest(this.v2Token, request);\n }\n throw error;\n }\n }\n\n private async v2Mutate(request: { query: string; variables?: Record<string, unknown> }): Promise<any> {\n const data = await this.v2Graphql(request);\n const failed = mutationErrorSummary(data);\n if (failed) {\n throw new HydrawiseV2Error(failed, 'mutation', data);\n }\n return data;\n }\n\n private async applyMappedNodes(nodes: MappedNode[], createObjects: boolean): Promise<void> {\n for (const node of nodes) {\n if (createObjects) {\n await this.setObjectNotExistsAsync(node.id, {\n type: node.type,\n common: node.common as any,\n native: {},\n });\n }\n if (node.type === 'state' && node.value !== undefined) {\n void this.setStateChangedAsync(node.id, node.value, true);\n }\n }\n }\n\n private async pollV2Status(): Promise<'ok' | 'rate-limit' | 'error'> {\n try {\n const data = await this.v2Graphql({ query: STATUS_QUERY });\n const controller = pickController(\n data?.me?.controllers,\n data?.me?.currentController?.id,\n this.v1ControllerId,\n );\n if (!controller) {\n this.log.warn('v2 status: no controller in response');\n void this.setStateChangedAsync('info.connectionV2', false, true);\n return 'error';\n }\n\n this.v2ControllerId = Number(controller.id);\n this.v2ZoneIds = zoneIdMap(controller);\n\n const nextKey = v2StructureKey(controller);\n const needObjects = !this.v2ObjectsReady || this.v2StructureSig !== nextKey;\n if (needObjects) {\n await this.applyMappedNodes(mapAllZoneCommandObjects(), true);\n for (const zone of controller.zones || []) {\n const n = zone?.number?.value;\n if (n !== undefined && n !== null) {\n await this.applyMappedNodes(mapZoneCommandObjects(n), true);\n }\n }\n this.v2StructureSig = nextKey;\n this.v2ObjectsReady = true;\n await this.subscribeV2Commands();\n }\n\n await this.applyMappedNodes(mapControllerStates(controller), needObjects);\n for (const zone of controller.zones || []) {\n await this.applyMappedNodes(mapZoneStates(zone), needObjects);\n }\n for (const sensor of controller.sensors || []) {\n await this.applyMappedNodes(mapSensorStates(sensor), needObjects);\n }\n\n void this.setStateChangedAsync('info.connectionV2', true, true);\n return 'ok';\n } catch (error: any) {\n this.v2LastRetryAfter = getRetryAfterSec(error);\n this.log.debug(`v2 status: ${error?.message || error}`);\n void this.setStateChangedAsync('info.connectionV2', false, true);\n return isRateLimitError(error) ? 'rate-limit' : 'error';\n }\n }\n\n private async pollV2Weather(): Promise<'ok' | 'rate-limit' | 'error'> {\n if (!this.v2ControllerId) {\n return 'error';\n }\n try {\n const data = await this.v2Graphql(weatherRequest(this.v2ControllerId));\n await this.applyMappedNodes(mapWeatherStates(data?.controller?.location?.forecast), true);\n return 'ok';\n } catch (error: any) {\n this.v2LastRetryAfter = getRetryAfterSec(error);\n this.log.debug(`v2 weather: ${error?.message || error}`);\n return isRateLimitError(error) ? 'rate-limit' : 'error';\n }\n }\n\n private async pollV2Water(period: WaterPeriod): Promise<'ok' | 'rate-limit' | 'error'> {\n if (!this.v2ControllerId) {\n return 'error';\n }\n try {\n const request = waterRequest(this.v2ControllerId, period);\n const data = await this.v2Graphql(request);\n const summary = computeWaterSummary(data?.controller);\n await this.applyMappedNodes(mapWaterStates(period, summary), true);\n return 'ok';\n } catch (error: any) {\n this.v2LastRetryAfter = getRetryAfterSec(error);\n this.log.debug(`v2 water (${period}): ${error?.message || error}`);\n return isRateLimitError(error) ? 'rate-limit' : 'error';\n }\n }\n\n private async handleV2Command(id: string, state: ioBroker.State): Promise<void> {\n if (!this.config.enableV2) {\n return;\n }\n if (!this.v2ControllerId) {\n this.log.warn('v2 command ignored: controller id is unknown');\n return;\n }\n\n try {\n let sent = false;\n const zoneMatch = id.match(/\\.zones\\.(\\d+)\\.(stopZone|runZone|suspendZone|runDefault)$/);\n\n if (id.endsWith('.zones.stopall')) {\n await this.v2Mutate(stopAllZonesMutation(this.v2ControllerId));\n sent = true;\n } else if (id.endsWith('.zones.runall') && (state.val || state.val === 0)) {\n await this.v2Mutate(startAllZonesMutation(this.v2ControllerId, Number(state.val)));\n sent = true;\n } else if (id.endsWith('.zones.suspendall') && (state.val || state.val === 0)) {\n const until = new Date(state.ts + Number(state.val) * 1000);\n await this.v2Mutate(suspendAllZonesMutation(this.v2ControllerId, until));\n sent = true;\n } else if (zoneMatch) {\n const zoneId = this.v2ZoneIds[zoneMatch[1]];\n if (!zoneId) {\n this.log.warn(`v2 command ignored: unknown zone ${zoneMatch[1]}`);\n return;\n }\n const action = zoneMatch[2];\n if (action === 'stopZone') {\n await this.v2Mutate(stopZoneMutation(zoneId));\n sent = true;\n } else if (action === 'runZone' && (state.val || state.val === 0)) {\n this.log.info(`v2 start zone ${zoneMatch[1]} for ${Number(state.val)}s`);\n await this.v2Mutate(startZoneMutation(zoneId, Number(state.val)));\n sent = true;\n } else if (action === 'runDefault' && state.val) {\n this.log.info(`v2 start zone ${zoneMatch[1]} with default duration`);\n await this.v2Mutate(startZoneMutation(zoneId));\n void this.setState(id, false, true);\n sent = true;\n } else if (action === 'suspendZone' && (state.val || state.val === 0)) {\n const until = new Date(state.ts + Number(state.val) * 1000);\n await this.v2Mutate(suspendZoneMutation(zoneId, until));\n sent = true;\n }\n }\n\n if (sent) {\n if (zoneMatch?.[2] !== 'runDefault') {\n void this.setState(id, state.val, true);\n }\n await this.pollV2Status();\n }\n } catch (error: any) {\n if (isRateLimitError(error)) {\n this.v2BackoffFails += 1;\n const delay = nextBackoffMs(this.v2BackoffFails, getRetryAfterSec(error));\n this.log.warn(`v2 command rate limited, delaying poll by ${Math.round(delay / 1000)}s`);\n this.scheduleV2Poll(delay);\n return;\n }\n this.log.error(`v2 command failed for ${id}: ${error?.message || error}`);\n }\n }\n\n name2id(pName: string): string {\n return sanitizeId(pName, this.FORBIDDEN_CHARS);\n }\n}\n\nif (require.main !== module) {\n // Export the constructor in compact mode\n module.exports = (options: Partial<utils.AdapterOptions> | undefined) => new Hydrawise(options);\n} else {\n // otherwise start the instance directly\n (() => new Hydrawise())();\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["/*\n * Created with @iobroker/create-adapter v2.3.0\n */\n\nimport * as utils from '@iobroker/adapter-core';\nimport {\n CUSTOMER_INTERVAL_MS,\n CUSTOMER_SKIP_KEYS,\n CUSTOMER_STAGGER_MS,\n HYDRAWISE_BASE_URL,\n SCHEDULE_SKIP_KEYS,\n buildHydrawiseUrl,\n getRetryAfterSec,\n instanceConnected,\n isRateLimitError,\n isScalarKey,\n name2id as sanitizeId,\n nextBackoffMs,\n parseRetryAfter,\n structureSignature,\n} from './lib/helpers';\nimport {\n HydrawiseV2Error,\n type V2Token,\n fetchAccessToken,\n graphqlRequest,\n refreshAccessToken,\n tokenNeedsRefresh,\n} from './lib/v2/auth';\nimport {\n type MappedNode,\n computeWaterSummary,\n mapAllZoneCommandObjects,\n mapControllerStates,\n mapSensorStates,\n mapWaterStates,\n mapWeatherStates,\n mapZoneCommandObjects,\n mapZoneStates,\n pickController,\n v2StructureKey,\n zoneIdMap,\n} from './lib/v2/mapper';\nimport {\n STATUS_QUERY,\n type WaterPeriod,\n nextLongPeriod,\n startAllZonesMutation,\n mutationErrorSummary,\n startZoneMutation,\n stopAllZonesMutation,\n stopZoneMutation,\n suspendAllZonesMutation,\n suspendZoneMutation,\n waterRequest,\n weatherRequest,\n} from './lib/v2/queries';\n\ninterface ApiResponse {\n status: number;\n data: any;\n}\n\nclass Hydrawise extends utils.Adapter {\n private pollScheduleTimer?: ioBroker.Interval;\n private pollCustomerTimer?: ioBroker.Timeout;\n private pollV2Timer?: ioBroker.Timeout;\n private resetSwitchTimer?: ioBroker.Timeout;\n private relays: Record<string, number> = {};\n private lastErrorCode: string | number = 0;\n private schedulePollRunning = false;\n private customerPollRunning = false;\n private scheduleStructureKey = '';\n private customerStructureKey = '';\n private scheduleObjectsReady = false;\n private customerObjectsReady = false;\n private customerBackoffFails = 0;\n private lastCustomerCallAt = 0;\n private v1ControllerId?: number;\n private v2Token: V2Token | null = null;\n private v2PollRunning = false;\n private v2BackoffFails = 0;\n private v2LongPeriodIndex = 0;\n private v2ControllerId?: number;\n private v2ZoneIds: Record<string, number> = {};\n private v2LastRetryAfter?: number;\n private v2StructureSig = '';\n private v2ObjectsReady = false;\n private v1Online = false;\n private v2Online = false;\n\n public constructor(options: Partial<utils.AdapterOptions> = {}) {\n super({\n ...options,\n name: 'hydrawise',\n });\n\n this.on('ready', this.onReady.bind(this));\n this.on('stateChange', this.onStateChange.bind(this));\n this.on('unload', this.onUnload.bind(this));\n }\n\n private isV1Enabled(): boolean {\n return this.config.enableV1 !== false;\n }\n\n private setV1Online(online: boolean): void {\n this.v1Online = online;\n this.syncInstanceConnection();\n }\n\n private setV2Online(online: boolean): void {\n this.v2Online = online;\n void this.setStateChangedAsync('info.connectionV2', online, true);\n this.syncInstanceConnection();\n }\n\n private syncInstanceConnection(): void {\n const connected = instanceConnected(this.isV1Enabled(), !!this.config.enableV2, this.v1Online, this.v2Online);\n void this.setStateChangedAsync('info.connection', connected, true);\n }\n\n private async onReady(): Promise<void> {\n const v1 = this.isV1Enabled();\n const v2 = !!this.config.enableV2;\n\n if (!v1 && !v2) {\n this.log.error('Neither v1 nor v2 API is enabled');\n return;\n }\n\n this.v1Online = false;\n this.v2Online = false;\n this.syncInstanceConnection();\n\n if (v1) {\n if (!this.config.apiKey) {\n this.log.error('No API-Key defined!');\n } else {\n await this.GetStatusSchedule();\n this.pollScheduleTimer = this.setInterval(() => {\n void this.GetStatusSchedule();\n }, this.config.apiInterval * 1000);\n\n this.scheduleCustomerPoll(CUSTOMER_STAGGER_MS);\n await this.subscribeV1Commands();\n }\n }\n\n if (v2) {\n if (!this.config.v2Username || !this.config.v2Password) {\n this.log.error('v2 API enabled but username or password is missing');\n } else {\n await this.subscribeV2Commands();\n this.setV2Online(false);\n this.scheduleV2Poll(0);\n }\n }\n }\n\n private async GetStatusSchedule(): Promise<void> {\n if (this.schedulePollRunning) {\n this.log.debug('Skipping overlapping status schedule poll');\n return;\n }\n\n this.schedulePollRunning = true;\n try {\n const response = await this.buildRequest('statusschedule.php', { api_key: this.config.apiKey });\n if (response.status !== 200) {\n return;\n }\n\n const content = response.data;\n this.setV1Online(true);\n\n const relayIds = (content.relays || []).map((r: any) => r.relay);\n const sensorInputs = (content.sensors || []).map((s: any) => s.input);\n const nextStructureKey = structureSignature(relayIds, sensorInputs);\n const needObjects = !this.scheduleObjectsReady || this.scheduleStructureKey !== nextStructureKey;\n\n if (needObjects) {\n await this.ensureScheduleObjects(content);\n this.scheduleStructureKey = nextStructureKey;\n this.scheduleObjectsReady = true;\n }\n\n this.updateScheduleStates(content);\n } catch (error: any) {\n this.log.debug(`(schedule) received error - API is now offline: ${error?.message || error}`);\n this.setV1Online(false);\n } finally {\n this.schedulePollRunning = false;\n }\n }\n\n private async ensureScheduleObjects(content: any): Promise<void> {\n await this.setObjectNotExistsAsync('schedule.stopall', {\n type: 'state',\n common: {\n name: {\n en: 'stop all zones',\n de: 'alle Zonen stoppen',\n ru: '\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C \u0432\u0441\u0435 \u0437\u043E\u043D\u044B',\n pt: 'parar todas as zonas',\n nl: 'stop alle zones',\n fr: 'arr\u00EAter toutes les zones',\n it: 'fermare tutte le zone',\n es: 'detener todas las zonas',\n pl: 'zatrzymuj\u0105 wszystkie strefy',\n uk: '\u0437\u0443\u043F\u0438\u043D\u0438\u0442\u0438 \u0432\u0441\u0456 \u0437\u043E\u043D\u0438',\n 'zh-cn': '\u505C\u6B62\u6240\u6709\u5730\u533A',\n },\n type: 'boolean',\n role: 'button.stop',\n read: false,\n write: true,\n },\n native: {},\n });\n\n await this.setObjectNotExistsAsync('schedule.runall', {\n type: 'state',\n common: {\n name: {\n en: 'run all zones for x seconds',\n de: 'alle Zonen f\u00FCr x Sekunden ausf\u00FChren',\n ru: '\u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u0432\u0441\u0435 \u0437\u043E\u043D\u044B \u0437\u0430 x \u0441\u0435\u043A\u0443\u043D\u0434\u044B',\n pt: 'executar todas as zonas por x segundos',\n nl: 'ren alle zones voor x seconden',\n fr: 'ex\u00E9cuter toutes les zones pendant x secondes',\n it: 'eseguire tutte le zone per x secondi',\n es: 'ejecutar todas las zonas durante x segundos',\n pl: 'wszystkie strefy startuj\u0105 dla x sekundy',\n uk: '\u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u0438 \u0432\u0441\u0456 \u0437\u043E\u043D\u0438 \u0434\u043B\u044F x \u0441\u0435\u043A\u0443\u043D\u0434',\n 'zh-cn': '\u8DD1\u9053\u533A',\n },\n type: 'number',\n role: 'level',\n unit: 'seconds',\n read: true,\n write: true,\n },\n native: {},\n });\n\n await this.setObjectNotExistsAsync('schedule.suspendall', {\n type: 'state',\n common: {\n name: {\n en: 'suspend all zones for x seconds',\n de: 'alle Zonen f\u00FCr x Sekunden aussetzen',\n ru: '\u043F\u0440\u0438\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C \u0432\u0441\u0435 \u0437\u043E\u043D\u044B \u0437\u0430 x \u0441\u0435\u043A\u0443\u043D\u0434\u044B',\n pt: 'suspender todas as zonas por x segundos',\n nl: 'vertaling:',\n fr: 'suspendre toutes les zones pendant x secondes',\n it: 'sospendere tutte le zone per x secondi',\n es: 'suspender todas las zonas durante x segundos',\n pl: 'wszystkie strefy zawieszenia dla x sekundy',\n uk: '\u043F\u0440\u0438\u0437\u0443\u043F\u0438\u043D\u0438\u0442\u0438 \u0432\u0441\u0456 \u0437\u043E\u043D\u0438 \u043D\u0430 x \u0441\u0435\u043A\u0443\u043D\u0434',\n 'zh-cn': '\u505C\u6B62\u6240\u6709\u00D7\u4E8C\u533A',\n },\n type: 'number',\n role: 'level',\n read: true,\n write: true,\n },\n native: {},\n });\n\n for (const rawKey of Object.keys(content)) {\n const key = this.name2id(rawKey);\n if (!isScalarKey(key, SCHEDULE_SKIP_KEYS)) {\n continue;\n }\n\n await this.setObjectNotExistsAsync(`schedule.${key}`, {\n type: 'state',\n common: {\n name: key,\n type: key === 'message' ? 'string' : 'number',\n role: key === 'message' ? 'text' : 'value',\n read: true,\n write: false,\n },\n native: {},\n });\n\n if (key === 'time') {\n await this.setObjectNotExistsAsync('schedule.timestr', {\n type: 'state',\n common: {\n name: 'last api call',\n type: 'string',\n role: 'text',\n read: true,\n write: false,\n },\n native: {},\n });\n }\n }\n\n for (const relay of content.relays || []) {\n await this.setObjectNotExistsAsync(`schedule.${relay.relay}`, {\n type: 'channel',\n common: {\n name: String(relay.relay),\n },\n native: {},\n });\n\n for (const rawKey of Object.keys(relay)) {\n const key = this.name2id(rawKey);\n await this.setObjectNotExistsAsync(`schedule.${relay.relay}.${key}`, {\n type: 'state',\n common: {\n name: key,\n type: key === 'name' || key === 'timestr' ? 'string' : 'number',\n role: key === 'name' || key === 'timestr' ? 'text' : 'value',\n read: true,\n write: false,\n },\n native: {},\n });\n }\n\n await this.setObjectNotExistsAsync(`schedule.${relay.relay}.stopZone`, {\n type: 'state',\n common: {\n name: {\n en: 'stop zone',\n de: 'Zone stoppen',\n ru: '\u0437\u043E\u043D\u0430 \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0438',\n pt: 'zona de paragem',\n nl: 'stop zone',\n fr: \"zone d ' arr\u00EAt\",\n it: 'zona di sosta',\n es: 'zona de parada',\n pl: 'strefa stopu',\n uk: '\u0437\u043E\u043D\u0430 \u0437\u0443\u043F\u0438\u043D\u043A\u0438',\n 'zh-cn': '\u505C\u6B62\u5730\u533A',\n },\n type: 'boolean',\n role: 'button.stop',\n read: false,\n write: true,\n },\n native: {},\n });\n\n await this.setObjectNotExistsAsync(`schedule.${relay.relay}.runZone`, {\n type: 'state',\n common: {\n name: {\n en: 'run zone for x seconds',\n de: 'Zone f\u00FCr x Sekunden starten',\n ru: '\u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u0437\u043E\u043D\u0443 \u0437\u0430 x \u0441\u0435\u043A\u0443\u043D\u0434\u044B',\n pt: 'zona de execu\u00E7\u00E3o por x segundos',\n nl: 'ren zone voor x seconden',\n fr: 'zone de course pour x secondes',\n it: 'zona di corsa per x secondi',\n es: 'zona de ejecuci\u00F3n por x segundos',\n pl: 'strefa x sekundy',\n uk: '\u0437\u043E\u043D\u0430 \u0437\u0430\u043F\u0443\u0441\u043A\u0443 \u0434\u043B\u044F x \u0441\u0435\u043A\u0443\u043D\u0434',\n 'zh-cn': '\u00D7\u4E8C\u533A',\n },\n type: 'number',\n role: 'level',\n read: true,\n write: true,\n },\n native: {},\n });\n\n await this.setObjectNotExistsAsync(`schedule.${relay.relay}.suspendZone`, {\n type: 'state',\n common: {\n name: {\n en: 'suspend zone for x seconds',\n de: 'Zone f\u00FCr x Sekunden aussetzen',\n ru: '\u043F\u0440\u0438\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C \u0437\u043E\u043D\u0443 \u043D\u0430 x \u0441\u0435\u043A\u0443\u043D\u0434\u044B',\n pt: 'zona de suspens\u00E3o por x segundos',\n nl: 'quality over quantity (qoq) releases vertaling:',\n fr: 'zone de suspension pour x secondes',\n it: 'zona di sospensione per x secondi',\n es: 'zona de suspensi\u00F3n por x segundos',\n pl: 'strefa zawies\u0142a na x sekundy',\n uk: '\u0437\u043E\u043D\u0430 \u043F\u0456\u0434\u0432\u0456\u0441\u043A\u0438 \u0434\u043B\u044F x \u0441\u0435\u043A\u0443\u043D\u0434',\n 'zh-cn': '\u505C\u6B62x\u4E8C\u533A',\n },\n type: 'number',\n role: 'level',\n read: true,\n write: true,\n },\n native: {},\n });\n\n await this.setObjectNotExistsAsync(`schedule.${relay.relay}.runDefault`, {\n type: 'state',\n common: {\n name: {\n en: 'run zone for default time',\n de: 'Zone mit Standardlaufzeit starten',\n ru: '\u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u0437\u043E\u043D\u0443 \u0434\u043B\u044F \u0432\u0440\u0435\u043C\u0435\u043D\u0438 \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E',\n pt: 'fuso de execu\u00E7\u00E3o para o tempo padr\u00E3o',\n nl: 'run zone for default time',\n fr: 'run zone for default time',\n it: 'run zone per il tempo predefinito',\n es: 'zona de ejecuci\u00F3n por tiempo predeterminado',\n pl: 'strefa czasu domy\u015Blnego',\n uk: '\u0437\u043E\u043D\u0430 \u0437\u0430\u043F\u0443\u0441\u043A\u0443 \u0437\u0430 \u0437\u0430\u043C\u043E\u0432\u0447\u0443\u0432\u0430\u043D\u043D\u044F\u043C',\n 'zh-cn': 'a. \u6682\u505C\u65F6\u95F4\u533A',\n },\n type: 'boolean',\n role: 'button.start',\n read: true,\n write: true,\n },\n native: {},\n });\n }\n\n for (const sensor of content.sensors || []) {\n await this.setObjectNotExistsAsync(`schedule.sensors.${sensor.input}`, {\n type: 'channel',\n common: {\n name: 'sensors',\n },\n native: {},\n });\n\n for (const rawKey of Object.keys(sensor)) {\n if (rawKey === 'relays') {\n continue;\n }\n const key = this.name2id(rawKey);\n await this.setObjectNotExistsAsync(`schedule.sensors.${sensor.input}.${key}`, {\n type: 'state',\n common: {\n name: key,\n type: 'number',\n role: 'value',\n read: true,\n write: false,\n },\n native: {},\n });\n }\n }\n }\n\n private updateScheduleStates(content: any): void {\n for (const rawKey of Object.keys(content)) {\n const key = this.name2id(rawKey);\n if (!isScalarKey(key, SCHEDULE_SKIP_KEYS)) {\n continue;\n }\n\n void this.setStateChangedAsync(`schedule.${key}`, content[rawKey], true);\n\n if (key === 'time') {\n const t = new Date(content[rawKey] * 1000);\n void this.setStateChangedAsync('schedule.timestr', t.toString(), true);\n }\n }\n\n for (const relay of content.relays || []) {\n this.relays[relay.relay] = relay.relay_id;\n\n for (const rawKey of Object.keys(relay)) {\n const key = this.name2id(rawKey);\n let value = relay[rawKey];\n\n if (key === 'timestr') {\n const t = new Date();\n t.setSeconds(t.getSeconds() + relay.time);\n value = t.toString();\n }\n\n void this.setStateChangedAsync(`schedule.${relay.relay}.${key}`, value, true);\n }\n }\n\n for (const sensor of content.sensors || []) {\n for (const rawKey of Object.keys(sensor)) {\n if (rawKey === 'relays') {\n continue;\n }\n const key = this.name2id(rawKey);\n void this.setStateChangedAsync(`schedule.sensors.${sensor.input}.${key}`, sensor[rawKey], true);\n }\n }\n }\n\n private scheduleCustomerPoll(delayMs: number): void {\n if (this.pollCustomerTimer) {\n this.clearTimeout(this.pollCustomerTimer);\n }\n this.pollCustomerTimer = this.setTimeout(\n () => {\n this.pollCustomerTimer = undefined;\n void this.runCustomerPoll();\n },\n Math.max(0, delayMs),\n );\n }\n\n private async runCustomerPoll(): Promise<void> {\n const delay = await this.GetCustomerDetails();\n this.scheduleCustomerPoll(delay);\n }\n\n private async GetCustomerDetails(): Promise<number> {\n if (this.customerPollRunning) {\n this.log.debug('Skipping overlapping customer details poll');\n return CUSTOMER_INTERVAL_MS;\n }\n\n const elapsed = this.lastCustomerCallAt ? Date.now() - this.lastCustomerCallAt : CUSTOMER_INTERVAL_MS;\n if (this.lastCustomerCallAt && elapsed < CUSTOMER_INTERVAL_MS) {\n this.log.debug('Skipping customer details poll (inside 5 minute window)');\n return CUSTOMER_INTERVAL_MS - elapsed;\n }\n\n this.customerPollRunning = true;\n this.lastCustomerCallAt = Date.now();\n try {\n const response = await this.buildRequest('customerdetails.php', { api_key: this.config.apiKey });\n if (response.status !== 200) {\n return CUSTOMER_INTERVAL_MS;\n }\n\n const content = response.data;\n this.customerBackoffFails = 0;\n if (content?.controller_id != null) {\n this.v1ControllerId = Number(content.controller_id);\n }\n\n const controllerNames = (content.controllers || []).map((c: any) => this.name2id(c.name));\n const nextStructureKey = structureSignature([], [], controllerNames);\n const needObjects = !this.customerObjectsReady || this.customerStructureKey !== nextStructureKey;\n\n if (needObjects) {\n await this.ensureCustomerObjects(content);\n this.customerStructureKey = nextStructureKey;\n this.customerObjectsReady = true;\n }\n\n this.updateCustomerStates(content);\n return CUSTOMER_INTERVAL_MS;\n } catch (error: any) {\n if (isRateLimitError(error)) {\n this.customerBackoffFails += 1;\n const delay = nextBackoffMs(this.customerBackoffFails, getRetryAfterSec(error));\n this.log.warn(\n `customerdetails rate limited, next poll in ${Math.round(delay / 1000)}s (attempt ${this.customerBackoffFails})`,\n );\n return delay;\n }\n\n this.log.debug(`(customer) received error: ${error?.message || error}`);\n return CUSTOMER_INTERVAL_MS;\n } finally {\n this.customerPollRunning = false;\n }\n }\n\n private async ensureCustomerObjects(content: any): Promise<void> {\n for (const rawKey of Object.keys(content)) {\n if (!isScalarKey(rawKey, CUSTOMER_SKIP_KEYS)) {\n continue;\n }\n const key = this.name2id(rawKey);\n\n await this.setObjectNotExistsAsync(`customer.${key}`, {\n type: 'state',\n common: {\n name: key,\n type: key === 'message' || key === 'current_controller' ? 'string' : 'number',\n role: key === 'message' || key === 'current_controller' ? 'text' : 'value',\n read: true,\n write: false,\n },\n native: {},\n });\n }\n\n for (const controller of content.controllers || []) {\n const controllerId = this.name2id(controller.name);\n await this.setObjectNotExistsAsync(`customer.controllers.${controllerId}`, {\n type: 'channel',\n common: {\n name: controller.name,\n },\n native: {},\n });\n\n for (const rawKey of Object.keys(controller)) {\n const key = this.name2id(rawKey);\n await this.setObjectNotExistsAsync(`customer.controllers.${controllerId}.${key}`, {\n type: 'state',\n common: {\n name: key,\n type: key !== 'controller_id' ? 'string' : 'number',\n role: key !== 'controller_id' ? 'text' : 'value',\n read: true,\n write: false,\n },\n native: {},\n });\n }\n }\n }\n\n private updateCustomerStates(content: any): void {\n for (const rawKey of Object.keys(content)) {\n if (!isScalarKey(rawKey, CUSTOMER_SKIP_KEYS)) {\n continue;\n }\n const key = this.name2id(rawKey);\n void this.setStateChangedAsync(`customer.${key}`, content[rawKey], true);\n }\n\n for (const controller of content.controllers || []) {\n const controllerId = this.name2id(controller.name);\n\n for (const rawKey of Object.keys(controller)) {\n const key = this.name2id(rawKey);\n let value = controller[rawKey];\n\n if (key === 'last_contact') {\n const t = new Date(controller[rawKey] * 1000);\n value = t.toString();\n }\n\n void this.setStateChangedAsync(`customer.controllers.${controllerId}.${key}`, value, true);\n }\n }\n }\n\n /**\n * Perform a GET request against the Hydrawise API.\n *\n * @param service endpoint file name\n * @param params query parameters\n */\n async buildRequest(service: string, params: Record<string, string | number | boolean>): Promise<ApiResponse> {\n if (!params.api_key) {\n throw new Error('API key is not configured');\n }\n\n const url = buildHydrawiseUrl(service, params);\n\n try {\n const response = await fetch(url, {\n method: 'GET',\n signal: AbortSignal.timeout(30_000),\n headers: { Accept: 'application/json' },\n });\n\n this.lastErrorCode = 0;\n\n let data: any = null;\n const text = await response.text();\n if (text) {\n try {\n data = JSON.parse(text);\n } catch {\n data = text;\n }\n }\n\n if (!response.ok) {\n this.log.warn(\n `received ${response.status} response from /api/v1/${service} with content: ${JSON.stringify(data)}`,\n );\n throw Object.assign(new Error(`HTTP ${response.status}`), {\n code: response.status,\n retryAfter: parseRetryAfter(response.headers.get('Retry-After')),\n response: { status: response.status, data },\n });\n }\n\n return { status: response.status, data };\n } catch (error: any) {\n if (error?.response) {\n // already logged above for HTTP errors\n } else if (error?.name === 'AbortError' || error?.name === 'TimeoutError') {\n const code = 'ECONNABORTED';\n if (code === this.lastErrorCode) {\n this.log.debug(`timeout from ${HYDRAWISE_BASE_URL}/api/v1/${service}`);\n } else {\n this.log.info(`error ${code} from /api/v1/${service}: request timed out`);\n this.lastErrorCode = code;\n }\n throw Object.assign(new Error('request timed out'), { code });\n } else if (error?.message) {\n const code = error.code || error.cause?.code || 'ENOTFOUND';\n if (code === this.lastErrorCode) {\n this.log.debug(error.message);\n } else {\n this.log.info(`error ${code} from /api/v1/${service}: ${error.message}`);\n this.lastErrorCode = code;\n }\n } else {\n this.log.error(String(error));\n }\n\n throw error instanceof Error ? error : new Error(String(error));\n }\n }\n\n /**\n * Is called when adapter shuts down - callback has to be called under any circumstances!\n *\n * @param callback callback function\n */\n private onUnload(callback: () => void): void {\n try {\n if (this.pollScheduleTimer) {\n this.clearInterval(this.pollScheduleTimer);\n this.pollScheduleTimer = undefined;\n }\n if (this.pollCustomerTimer) {\n this.clearTimeout(this.pollCustomerTimer);\n this.pollCustomerTimer = undefined;\n }\n if (this.pollV2Timer) {\n this.clearTimeout(this.pollV2Timer);\n this.pollV2Timer = undefined;\n }\n if (this.resetSwitchTimer) {\n this.clearTimeout(this.resetSwitchTimer);\n this.resetSwitchTimer = undefined;\n }\n\n callback();\n } catch {\n callback();\n }\n }\n\n /**\n * Is called if a subscribed state changes\n *\n * @param id id of the state\n * @param state state object\n */\n private onStateChange(id: string, state: ioBroker.State | null | undefined): void {\n if (!state) {\n return;\n }\n if (state.ack && state.from === `system.adapter.${this.namespace}`) {\n return;\n }\n void this.handleStateChange(id, state);\n }\n\n private async handleStateChange(id: string, state: ioBroker.State): Promise<void> {\n try {\n if (id.includes('.zones.')) {\n await this.handleV2Command(id, state);\n return;\n }\n if (!this.isV1Enabled() || !id.includes('.schedule.')) {\n return;\n }\n\n let commandSent = false;\n\n if (id.includes('stopall')) {\n await this.buildRequest('setzone.php', { api_key: this.config.apiKey, action: 'stopall' });\n commandSent = true;\n } else if (id.includes('stopZone')) {\n const relay = id.match(/.*schedule\\.(.*)\\.stopZone/);\n if (relay && relay.length > 1) {\n await this.buildRequest('setzone.php', {\n api_key: this.config.apiKey,\n action: 'stop',\n relay_id: this.relays[relay[1]],\n });\n commandSent = true;\n }\n }\n\n if (id.includes('runall') && (state.val || state.val === 0)) {\n await this.buildRequest('setzone.php', {\n api_key: this.config.apiKey,\n action: 'runall',\n period_id: 999,\n custom: state.val,\n });\n commandSent = true;\n } else if (id.includes('runZone') && (state.val || state.val === 0)) {\n const relay = id.match(/.*schedule\\.(.*)\\.runZone/);\n if (relay && relay.length > 1) {\n await this.buildRequest('setzone.php', {\n api_key: this.config.apiKey,\n action: 'run',\n period_id: 999,\n custom: state.val,\n relay_id: this.relays[relay[1]],\n });\n commandSent = true;\n }\n }\n\n if (id.includes('runDefault') && state.val !== null) {\n await this.initRunDefault(id, state.val as boolean);\n commandSent = true;\n }\n\n if (id.includes('suspendall') && (state.val || state.val === 0)) {\n const num = state.val as number;\n await this.buildRequest('setzone.php', {\n api_key: this.config.apiKey,\n action: 'suspendall',\n period_id: 999,\n custom: Math.trunc((state.ts + num) / 1000),\n });\n commandSent = true;\n } else if (id.includes('suspendZone') && (state.val || state.val === 0)) {\n const num = state.val as number;\n const relay = id.match(/.*schedule\\.(.*)\\.suspendZone/);\n if (relay && relay.length > 1) {\n await this.buildRequest('setzone.php', {\n api_key: this.config.apiKey,\n action: 'suspend',\n period_id: 999,\n custom: Math.trunc((state.ts + num) / 1000),\n relay_id: this.relays[relay[1]],\n });\n commandSent = true;\n }\n }\n\n if (commandSent && !id.includes('runDefault')) {\n await this.GetStatusSchedule();\n }\n } catch (error: any) {\n this.log.error(`Command failed for ${id}: ${error?.message || error}`);\n }\n }\n\n async initRunDefault(id: string, run: boolean): Promise<void> {\n const relay = id.match(/(.*schedule.*\\.)runDefault/);\n\n if (this.resetSwitchTimer) {\n this.clearTimeout(this.resetSwitchTimer);\n this.resetSwitchTimer = undefined;\n }\n\n if (relay) {\n if (run) {\n const defaultRunTime = await this.getStateAsync(`${relay[1]}run`);\n if (defaultRunTime && defaultRunTime.val) {\n void this.setState(`${relay[1]}runZone`, defaultRunTime.val, false);\n this.resetSwitchTimer = this.setTimeout(\n () => {\n void this.setState(id, false, true);\n this.resetSwitchTimer = undefined;\n },\n (defaultRunTime.val as number) * 1000,\n );\n }\n } else {\n void this.setState(`${relay[1]}stopZone`, true, false);\n }\n }\n }\n\n private async subscribeV1Commands(): Promise<void> {\n await this.subscribeStatesAsync('schedule.stopall');\n await this.subscribeStatesAsync('schedule.runall');\n await this.subscribeStatesAsync('schedule.suspendall');\n await this.subscribeStatesAsync('schedule.*.stopZone');\n await this.subscribeStatesAsync('schedule.*.runZone');\n await this.subscribeStatesAsync('schedule.*.suspendZone');\n await this.subscribeStatesAsync('schedule.*.runDefault');\n }\n\n private async subscribeV2Commands(): Promise<void> {\n await this.subscribeStatesAsync('zones.stopall');\n await this.subscribeStatesAsync('zones.runall');\n await this.subscribeStatesAsync('zones.suspendall');\n await this.subscribeStatesAsync('zones.*.stopZone');\n await this.subscribeStatesAsync('zones.*.runZone');\n await this.subscribeStatesAsync('zones.*.suspendZone');\n await this.subscribeStatesAsync('zones.*.runDefault');\n }\n\n private v2IntervalMs(): number {\n return Math.max(120, Number(this.config.apiIntervalV2) || 300) * 1000;\n }\n\n private scheduleV2Poll(delayMs: number): void {\n if (this.pollV2Timer) {\n this.clearTimeout(this.pollV2Timer);\n }\n this.pollV2Timer = this.setTimeout(\n () => {\n this.pollV2Timer = undefined;\n void this.runV2Poll();\n },\n Math.max(0, delayMs),\n );\n }\n\n private async runV2Poll(statusOnly = false): Promise<void> {\n if (this.v2PollRunning) {\n this.log.debug('Skipping overlapping v2 poll');\n return;\n }\n\n this.v2PollRunning = true;\n let delay = this.v2IntervalMs();\n try {\n const statusResult = await this.pollV2Status();\n if (statusResult === 'rate-limit') {\n this.v2BackoffFails += 1;\n delay = nextBackoffMs(this.v2BackoffFails, this.v2LastRetryAfter);\n this.log.warn(`v2 rate limited, next poll in ${Math.round(delay / 1000)}s`);\n return;\n }\n\n if (statusOnly || !this.v2ControllerId) {\n return;\n }\n\n const weatherResult = await this.pollV2Weather();\n if (weatherResult === 'rate-limit') {\n this.v2BackoffFails += 1;\n delay = nextBackoffMs(this.v2BackoffFails, this.v2LastRetryAfter);\n this.log.warn(`v2 rate limited, next poll in ${Math.round(delay / 1000)}s`);\n return;\n }\n\n const todayResult = await this.pollV2Water('today');\n if (todayResult === 'rate-limit') {\n this.v2BackoffFails += 1;\n delay = nextBackoffMs(this.v2BackoffFails, this.v2LastRetryAfter);\n this.log.warn(`v2 rate limited, next poll in ${Math.round(delay / 1000)}s`);\n return;\n }\n\n const { period, nextIndex } = nextLongPeriod(this.v2LongPeriodIndex);\n const longResult = await this.pollV2Water(period);\n if (longResult === 'rate-limit') {\n this.v2BackoffFails += 1;\n delay = nextBackoffMs(this.v2BackoffFails, this.v2LastRetryAfter);\n this.log.warn(`v2 rate limited, next poll in ${Math.round(delay / 1000)}s`);\n return;\n }\n this.v2LongPeriodIndex = nextIndex;\n this.v2BackoffFails = 0;\n } finally {\n this.v2PollRunning = false;\n if (!statusOnly) {\n this.scheduleV2Poll(delay);\n }\n }\n }\n\n private async ensureV2Token(): Promise<V2Token> {\n if (tokenNeedsRefresh(this.v2Token)) {\n if (this.v2Token?.refreshToken) {\n try {\n this.v2Token = await refreshAccessToken(this.v2Token.refreshToken);\n } catch {\n this.v2Token = await fetchAccessToken(this.config.v2Username, this.config.v2Password);\n }\n } else {\n this.v2Token = await fetchAccessToken(this.config.v2Username, this.config.v2Password);\n }\n }\n if (!this.v2Token) {\n throw new HydrawiseV2Error('No v2 token', 'unauthorized');\n }\n return this.v2Token;\n }\n\n private async v2Graphql(request: { query: string; variables?: Record<string, unknown> }): Promise<any> {\n const token = await this.ensureV2Token();\n try {\n return await graphqlRequest(token, request);\n } catch (error) {\n if (error instanceof HydrawiseV2Error && error.code === 401) {\n this.v2Token = await fetchAccessToken(this.config.v2Username, this.config.v2Password);\n return await graphqlRequest(this.v2Token, request);\n }\n throw error;\n }\n }\n\n private async v2Mutate(request: { query: string; variables?: Record<string, unknown> }): Promise<any> {\n const data = await this.v2Graphql(request);\n const failed = mutationErrorSummary(data);\n if (failed) {\n throw new HydrawiseV2Error(failed, 'mutation', data);\n }\n return data;\n }\n\n private async applyMappedNodes(nodes: MappedNode[], createObjects: boolean): Promise<void> {\n for (const node of nodes) {\n if (createObjects) {\n await this.setObjectNotExistsAsync(node.id, {\n type: node.type,\n common: node.common as any,\n native: {},\n });\n }\n if (node.type === 'state' && node.value !== undefined) {\n void this.setStateChangedAsync(node.id, node.value, true);\n }\n }\n }\n\n private async pollV2Status(): Promise<'ok' | 'rate-limit' | 'error'> {\n try {\n const data = await this.v2Graphql({ query: STATUS_QUERY });\n const controller = pickController(\n data?.me?.controllers,\n data?.me?.currentController?.id,\n this.v1ControllerId,\n );\n if (!controller) {\n this.log.warn('v2 status: no controller in response');\n this.setV2Online(false);\n return 'error';\n }\n\n this.v2ControllerId = Number(controller.id);\n this.v2ZoneIds = zoneIdMap(controller);\n\n const nextKey = v2StructureKey(controller);\n const needObjects = !this.v2ObjectsReady || this.v2StructureSig !== nextKey;\n if (needObjects) {\n await this.applyMappedNodes(mapAllZoneCommandObjects(), true);\n for (const zone of controller.zones || []) {\n const n = zone?.number?.value;\n if (n !== undefined && n !== null) {\n await this.applyMappedNodes(mapZoneCommandObjects(n), true);\n }\n }\n this.v2StructureSig = nextKey;\n this.v2ObjectsReady = true;\n await this.subscribeV2Commands();\n }\n\n await this.applyMappedNodes(mapControllerStates(controller), needObjects);\n for (const zone of controller.zones || []) {\n await this.applyMappedNodes(mapZoneStates(zone), needObjects);\n }\n for (const sensor of controller.sensors || []) {\n await this.applyMappedNodes(mapSensorStates(sensor), needObjects);\n }\n\n this.setV2Online(true);\n return 'ok';\n } catch (error: any) {\n this.v2LastRetryAfter = getRetryAfterSec(error);\n this.log.debug(`v2 status: ${error?.message || error}`);\n this.setV2Online(false);\n return isRateLimitError(error) ? 'rate-limit' : 'error';\n }\n }\n\n private async pollV2Weather(): Promise<'ok' | 'rate-limit' | 'error'> {\n if (!this.v2ControllerId) {\n return 'error';\n }\n try {\n const data = await this.v2Graphql(weatherRequest(this.v2ControllerId));\n await this.applyMappedNodes(mapWeatherStates(data?.controller?.location?.forecast), true);\n return 'ok';\n } catch (error: any) {\n this.v2LastRetryAfter = getRetryAfterSec(error);\n this.log.debug(`v2 weather: ${error?.message || error}`);\n return isRateLimitError(error) ? 'rate-limit' : 'error';\n }\n }\n\n private async pollV2Water(period: WaterPeriod): Promise<'ok' | 'rate-limit' | 'error'> {\n if (!this.v2ControllerId) {\n return 'error';\n }\n try {\n const request = waterRequest(this.v2ControllerId, period);\n const data = await this.v2Graphql(request);\n const summary = computeWaterSummary(data?.controller);\n await this.applyMappedNodes(mapWaterStates(period, summary), true);\n return 'ok';\n } catch (error: any) {\n this.v2LastRetryAfter = getRetryAfterSec(error);\n this.log.debug(`v2 water (${period}): ${error?.message || error}`);\n return isRateLimitError(error) ? 'rate-limit' : 'error';\n }\n }\n\n private async handleV2Command(id: string, state: ioBroker.State): Promise<void> {\n if (!this.config.enableV2) {\n return;\n }\n if (!this.v2ControllerId) {\n this.log.warn('v2 command ignored: controller id is unknown');\n return;\n }\n\n try {\n let sent = false;\n const zoneMatch = id.match(/\\.zones\\.(\\d+)\\.(stopZone|runZone|suspendZone|runDefault)$/);\n\n if (id.endsWith('.zones.stopall')) {\n await this.v2Mutate(stopAllZonesMutation(this.v2ControllerId));\n sent = true;\n } else if (id.endsWith('.zones.runall') && (state.val || state.val === 0)) {\n await this.v2Mutate(startAllZonesMutation(this.v2ControllerId, Number(state.val)));\n sent = true;\n } else if (id.endsWith('.zones.suspendall') && (state.val || state.val === 0)) {\n const until = new Date(state.ts + Number(state.val) * 1000);\n await this.v2Mutate(suspendAllZonesMutation(this.v2ControllerId, until));\n sent = true;\n } else if (zoneMatch) {\n const zoneId = this.v2ZoneIds[zoneMatch[1]];\n if (!zoneId) {\n this.log.warn(`v2 command ignored: unknown zone ${zoneMatch[1]}`);\n return;\n }\n const action = zoneMatch[2];\n if (action === 'stopZone') {\n await this.v2Mutate(stopZoneMutation(zoneId));\n sent = true;\n } else if (action === 'runZone' && (state.val || state.val === 0)) {\n this.log.info(`v2 start zone ${zoneMatch[1]} for ${Number(state.val)}s`);\n await this.v2Mutate(startZoneMutation(zoneId, Number(state.val)));\n sent = true;\n } else if (action === 'runDefault' && state.val) {\n this.log.info(`v2 start zone ${zoneMatch[1]} with default duration`);\n await this.v2Mutate(startZoneMutation(zoneId));\n void this.setState(id, false, true);\n sent = true;\n } else if (action === 'suspendZone' && (state.val || state.val === 0)) {\n const until = new Date(state.ts + Number(state.val) * 1000);\n await this.v2Mutate(suspendZoneMutation(zoneId, until));\n sent = true;\n }\n }\n\n if (sent) {\n if (zoneMatch?.[2] !== 'runDefault') {\n void this.setState(id, state.val, true);\n }\n await this.pollV2Status();\n }\n } catch (error: any) {\n if (isRateLimitError(error)) {\n this.v2BackoffFails += 1;\n const delay = nextBackoffMs(this.v2BackoffFails, getRetryAfterSec(error));\n this.log.warn(`v2 command rate limited, delaying poll by ${Math.round(delay / 1000)}s`);\n this.scheduleV2Poll(delay);\n return;\n }\n this.log.error(`v2 command failed for ${id}: ${error?.message || error}`);\n }\n }\n\n name2id(pName: string): string {\n return sanitizeId(pName, this.FORBIDDEN_CHARS);\n }\n}\n\nif (require.main !== module) {\n // Export the constructor in compact mode\n module.exports = (options: Partial<utils.AdapterOptions> | undefined) => new Hydrawise(options);\n} else {\n // otherwise start the instance directly\n (() => new Hydrawise())();\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["sanitizeId"]
|
|
7
7
|
}
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "hydrawise",
|
|
4
|
-
"version": "2.0.
|
|
4
|
+
"version": "2.0.2",
|
|
5
5
|
"news": {
|
|
6
|
+
"2.0.2": {
|
|
7
|
+
"en": "**FIXED**: Admin jsonConfig responsive sizes for repository checker\n**FIXED**: Admin jsonConfig uses `i18n: true` with translation files\n**FIXED**: HTTP request timeouts via AbortSignal.timeout instead of global setTimeout",
|
|
8
|
+
"de": "**REPARIERT**: Admin-JSONConfig-Größen für Repository-Checker\n**REPARIERT**: Admin JSONConfig benutzt `i18n: true` bei Übersetzungen\n**REPARIERT**: HTTP-Abfrage-Timeout über AbortSignal.Timeout statt global setTimeout",
|
|
9
|
+
"ru": "**ИСПРАВЛЕНО**: адаптивные размеры администратора JSONConfig для средства проверки репозитория\n**ИСПРАВЛЕНО**: администратор JSONConfig использует `i18n: true` с файлами переводов\n**ИСПРАВЛЕНО**: тайм-ауты HTTP-запроса через AbortSignal.Timeout вместо глобального setTimeout",
|
|
10
|
+
"pt": "**CORRIGIDO**: Admin JSONConfig responsivo para repositório\n**CORRIGIDO**: Admin JSONConfig usa `i18n: true` com tradução\n**CORRIGIDO**: Tempo limite de requisição HTTP via abortSignal.timeout em vez de setTimeout",
|
|
11
|
+
"nl": "**FIXED**: responsieve groottes van Admin JSONConfig voor de repository-checker\n**FIXED**: Admin JSONConfig gebruikt `i18n: true` voor vertaalbestanden\n**FIXED**: time-outs voor HTTP-aanvragen via AbortSignal.Timeout in plaats van globale setTimeout",
|
|
12
|
+
"fr": "**CORRIGÉ** : Admin JSONConfig responsive sizes for Repository Checker\n**CORRIGÉ** : Admin JSONConfig utilise « i18n : true » pour les traductions\n**CORRIGÉ** : délai d'attente des requêtes HTTP via AbortSignal.Timeout au lieu du setTimeout global",
|
|
13
|
+
"it": "**FISSO**: Admin JsonConfig reattivo per Repository Checker\n**FISSO**: Admin JSONConfig usa `i18n: true` con i file di traduzione\n**FISSO**: timeout delle richieste HTTP via AbortSignal.timeout anziché globale",
|
|
14
|
+
"es": "**FIJO**: Tamaños de respuesta de Admin JsonConfig para el comprobador de repositorios\n**CORREGIDO**: Admin JSONConfig usa `i18n: true` con los archivos de traducción\n**FIJO**: La solicitud HTTP se agota mediante AbortSignal.Timeout en lugar de setTimeout global",
|
|
15
|
+
"pl": "**NAPRAWIONO**: Responsywne rozmiary administratora JSONConfig dla narzędzia sprawdzającego repozytorium\n**NAPRAWIONO**: Administrator JSONConfig używa `i18n: true` z plikami tłumaczenia\n**NAPRAWIONO**: limity czasu żądania HTTP za pośrednictwem AbortSignal.Timeout zamiast globalnego setTimeout",
|
|
16
|
+
"uk": "** ВИПРАВЛЕНО**: Адаптивні розміри адміністратора JSONConfig для перевірки сховища\n** ВИПРАВЛЕНО**: Адміністратор JSONConfig використовує `i18n: true` з файлами перекладу\n** ВИПРАВЛЕНО**: таймаути запиту HTTP через abortSignal.timeout замість глобального setTimeout",
|
|
17
|
+
"zh-cn": "**已修复**:管理员 JsonConfig 存储库检查器的响应式大小\n**已修复**:管理员 JsonConfig 在翻译文件中使用 `i18n: true`\n**已修复**:通过 abortSignal.Timeout 而不是全局 setTimeout 实现 HTTP 请求超时"
|
|
18
|
+
},
|
|
19
|
+
"2.0.1": {
|
|
20
|
+
"en": "**FIXED**: Instance `info.connection` follows every enabled API (v2-only no longer stays red)",
|
|
21
|
+
"de": "**REPARIERT**: Instanz `info.connection` folgt jeder API (nur v2 bleibt nicht mehr rot)",
|
|
22
|
+
"ru": "**ИСПРАВЛЕНО**: экземпляр `info.connection` следует за всеми включенными API (только версия 2 больше не отображается красным цветом)",
|
|
23
|
+
"pt": "**CORRIGIDO**: A instância `info.connection` segue cada API (a v2 não fica mais vermelha)",
|
|
24
|
+
"nl": "**FIXED**: De instantie `info.connection` volgt elke ingeschakelde API (alleen v2 blijft niet langer rood)",
|
|
25
|
+
"fr": "**CORRIGÉ** : L'instance « info.connection » suit toutes les API activées (la v2 seule ne reste plus rouge)",
|
|
26
|
+
"it": "**FISSO**: L'istanza `info.connection` segue ogni API abilitata (solo v2 non è più rossa)",
|
|
27
|
+
"es": "**FIJO**: La instancia `info.connection` sigue todas las API habilitadas (solo la v2 ya no está en rojo)",
|
|
28
|
+
"pl": "**NAPRAWIONO**: Instancja `info.connection` podąża za każdym włączonym interfejsem API (tylko v2 nie pozostaje już czerwony)",
|
|
29
|
+
"uk": "** ВИПРАВЛЕНО**: Екземпляр `info.connection` слідує за кожним увімкненим API (лише v2 більше не залишається червоним)",
|
|
30
|
+
"zh-cn": "**已修复**:实例 “info.connection” 遵循每个已启用的 API(仅限 v2 不再保持红色)"
|
|
31
|
+
},
|
|
6
32
|
"2.0.0": {
|
|
7
33
|
"en": "**NEW**: Optional Hydrawise v2 GraphQL API (water usage, live sensors, weather, leak indicator, zone commands)\n**ENHANCED**: customerdetails.php polls on its own 5-minute timer with backoff after rate limits",
|
|
8
34
|
"de": "**NEU**: Optionale Hydrawise v2 GraphQL API (Wasserverbrauch, Livesensoren, Wetter, Zonenbefehle)\n**VERBESSERT**: customerdetails.php fragt im eigenen 5-Minuten-Timer ab, nach Ratenlimit",
|
|
@@ -67,32 +93,6 @@
|
|
|
67
93
|
"pl": "stałe zależności\nstałe schematy URL",
|
|
68
94
|
"uk": "фіксовані залежності\nфіксована схема Контакти",
|
|
69
95
|
"zh-cn": "固定依赖关系\n固定计划 URL( URL)"
|
|
70
|
-
},
|
|
71
|
-
"1.0.3": {
|
|
72
|
-
"en": "updated dependencies",
|
|
73
|
-
"de": "aktualisierte abhängigkeiten",
|
|
74
|
-
"ru": "обновленные зависимости",
|
|
75
|
-
"pt": "dependências atualizadas",
|
|
76
|
-
"nl": "bijgewerkte afhankelijkheden",
|
|
77
|
-
"fr": "dépendances actualisées",
|
|
78
|
-
"it": "dipendenze aggiornate",
|
|
79
|
-
"es": "dependencias actualizadas",
|
|
80
|
-
"pl": "zaktualizowane zależności",
|
|
81
|
-
"uk": "оновлені залежності",
|
|
82
|
-
"zh-cn": "更新的依赖关系"
|
|
83
|
-
},
|
|
84
|
-
"1.0.2": {
|
|
85
|
-
"en": "fixed issues detected by repository checker",
|
|
86
|
-
"de": "von repository checker erkannte probleme",
|
|
87
|
-
"ru": "фиксированные проблемы, выявленные регистратором",
|
|
88
|
-
"pt": "problemas corrigidos detectados pelo verificador de repositório",
|
|
89
|
-
"nl": "vaste problemen gedetecteerd door repository checker",
|
|
90
|
-
"fr": "problèmes corrigés détectés par le vérificateur du dépôt",
|
|
91
|
-
"it": "problemi fissi rilevati da repository checker",
|
|
92
|
-
"es": "problemas fijos detectados por el repositorio",
|
|
93
|
-
"pl": "stałe problemy wykryte przez kontroler repozytorium",
|
|
94
|
-
"uk": "виправлені питання, виявлені репозиторійним чекером",
|
|
95
|
-
"zh-cn": "仓库检查器检测到的固定问题"
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
98
|
"titleLang": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.hydrawise",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "Adapter to control the Hydrawise irrigation control system",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "SentiQ",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"@alcalzone/release-script-plugin-manual-review": "^5.2.0",
|
|
34
34
|
"@iobroker/adapter-dev": "^1.5.0",
|
|
35
35
|
"@iobroker/eslint-config": "^2.2.0",
|
|
36
|
-
"@iobroker/testing": "^
|
|
36
|
+
"@iobroker/testing": "^6.2.1",
|
|
37
37
|
"@tsconfig/node22": "^22.0.5",
|
|
38
38
|
"@types/node": "^22.19.0",
|
|
39
39
|
"@types/proxyquire": "^1.3.31",
|