iobroker.homewizard 0.10.0 → 0.11.0
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 +4 -4
- package/admin/i18n/de.json +0 -1
- package/admin/i18n/en.json +0 -1
- package/admin/i18n/es.json +0 -1
- package/admin/i18n/fr.json +0 -1
- package/admin/i18n/it.json +0 -1
- package/admin/i18n/nl.json +0 -1
- package/admin/i18n/pl.json +0 -1
- package/admin/i18n/pt.json +0 -1
- package/admin/i18n/ru.json +0 -1
- package/admin/i18n/uk.json +0 -1
- package/admin/i18n/zh-cn.json +0 -1
- package/build/lib/homewizard-client.js +1 -11
- package/build/lib/homewizard-client.js.map +2 -2
- package/build/lib/state-manager.js +3 -24
- package/build/lib/state-manager.js.map +2 -2
- package/build/main.js +0 -10
- package/build/main.js.map +2 -2
- package/io-package.json +14 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -177,6 +177,10 @@ homewizard.0.
|
|
|
177
177
|
Placeholder for the next version (at the beginning of the line):
|
|
178
178
|
### **WORK IN PROGRESS**
|
|
179
179
|
-->
|
|
180
|
+
### 0.11.0 (2026-06-03)
|
|
181
|
+
|
|
182
|
+
- Removed the raw P1 telegram datapoint — its data is already available as parsed measurement states; the leftover state is cleaned up automatically on existing P1 meters.
|
|
183
|
+
|
|
180
184
|
### 0.10.0 (2026-06-01)
|
|
181
185
|
|
|
182
186
|
- New battery controls: a forecast-based predictive charging mode and a one-shot charge-to-full switch, in addition to the existing zero, to-full and standby modes.
|
|
@@ -197,10 +201,6 @@ homewizard.0.
|
|
|
197
201
|
|
|
198
202
|
- Internal cleanup. No user-facing changes.
|
|
199
203
|
|
|
200
|
-
### 0.9.0 (2026-05-22)
|
|
201
|
-
|
|
202
|
-
- User-modified state names are no longer overwritten on adapter restart
|
|
203
|
-
|
|
204
204
|
Older entries are in [CHANGELOG_OLD.md](CHANGELOG_OLD.md).
|
|
205
205
|
|
|
206
206
|
## Support Development
|
package/admin/i18n/de.json
CHANGED
|
@@ -119,7 +119,6 @@
|
|
|
119
119
|
"modeToFull": "To full (auf Maximum laden)",
|
|
120
120
|
"modeStandby": "Standby",
|
|
121
121
|
"wifiSsid": "WLAN-Netzwerk (SSID)",
|
|
122
|
-
"telegram": "Rohes P1-Telegramm",
|
|
123
122
|
"modePredictive": "Vorausschauend (prognosebasiert)",
|
|
124
123
|
"batteryChargeToFull": "Voll aufladen"
|
|
125
124
|
}
|
package/admin/i18n/en.json
CHANGED
|
@@ -119,7 +119,6 @@
|
|
|
119
119
|
"modeToFull": "To full (charge to maximum)",
|
|
120
120
|
"modeStandby": "Standby",
|
|
121
121
|
"wifiSsid": "WiFi network (SSID)",
|
|
122
|
-
"telegram": "Raw P1 telegram",
|
|
123
122
|
"modePredictive": "Predictive (forecast-based)",
|
|
124
123
|
"batteryChargeToFull": "Charge to full"
|
|
125
124
|
}
|
package/admin/i18n/es.json
CHANGED
|
@@ -119,7 +119,6 @@
|
|
|
119
119
|
"modeToFull": "To full (cargar al máximo)",
|
|
120
120
|
"modeStandby": "Standby",
|
|
121
121
|
"wifiSsid": "Red Wi-Fi (SSID)",
|
|
122
|
-
"telegram": "Telegrama P1 sin procesar",
|
|
123
122
|
"modePredictive": "Predictivo (basado en previsión)",
|
|
124
123
|
"batteryChargeToFull": "Cargar al máximo"
|
|
125
124
|
}
|
package/admin/i18n/fr.json
CHANGED
|
@@ -119,7 +119,6 @@
|
|
|
119
119
|
"modeToFull": "To full (charge maximale)",
|
|
120
120
|
"modeStandby": "Standby",
|
|
121
121
|
"wifiSsid": "Réseau Wi-Fi (SSID)",
|
|
122
|
-
"telegram": "Télégramme P1 brut",
|
|
123
122
|
"modePredictive": "Prédictif (basé sur les prévisions)",
|
|
124
123
|
"batteryChargeToFull": "Charger à fond"
|
|
125
124
|
}
|
package/admin/i18n/it.json
CHANGED
|
@@ -119,7 +119,6 @@
|
|
|
119
119
|
"modeToFull": "To full (carica al massimo)",
|
|
120
120
|
"modeStandby": "Standby",
|
|
121
121
|
"wifiSsid": "Rete Wi-Fi (SSID)",
|
|
122
|
-
"telegram": "Telegramma P1 grezzo",
|
|
123
122
|
"modePredictive": "Predittivo (basato su previsioni)",
|
|
124
123
|
"batteryChargeToFull": "Carica al massimo"
|
|
125
124
|
}
|
package/admin/i18n/nl.json
CHANGED
|
@@ -119,7 +119,6 @@
|
|
|
119
119
|
"modeToFull": "To full (volledig laden)",
|
|
120
120
|
"modeStandby": "Standby",
|
|
121
121
|
"wifiSsid": "Wifi-netwerk (SSID)",
|
|
122
|
-
"telegram": "Ruw P1-telegram",
|
|
123
122
|
"modePredictive": "Voorspellend (op basis van prognose)",
|
|
124
123
|
"batteryChargeToFull": "Volledig opladen"
|
|
125
124
|
}
|
package/admin/i18n/pl.json
CHANGED
|
@@ -119,7 +119,6 @@
|
|
|
119
119
|
"modeToFull": "To full (ładuj do pełna)",
|
|
120
120
|
"modeStandby": "Standby",
|
|
121
121
|
"wifiSsid": "Sieć Wi-Fi (SSID)",
|
|
122
|
-
"telegram": "Surowy telegram P1",
|
|
123
122
|
"modePredictive": "Predykcyjny (na podstawie prognozy)",
|
|
124
123
|
"batteryChargeToFull": "Naładuj do pełna"
|
|
125
124
|
}
|
package/admin/i18n/pt.json
CHANGED
|
@@ -119,7 +119,6 @@
|
|
|
119
119
|
"modeToFull": "To full (carregar ao máximo)",
|
|
120
120
|
"modeStandby": "Standby",
|
|
121
121
|
"wifiSsid": "Rede Wi-Fi (SSID)",
|
|
122
|
-
"telegram": "Telegrama P1 bruto",
|
|
123
122
|
"modePredictive": "Preditivo (baseado em previsão)",
|
|
124
123
|
"batteryChargeToFull": "Carregar até cheio"
|
|
125
124
|
}
|
package/admin/i18n/ru.json
CHANGED
|
@@ -119,7 +119,6 @@
|
|
|
119
119
|
"modeToFull": "To full (заряд до максимума)",
|
|
120
120
|
"modeStandby": "Standby",
|
|
121
121
|
"wifiSsid": "Сеть Wi-Fi (SSID)",
|
|
122
|
-
"telegram": "Необработанная телеграмма P1",
|
|
123
122
|
"modePredictive": "Прогнозный (на основе прогноза)",
|
|
124
123
|
"batteryChargeToFull": "Зарядить полностью"
|
|
125
124
|
}
|
package/admin/i18n/uk.json
CHANGED
|
@@ -119,7 +119,6 @@
|
|
|
119
119
|
"modeToFull": "To full (заряд до максимуму)",
|
|
120
120
|
"modeStandby": "Standby",
|
|
121
121
|
"wifiSsid": "Мережа Wi-Fi (SSID)",
|
|
122
|
-
"telegram": "Необроблена телеграма P1",
|
|
123
122
|
"modePredictive": "Прогнозований (на основі прогнозу)",
|
|
124
123
|
"batteryChargeToFull": "Зарядити повністю"
|
|
125
124
|
}
|
package/admin/i18n/zh-cn.json
CHANGED
|
@@ -116,18 +116,12 @@ class HomeWizardClient {
|
|
|
116
116
|
async deleteUser() {
|
|
117
117
|
await this.request("DELETE", "/api/user", { name: "local/iobroker" });
|
|
118
118
|
}
|
|
119
|
-
/** Get the most recent raw P1 telegram (GET /api/telegram, plain text — P1 Meter only). */
|
|
120
|
-
async getTelegram() {
|
|
121
|
-
return this.request("GET", "/api/telegram", void 0, false);
|
|
122
|
-
}
|
|
123
119
|
/**
|
|
124
120
|
* @param method HTTP method
|
|
125
121
|
* @param path API path
|
|
126
122
|
* @param body Optional request body
|
|
127
|
-
* @param parseJson Parse the response as JSON (default). `false` resolves the raw response
|
|
128
|
-
* text — used for `GET /api/telegram`, which returns a plain-text P1 datagram, not JSON.
|
|
129
123
|
*/
|
|
130
|
-
request(method, path, body
|
|
124
|
+
request(method, path, body) {
|
|
131
125
|
return new Promise((resolve, reject) => {
|
|
132
126
|
var _a;
|
|
133
127
|
const bodyStr = body ? JSON.stringify(body) : void 0;
|
|
@@ -176,10 +170,6 @@ class HomeWizardClient {
|
|
|
176
170
|
resolve(void 0);
|
|
177
171
|
return;
|
|
178
172
|
}
|
|
179
|
-
if (!parseJson) {
|
|
180
|
-
resolve(data);
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
173
|
try {
|
|
184
174
|
resolve(JSON.parse(data));
|
|
185
175
|
} catch {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/homewizard-client.ts"],
|
|
4
|
-
"sourcesContent": ["import * as https from \"node:https\";\nimport { HW_AGENT } from \"./cacert\";\nimport type { BatteryControl, DeviceInfo, Measurement, PairingResponse, SystemInfo } from \"./types\";\n\n/** Minimal logger surface \u2014 only debug needed for HTTPS-trace. */\nexport interface HomeWizardClientLogger {\n /**\n * Debug-log delegate.\n *\n * @param msg Log message\n */\n debug(msg: string): void;\n}\n\n/** HTTPS client for HomeWizard API v2 */\nexport class HomeWizardClient {\n private readonly ip: string;\n private readonly token: string;\n private readonly agent: https.Agent;\n /** Override target port \u2014 only used by tests against a local stub-server. */\n private readonly port: number;\n /** Optional logger for per-call debug-trace (request entry + response success/fail). */\n private readonly log: HomeWizardClientLogger | null;\n\n /**\n * @param ip Device IP address\n * @param token Bearer token (empty string for pairing requests)\n * @param options Optional overrides \u2014 primarily for unit tests against a local TLS stub.\n * @param options.agent HTTPS agent to use; defaults to {@link HW_AGENT} (with HomeWizard CA pinning).\n * @param options.port Target port; defaults to 443.\n * @param options.log Optional logger for per-call debug-trace (request/success/fail).\n */\n constructor(\n ip: string,\n token: string = \"\",\n options: { agent?: https.Agent; port?: number; log?: HomeWizardClientLogger } = {},\n ) {\n this.ip = ip;\n this.token = token;\n this.agent = options.agent ?? HW_AGENT;\n this.port = options.port ?? 443;\n this.log = options.log ?? null;\n }\n\n /** Get device info (GET /api) */\n async getDeviceInfo(): Promise<DeviceInfo> {\n return this.request<DeviceInfo>(\"GET\", \"/api\");\n }\n\n /** Request pairing token (POST /api/user) \u2014 403 until button pressed */\n async requestPairing(): Promise<PairingResponse> {\n const result = await this.request<PairingResponse>(\"POST\", \"/api/user\", {\n name: \"local/iobroker\",\n });\n // Server returned 200 but we still validate the shape \u2014 a malformed or\n // missing token would otherwise crash later in this.encrypt(undefined).\n if (!result || typeof result.token !== \"string\" || result.token.length === 0) {\n throw new HomeWizardApiError(200, JSON.stringify(result), \"POST /api/user (no token in response)\");\n }\n return result;\n }\n\n /** Get current measurement (REST fallback) */\n async getMeasurement(): Promise<Measurement> {\n return this.request<Measurement>(\"GET\", \"/api/measurement\");\n }\n\n /** Get system info */\n async getSystem(): Promise<SystemInfo> {\n return this.request<SystemInfo>(\"GET\", \"/api/system\");\n }\n\n /**\n * Update system settings\n *\n * @param settings System settings to update\n */\n async setSystem(settings: Partial<SystemInfo>): Promise<SystemInfo> {\n return this.request<SystemInfo>(\"PUT\", \"/api/system\", settings);\n }\n\n /** Reboot device */\n async reboot(): Promise<void> {\n await this.request(\"PUT\", \"/api/system/reboot\");\n }\n\n /** Identify device (blink LED) */\n async identify(): Promise<void> {\n await this.request(\"PUT\", \"/api/system/identify\");\n }\n\n /** Get battery control status */\n async getBatteries(): Promise<BatteryControl> {\n return this.request<BatteryControl>(\"GET\", \"/api/batteries\");\n }\n\n /**\n * Set battery control\n *\n * @param settings Battery control settings to update\n */\n async setBatteries(settings: Partial<BatteryControl>): Promise<BatteryControl> {\n return this.request<BatteryControl>(\"PUT\", \"/api/batteries\", settings);\n }\n\n /**\n * Revoke the adapter's token on the device (DELETE /api/user). The token was created\n * under the fixed name `local/iobroker` during pairing; deleting it stops orphaned tokens\n * accumulating on the device across pair/unpair cycles. Best-effort \u2014 callers ignore errors.\n */\n async deleteUser(): Promise<void> {\n await this.request(\"DELETE\", \"/api/user\", { name: \"local/iobroker\" });\n }\n\n
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAAuB;AACvB,oBAAyB;AAclB,MAAM,iBAAiB;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUjB,YACE,IACA,QAAgB,IAChB,UAAgF,CAAC,GACjF;AApCJ;AAqCI,SAAK,KAAK;AACV,SAAK,QAAQ;AACb,SAAK,SAAQ,aAAQ,UAAR,YAAiB;AAC9B,SAAK,QAAO,aAAQ,SAAR,YAAgB;AAC5B,SAAK,OAAM,aAAQ,QAAR,YAAe;AAAA,EAC5B;AAAA;AAAA,EAGA,MAAM,gBAAqC;AACzC,WAAO,KAAK,QAAoB,OAAO,MAAM;AAAA,EAC/C;AAAA;AAAA,EAGA,MAAM,iBAA2C;AAC/C,UAAM,SAAS,MAAM,KAAK,QAAyB,QAAQ,aAAa;AAAA,MACtE,MAAM;AAAA,IACR,CAAC;AAGD,QAAI,CAAC,UAAU,OAAO,OAAO,UAAU,YAAY,OAAO,MAAM,WAAW,GAAG;AAC5E,YAAM,IAAI,mBAAmB,KAAK,KAAK,UAAU,MAAM,GAAG,uCAAuC;AAAA,IACnG;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,MAAM,iBAAuC;AAC3C,WAAO,KAAK,QAAqB,OAAO,kBAAkB;AAAA,EAC5D;AAAA;AAAA,EAGA,MAAM,YAAiC;AACrC,WAAO,KAAK,QAAoB,OAAO,aAAa;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,UAAU,UAAoD;AAClE,WAAO,KAAK,QAAoB,OAAO,eAAe,QAAQ;AAAA,EAChE;AAAA;AAAA,EAGA,MAAM,SAAwB;AAC5B,UAAM,KAAK,QAAQ,OAAO,oBAAoB;AAAA,EAChD;AAAA;AAAA,EAGA,MAAM,WAA0B;AAC9B,UAAM,KAAK,QAAQ,OAAO,sBAAsB;AAAA,EAClD;AAAA;AAAA,EAGA,MAAM,eAAwC;AAC5C,WAAO,KAAK,QAAwB,OAAO,gBAAgB;AAAA,EAC7D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,aAAa,UAA4D;AAC7E,WAAO,KAAK,QAAwB,OAAO,kBAAkB,QAAQ;AAAA,EACvE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,aAA4B;AAChC,UAAM,KAAK,QAAQ,UAAU,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAAA,EACtE;AAAA;AAAA
|
|
4
|
+
"sourcesContent": ["import * as https from \"node:https\";\nimport { HW_AGENT } from \"./cacert\";\nimport type { BatteryControl, DeviceInfo, Measurement, PairingResponse, SystemInfo } from \"./types\";\n\n/** Minimal logger surface \u2014 only debug needed for HTTPS-trace. */\nexport interface HomeWizardClientLogger {\n /**\n * Debug-log delegate.\n *\n * @param msg Log message\n */\n debug(msg: string): void;\n}\n\n/** HTTPS client for HomeWizard API v2 */\nexport class HomeWizardClient {\n private readonly ip: string;\n private readonly token: string;\n private readonly agent: https.Agent;\n /** Override target port \u2014 only used by tests against a local stub-server. */\n private readonly port: number;\n /** Optional logger for per-call debug-trace (request entry + response success/fail). */\n private readonly log: HomeWizardClientLogger | null;\n\n /**\n * @param ip Device IP address\n * @param token Bearer token (empty string for pairing requests)\n * @param options Optional overrides \u2014 primarily for unit tests against a local TLS stub.\n * @param options.agent HTTPS agent to use; defaults to {@link HW_AGENT} (with HomeWizard CA pinning).\n * @param options.port Target port; defaults to 443.\n * @param options.log Optional logger for per-call debug-trace (request/success/fail).\n */\n constructor(\n ip: string,\n token: string = \"\",\n options: { agent?: https.Agent; port?: number; log?: HomeWizardClientLogger } = {},\n ) {\n this.ip = ip;\n this.token = token;\n this.agent = options.agent ?? HW_AGENT;\n this.port = options.port ?? 443;\n this.log = options.log ?? null;\n }\n\n /** Get device info (GET /api) */\n async getDeviceInfo(): Promise<DeviceInfo> {\n return this.request<DeviceInfo>(\"GET\", \"/api\");\n }\n\n /** Request pairing token (POST /api/user) \u2014 403 until button pressed */\n async requestPairing(): Promise<PairingResponse> {\n const result = await this.request<PairingResponse>(\"POST\", \"/api/user\", {\n name: \"local/iobroker\",\n });\n // Server returned 200 but we still validate the shape \u2014 a malformed or\n // missing token would otherwise crash later in this.encrypt(undefined).\n if (!result || typeof result.token !== \"string\" || result.token.length === 0) {\n throw new HomeWizardApiError(200, JSON.stringify(result), \"POST /api/user (no token in response)\");\n }\n return result;\n }\n\n /** Get current measurement (REST fallback) */\n async getMeasurement(): Promise<Measurement> {\n return this.request<Measurement>(\"GET\", \"/api/measurement\");\n }\n\n /** Get system info */\n async getSystem(): Promise<SystemInfo> {\n return this.request<SystemInfo>(\"GET\", \"/api/system\");\n }\n\n /**\n * Update system settings\n *\n * @param settings System settings to update\n */\n async setSystem(settings: Partial<SystemInfo>): Promise<SystemInfo> {\n return this.request<SystemInfo>(\"PUT\", \"/api/system\", settings);\n }\n\n /** Reboot device */\n async reboot(): Promise<void> {\n await this.request(\"PUT\", \"/api/system/reboot\");\n }\n\n /** Identify device (blink LED) */\n async identify(): Promise<void> {\n await this.request(\"PUT\", \"/api/system/identify\");\n }\n\n /** Get battery control status */\n async getBatteries(): Promise<BatteryControl> {\n return this.request<BatteryControl>(\"GET\", \"/api/batteries\");\n }\n\n /**\n * Set battery control\n *\n * @param settings Battery control settings to update\n */\n async setBatteries(settings: Partial<BatteryControl>): Promise<BatteryControl> {\n return this.request<BatteryControl>(\"PUT\", \"/api/batteries\", settings);\n }\n\n /**\n * Revoke the adapter's token on the device (DELETE /api/user). The token was created\n * under the fixed name `local/iobroker` during pairing; deleting it stops orphaned tokens\n * accumulating on the device across pair/unpair cycles. Best-effort \u2014 callers ignore errors.\n */\n async deleteUser(): Promise<void> {\n await this.request(\"DELETE\", \"/api/user\", { name: \"local/iobroker\" });\n }\n\n /**\n * @param method HTTP method\n * @param path API path\n * @param body Optional request body\n */\n private request<T>(method: string, path: string, body?: unknown): Promise<T> {\n return new Promise((resolve, reject) => {\n const bodyStr = body ? JSON.stringify(body) : undefined;\n const headers: Record<string, string> = {\n \"X-Api-Version\": \"2\",\n };\n\n if (this.token) {\n headers.Authorization = `Bearer ${this.token}`;\n }\n if (bodyStr) {\n headers[\"Content-Type\"] = \"application/json\";\n headers[\"Content-Length\"] = Buffer.byteLength(bodyStr).toString();\n }\n\n // Per mcm-Linie + reference_iobroker_logging_levels: every API call gets\n // an entry-trace so a user-debug-log shows what the adapter actually\n // tried \u2014 not just what failed. auth=bearer/none discloses presence,\n // never the token itself.\n const startMs = Date.now();\n this.log?.debug(`HTTPS ${method} ${path} ip=${this.ip} auth=${this.token ? \"bearer\" : \"none\"}`);\n\n const req = https.request(\n {\n hostname: this.ip,\n port: this.port,\n path,\n method,\n headers,\n agent: this.agent,\n timeout: 10_000,\n },\n res => {\n const chunks: Buffer[] = [];\n res.on(\"error\", reject);\n res.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n res.on(\"end\", () => {\n const data = Buffer.concat(chunks).toString();\n const elapsedMs = Date.now() - startMs;\n const statusCode = res.statusCode ?? 0;\n if (!statusCode || statusCode >= 400) {\n // Fail: emit detailed trace BEFORE constructing the error so the\n // body snippet is in the log even when the caller catches the\n // throw silently. 200-char snippet cap mirrors the HomeWizardApiError\n // constructor's parsing depth.\n const snippet = data.length > 200 ? `${data.slice(0, 200)}\u2026` : data;\n this.log?.debug(`HTTPS ${method} ${path}: status=${statusCode} elapsed=${elapsedMs}ms body=\"${snippet}\"`);\n const error = new HomeWizardApiError(statusCode, data, `${method} ${path}`);\n reject(error);\n return;\n }\n // Success: status + size + timing. Body itself stays out \u2014 too\n // big to log on debug, available at silly if ever wired.\n this.log?.debug(\n `HTTPS ${method} ${path}: status=${statusCode} elapsed=${elapsedMs}ms bytes=${data.length}`,\n );\n if (!data) {\n resolve(undefined as T);\n return;\n }\n try {\n resolve(JSON.parse(data) as T);\n } catch {\n reject(new Error(`Invalid JSON from ${method} ${path}: ${data.substring(0, 200)}`));\n }\n });\n },\n );\n\n req.on(\"error\", err => {\n // Pre-response errors (DNS, TCP reset, TLS): log endpoint + elapsed so\n // chronic-bouncing patterns are correlatable in the per-device trace.\n this.log?.debug(`HTTPS ${method} ${path}: error=\"${err.message}\" elapsed=${Date.now() - startMs}ms`);\n reject(err);\n });\n req.on(\"timeout\", () => {\n req.destroy(new Error(`Timeout: ${method} ${path}`));\n });\n\n if (bodyStr) {\n req.write(bodyStr);\n }\n req.end();\n });\n }\n}\n\n/** API error with status code and parsed error body */\nexport class HomeWizardApiError extends Error {\n readonly statusCode: number;\n readonly errorCode: string;\n\n /**\n * @param statusCode HTTP status code\n * @param body Response body\n * @param context Request context for error message\n */\n constructor(statusCode: number, body: string, context: string) {\n let errorCode = \"unknown\";\n let description = body;\n try {\n const parsed = JSON.parse(body);\n errorCode = parsed.error?.code ?? parsed.error ?? \"unknown\";\n description = parsed.error?.description ?? parsed.error?.code ?? body;\n } catch {\n // body is not JSON\n }\n super(`${context}: HTTP ${statusCode} \u2014 ${description}`);\n this.name = \"HomeWizardApiError\";\n this.statusCode = statusCode;\n this.errorCode = errorCode;\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["_a"]
|
|
7
7
|
}
|
|
@@ -636,29 +636,6 @@ class StateManager {
|
|
|
636
636
|
}
|
|
637
637
|
}
|
|
638
638
|
}
|
|
639
|
-
/**
|
|
640
|
-
* Update the raw P1 telegram state (P1 Meter only). Written at the system-poll cadence,
|
|
641
|
-
* not the 1/s measurement feed — the raw DSMR datagram is bulky text.
|
|
642
|
-
*
|
|
643
|
-
* @param config Device configuration
|
|
644
|
-
* @param telegram Raw P1 telegram text
|
|
645
|
-
*/
|
|
646
|
-
async updateTelegram(config, telegram) {
|
|
647
|
-
const value = (0, import_coerce.coerceString)(telegram);
|
|
648
|
-
if (value === null) {
|
|
649
|
-
return;
|
|
650
|
-
}
|
|
651
|
-
const prefix = this.devicePrefix(config);
|
|
652
|
-
await this.ensureChannel(`${prefix}.measurement`, (0, import_i18n.tName)("measurement"));
|
|
653
|
-
await this.ensureAndSet({
|
|
654
|
-
id: `${prefix}.measurement.telegram`,
|
|
655
|
-
name: (0, import_i18n.tName)("telegram"),
|
|
656
|
-
type: "string",
|
|
657
|
-
role: "text",
|
|
658
|
-
value,
|
|
659
|
-
changedOnly: true
|
|
660
|
-
});
|
|
661
|
-
}
|
|
662
639
|
/**
|
|
663
640
|
* Update system states
|
|
664
641
|
*
|
|
@@ -866,7 +843,8 @@ class StateManager {
|
|
|
866
843
|
this.adapter.log.debug(`state-manager: removeDevice ${prefix} done (dropped ${dropped} cached IDs)`);
|
|
867
844
|
}
|
|
868
845
|
/**
|
|
869
|
-
* Remove
|
|
846
|
+
* Remove obsolete states: pre-v0.4.0 device-root paths (now under measurement/) plus
|
|
847
|
+
* states retired in later versions (v0.11.0: raw P1 telegram).
|
|
870
848
|
*
|
|
871
849
|
* @param config Device configuration
|
|
872
850
|
*/
|
|
@@ -878,6 +856,7 @@ class StateManager {
|
|
|
878
856
|
oldIds.push(`${prefix}.${def.id}`);
|
|
879
857
|
}
|
|
880
858
|
oldIds.push(`${prefix}.external`);
|
|
859
|
+
oldIds.push(`${prefix}.measurement.telegram`);
|
|
881
860
|
let removed = 0;
|
|
882
861
|
for (const id of oldIds) {
|
|
883
862
|
if (await this.adapter.getObjectAsync(id)) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/state-manager.ts"],
|
|
4
|
-
"sourcesContent": ["import type * as utils from \"@iobroker/adapter-core\";\nimport { coerceBoolean, coerceFiniteNumber, coerceString, isPlainObject } from \"./coerce\";\nimport type { I18nKey } from \"./i18n\";\nimport { resolveLabel, tName } from \"./i18n\";\nimport type { BatteryControl, DeviceConfig, Measurement, SystemInfo } from \"./types\";\n\n/** Measurement field to state definition mapping */\ninterface MeasurementStateDef {\n /** Measurement field key */\n key: string;\n /** ioBroker state ID suffix */\n id: string;\n /** Translation key for `common.name` (resolved via {@link tName}) */\n nameKey: I18nKey;\n /** Optional translation key for `common.desc` (resolved via {@link tName}) */\n descKey?: I18nKey;\n /** State value type */\n type: ioBroker.CommonType;\n /** ioBroker role */\n role: string;\n /** Unit string */\n unit?: string;\n}\n\n/** Options for {@link StateManager.createState} (avoids long positional argument lists). */\ninterface StateDef {\n /** Full state ID */\n id: string;\n /** State name (translation object or device-identifier string) */\n name: ioBroker.StringOrTranslated;\n /** Value type */\n type: ioBroker.CommonType;\n /** ioBroker role */\n role: string;\n /** Whether the state is writable (default false) */\n write?: boolean;\n /** Optional unit */\n unit?: string;\n /** Optional `common.desc` */\n desc?: ioBroker.StringOrTranslated;\n /** Optional `common.states` map (plain-string values) */\n states?: Record<string, string>;\n}\n\n/** Options for {@link StateManager.ensureAndSet} \u2014 a {@link StateDef} plus the value to write. */\ninterface StateSet extends StateDef {\n /** Value to write */\n value: ioBroker.StateValue;\n /** Use setStateChangedAsync (skip redundant writes) instead of setStateAsync */\n changedOnly?: boolean;\n}\n\n/**\n * Sanitize a string for use as ioBroker object ID (see adapter.FORBIDDEN_CHARS).\n *\n * @param str Raw string to sanitize\n */\nfunction sanitize(str: string): string {\n return str.replace(/[^a-zA-Z0-9_-]/g, \"_\").toLowerCase();\n}\n\nconst MEASUREMENT_STATE_DEFS: MeasurementStateDef[] = [\n // Power\n { key: \"power_w\", id: \"power_w\", nameKey: \"powerTotal\", type: \"number\", role: \"value.power\", unit: \"W\" },\n { key: \"power_l1_w\", id: \"power_l1_w\", nameKey: \"powerL1\", type: \"number\", role: \"value.power\", unit: \"W\" },\n { key: \"power_l2_w\", id: \"power_l2_w\", nameKey: \"powerL2\", type: \"number\", role: \"value.power\", unit: \"W\" },\n { key: \"power_l3_w\", id: \"power_l3_w\", nameKey: \"powerL3\", type: \"number\", role: \"value.power\", unit: \"W\" },\n // Voltage\n { key: \"voltage_v\", id: \"voltage_v\", nameKey: \"voltage\", type: \"number\", role: \"value.voltage\", unit: \"V\" },\n { key: \"voltage_l1_v\", id: \"voltage_l1_v\", nameKey: \"voltageL1\", type: \"number\", role: \"value.voltage\", unit: \"V\" },\n { key: \"voltage_l2_v\", id: \"voltage_l2_v\", nameKey: \"voltageL2\", type: \"number\", role: \"value.voltage\", unit: \"V\" },\n { key: \"voltage_l3_v\", id: \"voltage_l3_v\", nameKey: \"voltageL3\", type: \"number\", role: \"value.voltage\", unit: \"V\" },\n // Current\n { key: \"current_a\", id: \"current_a\", nameKey: \"current\", type: \"number\", role: \"value.current\", unit: \"A\" },\n { key: \"current_l1_a\", id: \"current_l1_a\", nameKey: \"currentL1\", type: \"number\", role: \"value.current\", unit: \"A\" },\n { key: \"current_l2_a\", id: \"current_l2_a\", nameKey: \"currentL2\", type: \"number\", role: \"value.current\", unit: \"A\" },\n { key: \"current_l3_a\", id: \"current_l3_a\", nameKey: \"currentL3\", type: \"number\", role: \"value.current\", unit: \"A\" },\n // Frequency\n { key: \"frequency_hz\", id: \"frequency_hz\", nameKey: \"frequency\", type: \"number\", role: \"value\", unit: \"Hz\" },\n // Energy import\n {\n key: \"energy_import_kwh\",\n id: \"energy_import_kwh\",\n nameKey: \"energyImportTotal\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n {\n key: \"energy_import_t1_kwh\",\n id: \"energy_import_t1_kwh\",\n nameKey: \"energyImportT1\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n {\n key: \"energy_import_t2_kwh\",\n id: \"energy_import_t2_kwh\",\n nameKey: \"energyImportT2\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n {\n key: \"energy_import_t3_kwh\",\n id: \"energy_import_t3_kwh\",\n nameKey: \"energyImportT3\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n {\n key: \"energy_import_t4_kwh\",\n id: \"energy_import_t4_kwh\",\n nameKey: \"energyImportT4\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n // Energy export\n {\n key: \"energy_export_kwh\",\n id: \"energy_export_kwh\",\n nameKey: \"energyExportTotal\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n {\n key: \"energy_export_t1_kwh\",\n id: \"energy_export_t1_kwh\",\n nameKey: \"energyExportT1\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n {\n key: \"energy_export_t2_kwh\",\n id: \"energy_export_t2_kwh\",\n nameKey: \"energyExportT2\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n {\n key: \"energy_export_t3_kwh\",\n id: \"energy_export_t3_kwh\",\n nameKey: \"energyExportT3\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n {\n key: \"energy_export_t4_kwh\",\n id: \"energy_export_t4_kwh\",\n nameKey: \"energyExportT4\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n // Tariff (common.states applied separately in updateMeasurement for translation labels)\n { key: \"tariff\", id: \"tariff\", nameKey: \"tariff\", type: \"number\", role: \"value\" },\n // Power quality\n {\n key: \"voltage_sag_l1_count\",\n id: \"quality.voltage_sag_l1_count\",\n nameKey: \"voltageSagL1\",\n descKey: \"voltageSag\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"voltage_sag_l2_count\",\n id: \"quality.voltage_sag_l2_count\",\n nameKey: \"voltageSagL2\",\n descKey: \"voltageSag\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"voltage_sag_l3_count\",\n id: \"quality.voltage_sag_l3_count\",\n nameKey: \"voltageSagL3\",\n descKey: \"voltageSag\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"voltage_swell_l1_count\",\n id: \"quality.voltage_swell_l1_count\",\n nameKey: \"voltageSwellL1\",\n descKey: \"voltageSwell\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"voltage_swell_l2_count\",\n id: \"quality.voltage_swell_l2_count\",\n nameKey: \"voltageSwellL2\",\n descKey: \"voltageSwell\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"voltage_swell_l3_count\",\n id: \"quality.voltage_swell_l3_count\",\n nameKey: \"voltageSwellL3\",\n descKey: \"voltageSwell\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"any_power_fail_count\",\n id: \"quality.power_fail_count\",\n nameKey: \"powerFailCount\",\n descKey: \"powerFailCountDesc\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"long_power_fail_count\",\n id: \"quality.long_power_fail_count\",\n nameKey: \"longPowerFailCount\",\n descKey: \"longPowerFailCountDesc\",\n type: \"number\",\n role: \"value\",\n },\n // Capacity tariff (Belgium)\n {\n key: \"average_power_15m_w\",\n id: \"average_power_15m_w\",\n nameKey: \"avgPower15m\",\n descKey: \"belgiumCapacityTariff\",\n type: \"number\",\n role: \"value.power\",\n unit: \"W\",\n },\n {\n key: \"monthly_power_peak_w\",\n id: \"monthly_power_peak_w\",\n nameKey: \"monthlyPowerPeak\",\n descKey: \"belgiumCapacityTariff\",\n type: \"number\",\n role: \"value.power\",\n unit: \"W\",\n },\n {\n key: \"monthly_power_peak_timestamp\",\n id: \"monthly_power_peak_timestamp\",\n nameKey: \"monthlyPowerPeakTimestamp\",\n descKey: \"belgiumCapacityTariff\",\n type: \"string\",\n role: \"date\",\n },\n // kWh meter specifics \u2014 apparent / reactive\n {\n key: \"apparent_current_a\",\n id: \"apparent_current_a\",\n nameKey: \"apparentCurrent\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n {\n key: \"apparent_current_l1_a\",\n id: \"apparent_current_l1_a\",\n nameKey: \"apparentCurrentL1\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n {\n key: \"apparent_current_l2_a\",\n id: \"apparent_current_l2_a\",\n nameKey: \"apparentCurrentL2\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n {\n key: \"apparent_current_l3_a\",\n id: \"apparent_current_l3_a\",\n nameKey: \"apparentCurrentL3\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n {\n key: \"reactive_current_a\",\n id: \"reactive_current_a\",\n nameKey: \"reactiveCurrent\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n {\n key: \"reactive_current_l1_a\",\n id: \"reactive_current_l1_a\",\n nameKey: \"reactiveCurrentL1\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n {\n key: \"reactive_current_l2_a\",\n id: \"reactive_current_l2_a\",\n nameKey: \"reactiveCurrentL2\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n {\n key: \"reactive_current_l3_a\",\n id: \"reactive_current_l3_a\",\n nameKey: \"reactiveCurrentL3\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n {\n key: \"apparent_power_va\",\n id: \"apparent_power_va\",\n nameKey: \"apparentPower\",\n type: \"number\",\n role: \"value.power\",\n unit: \"VA\",\n },\n {\n key: \"apparent_power_l1_va\",\n id: \"apparent_power_l1_va\",\n nameKey: \"apparentPowerL1\",\n type: \"number\",\n role: \"value.power\",\n unit: \"VA\",\n },\n {\n key: \"apparent_power_l2_va\",\n id: \"apparent_power_l2_va\",\n nameKey: \"apparentPowerL2\",\n type: \"number\",\n role: \"value.power\",\n unit: \"VA\",\n },\n {\n key: \"apparent_power_l3_va\",\n id: \"apparent_power_l3_va\",\n nameKey: \"apparentPowerL3\",\n type: \"number\",\n role: \"value.power\",\n unit: \"VA\",\n },\n {\n key: \"reactive_power_var\",\n id: \"reactive_power_var\",\n nameKey: \"reactivePower\",\n type: \"number\",\n role: \"value.power\",\n unit: \"var\",\n },\n {\n key: \"reactive_power_l1_var\",\n id: \"reactive_power_l1_var\",\n nameKey: \"reactivePowerL1\",\n type: \"number\",\n role: \"value.power\",\n unit: \"var\",\n },\n {\n key: \"reactive_power_l2_var\",\n id: \"reactive_power_l2_var\",\n nameKey: \"reactivePowerL2\",\n type: \"number\",\n role: \"value.power\",\n unit: \"var\",\n },\n {\n key: \"reactive_power_l3_var\",\n id: \"reactive_power_l3_var\",\n nameKey: \"reactivePowerL3\",\n type: \"number\",\n role: \"value.power\",\n unit: \"var\",\n },\n {\n key: \"power_factor\",\n id: \"power_factor\",\n nameKey: \"powerFactor\",\n descKey: \"powerFactorDesc\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"power_factor_l1\",\n id: \"power_factor_l1\",\n nameKey: \"powerFactorL1\",\n descKey: \"powerFactorDesc\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"power_factor_l2\",\n id: \"power_factor_l2\",\n nameKey: \"powerFactorL2\",\n descKey: \"powerFactorDesc\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"power_factor_l3\",\n id: \"power_factor_l3\",\n nameKey: \"powerFactorL3\",\n descKey: \"powerFactorDesc\",\n type: \"number\",\n role: \"value\",\n },\n // Battery specifics\n {\n key: \"state_of_charge_pct\",\n id: \"state_of_charge_pct\",\n nameKey: \"stateOfCharge\",\n type: \"number\",\n role: \"value.battery\",\n unit: \"%\",\n },\n { key: \"cycles\", id: \"cycles\", nameKey: \"cycles\", type: \"number\", role: \"value\" },\n // Metadata\n { key: \"meter_model\", id: \"meter_model\", nameKey: \"meterModel\", type: \"string\", role: \"text\" },\n { key: \"timestamp\", id: \"timestamp\", nameKey: \"measurementTimestamp\", type: \"string\", role: \"date\" },\n];\n\n// Instantaneous electrical values \u2014 change on (almost) every ~1/s push, so a setStateChanged\n// read-compare buys nothing. These stay on setStateAsync; every other measurement field\n// (energy totals, tariff, power-quality counts, capacity tariff, SoC/cycles, model/timestamp)\n// is slow/static and uses setStateChangedAsync to skip redundant 1/s writes.\nconst MOMENTARY_KEYS = new Set<string>([\n \"power_w\",\n \"power_l1_w\",\n \"power_l2_w\",\n \"power_l3_w\",\n \"voltage_v\",\n \"voltage_l1_v\",\n \"voltage_l2_v\",\n \"voltage_l3_v\",\n \"current_a\",\n \"current_l1_a\",\n \"current_l2_a\",\n \"current_l3_a\",\n \"frequency_hz\",\n \"apparent_current_a\",\n \"apparent_current_l1_a\",\n \"apparent_current_l2_a\",\n \"apparent_current_l3_a\",\n \"reactive_current_a\",\n \"reactive_current_l1_a\",\n \"reactive_current_l2_a\",\n \"reactive_current_l3_a\",\n \"apparent_power_va\",\n \"apparent_power_l1_va\",\n \"apparent_power_l2_va\",\n \"apparent_power_l3_va\",\n \"reactive_power_var\",\n \"reactive_power_l1_var\",\n \"reactive_power_l2_var\",\n \"reactive_power_l3_var\",\n \"power_factor\",\n \"power_factor_l1\",\n \"power_factor_l2\",\n \"power_factor_l3\",\n]);\n\n/**\n * Build a `common.states` map for tariff (T1-T4) with plain-string labels.\n *\n * **VALUES MUST be plain-string** \u2014 Admin renders states-values as React\n * children. Translation objects trigger React Error #31 \u2192 fatal \"Error in GUI\"\n * on dropdown open (verified hassemu v1.28.4, 2026-05-12).\n *\n */\n// Cached after first build \u2014 the system language is fixed for the adapter run\n// (I18n.init runs once in onReady), so these label maps never change at runtime.\n// Avoids rebuilding the object on every ~1/s measurement push.\nlet tariffStatesCache: Record<string, string> | null = null;\nfunction tariffStates(): Record<string, string> {\n return (tariffStatesCache ??= {\n 1: resolveLabel(\"tariff1\"),\n 2: resolveLabel(\"tariff2\"),\n 3: resolveLabel(\"tariff3\"),\n 4: resolveLabel(\"tariff4\"),\n });\n}\n\n/**\n * Build a `common.states` map for HWE-BAT battery.mode with plain-string labels.\n * Same constraint + same memoization as {@link tariffStates}. `predictive` since API 2.3.0.\n */\nlet batteryModeStatesCache: Record<string, string> | null = null;\nfunction batteryModeStates(): Record<string, string> {\n return (batteryModeStatesCache ??= {\n zero: resolveLabel(\"modeZero\"),\n to_full: resolveLabel(\"modeToFull\"),\n standby: resolveLabel(\"modeStandby\"),\n predictive: resolveLabel(\"modePredictive\"),\n });\n}\n\n/** Manages ioBroker state creation and updates for HomeWizard devices */\nexport class StateManager {\n private readonly adapter: utils.AdapterInstance;\n /**\n * Cache of state / channel IDs that have already passed\n * `setObjectNotExistsAsync`. Skips repeat DB lookups on the hot path \u2014\n * a P1 meter pushes ~1 measurement/s with up to ~30 active fields, which\n * otherwise meant ~30 Redis lookups per second just to ask \u201Edoes it\n * exist\". On `removeDevice(prefix)` all `prefix.*` IDs are dropped.\n */\n private readonly createdIds = new Set<string>();\n\n /** @param adapter The ioBroker adapter instance */\n constructor(adapter: utils.AdapterInstance) {\n this.adapter = adapter;\n }\n\n /**\n * Create device channel and info states\n *\n * @param config Device configuration\n */\n async createDeviceStates(config: DeviceConfig): Promise<void> {\n const prefix = this.devicePrefix(config);\n\n this.adapter.log.debug(`state-manager: createDeviceStates ${prefix} (productType=${config.productType})`);\n\n // Device-Object: common.name keeps the user-supplied product name (or product type as fallback) \u2014\n // these are device-specific identifiers, NOT translatable.\n await this.adapter.extendObjectAsync(\n prefix,\n {\n type: \"device\",\n common: {\n name: config.productName || config.productType,\n statusStates: {\n onlineId: `${this.adapter.namespace}.${prefix}.info.connected`,\n },\n },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n\n await this.adapter.extendObjectAsync(\n `${prefix}.info`,\n {\n type: \"channel\",\n common: { name: tName(\"deviceInformation\") },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n\n await this.createState({\n id: `${prefix}.info.productName`,\n name: tName(\"productName\"),\n type: \"string\",\n role: \"text\",\n });\n await this.createState({\n id: `${prefix}.info.productType`,\n name: tName(\"productType\"),\n type: \"string\",\n role: \"text\",\n });\n await this.createState({ id: `${prefix}.info.firmware`, name: tName(\"firmware\"), type: \"string\", role: \"text\" });\n await this.createState({\n id: `${prefix}.info.connected`,\n name: tName(\"connected\"),\n type: \"boolean\",\n role: \"indicator.reachable\",\n });\n await this.createState({ id: `${prefix}.info.wifi_ssid`, name: tName(\"wifiSsid\"), type: \"string\", role: \"text\" });\n await this.createState({\n id: `${prefix}.info.wifi_rssi_db`,\n name: tName(\"wifiRssi\"),\n type: \"number\",\n role: \"value\",\n unit: \"dBm\",\n });\n await this.createState({\n id: `${prefix}.info.uptime_s`,\n name: tName(\"uptime\"),\n type: \"number\",\n role: \"value\",\n unit: \"s\",\n });\n\n // Remove device button\n await this.createButton(`${prefix}.remove`, tName(\"removeDevice\"), tName(\"removeDeviceDesc\"));\n\n // Set initial info values\n await this.adapter.setStateAsync(`${prefix}.info.productName`, {\n val: config.productName,\n ack: true,\n });\n await this.adapter.setStateAsync(`${prefix}.info.productType`, {\n val: config.productType,\n ack: true,\n });\n }\n\n /**\n * Update measurement states \u2014 only creates states that have values\n *\n * @param config Device configuration\n * @param data Measurement data\n */\n async updateMeasurement(config: DeviceConfig, data: Measurement): Promise<void> {\n if (!isPlainObject(data)) {\n return;\n }\n const prefix = this.devicePrefix(config);\n const mPrefix = `${prefix}.measurement`;\n\n // Ensure measurement channel exists (cached after first call per device)\n await this.ensureChannel(mPrefix, tName(\"measurement\"));\n\n // Main measurement values \u2014 coerce per declared type. Once a state's object\n // is in the cache, ensureAndSet only does one setStateAsync per field \u2014 those\n // are independent and run in parallel via Promise.all instead of sequentially.\n const record = data;\n const writes: Promise<void>[] = [];\n for (const def of MEASUREMENT_STATE_DEFS) {\n const raw = record[def.key];\n let coerced: number | string | null = null;\n if (def.type === \"number\") {\n coerced = coerceFiniteNumber(raw);\n } else if (def.type === \"string\") {\n coerced = coerceString(raw);\n }\n if (coerced !== null) {\n writes.push(\n this.ensureAndSet({\n id: `${mPrefix}.${def.id}`,\n name: tName(def.nameKey),\n type: def.type,\n role: def.role,\n value: coerced,\n unit: def.unit,\n desc: def.descKey ? tName(def.descKey) : undefined,\n states: def.key === \"tariff\" ? tariffStates() : undefined,\n changedOnly: !MOMENTARY_KEYS.has(def.key),\n }),\n );\n }\n }\n await Promise.all(writes);\n\n // External meters (P1 gas/water/heat) \u2014 channel-create paths must run sequentially\n // because the parent `external` channel must exist before the per-meter channel\n // and the per-meter value/unit/timestamp states. Inside one meter, the three\n // value/unit/timestamp writes are independent and run in parallel.\n const external = record.external;\n if (Array.isArray(external) && external.length > 0) {\n for (const rawExt of external) {\n if (!isPlainObject(rawExt)) {\n continue;\n }\n const type = coerceString(rawExt.type);\n const uniqueId = coerceString(rawExt.unique_id);\n if (!type || !uniqueId) {\n continue;\n }\n\n const value = coerceFiniteNumber(rawExt.value);\n const unit = coerceString(rawExt.unit);\n const timestamp = coerceString(rawExt.timestamp);\n\n await this.ensureChannel(`${mPrefix}.external`, tName(\"externalMeters\"));\n\n const extId = `${mPrefix}.external.${sanitize(type)}_${sanitize(uniqueId)}`;\n // External meter channel keeps the device-supplied type (e.g. \"gas_meter\")\n // as channel name \u2014 identifies the physical meter, not localizable.\n await this.ensureChannel(extId, type);\n\n const extWrites: Promise<void>[] = [];\n if (value !== null) {\n extWrites.push(\n this.ensureAndSet({\n id: `${extId}.value`,\n name: tName(\"externalValue\"),\n type: \"number\",\n role: \"value\",\n value,\n unit: unit ?? undefined,\n changedOnly: true,\n }),\n );\n }\n if (unit) {\n extWrites.push(\n this.ensureAndSet({\n id: `${extId}.unit`,\n name: tName(\"externalUnit\"),\n type: \"string\",\n role: \"text\",\n value: unit,\n changedOnly: true,\n }),\n );\n }\n if (timestamp) {\n extWrites.push(\n this.ensureAndSet({\n id: `${extId}.timestamp`,\n name: tName(\"externalTimestamp\"),\n type: \"string\",\n role: \"date\",\n value: timestamp,\n changedOnly: true,\n }),\n );\n }\n await Promise.all(extWrites);\n }\n }\n }\n\n /**\n * Update the raw P1 telegram state (P1 Meter only). Written at the system-poll cadence,\n * not the 1/s measurement feed \u2014 the raw DSMR datagram is bulky text.\n *\n * @param config Device configuration\n * @param telegram Raw P1 telegram text\n */\n async updateTelegram(config: DeviceConfig, telegram: string): Promise<void> {\n const value = coerceString(telegram);\n if (value === null) {\n return;\n }\n const prefix = this.devicePrefix(config);\n await this.ensureChannel(`${prefix}.measurement`, tName(\"measurement\"));\n await this.ensureAndSet({\n id: `${prefix}.measurement.telegram`,\n name: tName(\"telegram\"),\n type: \"string\",\n role: \"text\",\n value,\n changedOnly: true,\n });\n }\n\n /**\n * Update system states\n *\n * @param config Device configuration\n * @param system System info data\n */\n async updateSystem(config: DeviceConfig, system: SystemInfo): Promise<void> {\n if (!isPlainObject(system)) {\n return;\n }\n const prefix = this.devicePrefix(config);\n const record = system as Record<string, unknown>;\n\n // WiFi SSID/RSSI + uptime in info channel \u2014 slow-changing \u2192 changedOnly.\n const ssid = coerceString(record.wifi_ssid);\n if (ssid !== null) {\n await this.ensureAndSet({\n id: `${prefix}.info.wifi_ssid`,\n name: tName(\"wifiSsid\"),\n type: \"string\",\n role: \"text\",\n value: ssid,\n changedOnly: true,\n });\n }\n const rssi = coerceFiniteNumber(record.wifi_rssi_db);\n if (rssi !== null) {\n await this.ensureAndSet({\n id: `${prefix}.info.wifi_rssi_db`,\n name: tName(\"wifiRssi\"),\n type: \"number\",\n role: \"value\",\n value: rssi,\n unit: \"dBm\",\n changedOnly: true,\n });\n }\n const uptime = coerceFiniteNumber(record.uptime_s);\n if (uptime !== null) {\n await this.ensureAndSet({\n id: `${prefix}.info.uptime_s`,\n name: tName(\"uptime\"),\n type: \"number\",\n role: \"value\",\n value: uptime,\n unit: \"s\",\n changedOnly: true,\n });\n }\n\n // System control channel (cached after first call per device)\n await this.ensureChannel(`${prefix}.system`, tName(\"systemSettings\"));\n\n // HWE-BAT: cloud_enabled is read-only (always true) and reboot is unsupported.\n const isBattery = config.productType === \"HWE-BAT\";\n\n const cloudEnabled = coerceBoolean(record.cloud_enabled);\n if (cloudEnabled !== null) {\n await this.ensureAndSet({\n id: `${prefix}.system.cloud_enabled`,\n name: tName(\"cloudEnabled\"),\n type: \"boolean\",\n role: \"switch\",\n value: cloudEnabled,\n write: !isBattery,\n changedOnly: true,\n });\n }\n const ledPct = coerceFiniteNumber(record.status_led_brightness_pct);\n if (ledPct !== null) {\n await this.ensureAndSet({\n id: `${prefix}.system.status_led_brightness_pct`,\n name: tName(\"ledBrightness\"),\n type: \"number\",\n role: \"level\",\n value: ledPct,\n unit: \"%\",\n write: true,\n changedOnly: true,\n });\n }\n\n const apiV1 = coerceBoolean(record.api_v1_enabled);\n if (apiV1 !== null) {\n await this.ensureAndSet({\n id: `${prefix}.system.api_v1_enabled`,\n name: tName(\"apiV1Enabled\"),\n type: \"boolean\",\n role: \"switch\",\n value: apiV1,\n write: true,\n changedOnly: true,\n });\n }\n\n // Action buttons (reboot is unsupported on the Plug-In Battery)\n if (!isBattery) {\n await this.createButton(`${prefix}.system.reboot`, tName(\"rebootDevice\"));\n }\n await this.createButton(`${prefix}.system.identify`, tName(\"identify\"));\n }\n\n /**\n * Update battery control states\n *\n * @param config Device configuration\n * @param battery Battery control data\n */\n async updateBattery(config: DeviceConfig, battery: BatteryControl): Promise<void> {\n if (!isPlainObject(battery)) {\n return;\n }\n const prefix = this.devicePrefix(config);\n const record = battery as Record<string, unknown>;\n\n await this.ensureChannel(`${prefix}.battery`, tName(\"batteryControl\"));\n\n const mode = coerceString(record.mode);\n if (mode) {\n await this.ensureAndSet({\n id: `${prefix}.battery.mode`,\n name: tName(\"batteryMode\"),\n type: \"string\",\n role: \"text\",\n value: mode,\n write: true,\n desc: tName(\"batteryModeDesc\"),\n states: batteryModeStates(),\n changedOnly: true,\n });\n }\n if (Array.isArray(record.permissions)) {\n await this.ensureAndSet({\n id: `${prefix}.battery.permissions`,\n name: tName(\"batteryPermissions\"),\n type: \"string\",\n role: \"json\",\n value: JSON.stringify(record.permissions),\n write: true,\n changedOnly: true,\n });\n }\n // charge_to_full (API 2.3.0) \u2014 writable switch: charge all batteries to 100%.\n const chargeToFull = coerceBoolean(record.charge_to_full);\n if (chargeToFull !== null) {\n await this.ensureAndSet({\n id: `${prefix}.battery.charge_to_full`,\n name: tName(\"batteryChargeToFull\"),\n type: \"boolean\",\n role: \"switch\",\n value: chargeToFull,\n write: true,\n changedOnly: true,\n });\n }\n\n const numberFields: Array<{\n key: string;\n id: string;\n nameKey: I18nKey;\n role: string;\n unit?: string;\n }> = [\n { key: \"battery_count\", id: \"battery_count\", nameKey: \"batteryCount\", role: \"value\" },\n { key: \"power_w\", id: \"power_w\", nameKey: \"batteryPower\", role: \"value.power\", unit: \"W\" },\n { key: \"target_power_w\", id: \"target_power_w\", nameKey: \"batteryTargetPower\", role: \"value.power\", unit: \"W\" },\n {\n key: \"max_consumption_w\",\n id: \"max_consumption_w\",\n nameKey: \"batteryMaxConsumption\",\n role: \"value.power\",\n unit: \"W\",\n },\n {\n key: \"max_production_w\",\n id: \"max_production_w\",\n nameKey: \"batteryMaxProduction\",\n role: \"value.power\",\n unit: \"W\",\n },\n ];\n for (const field of numberFields) {\n const coerced = coerceFiniteNumber(record[field.key]);\n if (coerced !== null) {\n await this.ensureAndSet({\n id: `${prefix}.battery.${field.id}`,\n name: tName(field.nameKey),\n type: \"number\",\n role: field.role,\n value: coerced,\n unit: field.unit,\n changedOnly: true,\n });\n }\n }\n }\n\n /**\n * Set device connected state\n *\n * @param config Device configuration\n * @param connected Connection status\n */\n async setDeviceConnected(config: DeviceConfig, connected: boolean): Promise<void> {\n const prefix = this.devicePrefix(config);\n await this.adapter.setStateChangedAsync(`${prefix}.info.connected`, {\n val: connected,\n ack: true,\n });\n }\n\n /**\n * Remove all states for a device\n *\n * @param config Device configuration\n */\n async removeDevice(config: DeviceConfig): Promise<void> {\n const prefix = this.devicePrefix(config);\n this.adapter.log.debug(`state-manager: removeDevice ${prefix}`);\n await this.adapter.delObjectAsync(prefix, { recursive: true });\n // Drop cache entries belonging to this device \u2014 re-pairing the same\n // device must re-create channels/states from scratch.\n let dropped = 0;\n for (const id of this.createdIds) {\n if (id === prefix || id.startsWith(`${prefix}.`)) {\n this.createdIds.delete(id);\n dropped++;\n }\n }\n this.adapter.log.debug(`state-manager: removeDevice ${prefix} done (dropped ${dropped} cached IDs)`);\n }\n\n /**\n * Remove measurement states from old locations (pre-v0.4.0: device root instead of measurement/ channel)\n *\n * @param config Device configuration\n */\n async cleanupMovedStates(config: DeviceConfig): Promise<void> {\n const prefix = this.devicePrefix(config);\n this.adapter.log.debug(`state-manager: cleanupMovedStates ${prefix} (scanning pre-v0.4.0 paths)`);\n\n // Old paths: states were at device root, now under measurement/\n const oldIds: string[] = [];\n for (const def of MEASUREMENT_STATE_DEFS) {\n oldIds.push(`${prefix}.${def.id}`);\n }\n // External was at device root too\n oldIds.push(`${prefix}.external`);\n\n let removed = 0;\n for (const id of oldIds) {\n if (await this.adapter.getObjectAsync(id)) {\n await this.adapter.delObjectAsync(id, { recursive: true });\n this.adapter.log.debug(`Removed obsolete state: ${id}`);\n removed++;\n }\n }\n if (removed > 0) {\n this.adapter.log.debug(`state-manager: cleanupMovedStates ${prefix} done (removed ${removed} obsolete paths)`);\n }\n }\n\n /**\n * Get device object ID prefix\n *\n * @param config Device configuration\n */\n devicePrefix(config: DeviceConfig): string {\n return `${sanitize(config.productType)}_${sanitize(config.serial)}`;\n }\n\n /**\n * Ensure a channel object exists. Skips the DB lookup once `id` is in the\n * cache \u2014 channels are static after first creation per device.\n *\n * @param id Full channel ID (`<prefix>.<channelName>`).\n * @param name Display name (translation object or device-supplied string).\n */\n private async ensureChannel(id: string, name: ioBroker.StringOrTranslated): Promise<void> {\n if (this.createdIds.has(id)) {\n return;\n }\n await this.adapter.setObjectNotExistsAsync(id, {\n type: \"channel\",\n common: { name },\n native: {},\n });\n this.createdIds.add(id);\n }\n\n /**\n * Create a state if it doesn't exist.\n *\n * @param def State definition (options object \u2014 avoids long positional argument lists).\n */\n private async createState(def: StateDef): Promise<void> {\n if (this.createdIds.has(def.id)) {\n return;\n }\n const common: Partial<ioBroker.StateCommon> = {\n name: def.name,\n type: def.type,\n role: def.role,\n read: true,\n write: def.write ?? false,\n };\n if (def.unit) {\n common.unit = def.unit;\n }\n if (def.desc) {\n common.desc = def.desc;\n }\n if (def.states) {\n common.states = def.states;\n }\n await this.adapter.setObjectNotExistsAsync(def.id, {\n type: \"state\",\n common: common as ioBroker.StateCommon,\n native: {},\n });\n if (def.states) {\n // Existing datapoints from earlier releases may carry translation-object\n // VALUES in `common.states` (v0.7.0 introduced tLabel-as-string casts).\n // setObjectNotExistsAsync is a no-op for those \u2014 actively replace if any\n // value is not plain-string. Admin renders states-values as React child:\n // an object triggers React Error #31 \u2192 fatal \"Error in GUI\" on dropdown.\n await this.repairCommonStatesIfBuggy(def.id, def.states);\n }\n this.createdIds.add(def.id);\n }\n\n /**\n * If the persisted object at `id` has `common.states` values that are not\n * plain-string (= translation objects from older releases), replace\n * `common.states` with the fresh map via `setObjectAsync`. Otherwise no-op.\n *\n * `extendObjectAsync` deep-merges and CANNOT replace an object-value with\n * a string \u2014 only a full `setObjectAsync` replaces. Pattern proven in\n * hassemu v1.27.2 (URL-dropdown) and v1.28.4 (mode-dropdown).\n *\n * @param id State ID to repair.\n * @param fresh Plain-string `common.states` map to write.\n */\n private async repairCommonStatesIfBuggy(id: string, fresh: Record<string, string>): Promise<void> {\n const existing = await this.adapter.getObjectAsync(id);\n if (!existing) {\n return;\n }\n const states = existing.common?.states;\n if (!states || typeof states !== \"object\") {\n return;\n }\n const buggy = Object.values(states as Record<string, unknown>).some(v => typeof v !== \"string\");\n if (!buggy) {\n return;\n }\n existing.common = { ...existing.common, states: fresh } as ioBroker.StateCommon;\n await this.adapter.setObjectAsync(id, existing);\n }\n\n /**\n * Create a button state (read: false, write: true) with initial value false\n *\n * @param id State ID\n * @param name Button label (translation object)\n * @param desc Optional translation object for `common.desc`\n */\n private async createButton(\n id: string,\n name: ioBroker.StringOrTranslated,\n desc?: ioBroker.StringOrTranslated,\n ): Promise<void> {\n if (this.createdIds.has(id)) {\n return;\n }\n const common: Partial<ioBroker.StateCommon> = {\n name: name,\n type: \"boolean\",\n role: \"button\",\n read: false,\n write: true,\n };\n if (desc) {\n common.desc = desc;\n }\n await this.adapter.setObjectNotExistsAsync(id, {\n type: \"state\",\n common: common as ioBroker.StateCommon,\n native: {},\n });\n await this.adapter.setStateAsync(id, { val: false, ack: true });\n this.createdIds.add(id);\n }\n\n /**\n * Ensure a state exists and set its value.\n *\n * `changedOnly` routes through `setStateChangedAsync` (skips the write when the value is\n * unchanged) \u2014 used for slow/static fields (energy totals, system, battery control) so the\n * ~1/s push doesn't churn the DB. Momentary 1 Hz values (power/voltage/current/\u2026) stay on\n * `setStateAsync`. `changedOnly` also prevents double-writes when REST poll + WS push the\n * same field.\n *\n * @param def State definition + value + optional `changedOnly` flag.\n */\n private async ensureAndSet(def: StateSet): Promise<void> {\n await this.createState(def);\n if (def.changedOnly) {\n await this.adapter.setStateChangedAsync(def.id, { val: def.value, ack: true });\n } else {\n await this.adapter.setStateAsync(def.id, { val: def.value, ack: true });\n }\n }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import type * as utils from \"@iobroker/adapter-core\";\nimport { coerceBoolean, coerceFiniteNumber, coerceString, isPlainObject } from \"./coerce\";\nimport type { I18nKey } from \"./i18n\";\nimport { resolveLabel, tName } from \"./i18n\";\nimport type { BatteryControl, DeviceConfig, Measurement, SystemInfo } from \"./types\";\n\n/** Measurement field to state definition mapping */\ninterface MeasurementStateDef {\n /** Measurement field key */\n key: string;\n /** ioBroker state ID suffix */\n id: string;\n /** Translation key for `common.name` (resolved via {@link tName}) */\n nameKey: I18nKey;\n /** Optional translation key for `common.desc` (resolved via {@link tName}) */\n descKey?: I18nKey;\n /** State value type */\n type: ioBroker.CommonType;\n /** ioBroker role */\n role: string;\n /** Unit string */\n unit?: string;\n}\n\n/** Options for {@link StateManager.createState} (avoids long positional argument lists). */\ninterface StateDef {\n /** Full state ID */\n id: string;\n /** State name (translation object or device-identifier string) */\n name: ioBroker.StringOrTranslated;\n /** Value type */\n type: ioBroker.CommonType;\n /** ioBroker role */\n role: string;\n /** Whether the state is writable (default false) */\n write?: boolean;\n /** Optional unit */\n unit?: string;\n /** Optional `common.desc` */\n desc?: ioBroker.StringOrTranslated;\n /** Optional `common.states` map (plain-string values) */\n states?: Record<string, string>;\n}\n\n/** Options for {@link StateManager.ensureAndSet} \u2014 a {@link StateDef} plus the value to write. */\ninterface StateSet extends StateDef {\n /** Value to write */\n value: ioBroker.StateValue;\n /** Use setStateChangedAsync (skip redundant writes) instead of setStateAsync */\n changedOnly?: boolean;\n}\n\n/**\n * Sanitize a string for use as ioBroker object ID (see adapter.FORBIDDEN_CHARS).\n *\n * @param str Raw string to sanitize\n */\nfunction sanitize(str: string): string {\n return str.replace(/[^a-zA-Z0-9_-]/g, \"_\").toLowerCase();\n}\n\nconst MEASUREMENT_STATE_DEFS: MeasurementStateDef[] = [\n // Power\n { key: \"power_w\", id: \"power_w\", nameKey: \"powerTotal\", type: \"number\", role: \"value.power\", unit: \"W\" },\n { key: \"power_l1_w\", id: \"power_l1_w\", nameKey: \"powerL1\", type: \"number\", role: \"value.power\", unit: \"W\" },\n { key: \"power_l2_w\", id: \"power_l2_w\", nameKey: \"powerL2\", type: \"number\", role: \"value.power\", unit: \"W\" },\n { key: \"power_l3_w\", id: \"power_l3_w\", nameKey: \"powerL3\", type: \"number\", role: \"value.power\", unit: \"W\" },\n // Voltage\n { key: \"voltage_v\", id: \"voltage_v\", nameKey: \"voltage\", type: \"number\", role: \"value.voltage\", unit: \"V\" },\n { key: \"voltage_l1_v\", id: \"voltage_l1_v\", nameKey: \"voltageL1\", type: \"number\", role: \"value.voltage\", unit: \"V\" },\n { key: \"voltage_l2_v\", id: \"voltage_l2_v\", nameKey: \"voltageL2\", type: \"number\", role: \"value.voltage\", unit: \"V\" },\n { key: \"voltage_l3_v\", id: \"voltage_l3_v\", nameKey: \"voltageL3\", type: \"number\", role: \"value.voltage\", unit: \"V\" },\n // Current\n { key: \"current_a\", id: \"current_a\", nameKey: \"current\", type: \"number\", role: \"value.current\", unit: \"A\" },\n { key: \"current_l1_a\", id: \"current_l1_a\", nameKey: \"currentL1\", type: \"number\", role: \"value.current\", unit: \"A\" },\n { key: \"current_l2_a\", id: \"current_l2_a\", nameKey: \"currentL2\", type: \"number\", role: \"value.current\", unit: \"A\" },\n { key: \"current_l3_a\", id: \"current_l3_a\", nameKey: \"currentL3\", type: \"number\", role: \"value.current\", unit: \"A\" },\n // Frequency\n { key: \"frequency_hz\", id: \"frequency_hz\", nameKey: \"frequency\", type: \"number\", role: \"value\", unit: \"Hz\" },\n // Energy import\n {\n key: \"energy_import_kwh\",\n id: \"energy_import_kwh\",\n nameKey: \"energyImportTotal\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n {\n key: \"energy_import_t1_kwh\",\n id: \"energy_import_t1_kwh\",\n nameKey: \"energyImportT1\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n {\n key: \"energy_import_t2_kwh\",\n id: \"energy_import_t2_kwh\",\n nameKey: \"energyImportT2\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n {\n key: \"energy_import_t3_kwh\",\n id: \"energy_import_t3_kwh\",\n nameKey: \"energyImportT3\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n {\n key: \"energy_import_t4_kwh\",\n id: \"energy_import_t4_kwh\",\n nameKey: \"energyImportT4\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n // Energy export\n {\n key: \"energy_export_kwh\",\n id: \"energy_export_kwh\",\n nameKey: \"energyExportTotal\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n {\n key: \"energy_export_t1_kwh\",\n id: \"energy_export_t1_kwh\",\n nameKey: \"energyExportT1\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n {\n key: \"energy_export_t2_kwh\",\n id: \"energy_export_t2_kwh\",\n nameKey: \"energyExportT2\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n {\n key: \"energy_export_t3_kwh\",\n id: \"energy_export_t3_kwh\",\n nameKey: \"energyExportT3\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n {\n key: \"energy_export_t4_kwh\",\n id: \"energy_export_t4_kwh\",\n nameKey: \"energyExportT4\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n // Tariff (common.states applied separately in updateMeasurement for translation labels)\n { key: \"tariff\", id: \"tariff\", nameKey: \"tariff\", type: \"number\", role: \"value\" },\n // Power quality\n {\n key: \"voltage_sag_l1_count\",\n id: \"quality.voltage_sag_l1_count\",\n nameKey: \"voltageSagL1\",\n descKey: \"voltageSag\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"voltage_sag_l2_count\",\n id: \"quality.voltage_sag_l2_count\",\n nameKey: \"voltageSagL2\",\n descKey: \"voltageSag\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"voltage_sag_l3_count\",\n id: \"quality.voltage_sag_l3_count\",\n nameKey: \"voltageSagL3\",\n descKey: \"voltageSag\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"voltage_swell_l1_count\",\n id: \"quality.voltage_swell_l1_count\",\n nameKey: \"voltageSwellL1\",\n descKey: \"voltageSwell\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"voltage_swell_l2_count\",\n id: \"quality.voltage_swell_l2_count\",\n nameKey: \"voltageSwellL2\",\n descKey: \"voltageSwell\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"voltage_swell_l3_count\",\n id: \"quality.voltage_swell_l3_count\",\n nameKey: \"voltageSwellL3\",\n descKey: \"voltageSwell\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"any_power_fail_count\",\n id: \"quality.power_fail_count\",\n nameKey: \"powerFailCount\",\n descKey: \"powerFailCountDesc\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"long_power_fail_count\",\n id: \"quality.long_power_fail_count\",\n nameKey: \"longPowerFailCount\",\n descKey: \"longPowerFailCountDesc\",\n type: \"number\",\n role: \"value\",\n },\n // Capacity tariff (Belgium)\n {\n key: \"average_power_15m_w\",\n id: \"average_power_15m_w\",\n nameKey: \"avgPower15m\",\n descKey: \"belgiumCapacityTariff\",\n type: \"number\",\n role: \"value.power\",\n unit: \"W\",\n },\n {\n key: \"monthly_power_peak_w\",\n id: \"monthly_power_peak_w\",\n nameKey: \"monthlyPowerPeak\",\n descKey: \"belgiumCapacityTariff\",\n type: \"number\",\n role: \"value.power\",\n unit: \"W\",\n },\n {\n key: \"monthly_power_peak_timestamp\",\n id: \"monthly_power_peak_timestamp\",\n nameKey: \"monthlyPowerPeakTimestamp\",\n descKey: \"belgiumCapacityTariff\",\n type: \"string\",\n role: \"date\",\n },\n // kWh meter specifics \u2014 apparent / reactive\n {\n key: \"apparent_current_a\",\n id: \"apparent_current_a\",\n nameKey: \"apparentCurrent\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n {\n key: \"apparent_current_l1_a\",\n id: \"apparent_current_l1_a\",\n nameKey: \"apparentCurrentL1\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n {\n key: \"apparent_current_l2_a\",\n id: \"apparent_current_l2_a\",\n nameKey: \"apparentCurrentL2\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n {\n key: \"apparent_current_l3_a\",\n id: \"apparent_current_l3_a\",\n nameKey: \"apparentCurrentL3\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n {\n key: \"reactive_current_a\",\n id: \"reactive_current_a\",\n nameKey: \"reactiveCurrent\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n {\n key: \"reactive_current_l1_a\",\n id: \"reactive_current_l1_a\",\n nameKey: \"reactiveCurrentL1\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n {\n key: \"reactive_current_l2_a\",\n id: \"reactive_current_l2_a\",\n nameKey: \"reactiveCurrentL2\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n {\n key: \"reactive_current_l3_a\",\n id: \"reactive_current_l3_a\",\n nameKey: \"reactiveCurrentL3\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n {\n key: \"apparent_power_va\",\n id: \"apparent_power_va\",\n nameKey: \"apparentPower\",\n type: \"number\",\n role: \"value.power\",\n unit: \"VA\",\n },\n {\n key: \"apparent_power_l1_va\",\n id: \"apparent_power_l1_va\",\n nameKey: \"apparentPowerL1\",\n type: \"number\",\n role: \"value.power\",\n unit: \"VA\",\n },\n {\n key: \"apparent_power_l2_va\",\n id: \"apparent_power_l2_va\",\n nameKey: \"apparentPowerL2\",\n type: \"number\",\n role: \"value.power\",\n unit: \"VA\",\n },\n {\n key: \"apparent_power_l3_va\",\n id: \"apparent_power_l3_va\",\n nameKey: \"apparentPowerL3\",\n type: \"number\",\n role: \"value.power\",\n unit: \"VA\",\n },\n {\n key: \"reactive_power_var\",\n id: \"reactive_power_var\",\n nameKey: \"reactivePower\",\n type: \"number\",\n role: \"value.power\",\n unit: \"var\",\n },\n {\n key: \"reactive_power_l1_var\",\n id: \"reactive_power_l1_var\",\n nameKey: \"reactivePowerL1\",\n type: \"number\",\n role: \"value.power\",\n unit: \"var\",\n },\n {\n key: \"reactive_power_l2_var\",\n id: \"reactive_power_l2_var\",\n nameKey: \"reactivePowerL2\",\n type: \"number\",\n role: \"value.power\",\n unit: \"var\",\n },\n {\n key: \"reactive_power_l3_var\",\n id: \"reactive_power_l3_var\",\n nameKey: \"reactivePowerL3\",\n type: \"number\",\n role: \"value.power\",\n unit: \"var\",\n },\n {\n key: \"power_factor\",\n id: \"power_factor\",\n nameKey: \"powerFactor\",\n descKey: \"powerFactorDesc\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"power_factor_l1\",\n id: \"power_factor_l1\",\n nameKey: \"powerFactorL1\",\n descKey: \"powerFactorDesc\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"power_factor_l2\",\n id: \"power_factor_l2\",\n nameKey: \"powerFactorL2\",\n descKey: \"powerFactorDesc\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"power_factor_l3\",\n id: \"power_factor_l3\",\n nameKey: \"powerFactorL3\",\n descKey: \"powerFactorDesc\",\n type: \"number\",\n role: \"value\",\n },\n // Battery specifics\n {\n key: \"state_of_charge_pct\",\n id: \"state_of_charge_pct\",\n nameKey: \"stateOfCharge\",\n type: \"number\",\n role: \"value.battery\",\n unit: \"%\",\n },\n { key: \"cycles\", id: \"cycles\", nameKey: \"cycles\", type: \"number\", role: \"value\" },\n // Metadata\n { key: \"meter_model\", id: \"meter_model\", nameKey: \"meterModel\", type: \"string\", role: \"text\" },\n { key: \"timestamp\", id: \"timestamp\", nameKey: \"measurementTimestamp\", type: \"string\", role: \"date\" },\n];\n\n// Instantaneous electrical values \u2014 change on (almost) every ~1/s push, so a setStateChanged\n// read-compare buys nothing. These stay on setStateAsync; every other measurement field\n// (energy totals, tariff, power-quality counts, capacity tariff, SoC/cycles, model/timestamp)\n// is slow/static and uses setStateChangedAsync to skip redundant 1/s writes.\nconst MOMENTARY_KEYS = new Set<string>([\n \"power_w\",\n \"power_l1_w\",\n \"power_l2_w\",\n \"power_l3_w\",\n \"voltage_v\",\n \"voltage_l1_v\",\n \"voltage_l2_v\",\n \"voltage_l3_v\",\n \"current_a\",\n \"current_l1_a\",\n \"current_l2_a\",\n \"current_l3_a\",\n \"frequency_hz\",\n \"apparent_current_a\",\n \"apparent_current_l1_a\",\n \"apparent_current_l2_a\",\n \"apparent_current_l3_a\",\n \"reactive_current_a\",\n \"reactive_current_l1_a\",\n \"reactive_current_l2_a\",\n \"reactive_current_l3_a\",\n \"apparent_power_va\",\n \"apparent_power_l1_va\",\n \"apparent_power_l2_va\",\n \"apparent_power_l3_va\",\n \"reactive_power_var\",\n \"reactive_power_l1_var\",\n \"reactive_power_l2_var\",\n \"reactive_power_l3_var\",\n \"power_factor\",\n \"power_factor_l1\",\n \"power_factor_l2\",\n \"power_factor_l3\",\n]);\n\n/**\n * Build a `common.states` map for tariff (T1-T4) with plain-string labels.\n *\n * **VALUES MUST be plain-string** \u2014 Admin renders states-values as React\n * children. Translation objects trigger React Error #31 \u2192 fatal \"Error in GUI\"\n * on dropdown open (verified hassemu v1.28.4, 2026-05-12).\n *\n */\n// Cached after first build \u2014 the system language is fixed for the adapter run\n// (I18n.init runs once in onReady), so these label maps never change at runtime.\n// Avoids rebuilding the object on every ~1/s measurement push.\nlet tariffStatesCache: Record<string, string> | null = null;\nfunction tariffStates(): Record<string, string> {\n return (tariffStatesCache ??= {\n 1: resolveLabel(\"tariff1\"),\n 2: resolveLabel(\"tariff2\"),\n 3: resolveLabel(\"tariff3\"),\n 4: resolveLabel(\"tariff4\"),\n });\n}\n\n/**\n * Build a `common.states` map for HWE-BAT battery.mode with plain-string labels.\n * Same constraint + same memoization as {@link tariffStates}. `predictive` since API 2.3.0.\n */\nlet batteryModeStatesCache: Record<string, string> | null = null;\nfunction batteryModeStates(): Record<string, string> {\n return (batteryModeStatesCache ??= {\n zero: resolveLabel(\"modeZero\"),\n to_full: resolveLabel(\"modeToFull\"),\n standby: resolveLabel(\"modeStandby\"),\n predictive: resolveLabel(\"modePredictive\"),\n });\n}\n\n/** Manages ioBroker state creation and updates for HomeWizard devices */\nexport class StateManager {\n private readonly adapter: utils.AdapterInstance;\n /**\n * Cache of state / channel IDs that have already passed\n * `setObjectNotExistsAsync`. Skips repeat DB lookups on the hot path \u2014\n * a P1 meter pushes ~1 measurement/s with up to ~30 active fields, which\n * otherwise meant ~30 Redis lookups per second just to ask \u201Edoes it\n * exist\". On `removeDevice(prefix)` all `prefix.*` IDs are dropped.\n */\n private readonly createdIds = new Set<string>();\n\n /** @param adapter The ioBroker adapter instance */\n constructor(adapter: utils.AdapterInstance) {\n this.adapter = adapter;\n }\n\n /**\n * Create device channel and info states\n *\n * @param config Device configuration\n */\n async createDeviceStates(config: DeviceConfig): Promise<void> {\n const prefix = this.devicePrefix(config);\n\n this.adapter.log.debug(`state-manager: createDeviceStates ${prefix} (productType=${config.productType})`);\n\n // Device-Object: common.name keeps the user-supplied product name (or product type as fallback) \u2014\n // these are device-specific identifiers, NOT translatable.\n await this.adapter.extendObjectAsync(\n prefix,\n {\n type: \"device\",\n common: {\n name: config.productName || config.productType,\n statusStates: {\n onlineId: `${this.adapter.namespace}.${prefix}.info.connected`,\n },\n },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n\n await this.adapter.extendObjectAsync(\n `${prefix}.info`,\n {\n type: \"channel\",\n common: { name: tName(\"deviceInformation\") },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n\n await this.createState({\n id: `${prefix}.info.productName`,\n name: tName(\"productName\"),\n type: \"string\",\n role: \"text\",\n });\n await this.createState({\n id: `${prefix}.info.productType`,\n name: tName(\"productType\"),\n type: \"string\",\n role: \"text\",\n });\n await this.createState({ id: `${prefix}.info.firmware`, name: tName(\"firmware\"), type: \"string\", role: \"text\" });\n await this.createState({\n id: `${prefix}.info.connected`,\n name: tName(\"connected\"),\n type: \"boolean\",\n role: \"indicator.reachable\",\n });\n await this.createState({ id: `${prefix}.info.wifi_ssid`, name: tName(\"wifiSsid\"), type: \"string\", role: \"text\" });\n await this.createState({\n id: `${prefix}.info.wifi_rssi_db`,\n name: tName(\"wifiRssi\"),\n type: \"number\",\n role: \"value\",\n unit: \"dBm\",\n });\n await this.createState({\n id: `${prefix}.info.uptime_s`,\n name: tName(\"uptime\"),\n type: \"number\",\n role: \"value\",\n unit: \"s\",\n });\n\n // Remove device button\n await this.createButton(`${prefix}.remove`, tName(\"removeDevice\"), tName(\"removeDeviceDesc\"));\n\n // Set initial info values\n await this.adapter.setStateAsync(`${prefix}.info.productName`, {\n val: config.productName,\n ack: true,\n });\n await this.adapter.setStateAsync(`${prefix}.info.productType`, {\n val: config.productType,\n ack: true,\n });\n }\n\n /**\n * Update measurement states \u2014 only creates states that have values\n *\n * @param config Device configuration\n * @param data Measurement data\n */\n async updateMeasurement(config: DeviceConfig, data: Measurement): Promise<void> {\n if (!isPlainObject(data)) {\n return;\n }\n const prefix = this.devicePrefix(config);\n const mPrefix = `${prefix}.measurement`;\n\n // Ensure measurement channel exists (cached after first call per device)\n await this.ensureChannel(mPrefix, tName(\"measurement\"));\n\n // Main measurement values \u2014 coerce per declared type. Once a state's object\n // is in the cache, ensureAndSet only does one setStateAsync per field \u2014 those\n // are independent and run in parallel via Promise.all instead of sequentially.\n const record = data;\n const writes: Promise<void>[] = [];\n for (const def of MEASUREMENT_STATE_DEFS) {\n const raw = record[def.key];\n let coerced: number | string | null = null;\n if (def.type === \"number\") {\n coerced = coerceFiniteNumber(raw);\n } else if (def.type === \"string\") {\n coerced = coerceString(raw);\n }\n if (coerced !== null) {\n writes.push(\n this.ensureAndSet({\n id: `${mPrefix}.${def.id}`,\n name: tName(def.nameKey),\n type: def.type,\n role: def.role,\n value: coerced,\n unit: def.unit,\n desc: def.descKey ? tName(def.descKey) : undefined,\n states: def.key === \"tariff\" ? tariffStates() : undefined,\n changedOnly: !MOMENTARY_KEYS.has(def.key),\n }),\n );\n }\n }\n await Promise.all(writes);\n\n // External meters (P1 gas/water/heat) \u2014 channel-create paths must run sequentially\n // because the parent `external` channel must exist before the per-meter channel\n // and the per-meter value/unit/timestamp states. Inside one meter, the three\n // value/unit/timestamp writes are independent and run in parallel.\n const external = record.external;\n if (Array.isArray(external) && external.length > 0) {\n for (const rawExt of external) {\n if (!isPlainObject(rawExt)) {\n continue;\n }\n const type = coerceString(rawExt.type);\n const uniqueId = coerceString(rawExt.unique_id);\n if (!type || !uniqueId) {\n continue;\n }\n\n const value = coerceFiniteNumber(rawExt.value);\n const unit = coerceString(rawExt.unit);\n const timestamp = coerceString(rawExt.timestamp);\n\n await this.ensureChannel(`${mPrefix}.external`, tName(\"externalMeters\"));\n\n const extId = `${mPrefix}.external.${sanitize(type)}_${sanitize(uniqueId)}`;\n // External meter channel keeps the device-supplied type (e.g. \"gas_meter\")\n // as channel name \u2014 identifies the physical meter, not localizable.\n await this.ensureChannel(extId, type);\n\n const extWrites: Promise<void>[] = [];\n if (value !== null) {\n extWrites.push(\n this.ensureAndSet({\n id: `${extId}.value`,\n name: tName(\"externalValue\"),\n type: \"number\",\n role: \"value\",\n value,\n unit: unit ?? undefined,\n changedOnly: true,\n }),\n );\n }\n if (unit) {\n extWrites.push(\n this.ensureAndSet({\n id: `${extId}.unit`,\n name: tName(\"externalUnit\"),\n type: \"string\",\n role: \"text\",\n value: unit,\n changedOnly: true,\n }),\n );\n }\n if (timestamp) {\n extWrites.push(\n this.ensureAndSet({\n id: `${extId}.timestamp`,\n name: tName(\"externalTimestamp\"),\n type: \"string\",\n role: \"date\",\n value: timestamp,\n changedOnly: true,\n }),\n );\n }\n await Promise.all(extWrites);\n }\n }\n }\n\n /**\n * Update system states\n *\n * @param config Device configuration\n * @param system System info data\n */\n async updateSystem(config: DeviceConfig, system: SystemInfo): Promise<void> {\n if (!isPlainObject(system)) {\n return;\n }\n const prefix = this.devicePrefix(config);\n const record = system as Record<string, unknown>;\n\n // WiFi SSID/RSSI + uptime in info channel \u2014 slow-changing \u2192 changedOnly.\n const ssid = coerceString(record.wifi_ssid);\n if (ssid !== null) {\n await this.ensureAndSet({\n id: `${prefix}.info.wifi_ssid`,\n name: tName(\"wifiSsid\"),\n type: \"string\",\n role: \"text\",\n value: ssid,\n changedOnly: true,\n });\n }\n const rssi = coerceFiniteNumber(record.wifi_rssi_db);\n if (rssi !== null) {\n await this.ensureAndSet({\n id: `${prefix}.info.wifi_rssi_db`,\n name: tName(\"wifiRssi\"),\n type: \"number\",\n role: \"value\",\n value: rssi,\n unit: \"dBm\",\n changedOnly: true,\n });\n }\n const uptime = coerceFiniteNumber(record.uptime_s);\n if (uptime !== null) {\n await this.ensureAndSet({\n id: `${prefix}.info.uptime_s`,\n name: tName(\"uptime\"),\n type: \"number\",\n role: \"value\",\n value: uptime,\n unit: \"s\",\n changedOnly: true,\n });\n }\n\n // System control channel (cached after first call per device)\n await this.ensureChannel(`${prefix}.system`, tName(\"systemSettings\"));\n\n // HWE-BAT: cloud_enabled is read-only (always true) and reboot is unsupported.\n const isBattery = config.productType === \"HWE-BAT\";\n\n const cloudEnabled = coerceBoolean(record.cloud_enabled);\n if (cloudEnabled !== null) {\n await this.ensureAndSet({\n id: `${prefix}.system.cloud_enabled`,\n name: tName(\"cloudEnabled\"),\n type: \"boolean\",\n role: \"switch\",\n value: cloudEnabled,\n write: !isBattery,\n changedOnly: true,\n });\n }\n const ledPct = coerceFiniteNumber(record.status_led_brightness_pct);\n if (ledPct !== null) {\n await this.ensureAndSet({\n id: `${prefix}.system.status_led_brightness_pct`,\n name: tName(\"ledBrightness\"),\n type: \"number\",\n role: \"level\",\n value: ledPct,\n unit: \"%\",\n write: true,\n changedOnly: true,\n });\n }\n\n const apiV1 = coerceBoolean(record.api_v1_enabled);\n if (apiV1 !== null) {\n await this.ensureAndSet({\n id: `${prefix}.system.api_v1_enabled`,\n name: tName(\"apiV1Enabled\"),\n type: \"boolean\",\n role: \"switch\",\n value: apiV1,\n write: true,\n changedOnly: true,\n });\n }\n\n // Action buttons (reboot is unsupported on the Plug-In Battery)\n if (!isBattery) {\n await this.createButton(`${prefix}.system.reboot`, tName(\"rebootDevice\"));\n }\n await this.createButton(`${prefix}.system.identify`, tName(\"identify\"));\n }\n\n /**\n * Update battery control states\n *\n * @param config Device configuration\n * @param battery Battery control data\n */\n async updateBattery(config: DeviceConfig, battery: BatteryControl): Promise<void> {\n if (!isPlainObject(battery)) {\n return;\n }\n const prefix = this.devicePrefix(config);\n const record = battery as Record<string, unknown>;\n\n await this.ensureChannel(`${prefix}.battery`, tName(\"batteryControl\"));\n\n const mode = coerceString(record.mode);\n if (mode) {\n await this.ensureAndSet({\n id: `${prefix}.battery.mode`,\n name: tName(\"batteryMode\"),\n type: \"string\",\n role: \"text\",\n value: mode,\n write: true,\n desc: tName(\"batteryModeDesc\"),\n states: batteryModeStates(),\n changedOnly: true,\n });\n }\n if (Array.isArray(record.permissions)) {\n await this.ensureAndSet({\n id: `${prefix}.battery.permissions`,\n name: tName(\"batteryPermissions\"),\n type: \"string\",\n role: \"json\",\n value: JSON.stringify(record.permissions),\n write: true,\n changedOnly: true,\n });\n }\n // charge_to_full (API 2.3.0) \u2014 writable switch: charge all batteries to 100%.\n const chargeToFull = coerceBoolean(record.charge_to_full);\n if (chargeToFull !== null) {\n await this.ensureAndSet({\n id: `${prefix}.battery.charge_to_full`,\n name: tName(\"batteryChargeToFull\"),\n type: \"boolean\",\n role: \"switch\",\n value: chargeToFull,\n write: true,\n changedOnly: true,\n });\n }\n\n const numberFields: Array<{\n key: string;\n id: string;\n nameKey: I18nKey;\n role: string;\n unit?: string;\n }> = [\n { key: \"battery_count\", id: \"battery_count\", nameKey: \"batteryCount\", role: \"value\" },\n { key: \"power_w\", id: \"power_w\", nameKey: \"batteryPower\", role: \"value.power\", unit: \"W\" },\n { key: \"target_power_w\", id: \"target_power_w\", nameKey: \"batteryTargetPower\", role: \"value.power\", unit: \"W\" },\n {\n key: \"max_consumption_w\",\n id: \"max_consumption_w\",\n nameKey: \"batteryMaxConsumption\",\n role: \"value.power\",\n unit: \"W\",\n },\n {\n key: \"max_production_w\",\n id: \"max_production_w\",\n nameKey: \"batteryMaxProduction\",\n role: \"value.power\",\n unit: \"W\",\n },\n ];\n for (const field of numberFields) {\n const coerced = coerceFiniteNumber(record[field.key]);\n if (coerced !== null) {\n await this.ensureAndSet({\n id: `${prefix}.battery.${field.id}`,\n name: tName(field.nameKey),\n type: \"number\",\n role: field.role,\n value: coerced,\n unit: field.unit,\n changedOnly: true,\n });\n }\n }\n }\n\n /**\n * Set device connected state\n *\n * @param config Device configuration\n * @param connected Connection status\n */\n async setDeviceConnected(config: DeviceConfig, connected: boolean): Promise<void> {\n const prefix = this.devicePrefix(config);\n await this.adapter.setStateChangedAsync(`${prefix}.info.connected`, {\n val: connected,\n ack: true,\n });\n }\n\n /**\n * Remove all states for a device\n *\n * @param config Device configuration\n */\n async removeDevice(config: DeviceConfig): Promise<void> {\n const prefix = this.devicePrefix(config);\n this.adapter.log.debug(`state-manager: removeDevice ${prefix}`);\n await this.adapter.delObjectAsync(prefix, { recursive: true });\n // Drop cache entries belonging to this device \u2014 re-pairing the same\n // device must re-create channels/states from scratch.\n let dropped = 0;\n for (const id of this.createdIds) {\n if (id === prefix || id.startsWith(`${prefix}.`)) {\n this.createdIds.delete(id);\n dropped++;\n }\n }\n this.adapter.log.debug(`state-manager: removeDevice ${prefix} done (dropped ${dropped} cached IDs)`);\n }\n\n /**\n * Remove obsolete states: pre-v0.4.0 device-root paths (now under measurement/) plus\n * states retired in later versions (v0.11.0: raw P1 telegram).\n *\n * @param config Device configuration\n */\n async cleanupMovedStates(config: DeviceConfig): Promise<void> {\n const prefix = this.devicePrefix(config);\n this.adapter.log.debug(`state-manager: cleanupMovedStates ${prefix} (scanning pre-v0.4.0 paths)`);\n\n // Old paths: states were at device root, now under measurement/\n const oldIds: string[] = [];\n for (const def of MEASUREMENT_STATE_DEFS) {\n oldIds.push(`${prefix}.${def.id}`);\n }\n // External was at device root too\n oldIds.push(`${prefix}.external`);\n // Retired in v0.11.0: raw P1 telegram (DSMR passthrough, not part of the v2 data model)\n oldIds.push(`${prefix}.measurement.telegram`);\n\n let removed = 0;\n for (const id of oldIds) {\n if (await this.adapter.getObjectAsync(id)) {\n await this.adapter.delObjectAsync(id, { recursive: true });\n this.adapter.log.debug(`Removed obsolete state: ${id}`);\n removed++;\n }\n }\n if (removed > 0) {\n this.adapter.log.debug(`state-manager: cleanupMovedStates ${prefix} done (removed ${removed} obsolete paths)`);\n }\n }\n\n /**\n * Get device object ID prefix\n *\n * @param config Device configuration\n */\n devicePrefix(config: DeviceConfig): string {\n return `${sanitize(config.productType)}_${sanitize(config.serial)}`;\n }\n\n /**\n * Ensure a channel object exists. Skips the DB lookup once `id` is in the\n * cache \u2014 channels are static after first creation per device.\n *\n * @param id Full channel ID (`<prefix>.<channelName>`).\n * @param name Display name (translation object or device-supplied string).\n */\n private async ensureChannel(id: string, name: ioBroker.StringOrTranslated): Promise<void> {\n if (this.createdIds.has(id)) {\n return;\n }\n await this.adapter.setObjectNotExistsAsync(id, {\n type: \"channel\",\n common: { name },\n native: {},\n });\n this.createdIds.add(id);\n }\n\n /**\n * Create a state if it doesn't exist.\n *\n * @param def State definition (options object \u2014 avoids long positional argument lists).\n */\n private async createState(def: StateDef): Promise<void> {\n if (this.createdIds.has(def.id)) {\n return;\n }\n const common: Partial<ioBroker.StateCommon> = {\n name: def.name,\n type: def.type,\n role: def.role,\n read: true,\n write: def.write ?? false,\n };\n if (def.unit) {\n common.unit = def.unit;\n }\n if (def.desc) {\n common.desc = def.desc;\n }\n if (def.states) {\n common.states = def.states;\n }\n await this.adapter.setObjectNotExistsAsync(def.id, {\n type: \"state\",\n common: common as ioBroker.StateCommon,\n native: {},\n });\n if (def.states) {\n // Existing datapoints from earlier releases may carry translation-object\n // VALUES in `common.states` (v0.7.0 introduced tLabel-as-string casts).\n // setObjectNotExistsAsync is a no-op for those \u2014 actively replace if any\n // value is not plain-string. Admin renders states-values as React child:\n // an object triggers React Error #31 \u2192 fatal \"Error in GUI\" on dropdown.\n await this.repairCommonStatesIfBuggy(def.id, def.states);\n }\n this.createdIds.add(def.id);\n }\n\n /**\n * If the persisted object at `id` has `common.states` values that are not\n * plain-string (= translation objects from older releases), replace\n * `common.states` with the fresh map via `setObjectAsync`. Otherwise no-op.\n *\n * `extendObjectAsync` deep-merges and CANNOT replace an object-value with\n * a string \u2014 only a full `setObjectAsync` replaces. Pattern proven in\n * hassemu v1.27.2 (URL-dropdown) and v1.28.4 (mode-dropdown).\n *\n * @param id State ID to repair.\n * @param fresh Plain-string `common.states` map to write.\n */\n private async repairCommonStatesIfBuggy(id: string, fresh: Record<string, string>): Promise<void> {\n const existing = await this.adapter.getObjectAsync(id);\n if (!existing) {\n return;\n }\n const states = existing.common?.states;\n if (!states || typeof states !== \"object\") {\n return;\n }\n const buggy = Object.values(states as Record<string, unknown>).some(v => typeof v !== \"string\");\n if (!buggy) {\n return;\n }\n existing.common = { ...existing.common, states: fresh } as ioBroker.StateCommon;\n await this.adapter.setObjectAsync(id, existing);\n }\n\n /**\n * Create a button state (read: false, write: true) with initial value false\n *\n * @param id State ID\n * @param name Button label (translation object)\n * @param desc Optional translation object for `common.desc`\n */\n private async createButton(\n id: string,\n name: ioBroker.StringOrTranslated,\n desc?: ioBroker.StringOrTranslated,\n ): Promise<void> {\n if (this.createdIds.has(id)) {\n return;\n }\n const common: Partial<ioBroker.StateCommon> = {\n name: name,\n type: \"boolean\",\n role: \"button\",\n read: false,\n write: true,\n };\n if (desc) {\n common.desc = desc;\n }\n await this.adapter.setObjectNotExistsAsync(id, {\n type: \"state\",\n common: common as ioBroker.StateCommon,\n native: {},\n });\n await this.adapter.setStateAsync(id, { val: false, ack: true });\n this.createdIds.add(id);\n }\n\n /**\n * Ensure a state exists and set its value.\n *\n * `changedOnly` routes through `setStateChangedAsync` (skips the write when the value is\n * unchanged) \u2014 used for slow/static fields (energy totals, system, battery control) so the\n * ~1/s push doesn't churn the DB. Momentary 1 Hz values (power/voltage/current/\u2026) stay on\n * `setStateAsync`. `changedOnly` also prevents double-writes when REST poll + WS push the\n * same field.\n *\n * @param def State definition + value + optional `changedOnly` flag.\n */\n private async ensureAndSet(def: StateSet): Promise<void> {\n await this.createState(def);\n if (def.changedOnly) {\n await this.adapter.setStateChangedAsync(def.id, { val: def.value, ack: true });\n } else {\n await this.adapter.setStateAsync(def.id, { val: def.value, ack: true });\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/build/main.js
CHANGED
|
@@ -809,16 +809,6 @@ class HomeWizard extends utils.Adapter {
|
|
|
809
809
|
}
|
|
810
810
|
} catch {
|
|
811
811
|
}
|
|
812
|
-
if (conn.config.productType === "HWE-P1" && !conn.removed && !this.unloading) {
|
|
813
|
-
try {
|
|
814
|
-
const telegram = await client.getTelegram();
|
|
815
|
-
if (!conn.removed && !this.unloading) {
|
|
816
|
-
await this.stateManager.updateTelegram(conn.config, telegram);
|
|
817
|
-
}
|
|
818
|
-
} catch (err) {
|
|
819
|
-
this.log.debug(`${conn.config.productName} telegram: ${(0, import_coerce.errText)(err)}`);
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
812
|
if (conn.removed || this.unloading) {
|
|
823
813
|
return;
|
|
824
814
|
}
|
package/build/main.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/main.ts"],
|
|
4
|
-
"sourcesContent": ["import * as utils from \"@iobroker/adapter-core\";\nimport { I18n } from \"@iobroker/adapter-core\";\nimport { join } from \"node:path\";\nimport { errText, isValidIpv4, parseBatteryPermissions, validateBatteryMode } from \"./lib/coerce\";\nimport { classifyError, createDeviceConnection, UNSTABLE_DISCONNECT_THRESHOLD } from \"./lib/connection-utils\";\nimport { HomeWizardDiscovery } from \"./lib/discovery\";\nimport { HomeWizardApiError, HomeWizardClient } from \"./lib/homewizard-client\";\nimport {\n computeReconnectDelay,\n decideUnstableTransition,\n findConnectionForState as resolveConnectionForState,\n pickRestPollInterval,\n shouldEmitAfterCooldown,\n shouldStartIpRecovery,\n} from \"./lib/main-helpers\";\nimport { StateManager } from \"./lib/state-manager\";\nimport type {\n BatteryControl,\n DeviceConfig,\n DeviceConnection,\n DiscoveredDevice,\n Measurement,\n SystemInfo,\n} from \"./lib/types\";\nimport { HomeWizardWebSocket, type TimerDeps, type WsCallbacks } from \"./lib/websocket-client\";\n\n/** Pairing timeout in milliseconds (60 seconds) */\nconst PAIRING_TIMEOUT_MS = 60_000;\n/** Pairing poll interval in milliseconds */\nconst PAIRING_POLL_MS = 2_000;\n/** WebSocket reconnect base delay in milliseconds */\nconst WS_RECONNECT_BASE_MS = 5_000;\n/** Maximum WebSocket reconnect delay in milliseconds */\nconst WS_RECONNECT_MAX_MS = 300_000;\n/** REST fallback poll interval in milliseconds */\nconst REST_POLL_MS = 10_000;\n/** System info poll interval in milliseconds */\nconst SYSTEM_POLL_MS = 60_000;\n/** Max auth failures before giving up */\nconst MAX_AUTH_FAILURES = 3;\n/** WS failures before starting mDNS IP recovery */\nconst WS_FAILURES_BEFORE_MDNS = 3;\n/** mDNS IP recovery timeout in milliseconds */\nconst IP_RECOVERY_TIMEOUT_MS = 60_000;\n/** Retry mDNS every N WS failures after first attempt (~1 hour at 5 min cap) */\nconst MDNS_RETRY_EVERY = 12;\n/** Connection must last this long to count as \"stable\" */\nconst STABLE_THRESHOLD_MS = 600_000;\n/** Max reconnect delay for unstable devices */\nconst WS_RECONNECT_MAX_UNSTABLE_MS = 60_000;\n/** REST fallback interval for unstable devices (slower, not stopped) */\nconst REST_POLL_UNSTABLE_MS = 30_000;\n/**\n * Cooldown window for `device unreachable` warns. Per-device, category-\n * spanning: bouncing hardware should produce max 1\u00D7 warn per window, regardless\n * of whether each cycle's failure was TIMEOUT, NETWORK, or HTTP_503. Survives\n * the lastErrorCode-reset on recovery so chronic bouncing doesn't flap warn /\n * debug at every cycle.\n */\nconst WARN_COOLDOWN_MS = 60 * 60 * 1000;\n/** Cooldown window for `connection restored` infos \u2014 analog to warn cooldown. */\nconst INFO_COOLDOWN_MS = 60 * 60 * 1000;\n\n/**\n * HomeWizard adapter \u2014 manages multiple devices over API v2 (HTTPS + WebSocket):\n * pairing, real-time push, REST fallback, reconnect/recovery and state mapping.\n * Exported so the orchestration unit tests can drive its handlers directly.\n */\nexport class HomeWizard extends utils.Adapter {\n private stateManager!: StateManager;\n private discovery: HomeWizardDiscovery | null = null;\n private readonly connections = new Map<string, DeviceConnection>();\n /**\n * Per-device last-warn timestamp for chronic-bouncing cooldown. Key =\n * `conn.config.serial` (kategorien\u00FCbergreifend). The classifyError-based\n * `lastErrorCode`-Dedup in {@link logDeviceError} resets on every recovery,\n * so on chronic bouncing a new disconnect counts as \"first occurrence\"\n * \u2192 wieder warn. This cooldown stamp persists across recoveries so the user\n * sees max one warn per WARN_COOLDOWN_MS per device.\n */\n private readonly lastWarnAt = new Map<string, number>();\n /** Per-device last-info timestamp for `connection restored`. Analog cooldown. */\n private readonly lastInfoAt = new Map<string, number>();\n private pairingTimer: ioBroker.Timeout | undefined = undefined;\n private pairingPollTimer: ioBroker.Interval | undefined = undefined;\n private systemPollTimer: ioBroker.Interval | undefined = undefined;\n private ipRecoveryTimer: ioBroker.Timeout | undefined = undefined;\n private isPairing = false;\n private pairingManualIp = \"\";\n private discoveredDuringPairing: DiscoveredDevice[] = [];\n /** Set during onUnload \u2014 async paths bail before further setStateAsync calls. */\n private unloading = false;\n /**\n * Factories for the REST/WS clients \u2014 default to the real constructors. Test seams:\n * a unit test can replace these with fakes to exercise the orchestration (initDevice,\n * onWsConnected/onWsDisconnected, onStateChange) without real network.\n *\n * @param ip Device IP address\n * @param token Bearer token (empty string for pairing requests)\n */\n private makeClient: (ip: string, token: string) => HomeWizardClient = (ip, token) =>\n new HomeWizardClient(ip, token, { log: this.log });\n private makeWebSocket: (ip: string, token: string, callbacks: WsCallbacks, timers: TimerDeps) => HomeWizardWebSocket =\n (ip, token, callbacks, timers) => new HomeWizardWebSocket(ip, token, callbacks, timers);\n\n /**\n * Close a connection's WebSocket and clear its poll + reconnect timers.\n *\n * @param conn Device connection to tear down\n */\n private teardownConnection(conn: DeviceConnection): void {\n conn.wsClient?.close();\n if (conn.pollTimer) {\n this.clearInterval(conn.pollTimer);\n conn.pollTimer = undefined;\n }\n if (conn.reconnectTimer) {\n this.clearTimeout(conn.reconnectTimer);\n conn.reconnectTimer = undefined;\n }\n }\n\n /** @param options Adapter options */\n public constructor(options: Partial<utils.AdapterOptions> = {}) {\n super({ ...options, name: \"homewizard\" });\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 // No process-level unhandledRejection/uncaughtException handlers: in compact mode they\n // are process-wide and cross-adapter-harmful. Every handler has .bind+try/catch and\n // fire-and-forget paths use .catch (Fleet pattern \u2014 hueemu/parcelapp/nut).\n }\n\n private async onReady(): Promise<void> {\n try {\n await I18n.init(join(this.adapterDir, \"admin\"), this);\n this.stateManager = new StateManager(this);\n\n await this.setStateAsync(\"startPairing\", { val: false, ack: true });\n await this.setStateAsync(\"pairingIp\", { val: \"\", ack: true });\n\n await this.subscribeStatesAsync(\"startPairing\");\n await this.subscribeStatesAsync(\"*.system.reboot\");\n await this.subscribeStatesAsync(\"*.system.identify\");\n await this.subscribeStatesAsync(\"*.system.cloud_enabled\");\n await this.subscribeStatesAsync(\"*.system.status_led_brightness_pct\");\n await this.subscribeStatesAsync(\"*.system.api_v1_enabled\");\n await this.subscribeStatesAsync(\"*.battery.mode\");\n await this.subscribeStatesAsync(\"*.battery.permissions\");\n await this.subscribeStatesAsync(\"*.battery.charge_to_full\");\n await this.subscribeStatesAsync(\"*.remove\");\n\n const devices = await this.loadDevicesFromObjects();\n if (devices.length === 0) {\n this.log.info(`No devices configured \u2014 set 'startPairing' to true to add a device`);\n await this.setStateChangedAsync(\"info.connection\", { val: false, ack: true });\n }\n\n for (const device of devices) {\n const key = this.stateManager.devicePrefix(device);\n await this.stateManager.cleanupMovedStates(device);\n await this.stateManager.createDeviceStates(device);\n const conn = createDeviceConnection(device, device.ip || \"\");\n this.connections.set(key, conn);\n\n if (conn.ip) {\n this.log.debug(`Using stored IP ${conn.ip} for ${device.productName}`);\n void this.initDevice(conn).catch((err: unknown) =>\n this.log.error(`initDevice failed for ${conn.config.productName}: ${errText(err)}`),\n );\n }\n }\n\n this.systemPollTimer = this.setInterval(() => {\n void this.pollAllSystemInfo();\n }, SYSTEM_POLL_MS);\n\n this.updateGlobalConnection();\n } catch (err: unknown) {\n this.log.error(`onReady failed: ${errText(err)}`);\n }\n }\n\n /**\n * Load device configs from existing device objects\n * Tokens are stored encrypted in device object native\n */\n private async loadDevicesFromObjects(): Promise<DeviceConfig[]> {\n const devices: DeviceConfig[] = [];\n\n // One-shot legacy migration: v0.1/0.2 stored devices in adapter `native.devices`;\n // v0.3.0 moved them to per-device objects. Any install that ran v0.3.0+ has already\n // migrated (native.devices cleared below), but removal is low-reward / non-zero-risk\n // for an install that has been dormant since v0.2 \u2014 keep until at least v1.0.0.\n // Defensive: native.devices could be a non-array if a previous version\n // wrote a different shape, or if the user edited it manually.\n const rawOldDevices = (this.config as Record<string, unknown>).devices;\n const oldDevices: DeviceConfig[] = Array.isArray(rawOldDevices) ? (rawOldDevices as DeviceConfig[]) : [];\n if (oldDevices.length > 0) {\n this.log.debug(`Migrating ${oldDevices.length} device(s) from adapter config to device objects`);\n for (const device of oldDevices) {\n await this.saveDeviceToObject(device);\n }\n // Clear old config (this triggers one restart, but only during migration)\n await this.extendForeignObjectAsync(`system.adapter.${this.namespace}`, {\n native: { devices: [] },\n });\n return oldDevices;\n }\n\n // Read device objects from our namespace. A corrupted encryptedToken\n // (e.g. after secret rotation, crypto-lib changes, manual DB edits) must\n // not take down the whole adapter \u2014 skip the broken device, keep the rest.\n const objects = await this.getAdapterObjectsAsync();\n for (const [id, obj] of Object.entries(objects)) {\n if (obj.type !== \"device\") {\n continue;\n }\n const native = obj.native as Record<string, string> | undefined;\n if (!native?.encryptedToken || !native.serial) {\n continue;\n }\n const localId = id.replace(`${this.namespace}.`, \"\");\n this.log.debug(`Loading device from object: ${localId}`);\n let token: string;\n try {\n token = this.decrypt(native.encryptedToken);\n } catch (err) {\n this.log.warn(\n `Cannot decrypt token for ${localId} \u2014 re-pair the device. ` +\n `(${errText(err)}). Other devices remain unaffected.`,\n );\n continue;\n }\n devices.push({\n token,\n productType: native.productType || \"unknown\",\n serial: native.serial,\n productName: native.productName || native.productType || \"unknown\",\n ...(native.ip ? { ip: native.ip } : {}),\n });\n }\n\n return devices;\n }\n\n /**\n * Save device config to its device object native (encrypted token)\n *\n * @param config Device configuration to save\n */\n private async saveDeviceToObject(config: DeviceConfig): Promise<void> {\n const prefix = this.stateManager.devicePrefix(config);\n const encryptedToken = this.encrypt(config.token);\n await this.extendObjectAsync(\n prefix,\n {\n type: \"device\",\n common: { name: config.productName || config.productType },\n native: {\n encryptedToken,\n productType: config.productType,\n serial: config.serial,\n productName: config.productName,\n ...(config.ip ? { ip: config.ip } : {}),\n },\n },\n { preserve: { common: [\"name\"] } },\n );\n }\n\n /**\n * Handle a discovered device from mDNS (only active during pairing)\n *\n * @param discovered Discovered device info\n */\n private onDeviceDiscovered(discovered: DiscoveredDevice): void {\n // Skip already paired devices\n const existing = Array.from(this.connections.values()).find(c => c.config.serial === discovered.serial);\n if (existing) {\n return;\n }\n\n // Skip duplicates\n if (this.discoveredDuringPairing.find(d => d.serial === discovered.serial)) {\n return;\n }\n\n this.discoveredDuringPairing.push(discovered);\n this.log.info(\n `Found ${discovered.name} (${discovered.productType}) at ${discovered.ip} \u2014 press the button on the device to pair`,\n );\n }\n\n /**\n * Adapter stopping \u2014 MUST be synchronous\n *\n * @param callback Completion callback\n */\n private onUnload(callback: () => void): void {\n // Set first, before any clearTimeout \u2014 in-flight async paths\n // (REST poll, getMeasurement, getSystem) check this after each await\n // and bail out before further setStateAsync on a tearing-down adapter.\n this.unloading = true;\n try {\n if (this.pairingTimer) {\n this.clearTimeout(this.pairingTimer);\n }\n if (this.pairingPollTimer) {\n this.clearInterval(this.pairingPollTimer);\n }\n if (this.systemPollTimer) {\n this.clearInterval(this.systemPollTimer);\n }\n if (this.ipRecoveryTimer) {\n this.clearTimeout(this.ipRecoveryTimer);\n }\n\n this.discovery?.stop();\n\n for (const conn of this.connections.values()) {\n this.teardownConnection(conn);\n }\n this.connections.clear();\n\n void this.setState(\"info.connection\", { val: false, ack: true });\n } finally {\n callback();\n }\n }\n\n private async onStateChange(id: string, state: ioBroker.State | null | undefined): Promise<void> {\n try {\n if (!state || state.ack || this.unloading) {\n return;\n }\n\n if (id.endsWith(\".startPairing\")) {\n if (state.val) {\n await this.startPairing();\n }\n return;\n }\n\n if (id.endsWith(\".remove\")) {\n if (state.val) {\n await this.removeDevice(id);\n }\n return;\n }\n\n const conn = this.findConnectionForState(id);\n if (!conn || !conn.ip) {\n return;\n }\n\n const client = this.makeClient(conn.ip, conn.config.token);\n\n try {\n if (id.endsWith(\".system.reboot\")) {\n this.log.info(`Rebooting ${conn.config.productName} (${conn.ip})`);\n await client.reboot();\n } else if (id.endsWith(\".system.identify\")) {\n await client.identify();\n } else if (id.endsWith(\".system.cloud_enabled\")) {\n await client.setSystem({ cloud_enabled: !!state.val });\n await this.setStateAsync(id, { val: state.val, ack: true });\n } else if (id.endsWith(\".system.status_led_brightness_pct\")) {\n await client.setSystem({\n status_led_brightness_pct: Number(state.val),\n });\n await this.setStateAsync(id, { val: state.val, ack: true });\n } else if (id.endsWith(\".system.api_v1_enabled\")) {\n await client.setSystem({ api_v1_enabled: !!state.val });\n await this.setStateAsync(id, { val: state.val, ack: true });\n } else if (id.endsWith(\".battery.mode\")) {\n const mode = validateBatteryMode(String(state.val));\n if (!mode) {\n this.log.warn(\n `Invalid battery.mode value: '${String(state.val)}' \u2014 expected one of: zero, to_full, standby, predictive`,\n );\n return;\n }\n await client.setBatteries({ mode });\n await this.setStateAsync(id, { val: state.val, ack: true });\n } else if (id.endsWith(\".battery.permissions\")) {\n const result = parseBatteryPermissions(String(state.val));\n if (!result.ok) {\n this.log.warn(\n `Invalid JSON for battery.permissions: ${result.reason} \u2014 expected array, got: ${result.sample}`,\n );\n return;\n }\n await client.setBatteries({ permissions: result.perms });\n await this.setStateAsync(id, { val: state.val, ack: true });\n } else if (id.endsWith(\".battery.charge_to_full\")) {\n await client.setBatteries({ charge_to_full: !!state.val });\n await this.setStateAsync(id, { val: state.val, ack: true });\n }\n } catch (err) {\n this.log.warn(`Failed to set ${id}: ${errText(err)}`);\n }\n } catch (err: unknown) {\n this.log.error(`stateChange failed: ${errText(err)}`);\n }\n }\n\n /** Start pairing mode \u2014 discover devices and attempt to pair */\n private async startPairing(): Promise<void> {\n if (this.isPairing) {\n this.log.debug(\"Pairing already active\");\n return;\n }\n\n // Reset startPairing immediately so it doesn't survive a restart\n await this.setStateAsync(\"startPairing\", { val: false, ack: true });\n\n this.isPairing = true;\n this.discoveredDuringPairing = [];\n\n // Stop IP recovery if running \u2014 pairing takes priority\n this.stopIpRecovery();\n\n // Check if manual IP is set, then clear pairingIp immediately\n const ipState = await this.getStateAsync(\"pairingIp\");\n this.pairingManualIp = ipState?.val ? String(ipState.val).trim() : \"\";\n await this.setStateAsync(\"pairingIp\", { val: \"\", ack: true });\n\n if (this.pairingManualIp) {\n // Validate manual-IP up front \u2014 better to fail fast than wait 60s while\n // requestPairing keeps timing out against a malformed input.\n if (!isValidIpv4(this.pairingManualIp)) {\n this.log.warn(`Invalid pairing IP '${this.pairingManualIp}' \u2014 expected IPv4 (e.g. 192.168.1.42)`);\n this.isPairing = false;\n this.pairingManualIp = \"\";\n return;\n }\n this.log.info(\n `Pairing mode enabled for ${this.pairingManualIp} \u2014 press the button on your HomeWizard device now (60 seconds timeout)`,\n );\n // Add as discovered device immediately\n this.discoveredDuringPairing.push({\n ip: this.pairingManualIp,\n productType: \"unknown\",\n serial: \"unknown\",\n name: this.pairingManualIp,\n });\n } else {\n this.log.info(\n `Pairing mode enabled \u2014 searching for devices via mDNS, press the button on your HomeWizard device now (60 seconds timeout)`,\n );\n // Restart mDNS browser to trigger fresh query \u2014 already-cached devices\n // won't be re-announced otherwise and pairing would never find them\n if (!this.discovery) {\n this.discovery = new HomeWizardDiscovery(this.log);\n }\n this.discovery.start(discovered => {\n this.onDeviceDiscovered(discovered);\n });\n }\n\n // Poll discovered devices for pairing\n this.pairingPollTimer = this.setInterval(() => {\n void this.pollPairing();\n }, PAIRING_POLL_MS);\n\n // Timeout pairing\n this.pairingTimer = this.setTimeout(() => {\n this.stopPairing();\n this.log.info(`Pairing mode automatically disabled after 60 seconds timeout`);\n }, PAIRING_TIMEOUT_MS);\n }\n\n /** Poll all discovered devices to attempt pairing */\n private async pollPairing(): Promise<void> {\n for (const device of this.discoveredDuringPairing) {\n try {\n const client = this.makeClient(device.ip, \"\");\n const result = await client.requestPairing();\n\n // Success! Button was pressed\n this.log.info(\n `Successfully paired with ${device.name} (${device.productType}) at ${device.ip} \u2014 connecting...`,\n );\n\n // Get device info\n const authedClient = this.makeClient(device.ip, result.token);\n const info = await authedClient.getDeviceInfo();\n\n const deviceConfig: DeviceConfig = {\n token: result.token,\n productType: info.product_type,\n serial: info.serial,\n productName: info.product_name,\n ip: device.ip,\n };\n\n // Save to device object (no adapter restart!)\n await this.saveDeviceToObject(deviceConfig);\n await this.stateManager.createDeviceStates(deviceConfig);\n\n // Re-pair of an existing device (e.g. after factory reset): close the\n // old connection's wsClient + timers before overwriting the map entry,\n // otherwise the old WS keeps running as a zombie until restart.\n const key = this.stateManager.devicePrefix(deviceConfig);\n const previous = this.connections.get(key);\n if (previous) {\n this.log.debug(`Re-pair: closing previous connection for ${deviceConfig.productName}`);\n this.teardownConnection(previous);\n }\n\n // Create connection and connect\n const conn = createDeviceConnection(deviceConfig, device.ip);\n this.connections.set(key, conn);\n void this.initDevice(conn).catch((err: unknown) =>\n this.log.error(`initDevice failed for ${conn.config.productName}: ${errText(err)}`),\n );\n\n // Remove from discovery list \u2014 but keep pairing window open so the\n // user can button-press additional devices in the same session.\n this.discoveredDuringPairing = this.discoveredDuringPairing.filter(d => d.serial !== info.serial);\n\n this.updateGlobalConnection();\n // Do NOT call stopPairing() here \u2014 pairingTimer (60 s) closes the\n // window naturally; meanwhile the user can pair more devices.\n continue;\n } catch (err) {\n // 403 = button not pressed yet \u2014 expected, keep polling\n if (err instanceof HomeWizardApiError && err.statusCode === 403) {\n continue;\n }\n this.log.debug(`Pairing poll error for ${device.ip}: ${errText(err)}`);\n }\n }\n }\n\n /** Stop pairing mode */\n private stopPairing(): void {\n this.isPairing = false;\n this.pairingManualIp = \"\";\n this.discoveredDuringPairing = [];\n\n // Stop mDNS \u2014 only needed during pairing\n if (this.discovery) {\n this.discovery.stop();\n this.discovery = null;\n }\n\n if (this.pairingPollTimer) {\n this.clearInterval(this.pairingPollTimer);\n this.pairingPollTimer = undefined;\n }\n if (this.pairingTimer) {\n this.clearTimeout(this.pairingTimer);\n this.pairingTimer = undefined;\n }\n }\n\n /** Start mDNS to find devices that changed IP */\n private startIpRecovery(): void {\n // Don't start if already running or pairing\n if (this.discovery || this.isPairing) {\n return;\n }\n\n // Internal recovery \u2014 debug only. The initial disconnect already produced\n // one warn via logDeviceError; repeating that hourly while a device stays\n // offline is just spam.\n this.log.debug(`Device unreachable \u2014 searching for new IP via mDNS`);\n\n this.discovery = new HomeWizardDiscovery(this.log);\n this.discovery.start(discovered => {\n // Match against disconnected devices\n for (const conn of this.connections.values()) {\n if (conn.config.serial !== discovered.serial) {\n continue;\n }\n if (discovered.ip === conn.ip || conn.wsAuthenticated) {\n return; // Same IP or already connected\n }\n // Multiple mDNS broadcasts can arrive within one recovery window\n // (e.g. AP roam). Skip if a connect cycle is already in flight.\n if (conn.recovering) {\n return;\n }\n\n this.log.info(`${conn.config.productName}: found at new IP ${discovered.ip} (was ${conn.ip})`);\n\n // Update IP and persist \u2014 reset stability (new network conditions)\n conn.ip = discovered.ip;\n conn.config.ip = discovered.ip;\n conn.wsFailCount = 0;\n conn.recentDisconnects = 0;\n // Surface persist-failures (e.g. js-controller hiccup) instead of\n // swallowing them \u2014 the user otherwise sees \"new IP\" log but the\n // change is lost on next restart.\n this.saveDeviceToObject(conn.config).catch((err: unknown) =>\n this.log.debug(`Failed to persist new IP for ${conn.config.productName}: ${errText(err)}`),\n );\n\n // Cancel pending reconnect and connect immediately\n if (conn.reconnectTimer) {\n this.clearTimeout(conn.reconnectTimer);\n conn.reconnectTimer = undefined;\n }\n if (conn.pollTimer) {\n this.clearInterval(conn.pollTimer);\n conn.pollTimer = undefined;\n }\n this.connectWebSocket(conn);\n return;\n }\n });\n\n // Stop mDNS after timeout \u2014 WS reconnect continues with exponential\n // backoff. Don't log per-device warns here: the initial disconnect already\n // produced a `deviceUnreachable` warn via logDeviceError; spamming the\n // user hourly while the device stays offline adds zero information. If\n // someone needs to see retry cadence they can enable debug logging.\n this.ipRecoveryTimer = this.setTimeout(() => {\n this.ipRecoveryTimer = undefined;\n this.stopIpRecovery();\n\n for (const conn of this.connections.values()) {\n if (!conn.wsAuthenticated && conn.wsFailCount > 0) {\n this.log.debug(\n `${conn.config.productName}: device offline \u2014 will keep retrying every ${WS_RECONNECT_MAX_MS / 1000}s`,\n );\n }\n }\n }, IP_RECOVERY_TIMEOUT_MS);\n }\n\n /** Stop mDNS IP recovery */\n private stopIpRecovery(): void {\n if (this.ipRecoveryTimer) {\n this.clearTimeout(this.ipRecoveryTimer);\n this.ipRecoveryTimer = undefined;\n }\n if (this.discovery && !this.isPairing) {\n this.discovery.stop();\n this.discovery = null;\n }\n }\n\n /**\n * Initialize a newly discovered device \u2014 fetch info and connect WebSocket\n *\n * @param conn Device connection with IP set\n */\n private async initDevice(conn: DeviceConnection): Promise<void> {\n if (this.unloading || conn.removed) {\n return;\n }\n try {\n const client = this.makeClient(conn.ip, conn.config.token);\n const info = await client.getDeviceInfo();\n if (this.unloading || conn.removed) {\n return;\n }\n const key = this.stateManager.devicePrefix(conn.config);\n await this.setStateAsync(`${key}.info.firmware`, {\n val: info.firmware_version,\n ack: true,\n });\n } catch (err) {\n if (this.unloading) {\n return;\n }\n this.logDeviceError(conn, \"init\", err);\n }\n\n if (this.unloading || conn.removed) {\n return;\n }\n this.connectWebSocket(conn);\n void this.pollSystemInfo(conn);\n }\n\n /**\n * Connect WebSocket for a device\n *\n * @param conn Device connection\n */\n private connectWebSocket(conn: DeviceConnection): void {\n if (!conn.ip) {\n return; // No IP yet \u2014 wait for mDNS\n }\n\n // Stop reconnecting if auth keeps failing\n if (conn.authFailCount >= MAX_AUTH_FAILURES) {\n return;\n }\n\n // Mark as recovering so concurrent triggers (mDNS broadcast race,\n // overlapping reconnect timer) don't spawn a second wsClient.\n conn.recovering = true;\n\n // Close any existing wsClient before creating a new one. The normal\n // disconnect path nulls conn.wsClient, but IP-recovery jumps in directly\n // and would otherwise leak the old socket.\n if (conn.wsClient) {\n conn.wsClient.close();\n conn.wsClient = null;\n }\n\n // After repeated failures, try mDNS periodically to find a new IP\n if (shouldStartIpRecovery(conn.wsFailCount, WS_FAILURES_BEFORE_MDNS, MDNS_RETRY_EVERY)) {\n this.startIpRecovery();\n }\n\n // Thin callbacks delegating to instance methods (extracted for readability + unit-testability).\n const wsClient = this.makeWebSocket(\n conn.ip,\n conn.config.token,\n {\n onMeasurement: data => this.onWsMeasurement(conn, data),\n onSystem: data => this.onWsSystem(conn, data),\n onBattery: data => this.onWsBattery(conn, data),\n onConnected: () => this.onWsConnected(conn),\n onDisconnected: error => this.onWsDisconnected(conn, error),\n log: this.log,\n },\n {\n schedule: (cb, ms) => this.setTimeout(cb, ms),\n cancel: h => {\n this.clearTimeout(h as ioBroker.Timeout);\n },\n scheduleRepeating: (cb, ms) => this.setInterval(cb, ms),\n cancelRepeating: h => {\n this.clearInterval(h as ioBroker.Interval);\n },\n },\n );\n\n conn.wsClient = wsClient;\n wsClient.connect();\n }\n\n /**\n * Handle a measurement push.\n *\n * @param conn Device connection\n * @param data Measurement payload\n */\n private onWsMeasurement(conn: DeviceConnection, data: Measurement): void {\n // Skip updates for devices removed mid-flight (frame can race delObjectAsync) + teardown.\n if (conn.removed || this.unloading) {\n return;\n }\n // Defensive .catch \u2014 writes inside updateMeasurement may reject on transient Redis hiccups;\n // we want a debug-log, not an unhandled rejection.\n this.stateManager.updateMeasurement(conn.config, data).catch((err: unknown) => {\n this.log.debug(`updateMeasurement failed for ${conn.config.productName}: ${errText(err)}`);\n });\n }\n\n /**\n * Handle a real-time system push (cloud/led changes etc.).\n *\n * @param conn Device connection\n * @param data System payload\n */\n private onWsSystem(conn: DeviceConnection, data: SystemInfo): void {\n if (conn.removed || this.unloading) {\n return;\n }\n this.stateManager.updateSystem(conn.config, data).catch((err: unknown) => {\n this.log.debug(`updateSystem (ws) failed for ${conn.config.productName}: ${errText(err)}`);\n });\n }\n\n /**\n * Handle a real-time battery-group push (mode/permissions/target power).\n *\n * @param conn Device connection\n * @param data Battery-control payload\n */\n private onWsBattery(conn: DeviceConnection, data: BatteryControl): void {\n if (conn.removed || this.unloading) {\n return;\n }\n // Only surface battery states when batteries are actually connected.\n if (!data.battery_count || data.battery_count <= 0) {\n return;\n }\n this.stateManager.updateBattery(conn.config, data).catch((err: unknown) => {\n this.log.debug(`updateBattery (ws) failed for ${conn.config.productName}: ${errText(err)}`);\n });\n }\n\n /**\n * WebSocket authenticated \u2014 mark connected, stop REST fallback, log recovery (cooldowned).\n *\n * @param conn Device connection\n */\n private onWsConnected(conn: DeviceConnection): void {\n conn.wsAuthenticated = true;\n conn.wsFailCount = 0;\n conn.authFailCount = 0;\n conn.lastConnectedAt = Date.now();\n conn.recovering = false;\n void this.stateManager.setDeviceConnected(conn.config, true);\n this.updateGlobalConnection();\n\n // Stop REST fallback if active\n if (conn.pollTimer) {\n this.clearInterval(conn.pollTimer);\n conn.pollTimer = undefined;\n }\n\n // Stop IP recovery if all devices are connected\n if (this.discovery && !this.isPairing) {\n const allConnected = Array.from(this.connections.values()).every(c => c.wsAuthenticated);\n if (allConnected) {\n this.stopIpRecovery();\n }\n }\n\n // Log restoration if we had errors before. Per-device cooldown so chronic bouncing\n // doesn't emit one info per cycle \u2014 repeats go to debug.\n if (conn.lastErrorCode) {\n const now = Date.now();\n const lastInfo = this.lastInfoAt.get(conn.config.serial) ?? 0;\n const msg = this.isUnstable(conn)\n ? `${conn.config.productName}: connection restored (unstable mode)`\n : `${conn.config.productName}: connection restored`;\n if (shouldEmitAfterCooldown(lastInfo, now, INFO_COOLDOWN_MS)) {\n this.lastInfoAt.set(conn.config.serial, now);\n this.log.info(msg);\n } else {\n this.log.debug(`${msg} (cooldown)`);\n }\n conn.lastErrorCode = \"\";\n }\n\n this.log.debug(`WebSocket connected to ${conn.config.productName} (${conn.ip})`);\n }\n\n /**\n * WebSocket disconnected \u2014 track stability, start REST fallback, schedule backed-off reconnect\n * (unless an auth failure stops the loop).\n *\n * @param conn Device connection\n * @param error Disconnect error, if any\n */\n private onWsDisconnected(conn: DeviceConnection, error?: Error): void {\n // Auth failures are not a connectivity-stability signal \u2014 they mean the token is bad,\n // not the WiFi. Counting them as short connections would flip the device into unstable mode.\n const isAuthError = error instanceof HomeWizardApiError && error.errorCode === \"user:unauthorized\";\n\n // Track connection stability \u2014 pure decision in main-helpers, side-effects here.\n if (conn.lastConnectedAt > 0 && !isAuthError) {\n const duration = Date.now() - conn.lastConnectedAt;\n const transition = decideUnstableTransition(\n conn.recentDisconnects,\n duration,\n STABLE_THRESHOLD_MS,\n UNSTABLE_DISCONNECT_THRESHOLD,\n );\n if (duration < STABLE_THRESHOLD_MS) {\n conn.recentDisconnects++;\n } else {\n conn.recentDisconnects = 0;\n }\n // Hysterese-transitions are internal reconnect-strategy adjustments \u2192 debug, not info.\n if (transition === \"becameUnstable\") {\n this.log.debug(`${conn.config.productName}: unstable connection detected \u2014 using faster reconnect`);\n } else if (transition === \"stabilized\") {\n this.log.debug(`${conn.config.productName}: connection stabilized \u2014 using normal reconnect`);\n }\n }\n\n conn.wsAuthenticated = false;\n conn.wsClient = null;\n conn.recovering = false;\n void this.stateManager.setDeviceConnected(conn.config, false);\n this.updateGlobalConnection();\n\n if (error) {\n this.logDeviceError(conn, \"ws\", error);\n }\n\n // Auth failure \u2192 stop the reconnect path.\n if (!this.handleAuthFailure(conn, error, /* cleanupTimers */ false)) {\n return;\n }\n\n // Start REST fallback\n this.startRestFallback(conn);\n\n // Schedule reconnect with exponential backoff (faster for unstable devices).\n conn.wsFailCount++;\n const maxDelay = this.isUnstable(conn) ? WS_RECONNECT_MAX_UNSTABLE_MS : WS_RECONNECT_MAX_MS;\n const delay = computeReconnectDelay(conn.wsFailCount, WS_RECONNECT_BASE_MS, maxDelay);\n const key = this.stateManager.devicePrefix(conn.config);\n this.log.debug(`${key}: WS reconnect in ${delay / 1000}s (attempt ${conn.wsFailCount})`);\n\n conn.reconnectTimer = this.setTimeout(() => {\n conn.reconnectTimer = undefined;\n this.connectWebSocket(conn);\n }, delay);\n }\n\n /**\n * Start REST polling as fallback when WebSocket is down.\n * For stable devices: stops on network errors (WS reconnect handles recovery).\n * For unstable devices: slows down instead of stopping to minimize data gaps.\n *\n * @param conn Device connection\n */\n private startRestFallback(conn: DeviceConnection): void {\n if (conn.pollTimer || !conn.ip) {\n return;\n }\n\n const unstable = this.isUnstable(conn);\n const interval = pickRestPollInterval(unstable, REST_POLL_MS, REST_POLL_UNSTABLE_MS);\n const client = this.makeClient(conn.ip, conn.config.token);\n\n conn.pollTimer = this.setInterval(async () => {\n // Bail out if device was removed or adapter is shutting down \u2014 the\n // setStateAsync chain inside updateMeasurement would otherwise either\n // recreate deleted objects or hit a torn-down adapter.\n if (conn.removed || this.unloading) {\n return;\n }\n try {\n const data = await client.getMeasurement();\n if (conn.removed || this.unloading) {\n return;\n }\n await this.stateManager.updateMeasurement(conn.config, data);\n } catch (err) {\n if (this.unloading) {\n return;\n }\n this.logDeviceError(conn, \"rest\", err);\n\n // Auth failures: stop everything \u2014 token is bad, re-pair required.\n if (err instanceof HomeWizardApiError && err.errorCode === \"user:unauthorized\") {\n this.handleAuthFailure(conn, err, /* cleanupTimers */ true);\n return;\n }\n\n // Stop REST polling on network errors for stable devices.\n // Unstable devices keep polling (slower) to minimize data gaps.\n if (!unstable && classifyError(err) === \"NETWORK\" && conn.pollTimer) {\n this.clearInterval(conn.pollTimer);\n conn.pollTimer = undefined;\n }\n }\n }, interval);\n }\n\n /** Poll system info for all connected devices in parallel */\n private async pollAllSystemInfo(): Promise<void> {\n if (this.unloading) {\n return;\n }\n const tasks = Array.from(this.connections.values())\n .filter(c => c.ip && c.wsAuthenticated && !c.removed)\n .map(c => this.pollSystemInfo(c));\n await Promise.all(tasks);\n }\n\n /**\n * Poll system info for a single device\n *\n * @param conn Device connection\n */\n private async pollSystemInfo(conn: DeviceConnection): Promise<void> {\n if (!conn.ip || conn.removed || this.unloading) {\n return;\n }\n\n try {\n const client = this.makeClient(conn.ip, conn.config.token);\n const system = await client.getSystem();\n if (conn.removed || this.unloading) {\n return;\n }\n await this.stateManager.updateSystem(conn.config, system);\n\n // Sync productName drift: if the user renamed the device in the\n // HomeWizard app (or a firmware update changed the product_name), pick\n // up the new value once per system-poll instead of staying stale until\n // re-pair. Cheap \u2014 only writes on actual change.\n try {\n const info = await client.getDeviceInfo();\n if (!conn.removed && !this.unloading && info.product_name && info.product_name !== conn.config.productName) {\n this.log.info(`${conn.config.productName}: name changed to '${info.product_name}' \u2014 updating object`);\n conn.config.productName = info.product_name;\n await this.saveDeviceToObject(conn.config);\n }\n } catch {\n // device-info is best-effort here; the system-poll log already\n // surfaces real connectivity issues.\n }\n\n // Raw P1 telegram (P1 Meter only) \u2014 fetched at the system-poll cadence, not the 1/s\n // measurement feed (the DSMR datagram is bulky). Placed before the battery block, whose\n // 404 path returns early on no-battery devices like the P1.\n if (conn.config.productType === \"HWE-P1\" && !conn.removed && !this.unloading) {\n try {\n const telegram = await client.getTelegram();\n if (!conn.removed && !this.unloading) {\n await this.stateManager.updateTelegram(conn.config, telegram);\n }\n } catch (err) {\n this.log.debug(`${conn.config.productName} telegram: ${errText(err)}`);\n }\n }\n\n // Also poll battery if device supports it. 404 = no battery \u2014 silent.\n // Other errors (500, timeout, malformed body) used to be swallowed\n // entirely; now they surface at debug so post-mortem diagnosis is\n // possible without losing any normal-flow logging.\n if (conn.removed || this.unloading) {\n return;\n }\n try {\n const battery = await client.getBatteries();\n if (conn.removed || this.unloading) {\n return;\n }\n // Only create battery states if batteries are actually connected\n if (battery.battery_count && battery.battery_count > 0) {\n await this.stateManager.updateBattery(conn.config, battery);\n }\n } catch (err) {\n if (err instanceof HomeWizardApiError && err.statusCode === 404) {\n return; // device doesn't support batteries \u2014 expected\n }\n this.log.debug(`${conn.config.productName} batteries: ${errText(err)}`);\n }\n } catch (err) {\n if (this.unloading) {\n return;\n }\n this.logDeviceError(conn, \"system\", err);\n }\n }\n\n /** Update global info.connection based on all device states */\n private updateGlobalConnection(): void {\n const anyConnected = Array.from(this.connections.values()).some(c => c.wsAuthenticated);\n // setStateChanged: flips rarely (connect/disconnect), called on every WS event \u2014 skip no-op writes.\n void this.setStateChangedAsync(\"info.connection\", {\n val: anyConnected,\n ack: true,\n });\n }\n\n /**\n * Remove a device \u2014 disconnect, delete states and object\n *\n * @param stateId The remove state ID\n */\n private async removeDevice(stateId: string): Promise<void> {\n const conn = this.findConnectionForState(stateId);\n if (!conn) {\n return;\n }\n\n const key = this.stateManager.devicePrefix(conn.config);\n this.log.info(`Removing device ${conn.config.productName} (${conn.config.serial})`);\n\n // Mark as removed FIRST \u2014 async tasks (in-flight WS frames, REST polls,\n // outstanding pollSystemInfo) check this flag after each await and bail\n // out before recreating just-deleted objects via setStateAsync.\n conn.removed = true;\n\n // Best-effort token revoke on the device (DELETE /api/user) so the local/iobroker user\n // doesn't linger across pair/unpair cycles. Fire-and-forget \u2014 never block removal on a\n // (possibly offline) device's 10s timeout.\n if (conn.ip && conn.config.token) {\n void this.makeClient(conn.ip, conn.config.token)\n .deleteUser()\n .catch((err: unknown) => this.log.debug(`Token revoke failed for ${conn.config.productName}: ${errText(err)}`));\n }\n\n // Disconnect\n this.teardownConnection(conn);\n this.connections.delete(key);\n\n // Delete device object and all states (no adapter restart!)\n await this.stateManager.removeDevice(conn.config);\n\n this.updateGlobalConnection();\n }\n\n /**\n * Find connection for a state ID. Delegates to the pure helper so the\n * lookup math is unit-tested separately (`lib/main-helpers.test.ts`).\n *\n * @param stateId Full state ID\n */\n private findConnectionForState(stateId: string): DeviceConnection | undefined {\n return resolveConnectionForState(stateId, this.namespace, this.connections);\n }\n\n /**\n * Whether a device has unstable connectivity (frequent short-lived connections).\n * Unstable devices get faster reconnect and persistent REST fallback.\n *\n * @param conn Device connection\n */\n private isUnstable(conn: DeviceConnection): boolean {\n return conn.recentDisconnects >= UNSTABLE_DISCONNECT_THRESHOLD;\n }\n\n /**\n * Handle a possible auth failure on a device connection. Counts failures and,\n * once `MAX_AUTH_FAILURES` is reached, warns the user and (optionally) tears\n * down active timers and the WebSocket \u2014 stops bombarding the device with a\n * known-bad token.\n *\n * @param conn Device connection.\n * @param error The error from the failing call (any error type accepted).\n * @param cleanupTimers If `true`, clears poll/reconnect timers and closes the WS\n * on threshold reach. Used by REST-fallback paths where\n * the WS would otherwise keep retrying indefinitely. The\n * WS-disconnect path passes `false` because the caller\n * decides the next step itself.\n * @returns `true` if the caller should continue normal flow (no auth-stop),\n * `false` if the auth-stop fired and the caller should bail out.\n */\n private handleAuthFailure(conn: DeviceConnection, error: unknown, cleanupTimers: boolean): boolean {\n if (!(error instanceof HomeWizardApiError) || error.errorCode !== \"user:unauthorized\") {\n return true;\n }\n conn.authFailCount++;\n if (conn.authFailCount < MAX_AUTH_FAILURES) {\n return true;\n }\n this.log.warn(`${conn.config.productName}: token invalid \u2014 re-pair device to fix`);\n if (cleanupTimers) {\n if (conn.pollTimer) {\n this.clearInterval(conn.pollTimer);\n conn.pollTimer = undefined;\n }\n if (conn.reconnectTimer) {\n this.clearTimeout(conn.reconnectTimer);\n conn.reconnectTimer = undefined;\n }\n conn.wsClient?.close();\n }\n return false;\n }\n\n /**\n * Log device error with deduplication.\n *\n * Two-stage dedup:\n * 1. `lastErrorCode` per connection \u2014 repeats of the same error category go\n * to debug. Resets on recovery (`onConnected` clears it) so a new category\n * after recovery surfaces as warn again. Designtechnisch correct for\n * \u201Enew failure mode\" but blind to chronic bouncing.\n * 2. {@link lastWarnAt} per device serial \u2014 survives recovery. Even if the\n * `lastErrorCode`-Dedup says \u201Efirst occurrence\", the cooldown stamp keeps\n * the warn-emit suppressed if we've warned for this device within\n * {@link WARN_COOLDOWN_MS}. Chronic bouncing produces at most 1\u00D7 warn per\n * hour per device.\n *\n * Cooldown key is the device serial \u2014 category-spanning. A flapping P1 that\n * cycles TIMEOUT\u2192NETWORK\u2192TIMEOUT is one phenomenon, one warn-budget.\n *\n * @param conn Device connection\n * @param context Error context (for debug messages only)\n * @param err Error object\n */\n private logDeviceError(conn: DeviceConnection, context: string, err: unknown): void {\n const errorCode = classifyError(err);\n const isRepeat = errorCode === conn.lastErrorCode;\n conn.lastErrorCode = errorCode;\n\n if (isRepeat) {\n this.log.debug(`${conn.config.productName} ${context}: ${errText(err)}`);\n return;\n }\n\n // New category \u2014 apply per-device cooldown so chronic bouncing doesn't\n // emit warn at every cycle just because each cycle's first failure is\n // a fresh `lastErrorCode`.\n const now = Date.now();\n const lastWarn = this.lastWarnAt.get(conn.config.serial) ?? 0;\n if (!shouldEmitAfterCooldown(lastWarn, now, WARN_COOLDOWN_MS)) {\n this.log.debug(`${conn.config.productName} ${context} (cooldown): ${errText(err)}`);\n return;\n }\n\n this.lastWarnAt.set(conn.config.serial, now);\n if (errorCode === \"NETWORK\") {\n this.log.warn(`${conn.config.productName}: device unreachable \u2014 will keep retrying`);\n } else {\n this.log.warn(`${conn.config.productName} ${context}: ${errText(err)}`);\n }\n }\n}\n\nif (require.main !== module) {\n module.exports = (options: Partial<utils.AdapterOptions> | undefined) => new HomeWizard(options);\n} else {\n (() => new HomeWizard())();\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import * as utils from \"@iobroker/adapter-core\";\nimport { I18n } from \"@iobroker/adapter-core\";\nimport { join } from \"node:path\";\nimport { errText, isValidIpv4, parseBatteryPermissions, validateBatteryMode } from \"./lib/coerce\";\nimport { classifyError, createDeviceConnection, UNSTABLE_DISCONNECT_THRESHOLD } from \"./lib/connection-utils\";\nimport { HomeWizardDiscovery } from \"./lib/discovery\";\nimport { HomeWizardApiError, HomeWizardClient } from \"./lib/homewizard-client\";\nimport {\n computeReconnectDelay,\n decideUnstableTransition,\n findConnectionForState as resolveConnectionForState,\n pickRestPollInterval,\n shouldEmitAfterCooldown,\n shouldStartIpRecovery,\n} from \"./lib/main-helpers\";\nimport { StateManager } from \"./lib/state-manager\";\nimport type {\n BatteryControl,\n DeviceConfig,\n DeviceConnection,\n DiscoveredDevice,\n Measurement,\n SystemInfo,\n} from \"./lib/types\";\nimport { HomeWizardWebSocket, type TimerDeps, type WsCallbacks } from \"./lib/websocket-client\";\n\n/** Pairing timeout in milliseconds (60 seconds) */\nconst PAIRING_TIMEOUT_MS = 60_000;\n/** Pairing poll interval in milliseconds */\nconst PAIRING_POLL_MS = 2_000;\n/** WebSocket reconnect base delay in milliseconds */\nconst WS_RECONNECT_BASE_MS = 5_000;\n/** Maximum WebSocket reconnect delay in milliseconds */\nconst WS_RECONNECT_MAX_MS = 300_000;\n/** REST fallback poll interval in milliseconds */\nconst REST_POLL_MS = 10_000;\n/** System info poll interval in milliseconds */\nconst SYSTEM_POLL_MS = 60_000;\n/** Max auth failures before giving up */\nconst MAX_AUTH_FAILURES = 3;\n/** WS failures before starting mDNS IP recovery */\nconst WS_FAILURES_BEFORE_MDNS = 3;\n/** mDNS IP recovery timeout in milliseconds */\nconst IP_RECOVERY_TIMEOUT_MS = 60_000;\n/** Retry mDNS every N WS failures after first attempt (~1 hour at 5 min cap) */\nconst MDNS_RETRY_EVERY = 12;\n/** Connection must last this long to count as \"stable\" */\nconst STABLE_THRESHOLD_MS = 600_000;\n/** Max reconnect delay for unstable devices */\nconst WS_RECONNECT_MAX_UNSTABLE_MS = 60_000;\n/** REST fallback interval for unstable devices (slower, not stopped) */\nconst REST_POLL_UNSTABLE_MS = 30_000;\n/**\n * Cooldown window for `device unreachable` warns. Per-device, category-\n * spanning: bouncing hardware should produce max 1\u00D7 warn per window, regardless\n * of whether each cycle's failure was TIMEOUT, NETWORK, or HTTP_503. Survives\n * the lastErrorCode-reset on recovery so chronic bouncing doesn't flap warn /\n * debug at every cycle.\n */\nconst WARN_COOLDOWN_MS = 60 * 60 * 1000;\n/** Cooldown window for `connection restored` infos \u2014 analog to warn cooldown. */\nconst INFO_COOLDOWN_MS = 60 * 60 * 1000;\n\n/**\n * HomeWizard adapter \u2014 manages multiple devices over API v2 (HTTPS + WebSocket):\n * pairing, real-time push, REST fallback, reconnect/recovery and state mapping.\n * Exported so the orchestration unit tests can drive its handlers directly.\n */\nexport class HomeWizard extends utils.Adapter {\n private stateManager!: StateManager;\n private discovery: HomeWizardDiscovery | null = null;\n private readonly connections = new Map<string, DeviceConnection>();\n /**\n * Per-device last-warn timestamp for chronic-bouncing cooldown. Key =\n * `conn.config.serial` (kategorien\u00FCbergreifend). The classifyError-based\n * `lastErrorCode`-Dedup in {@link logDeviceError} resets on every recovery,\n * so on chronic bouncing a new disconnect counts as \"first occurrence\"\n * \u2192 wieder warn. This cooldown stamp persists across recoveries so the user\n * sees max one warn per WARN_COOLDOWN_MS per device.\n */\n private readonly lastWarnAt = new Map<string, number>();\n /** Per-device last-info timestamp for `connection restored`. Analog cooldown. */\n private readonly lastInfoAt = new Map<string, number>();\n private pairingTimer: ioBroker.Timeout | undefined = undefined;\n private pairingPollTimer: ioBroker.Interval | undefined = undefined;\n private systemPollTimer: ioBroker.Interval | undefined = undefined;\n private ipRecoveryTimer: ioBroker.Timeout | undefined = undefined;\n private isPairing = false;\n private pairingManualIp = \"\";\n private discoveredDuringPairing: DiscoveredDevice[] = [];\n /** Set during onUnload \u2014 async paths bail before further setStateAsync calls. */\n private unloading = false;\n /**\n * Factories for the REST/WS clients \u2014 default to the real constructors. Test seams:\n * a unit test can replace these with fakes to exercise the orchestration (initDevice,\n * onWsConnected/onWsDisconnected, onStateChange) without real network.\n *\n * @param ip Device IP address\n * @param token Bearer token (empty string for pairing requests)\n */\n private makeClient: (ip: string, token: string) => HomeWizardClient = (ip, token) =>\n new HomeWizardClient(ip, token, { log: this.log });\n private makeWebSocket: (ip: string, token: string, callbacks: WsCallbacks, timers: TimerDeps) => HomeWizardWebSocket =\n (ip, token, callbacks, timers) => new HomeWizardWebSocket(ip, token, callbacks, timers);\n\n /**\n * Close a connection's WebSocket and clear its poll + reconnect timers.\n *\n * @param conn Device connection to tear down\n */\n private teardownConnection(conn: DeviceConnection): void {\n conn.wsClient?.close();\n if (conn.pollTimer) {\n this.clearInterval(conn.pollTimer);\n conn.pollTimer = undefined;\n }\n if (conn.reconnectTimer) {\n this.clearTimeout(conn.reconnectTimer);\n conn.reconnectTimer = undefined;\n }\n }\n\n /** @param options Adapter options */\n public constructor(options: Partial<utils.AdapterOptions> = {}) {\n super({ ...options, name: \"homewizard\" });\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 // No process-level unhandledRejection/uncaughtException handlers: in compact mode they\n // are process-wide and cross-adapter-harmful. Every handler has .bind+try/catch and\n // fire-and-forget paths use .catch (Fleet pattern \u2014 hueemu/parcelapp/nut).\n }\n\n private async onReady(): Promise<void> {\n try {\n await I18n.init(join(this.adapterDir, \"admin\"), this);\n this.stateManager = new StateManager(this);\n\n await this.setStateAsync(\"startPairing\", { val: false, ack: true });\n await this.setStateAsync(\"pairingIp\", { val: \"\", ack: true });\n\n await this.subscribeStatesAsync(\"startPairing\");\n await this.subscribeStatesAsync(\"*.system.reboot\");\n await this.subscribeStatesAsync(\"*.system.identify\");\n await this.subscribeStatesAsync(\"*.system.cloud_enabled\");\n await this.subscribeStatesAsync(\"*.system.status_led_brightness_pct\");\n await this.subscribeStatesAsync(\"*.system.api_v1_enabled\");\n await this.subscribeStatesAsync(\"*.battery.mode\");\n await this.subscribeStatesAsync(\"*.battery.permissions\");\n await this.subscribeStatesAsync(\"*.battery.charge_to_full\");\n await this.subscribeStatesAsync(\"*.remove\");\n\n const devices = await this.loadDevicesFromObjects();\n if (devices.length === 0) {\n this.log.info(`No devices configured \u2014 set 'startPairing' to true to add a device`);\n await this.setStateChangedAsync(\"info.connection\", { val: false, ack: true });\n }\n\n for (const device of devices) {\n const key = this.stateManager.devicePrefix(device);\n await this.stateManager.cleanupMovedStates(device);\n await this.stateManager.createDeviceStates(device);\n const conn = createDeviceConnection(device, device.ip || \"\");\n this.connections.set(key, conn);\n\n if (conn.ip) {\n this.log.debug(`Using stored IP ${conn.ip} for ${device.productName}`);\n void this.initDevice(conn).catch((err: unknown) =>\n this.log.error(`initDevice failed for ${conn.config.productName}: ${errText(err)}`),\n );\n }\n }\n\n this.systemPollTimer = this.setInterval(() => {\n void this.pollAllSystemInfo();\n }, SYSTEM_POLL_MS);\n\n this.updateGlobalConnection();\n } catch (err: unknown) {\n this.log.error(`onReady failed: ${errText(err)}`);\n }\n }\n\n /**\n * Load device configs from existing device objects\n * Tokens are stored encrypted in device object native\n */\n private async loadDevicesFromObjects(): Promise<DeviceConfig[]> {\n const devices: DeviceConfig[] = [];\n\n // One-shot legacy migration: v0.1/0.2 stored devices in adapter `native.devices`;\n // v0.3.0 moved them to per-device objects. Any install that ran v0.3.0+ has already\n // migrated (native.devices cleared below), but removal is low-reward / non-zero-risk\n // for an install that has been dormant since v0.2 \u2014 keep until at least v1.0.0.\n // Defensive: native.devices could be a non-array if a previous version\n // wrote a different shape, or if the user edited it manually.\n const rawOldDevices = (this.config as Record<string, unknown>).devices;\n const oldDevices: DeviceConfig[] = Array.isArray(rawOldDevices) ? (rawOldDevices as DeviceConfig[]) : [];\n if (oldDevices.length > 0) {\n this.log.debug(`Migrating ${oldDevices.length} device(s) from adapter config to device objects`);\n for (const device of oldDevices) {\n await this.saveDeviceToObject(device);\n }\n // Clear old config (this triggers one restart, but only during migration)\n await this.extendForeignObjectAsync(`system.adapter.${this.namespace}`, {\n native: { devices: [] },\n });\n return oldDevices;\n }\n\n // Read device objects from our namespace. A corrupted encryptedToken\n // (e.g. after secret rotation, crypto-lib changes, manual DB edits) must\n // not take down the whole adapter \u2014 skip the broken device, keep the rest.\n const objects = await this.getAdapterObjectsAsync();\n for (const [id, obj] of Object.entries(objects)) {\n if (obj.type !== \"device\") {\n continue;\n }\n const native = obj.native as Record<string, string> | undefined;\n if (!native?.encryptedToken || !native.serial) {\n continue;\n }\n const localId = id.replace(`${this.namespace}.`, \"\");\n this.log.debug(`Loading device from object: ${localId}`);\n let token: string;\n try {\n token = this.decrypt(native.encryptedToken);\n } catch (err) {\n this.log.warn(\n `Cannot decrypt token for ${localId} \u2014 re-pair the device. ` +\n `(${errText(err)}). Other devices remain unaffected.`,\n );\n continue;\n }\n devices.push({\n token,\n productType: native.productType || \"unknown\",\n serial: native.serial,\n productName: native.productName || native.productType || \"unknown\",\n ...(native.ip ? { ip: native.ip } : {}),\n });\n }\n\n return devices;\n }\n\n /**\n * Save device config to its device object native (encrypted token)\n *\n * @param config Device configuration to save\n */\n private async saveDeviceToObject(config: DeviceConfig): Promise<void> {\n const prefix = this.stateManager.devicePrefix(config);\n const encryptedToken = this.encrypt(config.token);\n await this.extendObjectAsync(\n prefix,\n {\n type: \"device\",\n common: { name: config.productName || config.productType },\n native: {\n encryptedToken,\n productType: config.productType,\n serial: config.serial,\n productName: config.productName,\n ...(config.ip ? { ip: config.ip } : {}),\n },\n },\n { preserve: { common: [\"name\"] } },\n );\n }\n\n /**\n * Handle a discovered device from mDNS (only active during pairing)\n *\n * @param discovered Discovered device info\n */\n private onDeviceDiscovered(discovered: DiscoveredDevice): void {\n // Skip already paired devices\n const existing = Array.from(this.connections.values()).find(c => c.config.serial === discovered.serial);\n if (existing) {\n return;\n }\n\n // Skip duplicates\n if (this.discoveredDuringPairing.find(d => d.serial === discovered.serial)) {\n return;\n }\n\n this.discoveredDuringPairing.push(discovered);\n this.log.info(\n `Found ${discovered.name} (${discovered.productType}) at ${discovered.ip} \u2014 press the button on the device to pair`,\n );\n }\n\n /**\n * Adapter stopping \u2014 MUST be synchronous\n *\n * @param callback Completion callback\n */\n private onUnload(callback: () => void): void {\n // Set first, before any clearTimeout \u2014 in-flight async paths\n // (REST poll, getMeasurement, getSystem) check this after each await\n // and bail out before further setStateAsync on a tearing-down adapter.\n this.unloading = true;\n try {\n if (this.pairingTimer) {\n this.clearTimeout(this.pairingTimer);\n }\n if (this.pairingPollTimer) {\n this.clearInterval(this.pairingPollTimer);\n }\n if (this.systemPollTimer) {\n this.clearInterval(this.systemPollTimer);\n }\n if (this.ipRecoveryTimer) {\n this.clearTimeout(this.ipRecoveryTimer);\n }\n\n this.discovery?.stop();\n\n for (const conn of this.connections.values()) {\n this.teardownConnection(conn);\n }\n this.connections.clear();\n\n void this.setState(\"info.connection\", { val: false, ack: true });\n } finally {\n callback();\n }\n }\n\n private async onStateChange(id: string, state: ioBroker.State | null | undefined): Promise<void> {\n try {\n if (!state || state.ack || this.unloading) {\n return;\n }\n\n if (id.endsWith(\".startPairing\")) {\n if (state.val) {\n await this.startPairing();\n }\n return;\n }\n\n if (id.endsWith(\".remove\")) {\n if (state.val) {\n await this.removeDevice(id);\n }\n return;\n }\n\n const conn = this.findConnectionForState(id);\n if (!conn || !conn.ip) {\n return;\n }\n\n const client = this.makeClient(conn.ip, conn.config.token);\n\n try {\n if (id.endsWith(\".system.reboot\")) {\n this.log.info(`Rebooting ${conn.config.productName} (${conn.ip})`);\n await client.reboot();\n } else if (id.endsWith(\".system.identify\")) {\n await client.identify();\n } else if (id.endsWith(\".system.cloud_enabled\")) {\n await client.setSystem({ cloud_enabled: !!state.val });\n await this.setStateAsync(id, { val: state.val, ack: true });\n } else if (id.endsWith(\".system.status_led_brightness_pct\")) {\n await client.setSystem({\n status_led_brightness_pct: Number(state.val),\n });\n await this.setStateAsync(id, { val: state.val, ack: true });\n } else if (id.endsWith(\".system.api_v1_enabled\")) {\n await client.setSystem({ api_v1_enabled: !!state.val });\n await this.setStateAsync(id, { val: state.val, ack: true });\n } else if (id.endsWith(\".battery.mode\")) {\n const mode = validateBatteryMode(String(state.val));\n if (!mode) {\n this.log.warn(\n `Invalid battery.mode value: '${String(state.val)}' \u2014 expected one of: zero, to_full, standby, predictive`,\n );\n return;\n }\n await client.setBatteries({ mode });\n await this.setStateAsync(id, { val: state.val, ack: true });\n } else if (id.endsWith(\".battery.permissions\")) {\n const result = parseBatteryPermissions(String(state.val));\n if (!result.ok) {\n this.log.warn(\n `Invalid JSON for battery.permissions: ${result.reason} \u2014 expected array, got: ${result.sample}`,\n );\n return;\n }\n await client.setBatteries({ permissions: result.perms });\n await this.setStateAsync(id, { val: state.val, ack: true });\n } else if (id.endsWith(\".battery.charge_to_full\")) {\n await client.setBatteries({ charge_to_full: !!state.val });\n await this.setStateAsync(id, { val: state.val, ack: true });\n }\n } catch (err) {\n this.log.warn(`Failed to set ${id}: ${errText(err)}`);\n }\n } catch (err: unknown) {\n this.log.error(`stateChange failed: ${errText(err)}`);\n }\n }\n\n /** Start pairing mode \u2014 discover devices and attempt to pair */\n private async startPairing(): Promise<void> {\n if (this.isPairing) {\n this.log.debug(\"Pairing already active\");\n return;\n }\n\n // Reset startPairing immediately so it doesn't survive a restart\n await this.setStateAsync(\"startPairing\", { val: false, ack: true });\n\n this.isPairing = true;\n this.discoveredDuringPairing = [];\n\n // Stop IP recovery if running \u2014 pairing takes priority\n this.stopIpRecovery();\n\n // Check if manual IP is set, then clear pairingIp immediately\n const ipState = await this.getStateAsync(\"pairingIp\");\n this.pairingManualIp = ipState?.val ? String(ipState.val).trim() : \"\";\n await this.setStateAsync(\"pairingIp\", { val: \"\", ack: true });\n\n if (this.pairingManualIp) {\n // Validate manual-IP up front \u2014 better to fail fast than wait 60s while\n // requestPairing keeps timing out against a malformed input.\n if (!isValidIpv4(this.pairingManualIp)) {\n this.log.warn(`Invalid pairing IP '${this.pairingManualIp}' \u2014 expected IPv4 (e.g. 192.168.1.42)`);\n this.isPairing = false;\n this.pairingManualIp = \"\";\n return;\n }\n this.log.info(\n `Pairing mode enabled for ${this.pairingManualIp} \u2014 press the button on your HomeWizard device now (60 seconds timeout)`,\n );\n // Add as discovered device immediately\n this.discoveredDuringPairing.push({\n ip: this.pairingManualIp,\n productType: \"unknown\",\n serial: \"unknown\",\n name: this.pairingManualIp,\n });\n } else {\n this.log.info(\n `Pairing mode enabled \u2014 searching for devices via mDNS, press the button on your HomeWizard device now (60 seconds timeout)`,\n );\n // Restart mDNS browser to trigger fresh query \u2014 already-cached devices\n // won't be re-announced otherwise and pairing would never find them\n if (!this.discovery) {\n this.discovery = new HomeWizardDiscovery(this.log);\n }\n this.discovery.start(discovered => {\n this.onDeviceDiscovered(discovered);\n });\n }\n\n // Poll discovered devices for pairing\n this.pairingPollTimer = this.setInterval(() => {\n void this.pollPairing();\n }, PAIRING_POLL_MS);\n\n // Timeout pairing\n this.pairingTimer = this.setTimeout(() => {\n this.stopPairing();\n this.log.info(`Pairing mode automatically disabled after 60 seconds timeout`);\n }, PAIRING_TIMEOUT_MS);\n }\n\n /** Poll all discovered devices to attempt pairing */\n private async pollPairing(): Promise<void> {\n for (const device of this.discoveredDuringPairing) {\n try {\n const client = this.makeClient(device.ip, \"\");\n const result = await client.requestPairing();\n\n // Success! Button was pressed\n this.log.info(\n `Successfully paired with ${device.name} (${device.productType}) at ${device.ip} \u2014 connecting...`,\n );\n\n // Get device info\n const authedClient = this.makeClient(device.ip, result.token);\n const info = await authedClient.getDeviceInfo();\n\n const deviceConfig: DeviceConfig = {\n token: result.token,\n productType: info.product_type,\n serial: info.serial,\n productName: info.product_name,\n ip: device.ip,\n };\n\n // Save to device object (no adapter restart!)\n await this.saveDeviceToObject(deviceConfig);\n await this.stateManager.createDeviceStates(deviceConfig);\n\n // Re-pair of an existing device (e.g. after factory reset): close the\n // old connection's wsClient + timers before overwriting the map entry,\n // otherwise the old WS keeps running as a zombie until restart.\n const key = this.stateManager.devicePrefix(deviceConfig);\n const previous = this.connections.get(key);\n if (previous) {\n this.log.debug(`Re-pair: closing previous connection for ${deviceConfig.productName}`);\n this.teardownConnection(previous);\n }\n\n // Create connection and connect\n const conn = createDeviceConnection(deviceConfig, device.ip);\n this.connections.set(key, conn);\n void this.initDevice(conn).catch((err: unknown) =>\n this.log.error(`initDevice failed for ${conn.config.productName}: ${errText(err)}`),\n );\n\n // Remove from discovery list \u2014 but keep pairing window open so the\n // user can button-press additional devices in the same session.\n this.discoveredDuringPairing = this.discoveredDuringPairing.filter(d => d.serial !== info.serial);\n\n this.updateGlobalConnection();\n // Do NOT call stopPairing() here \u2014 pairingTimer (60 s) closes the\n // window naturally; meanwhile the user can pair more devices.\n continue;\n } catch (err) {\n // 403 = button not pressed yet \u2014 expected, keep polling\n if (err instanceof HomeWizardApiError && err.statusCode === 403) {\n continue;\n }\n this.log.debug(`Pairing poll error for ${device.ip}: ${errText(err)}`);\n }\n }\n }\n\n /** Stop pairing mode */\n private stopPairing(): void {\n this.isPairing = false;\n this.pairingManualIp = \"\";\n this.discoveredDuringPairing = [];\n\n // Stop mDNS \u2014 only needed during pairing\n if (this.discovery) {\n this.discovery.stop();\n this.discovery = null;\n }\n\n if (this.pairingPollTimer) {\n this.clearInterval(this.pairingPollTimer);\n this.pairingPollTimer = undefined;\n }\n if (this.pairingTimer) {\n this.clearTimeout(this.pairingTimer);\n this.pairingTimer = undefined;\n }\n }\n\n /** Start mDNS to find devices that changed IP */\n private startIpRecovery(): void {\n // Don't start if already running or pairing\n if (this.discovery || this.isPairing) {\n return;\n }\n\n // Internal recovery \u2014 debug only. The initial disconnect already produced\n // one warn via logDeviceError; repeating that hourly while a device stays\n // offline is just spam.\n this.log.debug(`Device unreachable \u2014 searching for new IP via mDNS`);\n\n this.discovery = new HomeWizardDiscovery(this.log);\n this.discovery.start(discovered => {\n // Match against disconnected devices\n for (const conn of this.connections.values()) {\n if (conn.config.serial !== discovered.serial) {\n continue;\n }\n if (discovered.ip === conn.ip || conn.wsAuthenticated) {\n return; // Same IP or already connected\n }\n // Multiple mDNS broadcasts can arrive within one recovery window\n // (e.g. AP roam). Skip if a connect cycle is already in flight.\n if (conn.recovering) {\n return;\n }\n\n this.log.info(`${conn.config.productName}: found at new IP ${discovered.ip} (was ${conn.ip})`);\n\n // Update IP and persist \u2014 reset stability (new network conditions)\n conn.ip = discovered.ip;\n conn.config.ip = discovered.ip;\n conn.wsFailCount = 0;\n conn.recentDisconnects = 0;\n // Surface persist-failures (e.g. js-controller hiccup) instead of\n // swallowing them \u2014 the user otherwise sees \"new IP\" log but the\n // change is lost on next restart.\n this.saveDeviceToObject(conn.config).catch((err: unknown) =>\n this.log.debug(`Failed to persist new IP for ${conn.config.productName}: ${errText(err)}`),\n );\n\n // Cancel pending reconnect and connect immediately\n if (conn.reconnectTimer) {\n this.clearTimeout(conn.reconnectTimer);\n conn.reconnectTimer = undefined;\n }\n if (conn.pollTimer) {\n this.clearInterval(conn.pollTimer);\n conn.pollTimer = undefined;\n }\n this.connectWebSocket(conn);\n return;\n }\n });\n\n // Stop mDNS after timeout \u2014 WS reconnect continues with exponential\n // backoff. Don't log per-device warns here: the initial disconnect already\n // produced a `deviceUnreachable` warn via logDeviceError; spamming the\n // user hourly while the device stays offline adds zero information. If\n // someone needs to see retry cadence they can enable debug logging.\n this.ipRecoveryTimer = this.setTimeout(() => {\n this.ipRecoveryTimer = undefined;\n this.stopIpRecovery();\n\n for (const conn of this.connections.values()) {\n if (!conn.wsAuthenticated && conn.wsFailCount > 0) {\n this.log.debug(\n `${conn.config.productName}: device offline \u2014 will keep retrying every ${WS_RECONNECT_MAX_MS / 1000}s`,\n );\n }\n }\n }, IP_RECOVERY_TIMEOUT_MS);\n }\n\n /** Stop mDNS IP recovery */\n private stopIpRecovery(): void {\n if (this.ipRecoveryTimer) {\n this.clearTimeout(this.ipRecoveryTimer);\n this.ipRecoveryTimer = undefined;\n }\n if (this.discovery && !this.isPairing) {\n this.discovery.stop();\n this.discovery = null;\n }\n }\n\n /**\n * Initialize a newly discovered device \u2014 fetch info and connect WebSocket\n *\n * @param conn Device connection with IP set\n */\n private async initDevice(conn: DeviceConnection): Promise<void> {\n if (this.unloading || conn.removed) {\n return;\n }\n try {\n const client = this.makeClient(conn.ip, conn.config.token);\n const info = await client.getDeviceInfo();\n if (this.unloading || conn.removed) {\n return;\n }\n const key = this.stateManager.devicePrefix(conn.config);\n await this.setStateAsync(`${key}.info.firmware`, {\n val: info.firmware_version,\n ack: true,\n });\n } catch (err) {\n if (this.unloading) {\n return;\n }\n this.logDeviceError(conn, \"init\", err);\n }\n\n if (this.unloading || conn.removed) {\n return;\n }\n this.connectWebSocket(conn);\n void this.pollSystemInfo(conn);\n }\n\n /**\n * Connect WebSocket for a device\n *\n * @param conn Device connection\n */\n private connectWebSocket(conn: DeviceConnection): void {\n if (!conn.ip) {\n return; // No IP yet \u2014 wait for mDNS\n }\n\n // Stop reconnecting if auth keeps failing\n if (conn.authFailCount >= MAX_AUTH_FAILURES) {\n return;\n }\n\n // Mark as recovering so concurrent triggers (mDNS broadcast race,\n // overlapping reconnect timer) don't spawn a second wsClient.\n conn.recovering = true;\n\n // Close any existing wsClient before creating a new one. The normal\n // disconnect path nulls conn.wsClient, but IP-recovery jumps in directly\n // and would otherwise leak the old socket.\n if (conn.wsClient) {\n conn.wsClient.close();\n conn.wsClient = null;\n }\n\n // After repeated failures, try mDNS periodically to find a new IP\n if (shouldStartIpRecovery(conn.wsFailCount, WS_FAILURES_BEFORE_MDNS, MDNS_RETRY_EVERY)) {\n this.startIpRecovery();\n }\n\n // Thin callbacks delegating to instance methods (extracted for readability + unit-testability).\n const wsClient = this.makeWebSocket(\n conn.ip,\n conn.config.token,\n {\n onMeasurement: data => this.onWsMeasurement(conn, data),\n onSystem: data => this.onWsSystem(conn, data),\n onBattery: data => this.onWsBattery(conn, data),\n onConnected: () => this.onWsConnected(conn),\n onDisconnected: error => this.onWsDisconnected(conn, error),\n log: this.log,\n },\n {\n schedule: (cb, ms) => this.setTimeout(cb, ms),\n cancel: h => {\n this.clearTimeout(h as ioBroker.Timeout);\n },\n scheduleRepeating: (cb, ms) => this.setInterval(cb, ms),\n cancelRepeating: h => {\n this.clearInterval(h as ioBroker.Interval);\n },\n },\n );\n\n conn.wsClient = wsClient;\n wsClient.connect();\n }\n\n /**\n * Handle a measurement push.\n *\n * @param conn Device connection\n * @param data Measurement payload\n */\n private onWsMeasurement(conn: DeviceConnection, data: Measurement): void {\n // Skip updates for devices removed mid-flight (frame can race delObjectAsync) + teardown.\n if (conn.removed || this.unloading) {\n return;\n }\n // Defensive .catch \u2014 writes inside updateMeasurement may reject on transient Redis hiccups;\n // we want a debug-log, not an unhandled rejection.\n this.stateManager.updateMeasurement(conn.config, data).catch((err: unknown) => {\n this.log.debug(`updateMeasurement failed for ${conn.config.productName}: ${errText(err)}`);\n });\n }\n\n /**\n * Handle a real-time system push (cloud/led changes etc.).\n *\n * @param conn Device connection\n * @param data System payload\n */\n private onWsSystem(conn: DeviceConnection, data: SystemInfo): void {\n if (conn.removed || this.unloading) {\n return;\n }\n this.stateManager.updateSystem(conn.config, data).catch((err: unknown) => {\n this.log.debug(`updateSystem (ws) failed for ${conn.config.productName}: ${errText(err)}`);\n });\n }\n\n /**\n * Handle a real-time battery-group push (mode/permissions/target power).\n *\n * @param conn Device connection\n * @param data Battery-control payload\n */\n private onWsBattery(conn: DeviceConnection, data: BatteryControl): void {\n if (conn.removed || this.unloading) {\n return;\n }\n // Only surface battery states when batteries are actually connected.\n if (!data.battery_count || data.battery_count <= 0) {\n return;\n }\n this.stateManager.updateBattery(conn.config, data).catch((err: unknown) => {\n this.log.debug(`updateBattery (ws) failed for ${conn.config.productName}: ${errText(err)}`);\n });\n }\n\n /**\n * WebSocket authenticated \u2014 mark connected, stop REST fallback, log recovery (cooldowned).\n *\n * @param conn Device connection\n */\n private onWsConnected(conn: DeviceConnection): void {\n conn.wsAuthenticated = true;\n conn.wsFailCount = 0;\n conn.authFailCount = 0;\n conn.lastConnectedAt = Date.now();\n conn.recovering = false;\n void this.stateManager.setDeviceConnected(conn.config, true);\n this.updateGlobalConnection();\n\n // Stop REST fallback if active\n if (conn.pollTimer) {\n this.clearInterval(conn.pollTimer);\n conn.pollTimer = undefined;\n }\n\n // Stop IP recovery if all devices are connected\n if (this.discovery && !this.isPairing) {\n const allConnected = Array.from(this.connections.values()).every(c => c.wsAuthenticated);\n if (allConnected) {\n this.stopIpRecovery();\n }\n }\n\n // Log restoration if we had errors before. Per-device cooldown so chronic bouncing\n // doesn't emit one info per cycle \u2014 repeats go to debug.\n if (conn.lastErrorCode) {\n const now = Date.now();\n const lastInfo = this.lastInfoAt.get(conn.config.serial) ?? 0;\n const msg = this.isUnstable(conn)\n ? `${conn.config.productName}: connection restored (unstable mode)`\n : `${conn.config.productName}: connection restored`;\n if (shouldEmitAfterCooldown(lastInfo, now, INFO_COOLDOWN_MS)) {\n this.lastInfoAt.set(conn.config.serial, now);\n this.log.info(msg);\n } else {\n this.log.debug(`${msg} (cooldown)`);\n }\n conn.lastErrorCode = \"\";\n }\n\n this.log.debug(`WebSocket connected to ${conn.config.productName} (${conn.ip})`);\n }\n\n /**\n * WebSocket disconnected \u2014 track stability, start REST fallback, schedule backed-off reconnect\n * (unless an auth failure stops the loop).\n *\n * @param conn Device connection\n * @param error Disconnect error, if any\n */\n private onWsDisconnected(conn: DeviceConnection, error?: Error): void {\n // Auth failures are not a connectivity-stability signal \u2014 they mean the token is bad,\n // not the WiFi. Counting them as short connections would flip the device into unstable mode.\n const isAuthError = error instanceof HomeWizardApiError && error.errorCode === \"user:unauthorized\";\n\n // Track connection stability \u2014 pure decision in main-helpers, side-effects here.\n if (conn.lastConnectedAt > 0 && !isAuthError) {\n const duration = Date.now() - conn.lastConnectedAt;\n const transition = decideUnstableTransition(\n conn.recentDisconnects,\n duration,\n STABLE_THRESHOLD_MS,\n UNSTABLE_DISCONNECT_THRESHOLD,\n );\n if (duration < STABLE_THRESHOLD_MS) {\n conn.recentDisconnects++;\n } else {\n conn.recentDisconnects = 0;\n }\n // Hysterese-transitions are internal reconnect-strategy adjustments \u2192 debug, not info.\n if (transition === \"becameUnstable\") {\n this.log.debug(`${conn.config.productName}: unstable connection detected \u2014 using faster reconnect`);\n } else if (transition === \"stabilized\") {\n this.log.debug(`${conn.config.productName}: connection stabilized \u2014 using normal reconnect`);\n }\n }\n\n conn.wsAuthenticated = false;\n conn.wsClient = null;\n conn.recovering = false;\n void this.stateManager.setDeviceConnected(conn.config, false);\n this.updateGlobalConnection();\n\n if (error) {\n this.logDeviceError(conn, \"ws\", error);\n }\n\n // Auth failure \u2192 stop the reconnect path.\n if (!this.handleAuthFailure(conn, error, /* cleanupTimers */ false)) {\n return;\n }\n\n // Start REST fallback\n this.startRestFallback(conn);\n\n // Schedule reconnect with exponential backoff (faster for unstable devices).\n conn.wsFailCount++;\n const maxDelay = this.isUnstable(conn) ? WS_RECONNECT_MAX_UNSTABLE_MS : WS_RECONNECT_MAX_MS;\n const delay = computeReconnectDelay(conn.wsFailCount, WS_RECONNECT_BASE_MS, maxDelay);\n const key = this.stateManager.devicePrefix(conn.config);\n this.log.debug(`${key}: WS reconnect in ${delay / 1000}s (attempt ${conn.wsFailCount})`);\n\n conn.reconnectTimer = this.setTimeout(() => {\n conn.reconnectTimer = undefined;\n this.connectWebSocket(conn);\n }, delay);\n }\n\n /**\n * Start REST polling as fallback when WebSocket is down.\n * For stable devices: stops on network errors (WS reconnect handles recovery).\n * For unstable devices: slows down instead of stopping to minimize data gaps.\n *\n * @param conn Device connection\n */\n private startRestFallback(conn: DeviceConnection): void {\n if (conn.pollTimer || !conn.ip) {\n return;\n }\n\n const unstable = this.isUnstable(conn);\n const interval = pickRestPollInterval(unstable, REST_POLL_MS, REST_POLL_UNSTABLE_MS);\n const client = this.makeClient(conn.ip, conn.config.token);\n\n conn.pollTimer = this.setInterval(async () => {\n // Bail out if device was removed or adapter is shutting down \u2014 the\n // setStateAsync chain inside updateMeasurement would otherwise either\n // recreate deleted objects or hit a torn-down adapter.\n if (conn.removed || this.unloading) {\n return;\n }\n try {\n const data = await client.getMeasurement();\n if (conn.removed || this.unloading) {\n return;\n }\n await this.stateManager.updateMeasurement(conn.config, data);\n } catch (err) {\n if (this.unloading) {\n return;\n }\n this.logDeviceError(conn, \"rest\", err);\n\n // Auth failures: stop everything \u2014 token is bad, re-pair required.\n if (err instanceof HomeWizardApiError && err.errorCode === \"user:unauthorized\") {\n this.handleAuthFailure(conn, err, /* cleanupTimers */ true);\n return;\n }\n\n // Stop REST polling on network errors for stable devices.\n // Unstable devices keep polling (slower) to minimize data gaps.\n if (!unstable && classifyError(err) === \"NETWORK\" && conn.pollTimer) {\n this.clearInterval(conn.pollTimer);\n conn.pollTimer = undefined;\n }\n }\n }, interval);\n }\n\n /** Poll system info for all connected devices in parallel */\n private async pollAllSystemInfo(): Promise<void> {\n if (this.unloading) {\n return;\n }\n const tasks = Array.from(this.connections.values())\n .filter(c => c.ip && c.wsAuthenticated && !c.removed)\n .map(c => this.pollSystemInfo(c));\n await Promise.all(tasks);\n }\n\n /**\n * Poll system info for a single device\n *\n * @param conn Device connection\n */\n private async pollSystemInfo(conn: DeviceConnection): Promise<void> {\n if (!conn.ip || conn.removed || this.unloading) {\n return;\n }\n\n try {\n const client = this.makeClient(conn.ip, conn.config.token);\n const system = await client.getSystem();\n if (conn.removed || this.unloading) {\n return;\n }\n await this.stateManager.updateSystem(conn.config, system);\n\n // Sync productName drift: if the user renamed the device in the\n // HomeWizard app (or a firmware update changed the product_name), pick\n // up the new value once per system-poll instead of staying stale until\n // re-pair. Cheap \u2014 only writes on actual change.\n try {\n const info = await client.getDeviceInfo();\n if (!conn.removed && !this.unloading && info.product_name && info.product_name !== conn.config.productName) {\n this.log.info(`${conn.config.productName}: name changed to '${info.product_name}' \u2014 updating object`);\n conn.config.productName = info.product_name;\n await this.saveDeviceToObject(conn.config);\n }\n } catch {\n // device-info is best-effort here; the system-poll log already\n // surfaces real connectivity issues.\n }\n\n // Also poll battery if device supports it. 404 = no battery \u2014 silent.\n // Other errors (500, timeout, malformed body) used to be swallowed\n // entirely; now they surface at debug so post-mortem diagnosis is\n // possible without losing any normal-flow logging.\n if (conn.removed || this.unloading) {\n return;\n }\n try {\n const battery = await client.getBatteries();\n if (conn.removed || this.unloading) {\n return;\n }\n // Only create battery states if batteries are actually connected\n if (battery.battery_count && battery.battery_count > 0) {\n await this.stateManager.updateBattery(conn.config, battery);\n }\n } catch (err) {\n if (err instanceof HomeWizardApiError && err.statusCode === 404) {\n return; // device doesn't support batteries \u2014 expected\n }\n this.log.debug(`${conn.config.productName} batteries: ${errText(err)}`);\n }\n } catch (err) {\n if (this.unloading) {\n return;\n }\n this.logDeviceError(conn, \"system\", err);\n }\n }\n\n /** Update global info.connection based on all device states */\n private updateGlobalConnection(): void {\n const anyConnected = Array.from(this.connections.values()).some(c => c.wsAuthenticated);\n // setStateChanged: flips rarely (connect/disconnect), called on every WS event \u2014 skip no-op writes.\n void this.setStateChangedAsync(\"info.connection\", {\n val: anyConnected,\n ack: true,\n });\n }\n\n /**\n * Remove a device \u2014 disconnect, delete states and object\n *\n * @param stateId The remove state ID\n */\n private async removeDevice(stateId: string): Promise<void> {\n const conn = this.findConnectionForState(stateId);\n if (!conn) {\n return;\n }\n\n const key = this.stateManager.devicePrefix(conn.config);\n this.log.info(`Removing device ${conn.config.productName} (${conn.config.serial})`);\n\n // Mark as removed FIRST \u2014 async tasks (in-flight WS frames, REST polls,\n // outstanding pollSystemInfo) check this flag after each await and bail\n // out before recreating just-deleted objects via setStateAsync.\n conn.removed = true;\n\n // Best-effort token revoke on the device (DELETE /api/user) so the local/iobroker user\n // doesn't linger across pair/unpair cycles. Fire-and-forget \u2014 never block removal on a\n // (possibly offline) device's 10s timeout.\n if (conn.ip && conn.config.token) {\n void this.makeClient(conn.ip, conn.config.token)\n .deleteUser()\n .catch((err: unknown) => this.log.debug(`Token revoke failed for ${conn.config.productName}: ${errText(err)}`));\n }\n\n // Disconnect\n this.teardownConnection(conn);\n this.connections.delete(key);\n\n // Delete device object and all states (no adapter restart!)\n await this.stateManager.removeDevice(conn.config);\n\n this.updateGlobalConnection();\n }\n\n /**\n * Find connection for a state ID. Delegates to the pure helper so the\n * lookup math is unit-tested separately (`lib/main-helpers.test.ts`).\n *\n * @param stateId Full state ID\n */\n private findConnectionForState(stateId: string): DeviceConnection | undefined {\n return resolveConnectionForState(stateId, this.namespace, this.connections);\n }\n\n /**\n * Whether a device has unstable connectivity (frequent short-lived connections).\n * Unstable devices get faster reconnect and persistent REST fallback.\n *\n * @param conn Device connection\n */\n private isUnstable(conn: DeviceConnection): boolean {\n return conn.recentDisconnects >= UNSTABLE_DISCONNECT_THRESHOLD;\n }\n\n /**\n * Handle a possible auth failure on a device connection. Counts failures and,\n * once `MAX_AUTH_FAILURES` is reached, warns the user and (optionally) tears\n * down active timers and the WebSocket \u2014 stops bombarding the device with a\n * known-bad token.\n *\n * @param conn Device connection.\n * @param error The error from the failing call (any error type accepted).\n * @param cleanupTimers If `true`, clears poll/reconnect timers and closes the WS\n * on threshold reach. Used by REST-fallback paths where\n * the WS would otherwise keep retrying indefinitely. The\n * WS-disconnect path passes `false` because the caller\n * decides the next step itself.\n * @returns `true` if the caller should continue normal flow (no auth-stop),\n * `false` if the auth-stop fired and the caller should bail out.\n */\n private handleAuthFailure(conn: DeviceConnection, error: unknown, cleanupTimers: boolean): boolean {\n if (!(error instanceof HomeWizardApiError) || error.errorCode !== \"user:unauthorized\") {\n return true;\n }\n conn.authFailCount++;\n if (conn.authFailCount < MAX_AUTH_FAILURES) {\n return true;\n }\n this.log.warn(`${conn.config.productName}: token invalid \u2014 re-pair device to fix`);\n if (cleanupTimers) {\n if (conn.pollTimer) {\n this.clearInterval(conn.pollTimer);\n conn.pollTimer = undefined;\n }\n if (conn.reconnectTimer) {\n this.clearTimeout(conn.reconnectTimer);\n conn.reconnectTimer = undefined;\n }\n conn.wsClient?.close();\n }\n return false;\n }\n\n /**\n * Log device error with deduplication.\n *\n * Two-stage dedup:\n * 1. `lastErrorCode` per connection \u2014 repeats of the same error category go\n * to debug. Resets on recovery (`onConnected` clears it) so a new category\n * after recovery surfaces as warn again. Designtechnisch correct for\n * \u201Enew failure mode\" but blind to chronic bouncing.\n * 2. {@link lastWarnAt} per device serial \u2014 survives recovery. Even if the\n * `lastErrorCode`-Dedup says \u201Efirst occurrence\", the cooldown stamp keeps\n * the warn-emit suppressed if we've warned for this device within\n * {@link WARN_COOLDOWN_MS}. Chronic bouncing produces at most 1\u00D7 warn per\n * hour per device.\n *\n * Cooldown key is the device serial \u2014 category-spanning. A flapping P1 that\n * cycles TIMEOUT\u2192NETWORK\u2192TIMEOUT is one phenomenon, one warn-budget.\n *\n * @param conn Device connection\n * @param context Error context (for debug messages only)\n * @param err Error object\n */\n private logDeviceError(conn: DeviceConnection, context: string, err: unknown): void {\n const errorCode = classifyError(err);\n const isRepeat = errorCode === conn.lastErrorCode;\n conn.lastErrorCode = errorCode;\n\n if (isRepeat) {\n this.log.debug(`${conn.config.productName} ${context}: ${errText(err)}`);\n return;\n }\n\n // New category \u2014 apply per-device cooldown so chronic bouncing doesn't\n // emit warn at every cycle just because each cycle's first failure is\n // a fresh `lastErrorCode`.\n const now = Date.now();\n const lastWarn = this.lastWarnAt.get(conn.config.serial) ?? 0;\n if (!shouldEmitAfterCooldown(lastWarn, now, WARN_COOLDOWN_MS)) {\n this.log.debug(`${conn.config.productName} ${context} (cooldown): ${errText(err)}`);\n return;\n }\n\n this.lastWarnAt.set(conn.config.serial, now);\n if (errorCode === \"NETWORK\") {\n this.log.warn(`${conn.config.productName}: device unreachable \u2014 will keep retrying`);\n } else {\n this.log.warn(`${conn.config.productName} ${context}: ${errText(err)}`);\n }\n }\n}\n\nif (require.main !== module) {\n module.exports = (options: Partial<utils.AdapterOptions> | undefined) => new HomeWizard(options);\n} else {\n (() => new HomeWizard())();\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["resolveConnectionForState"]
|
|
7
7
|
}
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "homewizard",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.11.0",
|
|
5
5
|
"news": {
|
|
6
|
+
"0.11.0": {
|
|
7
|
+
"en": "Removed the raw P1 telegram datapoint — its data is already available as parsed measurement states; the leftover state is cleaned up automatically on existing P1 meters.",
|
|
8
|
+
"de": "Roher P1-Telegramm-Datenpunkt entfernt — seine Werte stehen ohnehin als geparste Messdaten-States bereit; der verbliebene State wird auf bestehenden P1-Zählern automatisch aufgeräumt.",
|
|
9
|
+
"ru": "Удалён необработанный датапоинт телеграммы P1 — его значения и так доступны как разобранные состояния измерений; оставшийся стейт на существующих счётчиках P1 удаляется автоматически.",
|
|
10
|
+
"pt": "Removido o datapoint do telegrama P1 bruto — os seus valores já estão disponíveis como estados de medição processados; o estado restante é limpo automaticamente nos medidores P1 existentes.",
|
|
11
|
+
"nl": "Ruwe P1-telegram-datapunt verwijderd — de waarden zijn al beschikbaar als verwerkte meet-states; de achtergebleven state wordt automatisch opgeruimd op bestaande P1-meters.",
|
|
12
|
+
"fr": "Point de données du télégramme P1 brut supprimé — ses valeurs sont déjà disponibles comme états de mesure analysés ; l'état résiduel est nettoyé automatiquement sur les compteurs P1 existants.",
|
|
13
|
+
"it": "Rimosso il datapoint del telegramma P1 grezzo — i suoi valori sono già disponibili come stati di misura elaborati; lo stato residuo viene rimosso automaticamente sui contatori P1 esistenti.",
|
|
14
|
+
"es": "Eliminado el datapoint del telegrama P1 sin procesar — sus valores ya están como estados de medición procesados; el estado restante se limpia automáticamente en los medidores P1 existentes.",
|
|
15
|
+
"pl": "Usunięto surowy punkt danych telegramu P1 — jego wartości są już dostępne jako przetworzone stany pomiarowe; pozostały stan jest automatycznie usuwany na istniejących licznikach P1.",
|
|
16
|
+
"uk": "Видалено необроблений датапоінт телеграми P1 — його значення вже доступні як оброблені стани вимірювань; залишковий стан автоматично видаляється на наявних лічильниках P1.",
|
|
17
|
+
"zh-cn": "移除了原始 P1 报文数据点——其数据已作为解析后的测量状态提供;现有 P1 电表上的残留状态会自动清理。"
|
|
18
|
+
},
|
|
6
19
|
"0.10.0": {
|
|
7
20
|
"en": "New predictive battery mode and charge-to-full switch, real-time system and battery updates, plus new WiFi-network and raw P1-telegram states.",
|
|
8
21
|
"de": "Neuer Predictive-Batteriemodus und Voll-laden-Schalter, Echtzeit-Updates für System und Batterie sowie neue Datenpunkte für WLAN-Netzwerk und rohes P1-Telegramm.",
|
|
@@ -80,19 +93,6 @@
|
|
|
80
93
|
"pl": "Poprawiona obsługa błędów i stabilność.",
|
|
81
94
|
"uk": "Покращено обробку помилок та стабільність.",
|
|
82
95
|
"zh-cn": "改进了错误处理和稳定性。"
|
|
83
|
-
},
|
|
84
|
-
"0.8.2": {
|
|
85
|
-
"en": "Internal cleanup. No user-facing changes.",
|
|
86
|
-
"de": "Interne Bereinigung. Keine sichtbaren Änderungen.",
|
|
87
|
-
"ru": "Внутренняя очистка. Нет видимых изменений для пользователей.",
|
|
88
|
-
"pt": "Limpeza interna. Sem alterações visíveis para o utilizador.",
|
|
89
|
-
"nl": "Interne opruiming. Geen zichtbare wijzigingen voor gebruikers.",
|
|
90
|
-
"fr": "Nettoyage interne. Aucun changement visible pour l utilisateur.",
|
|
91
|
-
"it": "Pulizia interna. Nessuna modifica visibile per l utente.",
|
|
92
|
-
"es": "Limpieza interna. Sin cambios visibles para el usuario.",
|
|
93
|
-
"pl": "Wewnętrzne porządki. Brak widocznych zmian dla użytkownika.",
|
|
94
|
-
"uk": "Внутрішнє прибирання. Без видимих змін для користувача.",
|
|
95
|
-
"zh-cn": "内部清理。无用户可见的更改。"
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
98
|
"titleLang": {
|