iobroker.homewizard 0.7.7 → 0.7.8
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 -3
- package/build/lib/homewizard-client.js +23 -5
- package/build/lib/homewizard-client.js.map +3 -3
- package/build/lib/state-manager.js +11 -0
- package/build/lib/state-manager.js.map +2 -2
- package/build/main.js +6 -6
- package/build/main.js.map +2 -2
- package/io-package.json +14 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -170,6 +170,10 @@ homewizard.0.
|
|
|
170
170
|
Placeholder for the next version (at the beginning of the line):
|
|
171
171
|
### **WORK IN PROGRESS**
|
|
172
172
|
-->
|
|
173
|
+
### 0.7.8 (2026-05-13)
|
|
174
|
+
|
|
175
|
+
- Debug log now traces every HTTPS API call and device-state lifecycle — easier diagnostics for chronic bouncing or pairing/recovery issues.
|
|
176
|
+
|
|
173
177
|
### 0.7.7 (2026-05-13)
|
|
174
178
|
|
|
175
179
|
- Devices with chronically bad WiFi no longer flood the log: max one warn per hour when the device drops out, one info when it comes back. Full timeline stays at debug level.
|
|
@@ -192,9 +196,6 @@ homewizard.0.
|
|
|
192
196
|
### 0.7.4 (2026-05-09)
|
|
193
197
|
- Adapter log messages are now English only, in line with the ioBroker community standard. Localized state names, descriptions and dropdown labels (11 languages) are unchanged.
|
|
194
198
|
|
|
195
|
-
### 0.7.3 (2026-05-07)
|
|
196
|
-
- Less log spam when a device stays offline for longer periods — the initial `device unreachable` warning is enough; mDNS recovery attempts and offline-retry status now log at debug level only.
|
|
197
|
-
|
|
198
199
|
### Support Development
|
|
199
200
|
|
|
200
201
|
This adapter is free and open source. If you find it useful, consider buying me a coffee:
|
|
@@ -40,19 +40,23 @@ class HomeWizardClient {
|
|
|
40
40
|
agent;
|
|
41
41
|
/** Override target port — only used by tests against a local stub-server. */
|
|
42
42
|
port;
|
|
43
|
+
/** Optional logger for per-call debug-trace (request entry + response success/fail). */
|
|
44
|
+
log;
|
|
43
45
|
/**
|
|
44
46
|
* @param ip Device IP address
|
|
45
47
|
* @param token Bearer token (empty string for pairing requests)
|
|
46
48
|
* @param options Optional overrides — primarily for unit tests against a local TLS stub.
|
|
47
49
|
* @param options.agent HTTPS agent to use; defaults to {@link HW_AGENT} (with HomeWizard CA pinning).
|
|
48
50
|
* @param options.port Target port; defaults to 443.
|
|
51
|
+
* @param options.log Optional logger for per-call debug-trace (request/success/fail).
|
|
49
52
|
*/
|
|
50
53
|
constructor(ip, token = "", options = {}) {
|
|
51
|
-
var _a, _b;
|
|
54
|
+
var _a, _b, _c;
|
|
52
55
|
this.ip = ip;
|
|
53
56
|
this.token = token;
|
|
54
57
|
this.agent = (_a = options.agent) != null ? _a : import_cacert.HW_AGENT;
|
|
55
58
|
this.port = (_b = options.port) != null ? _b : 443;
|
|
59
|
+
this.log = (_c = options.log) != null ? _c : null;
|
|
56
60
|
}
|
|
57
61
|
/** Get device info (GET /api) */
|
|
58
62
|
async getDeviceInfo() {
|
|
@@ -111,6 +115,7 @@ class HomeWizardClient {
|
|
|
111
115
|
*/
|
|
112
116
|
request(method, path, body) {
|
|
113
117
|
return new Promise((resolve, reject) => {
|
|
118
|
+
var _a;
|
|
114
119
|
const bodyStr = body ? JSON.stringify(body) : void 0;
|
|
115
120
|
const headers = {
|
|
116
121
|
"X-Api-Version": "2"
|
|
@@ -122,6 +127,8 @@ class HomeWizardClient {
|
|
|
122
127
|
headers["Content-Type"] = "application/json";
|
|
123
128
|
headers["Content-Length"] = Buffer.byteLength(bodyStr).toString();
|
|
124
129
|
}
|
|
130
|
+
const startMs = Date.now();
|
|
131
|
+
(_a = this.log) == null ? void 0 : _a.debug(`HTTPS ${method} ${path} ip=${this.ip} auth=${this.token ? "bearer" : "none"}`);
|
|
125
132
|
const req = https.request(
|
|
126
133
|
{
|
|
127
134
|
hostname: this.ip,
|
|
@@ -137,13 +144,20 @@ class HomeWizardClient {
|
|
|
137
144
|
res.on("error", reject);
|
|
138
145
|
res.on("data", (chunk) => chunks.push(chunk));
|
|
139
146
|
res.on("end", () => {
|
|
140
|
-
var
|
|
147
|
+
var _a2, _b, _c;
|
|
141
148
|
const data = Buffer.concat(chunks).toString();
|
|
142
|
-
|
|
143
|
-
|
|
149
|
+
const elapsedMs = Date.now() - startMs;
|
|
150
|
+
const statusCode = (_a2 = res.statusCode) != null ? _a2 : 0;
|
|
151
|
+
if (!statusCode || statusCode >= 400) {
|
|
152
|
+
const snippet = data.length > 200 ? `${data.slice(0, 200)}\u2026` : data;
|
|
153
|
+
(_b = this.log) == null ? void 0 : _b.debug(`HTTPS ${method} ${path}: status=${statusCode} elapsed=${elapsedMs}ms body="${snippet}"`);
|
|
154
|
+
const error = new HomeWizardApiError(statusCode, data, `${method} ${path}`);
|
|
144
155
|
reject(error);
|
|
145
156
|
return;
|
|
146
157
|
}
|
|
158
|
+
(_c = this.log) == null ? void 0 : _c.debug(
|
|
159
|
+
`HTTPS ${method} ${path}: status=${statusCode} elapsed=${elapsedMs}ms bytes=${data.length}`
|
|
160
|
+
);
|
|
147
161
|
if (!data) {
|
|
148
162
|
resolve(void 0);
|
|
149
163
|
return;
|
|
@@ -156,7 +170,11 @@ class HomeWizardClient {
|
|
|
156
170
|
});
|
|
157
171
|
}
|
|
158
172
|
);
|
|
159
|
-
req.on("error",
|
|
173
|
+
req.on("error", (err) => {
|
|
174
|
+
var _a2;
|
|
175
|
+
(_a2 = this.log) == null ? void 0 : _a2.debug(`HTTPS ${method} ${path}: error="${err.message}" elapsed=${Date.now() - startMs}ms`);
|
|
176
|
+
reject(err);
|
|
177
|
+
});
|
|
160
178
|
req.on("timeout", () => {
|
|
161
179
|
req.destroy(new Error(`Timeout: ${method} ${path}`));
|
|
162
180
|
});
|
|
@@ -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/** 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\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 */\n constructor(ip: string
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAAuB;AACvB,oBAAyB;
|
|
6
|
-
"names": []
|
|
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 * @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
|
+
"names": ["_a"]
|
|
7
7
|
}
|
|
@@ -434,6 +434,7 @@ class StateManager {
|
|
|
434
434
|
*/
|
|
435
435
|
async createDeviceStates(config) {
|
|
436
436
|
const prefix = this.devicePrefix(config);
|
|
437
|
+
this.adapter.log.debug(`state-manager: createDeviceStates ${prefix} (productType=${config.productType})`);
|
|
437
438
|
await this.adapter.extendObjectAsync(prefix, {
|
|
438
439
|
type: "device",
|
|
439
440
|
common: {
|
|
@@ -693,12 +694,16 @@ class StateManager {
|
|
|
693
694
|
*/
|
|
694
695
|
async removeDevice(config) {
|
|
695
696
|
const prefix = this.devicePrefix(config);
|
|
697
|
+
this.adapter.log.debug(`state-manager: removeDevice ${prefix}`);
|
|
696
698
|
await this.adapter.delObjectAsync(prefix, { recursive: true });
|
|
699
|
+
let dropped = 0;
|
|
697
700
|
for (const id of this.createdIds) {
|
|
698
701
|
if (id === prefix || id.startsWith(`${prefix}.`)) {
|
|
699
702
|
this.createdIds.delete(id);
|
|
703
|
+
dropped++;
|
|
700
704
|
}
|
|
701
705
|
}
|
|
706
|
+
this.adapter.log.debug(`state-manager: removeDevice ${prefix} done (dropped ${dropped} cached IDs)`);
|
|
702
707
|
}
|
|
703
708
|
/**
|
|
704
709
|
* Remove measurement states from old locations (pre-v0.4.0: device root instead of measurement/ channel)
|
|
@@ -707,17 +712,23 @@ class StateManager {
|
|
|
707
712
|
*/
|
|
708
713
|
async cleanupMovedStates(config) {
|
|
709
714
|
const prefix = this.devicePrefix(config);
|
|
715
|
+
this.adapter.log.debug(`state-manager: cleanupMovedStates ${prefix} (scanning pre-v0.4.0 paths)`);
|
|
710
716
|
const oldIds = [];
|
|
711
717
|
for (const def of MEASUREMENT_STATE_DEFS) {
|
|
712
718
|
oldIds.push(`${prefix}.${def.id}`);
|
|
713
719
|
}
|
|
714
720
|
oldIds.push(`${prefix}.external`);
|
|
721
|
+
let removed = 0;
|
|
715
722
|
for (const id of oldIds) {
|
|
716
723
|
if (await this.adapter.getObjectAsync(id)) {
|
|
717
724
|
await this.adapter.delObjectAsync(id, { recursive: true });
|
|
718
725
|
this.adapter.log.debug(`Removed obsolete state: ${id}`);
|
|
726
|
+
removed++;
|
|
719
727
|
}
|
|
720
728
|
}
|
|
729
|
+
if (removed > 0) {
|
|
730
|
+
this.adapter.log.debug(`state-manager: cleanupMovedStates ${prefix} done (removed ${removed} obsolete paths)`);
|
|
731
|
+
}
|
|
721
732
|
}
|
|
722
733
|
/**
|
|
723
734
|
* Get device object ID prefix
|
|
@@ -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 { STATE_DESCS, STATE_NAMES, StateName } from \"./i18n-states\";\nimport { resolveLabel, tDesc, tName } from \"./i18n-states\";\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: keyof typeof STATE_NAMES;\n /** Optional translation key for `common.desc` (resolved via {@link tDesc}) */\n descKey?: keyof typeof STATE_DESCS;\n /** State value type */\n type: ioBroker.CommonType;\n /** ioBroker role */\n role: string;\n /** Unit string */\n unit?: string;\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/**\n * Translation-object cast helper \u2014 ioBroker's `common.name`/`desc` accept `StringOrTranslated`.\n *\n * @param name Translation object from {@link STATE_NAMES} or {@link STATE_DESCS}.\n */\nfunction asName(name: StateName): ioBroker.StringOrTranslated {\n return name;\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 * @param lang Two-letter ISO language code (use `adapter.language ?? \"en\"`).\n */\nfunction tariffStates(lang: string): Record<string, string> {\n return {\n 1: resolveLabel(\"tariff1\", lang),\n 2: resolveLabel(\"tariff2\", lang),\n 3: resolveLabel(\"tariff3\", lang),\n 4: resolveLabel(\"tariff4\", lang),\n };\n}\n\n/**\n * Build a `common.states` map for HWE-BAT battery.mode with plain-string labels.\n * Same constraint as {@link tariffStates}.\n *\n * @param lang Two-letter ISO language code.\n */\nfunction batteryModeStates(lang: string): Record<string, string> {\n return {\n zero: resolveLabel(\"modeZero\", lang),\n to_full: resolveLabel(\"modeToFull\", lang),\n standby: resolveLabel(\"modeStandby\", lang),\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 // 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(prefix, {\n type: \"device\",\n common: {\n name: config.productName || config.productType,\n statusStates: {\n onlineId: `${this.adapter.namespace}.${prefix}.info.connected`,\n },\n } as ioBroker.DeviceCommon,\n native: {},\n });\n\n await this.adapter.extendObjectAsync(`${prefix}.info`, {\n type: \"channel\",\n common: { name: asName(tName(\"deviceInformation\")) },\n native: {},\n });\n\n await this.createState(`${prefix}.info.productName`, tName(\"productName\"), \"string\", \"text\", false);\n await this.createState(`${prefix}.info.productType`, tName(\"productType\"), \"string\", \"text\", false);\n await this.createState(`${prefix}.info.firmware`, tName(\"firmware\"), \"string\", \"text\", false);\n await this.createState(`${prefix}.info.connected`, tName(\"connected\"), \"boolean\", \"indicator.reachable\", false);\n await this.createState(`${prefix}.info.wifi_rssi_db`, tName(\"wifiRssi\"), \"number\", \"value\", false, \"dBm\");\n await this.createState(`${prefix}.info.uptime_s`, tName(\"uptime\"), \"number\", \"value\", false, \"s\");\n\n // Remove device button\n await this.createButton(`${prefix}.remove`, tName(\"removeDevice\"), tDesc(\"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, asName(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 `${mPrefix}.${def.id}`,\n tName(def.nameKey),\n def.type,\n def.role,\n coerced,\n def.unit,\n undefined,\n def.descKey ? tDesc(def.descKey) : undefined,\n def.key === \"tariff\" ? tariffStates(this.adapter.language ?? \"en\") : undefined,\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`, asName(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(`${extId}.value`, tName(\"externalValue\"), \"number\", \"value\", value, unit ?? undefined),\n );\n }\n if (unit) {\n extWrites.push(this.ensureAndSet(`${extId}.unit`, tName(\"externalUnit\"), \"string\", \"text\", unit));\n }\n if (timestamp) {\n extWrites.push(\n this.ensureAndSet(`${extId}.timestamp`, tName(\"externalTimestamp\"), \"string\", \"date\", timestamp),\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/uptime in info channel\n const rssi = coerceFiniteNumber(record.wifi_rssi_db);\n if (rssi !== null) {\n await this.ensureAndSet(`${prefix}.info.wifi_rssi_db`, tName(\"wifiRssi\"), \"number\", \"value\", rssi, \"dBm\");\n }\n const uptime = coerceFiniteNumber(record.uptime_s);\n if (uptime !== null) {\n await this.ensureAndSet(`${prefix}.info.uptime_s`, tName(\"uptime\"), \"number\", \"value\", uptime, \"s\");\n }\n\n // System control channel (cached after first call per device)\n await this.ensureChannel(`${prefix}.system`, asName(tName(\"systemSettings\")));\n\n const cloudEnabled = coerceBoolean(record.cloud_enabled);\n if (cloudEnabled !== null) {\n await this.ensureAndSet(\n `${prefix}.system.cloud_enabled`,\n tName(\"cloudEnabled\"),\n \"boolean\",\n \"switch\",\n cloudEnabled,\n undefined,\n true,\n );\n }\n const ledPct = coerceFiniteNumber(record.status_led_brightness_pct);\n if (ledPct !== null) {\n await this.ensureAndSet(\n `${prefix}.system.status_led_brightness_pct`,\n tName(\"ledBrightness\"),\n \"number\",\n \"level\",\n ledPct,\n \"%\",\n true,\n );\n }\n\n const apiV1 = coerceBoolean(record.api_v1_enabled);\n if (apiV1 !== null) {\n await this.ensureAndSet(\n `${prefix}.system.api_v1_enabled`,\n tName(\"apiV1Enabled\"),\n \"boolean\",\n \"switch\",\n apiV1,\n undefined,\n true,\n );\n }\n\n // Action buttons\n await this.createButton(`${prefix}.system.reboot`, tName(\"rebootDevice\"));\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`, asName(tName(\"batteryControl\")));\n\n const mode = coerceString(record.mode);\n if (mode) {\n await this.ensureAndSet(\n `${prefix}.battery.mode`,\n tName(\"batteryMode\"),\n \"string\",\n \"text\",\n mode,\n undefined,\n true,\n tDesc(\"batteryModeDesc\"),\n batteryModeStates(this.adapter.language ?? \"en\"),\n );\n }\n if (Array.isArray(record.permissions)) {\n await this.ensureAndSet(\n `${prefix}.battery.permissions`,\n tName(\"batteryPermissions\"),\n \"string\",\n \"json\",\n JSON.stringify(record.permissions),\n undefined,\n true,\n );\n }\n\n const numberFields: Array<{\n key: string;\n id: string;\n nameKey: keyof typeof STATE_NAMES;\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 `${prefix}.battery.${field.id}`,\n tName(field.nameKey),\n \"number\",\n field.role,\n coerced,\n field.unit,\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.setStateAsync(`${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 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 for (const id of this.createdIds) {\n if (id === prefix || id.startsWith(`${prefix}.`)) {\n this.createdIds.delete(id);\n }\n }\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\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 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 }\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 id State ID\n * @param name State name (translation object or string for device identifiers)\n * @param type Value type\n * @param role ioBroker role\n * @param write Whether state is writable\n * @param unit Optional unit\n * @param desc Optional translation object for `common.desc`\n * @param states Optional `common.states` map\n */\n private async createState(\n id: string,\n name: StateName | string,\n type: ioBroker.CommonType,\n role: string,\n write: boolean,\n unit?: string,\n desc?: StateName,\n states?: Record<string, string>,\n ): Promise<void> {\n if (this.createdIds.has(id)) {\n return;\n }\n const common: Partial<ioBroker.StateCommon> = {\n name: typeof name === \"string\" ? name : asName(name),\n type,\n role,\n read: true,\n write,\n };\n if (unit) {\n common.unit = unit;\n }\n if (desc) {\n common.desc = asName(desc);\n }\n if (states) {\n common.states = states;\n }\n await this.adapter.setObjectNotExistsAsync(id, {\n type: \"state\",\n common: common as ioBroker.StateCommon,\n native: {},\n });\n if (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(id, states);\n }\n this.createdIds.add(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(id: string, name: StateName, desc?: StateName): Promise<void> {\n if (this.createdIds.has(id)) {\n return;\n }\n const common: Partial<ioBroker.StateCommon> = {\n name: asName(name),\n type: \"boolean\",\n role: \"button\",\n read: false,\n write: true,\n };\n if (desc) {\n common.desc = asName(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 state exists and set value\n *\n * @param id State ID\n * @param name State name (translation object or string)\n * @param type Value type\n * @param role ioBroker role\n * @param value State value\n * @param unit Optional unit\n * @param write Whether state is writable\n * @param desc Optional translation object for `common.desc`\n * @param states Optional `common.states` map (translation objects)\n */\n private async ensureAndSet(\n id: string,\n name: StateName | string,\n type: ioBroker.CommonType,\n role: string,\n value: ioBroker.StateValue,\n unit?: string,\n write?: boolean,\n desc?: StateName,\n states?: Record<string, string>,\n ): Promise<void> {\n await this.createState(id, name, type, role, write ?? false, unit, desc, states);\n await this.adapter.setStateAsync(id, { val: value, ack: true });\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 { STATE_DESCS, STATE_NAMES, StateName } from \"./i18n-states\";\nimport { resolveLabel, tDesc, tName } from \"./i18n-states\";\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: keyof typeof STATE_NAMES;\n /** Optional translation key for `common.desc` (resolved via {@link tDesc}) */\n descKey?: keyof typeof STATE_DESCS;\n /** State value type */\n type: ioBroker.CommonType;\n /** ioBroker role */\n role: string;\n /** Unit string */\n unit?: string;\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/**\n * Translation-object cast helper \u2014 ioBroker's `common.name`/`desc` accept `StringOrTranslated`.\n *\n * @param name Translation object from {@link STATE_NAMES} or {@link STATE_DESCS}.\n */\nfunction asName(name: StateName): ioBroker.StringOrTranslated {\n return name;\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 * @param lang Two-letter ISO language code (use `adapter.language ?? \"en\"`).\n */\nfunction tariffStates(lang: string): Record<string, string> {\n return {\n 1: resolveLabel(\"tariff1\", lang),\n 2: resolveLabel(\"tariff2\", lang),\n 3: resolveLabel(\"tariff3\", lang),\n 4: resolveLabel(\"tariff4\", lang),\n };\n}\n\n/**\n * Build a `common.states` map for HWE-BAT battery.mode with plain-string labels.\n * Same constraint as {@link tariffStates}.\n *\n * @param lang Two-letter ISO language code.\n */\nfunction batteryModeStates(lang: string): Record<string, string> {\n return {\n zero: resolveLabel(\"modeZero\", lang),\n to_full: resolveLabel(\"modeToFull\", lang),\n standby: resolveLabel(\"modeStandby\", lang),\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(prefix, {\n type: \"device\",\n common: {\n name: config.productName || config.productType,\n statusStates: {\n onlineId: `${this.adapter.namespace}.${prefix}.info.connected`,\n },\n } as ioBroker.DeviceCommon,\n native: {},\n });\n\n await this.adapter.extendObjectAsync(`${prefix}.info`, {\n type: \"channel\",\n common: { name: asName(tName(\"deviceInformation\")) },\n native: {},\n });\n\n await this.createState(`${prefix}.info.productName`, tName(\"productName\"), \"string\", \"text\", false);\n await this.createState(`${prefix}.info.productType`, tName(\"productType\"), \"string\", \"text\", false);\n await this.createState(`${prefix}.info.firmware`, tName(\"firmware\"), \"string\", \"text\", false);\n await this.createState(`${prefix}.info.connected`, tName(\"connected\"), \"boolean\", \"indicator.reachable\", false);\n await this.createState(`${prefix}.info.wifi_rssi_db`, tName(\"wifiRssi\"), \"number\", \"value\", false, \"dBm\");\n await this.createState(`${prefix}.info.uptime_s`, tName(\"uptime\"), \"number\", \"value\", false, \"s\");\n\n // Remove device button\n await this.createButton(`${prefix}.remove`, tName(\"removeDevice\"), tDesc(\"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, asName(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 `${mPrefix}.${def.id}`,\n tName(def.nameKey),\n def.type,\n def.role,\n coerced,\n def.unit,\n undefined,\n def.descKey ? tDesc(def.descKey) : undefined,\n def.key === \"tariff\" ? tariffStates(this.adapter.language ?? \"en\") : undefined,\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`, asName(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(`${extId}.value`, tName(\"externalValue\"), \"number\", \"value\", value, unit ?? undefined),\n );\n }\n if (unit) {\n extWrites.push(this.ensureAndSet(`${extId}.unit`, tName(\"externalUnit\"), \"string\", \"text\", unit));\n }\n if (timestamp) {\n extWrites.push(\n this.ensureAndSet(`${extId}.timestamp`, tName(\"externalTimestamp\"), \"string\", \"date\", timestamp),\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/uptime in info channel\n const rssi = coerceFiniteNumber(record.wifi_rssi_db);\n if (rssi !== null) {\n await this.ensureAndSet(`${prefix}.info.wifi_rssi_db`, tName(\"wifiRssi\"), \"number\", \"value\", rssi, \"dBm\");\n }\n const uptime = coerceFiniteNumber(record.uptime_s);\n if (uptime !== null) {\n await this.ensureAndSet(`${prefix}.info.uptime_s`, tName(\"uptime\"), \"number\", \"value\", uptime, \"s\");\n }\n\n // System control channel (cached after first call per device)\n await this.ensureChannel(`${prefix}.system`, asName(tName(\"systemSettings\")));\n\n const cloudEnabled = coerceBoolean(record.cloud_enabled);\n if (cloudEnabled !== null) {\n await this.ensureAndSet(\n `${prefix}.system.cloud_enabled`,\n tName(\"cloudEnabled\"),\n \"boolean\",\n \"switch\",\n cloudEnabled,\n undefined,\n true,\n );\n }\n const ledPct = coerceFiniteNumber(record.status_led_brightness_pct);\n if (ledPct !== null) {\n await this.ensureAndSet(\n `${prefix}.system.status_led_brightness_pct`,\n tName(\"ledBrightness\"),\n \"number\",\n \"level\",\n ledPct,\n \"%\",\n true,\n );\n }\n\n const apiV1 = coerceBoolean(record.api_v1_enabled);\n if (apiV1 !== null) {\n await this.ensureAndSet(\n `${prefix}.system.api_v1_enabled`,\n tName(\"apiV1Enabled\"),\n \"boolean\",\n \"switch\",\n apiV1,\n undefined,\n true,\n );\n }\n\n // Action buttons\n await this.createButton(`${prefix}.system.reboot`, tName(\"rebootDevice\"));\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`, asName(tName(\"batteryControl\")));\n\n const mode = coerceString(record.mode);\n if (mode) {\n await this.ensureAndSet(\n `${prefix}.battery.mode`,\n tName(\"batteryMode\"),\n \"string\",\n \"text\",\n mode,\n undefined,\n true,\n tDesc(\"batteryModeDesc\"),\n batteryModeStates(this.adapter.language ?? \"en\"),\n );\n }\n if (Array.isArray(record.permissions)) {\n await this.ensureAndSet(\n `${prefix}.battery.permissions`,\n tName(\"batteryPermissions\"),\n \"string\",\n \"json\",\n JSON.stringify(record.permissions),\n undefined,\n true,\n );\n }\n\n const numberFields: Array<{\n key: string;\n id: string;\n nameKey: keyof typeof STATE_NAMES;\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 `${prefix}.battery.${field.id}`,\n tName(field.nameKey),\n \"number\",\n field.role,\n coerced,\n field.unit,\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.setStateAsync(`${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 id State ID\n * @param name State name (translation object or string for device identifiers)\n * @param type Value type\n * @param role ioBroker role\n * @param write Whether state is writable\n * @param unit Optional unit\n * @param desc Optional translation object for `common.desc`\n * @param states Optional `common.states` map\n */\n private async createState(\n id: string,\n name: StateName | string,\n type: ioBroker.CommonType,\n role: string,\n write: boolean,\n unit?: string,\n desc?: StateName,\n states?: Record<string, string>,\n ): Promise<void> {\n if (this.createdIds.has(id)) {\n return;\n }\n const common: Partial<ioBroker.StateCommon> = {\n name: typeof name === \"string\" ? name : asName(name),\n type,\n role,\n read: true,\n write,\n };\n if (unit) {\n common.unit = unit;\n }\n if (desc) {\n common.desc = asName(desc);\n }\n if (states) {\n common.states = states;\n }\n await this.adapter.setObjectNotExistsAsync(id, {\n type: \"state\",\n common: common as ioBroker.StateCommon,\n native: {},\n });\n if (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(id, states);\n }\n this.createdIds.add(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(id: string, name: StateName, desc?: StateName): Promise<void> {\n if (this.createdIds.has(id)) {\n return;\n }\n const common: Partial<ioBroker.StateCommon> = {\n name: asName(name),\n type: \"boolean\",\n role: \"button\",\n read: false,\n write: true,\n };\n if (desc) {\n common.desc = asName(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 state exists and set value\n *\n * @param id State ID\n * @param name State name (translation object or string)\n * @param type Value type\n * @param role ioBroker role\n * @param value State value\n * @param unit Optional unit\n * @param write Whether state is writable\n * @param desc Optional translation object for `common.desc`\n * @param states Optional `common.states` map (translation objects)\n */\n private async ensureAndSet(\n id: string,\n name: StateName | string,\n type: ioBroker.CommonType,\n role: string,\n value: ioBroker.StateValue,\n unit?: string,\n write?: boolean,\n desc?: StateName,\n states?: Record<string, string>,\n ): Promise<void> {\n await this.createState(id, name, type, role, write ?? false, unit, desc, states);\n await this.adapter.setStateAsync(id, { val: value, ack: true });\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/build/main.js
CHANGED
|
@@ -281,7 +281,7 @@ class HomeWizard extends utils.Adapter {
|
|
|
281
281
|
if (!conn || !conn.ip) {
|
|
282
282
|
return;
|
|
283
283
|
}
|
|
284
|
-
const client = new import_homewizard_client.HomeWizardClient(conn.ip, conn.config.token);
|
|
284
|
+
const client = new import_homewizard_client.HomeWizardClient(conn.ip, conn.config.token, { log: this.log });
|
|
285
285
|
try {
|
|
286
286
|
if (id.endsWith(".system.reboot")) {
|
|
287
287
|
this.log.info(`Rebooting ${conn.config.productName} (${conn.ip})`);
|
|
@@ -375,12 +375,12 @@ class HomeWizard extends utils.Adapter {
|
|
|
375
375
|
var _a;
|
|
376
376
|
for (const device of this.discoveredDuringPairing) {
|
|
377
377
|
try {
|
|
378
|
-
const client = new import_homewizard_client.HomeWizardClient(device.ip);
|
|
378
|
+
const client = new import_homewizard_client.HomeWizardClient(device.ip, "", { log: this.log });
|
|
379
379
|
const result = await client.requestPairing();
|
|
380
380
|
this.log.info(
|
|
381
381
|
`Successfully paired with ${device.name} (${device.productType}) at ${device.ip} \u2014 connecting...`
|
|
382
382
|
);
|
|
383
|
-
const authedClient = new import_homewizard_client.HomeWizardClient(device.ip, result.token);
|
|
383
|
+
const authedClient = new import_homewizard_client.HomeWizardClient(device.ip, result.token, { log: this.log });
|
|
384
384
|
const info = await authedClient.getDeviceInfo();
|
|
385
385
|
const deviceConfig = {
|
|
386
386
|
token: result.token,
|
|
@@ -506,7 +506,7 @@ class HomeWizard extends utils.Adapter {
|
|
|
506
506
|
return;
|
|
507
507
|
}
|
|
508
508
|
try {
|
|
509
|
-
const client = new import_homewizard_client.HomeWizardClient(conn.ip, conn.config.token);
|
|
509
|
+
const client = new import_homewizard_client.HomeWizardClient(conn.ip, conn.config.token, { log: this.log });
|
|
510
510
|
const info = await client.getDeviceInfo();
|
|
511
511
|
if (this.unloading || conn.removed) {
|
|
512
512
|
return;
|
|
@@ -656,7 +656,7 @@ class HomeWizard extends utils.Adapter {
|
|
|
656
656
|
}
|
|
657
657
|
const unstable = this.isUnstable(conn);
|
|
658
658
|
const interval = (0, import_main_helpers.pickRestPollInterval)(unstable, REST_POLL_MS, REST_POLL_UNSTABLE_MS);
|
|
659
|
-
const client = new import_homewizard_client.HomeWizardClient(conn.ip, conn.config.token);
|
|
659
|
+
const client = new import_homewizard_client.HomeWizardClient(conn.ip, conn.config.token, { log: this.log });
|
|
660
660
|
conn.pollTimer = this.setInterval(async () => {
|
|
661
661
|
if (conn.removed || this.unloading) {
|
|
662
662
|
return;
|
|
@@ -706,7 +706,7 @@ class HomeWizard extends utils.Adapter {
|
|
|
706
706
|
return;
|
|
707
707
|
}
|
|
708
708
|
try {
|
|
709
|
-
const client = new import_homewizard_client.HomeWizardClient(conn.ip, conn.config.token);
|
|
709
|
+
const client = new import_homewizard_client.HomeWizardClient(conn.ip, conn.config.token, { log: this.log });
|
|
710
710
|
const system = await client.getSystem();
|
|
711
711
|
if (conn.removed || this.unloading) {
|
|
712
712
|
return;
|
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 { 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 { DeviceConfig, DeviceConnection, DiscoveredDevice, Measurement } from \"./lib/types\";\nimport { HomeWizardWebSocket } 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\nclass 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 private unhandledRejectionHandler: ((reason: unknown) => void) | null = null;\n private uncaughtExceptionHandler: ((err: Error) => void) | null = null;\n /** Set during onUnload \u2014 async paths bail before further setStateAsync calls. */\n private unloading = false;\n\n /** @param options Adapter options */\n public constructor(options: Partial<utils.AdapterOptions> = {}) {\n super({ ...options, name: \"homewizard\" });\n // Wrap async handlers with .catch() so a rejection can never become an\n // unhandled promise rejection (\u2192 SIGKILL \u2192 js-controller restart loop).\n this.on(\"ready\", () => {\n this.onReady().catch((err: unknown) => this.log.error(`onReady failed: ${errText(err)}`));\n });\n this.on(\"stateChange\", (id, state) => {\n this.onStateChange(id, state).catch((err: unknown) => this.log.error(`stateChange failed: ${errText(err)}`));\n });\n this.on(\"unload\", callback => this.onUnload(callback));\n\n // Last-line-of-defence against unhandled rejections / sync throws from\n // fire-and-forget paths. The per-handler wrappers cover documented async\n // paths; this catches anything that slips past during refactors.\n this.unhandledRejectionHandler = (reason: unknown) => {\n this.log.error(`Unhandled rejection: ${errText(reason)}`);\n };\n this.uncaughtExceptionHandler = (err: Error) => {\n this.log.error(`Uncaught exception: ${err.message}`);\n };\n process.on(\"unhandledRejection\", this.unhandledRejectionHandler);\n process.on(\"uncaughtException\", this.uncaughtExceptionHandler);\n }\n\n /** Adapter started */\n private async onReady(): Promise<void> {\n this.stateManager = new StateManager(this);\n\n // `pairingIp` is declared in io-package.json instanceObjects \u2014 just reset state.\n // Reset pairing states on start (in case previous run was killed mid-pairing)\n await this.setStateAsync(\"startPairing\", { val: false, ack: true });\n await this.setStateAsync(\"pairingIp\", { val: \"\", ack: true });\n\n // Subscribe to pairing button and writable device states\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(\"*.remove\");\n\n // Load devices from device objects (not from adapter config)\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.setStateAsync(\"info.connection\", { val: false, ack: true });\n }\n\n // Create connection entries for all configured devices\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 we have a stored IP, connect immediately\n if (conn.ip) {\n this.log.debug(`Using stored IP ${conn.ip} for ${device.productName}`);\n void this.initDevice(conn);\n }\n }\n\n // Periodic system info poll\n this.systemPollTimer = this.setInterval(() => {\n void this.pollAllSystemInfo();\n }, SYSTEM_POLL_MS);\n\n this.updateGlobalConnection();\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 // Also migrate from old adapter config if devices exist there.\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(prefix, {\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 }\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 conn.wsClient?.close();\n if (conn.pollTimer) {\n this.clearInterval(conn.pollTimer);\n }\n if (conn.reconnectTimer) {\n this.clearTimeout(conn.reconnectTimer);\n }\n }\n this.connections.clear();\n\n // Detach process-level last-line-of-defence handlers\n if (this.unhandledRejectionHandler) {\n process.off(\"unhandledRejection\", this.unhandledRejectionHandler);\n this.unhandledRejectionHandler = null;\n }\n if (this.uncaughtExceptionHandler) {\n process.off(\"uncaughtException\", this.uncaughtExceptionHandler);\n this.uncaughtExceptionHandler = null;\n }\n\n void this.setState(\"info.connection\", { val: false, ack: true });\n } finally {\n callback();\n }\n }\n\n /**\n * Handle state changes\n *\n * @param id State ID\n * @param state State value\n */\n private async onStateChange(id: string, state: ioBroker.State | null | undefined): Promise<void> {\n if (!state || state.ack || this.unloading) {\n return;\n }\n\n // Pairing button\n if (id.endsWith(\".startPairing\")) {\n if (state.val) {\n await this.startPairing();\n }\n return;\n }\n\n // Remove device button\n if (id.endsWith(\".remove\")) {\n if (state.val) {\n await this.removeDevice(id);\n }\n return;\n }\n\n // Find which device this state belongs to\n const conn = this.findConnectionForState(id);\n if (!conn || !conn.ip) {\n return;\n }\n\n const client = new HomeWizardClient(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(`Invalid battery.mode value: '${String(state.val)}' \u2014 expected one of: zero, to_full, standby`);\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 }\n } catch (err) {\n this.log.warn(`Failed to set ${id}: ${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 = new HomeWizardClient(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 = new HomeWizardClient(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 previous.wsClient?.close();\n if (previous.pollTimer) {\n this.clearInterval(previous.pollTimer);\n }\n if (previous.reconnectTimer) {\n this.clearTimeout(previous.reconnectTimer);\n }\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);\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 = new HomeWizardClient(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 const key = this.stateManager.devicePrefix(conn.config);\n\n const wsClient = new HomeWizardWebSocket(conn.ip, conn.config.token, {\n onMeasurement: (data: Measurement) => {\n // Skip updates for devices removed mid-flight (frame can race\n // delObjectAsync), and for adapter teardown.\n if (conn.removed || this.unloading) {\n return;\n }\n // Defensive .catch \u2014 Promise.all writes inside updateMeasurement may\n // reject on transient Redis hiccups; we want a debug-log, not an\n // unhandled rejection that bubbles to the process-level handler.\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 onConnected: () => {\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\n // (analog to logDeviceError) so chronic bouncing doesn't emit one\n // info per cycle \u2014 bouncing hardware is one phenomenon and one\n // restoration-info per hour is enough. 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 onDisconnected: (error?: Error) => {\n // Auth failures are not a connectivity-stability signal \u2014 they mean\n // the token is bad, not the WiFi. Counting them as short connections\n // would flip the device into unstable mode (faster reconnect spam,\n // misleading \"unstable\" log) on what is purely an auth issue.\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,\n // not user-actionable events. Per the gesch\u00E4rfte mcm-Linie\n // (reference_iobroker_logging_levels HART-block): diagnostische\n // Prefixe / interne Hysterese-State geh\u00F6ren auf debug, nicht 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 // Check if this was an auth failure (returns false \u2192 stop 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 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 log: this.log,\n });\n\n conn.wsClient = wsClient;\n wsClient.connect();\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 = new HomeWizardClient(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 = new HomeWizardClient(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 void this.setStateAsync(\"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 // Disconnect\n conn.wsClient?.close();\n if (conn.pollTimer) {\n this.clearInterval(conn.pollTimer);\n }\n if (conn.reconnectTimer) {\n this.clearTimeout(conn.reconnectTimer);\n }\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 { 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 { DeviceConfig, DeviceConnection, DiscoveredDevice, Measurement } from \"./lib/types\";\nimport { HomeWizardWebSocket } 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\nclass 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 private unhandledRejectionHandler: ((reason: unknown) => void) | null = null;\n private uncaughtExceptionHandler: ((err: Error) => void) | null = null;\n /** Set during onUnload \u2014 async paths bail before further setStateAsync calls. */\n private unloading = false;\n\n /** @param options Adapter options */\n public constructor(options: Partial<utils.AdapterOptions> = {}) {\n super({ ...options, name: \"homewizard\" });\n // Wrap async handlers with .catch() so a rejection can never become an\n // unhandled promise rejection (\u2192 SIGKILL \u2192 js-controller restart loop).\n this.on(\"ready\", () => {\n this.onReady().catch((err: unknown) => this.log.error(`onReady failed: ${errText(err)}`));\n });\n this.on(\"stateChange\", (id, state) => {\n this.onStateChange(id, state).catch((err: unknown) => this.log.error(`stateChange failed: ${errText(err)}`));\n });\n this.on(\"unload\", callback => this.onUnload(callback));\n\n // Last-line-of-defence against unhandled rejections / sync throws from\n // fire-and-forget paths. The per-handler wrappers cover documented async\n // paths; this catches anything that slips past during refactors.\n this.unhandledRejectionHandler = (reason: unknown) => {\n this.log.error(`Unhandled rejection: ${errText(reason)}`);\n };\n this.uncaughtExceptionHandler = (err: Error) => {\n this.log.error(`Uncaught exception: ${err.message}`);\n };\n process.on(\"unhandledRejection\", this.unhandledRejectionHandler);\n process.on(\"uncaughtException\", this.uncaughtExceptionHandler);\n }\n\n /** Adapter started */\n private async onReady(): Promise<void> {\n this.stateManager = new StateManager(this);\n\n // `pairingIp` is declared in io-package.json instanceObjects \u2014 just reset state.\n // Reset pairing states on start (in case previous run was killed mid-pairing)\n await this.setStateAsync(\"startPairing\", { val: false, ack: true });\n await this.setStateAsync(\"pairingIp\", { val: \"\", ack: true });\n\n // Subscribe to pairing button and writable device states\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(\"*.remove\");\n\n // Load devices from device objects (not from adapter config)\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.setStateAsync(\"info.connection\", { val: false, ack: true });\n }\n\n // Create connection entries for all configured devices\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 we have a stored IP, connect immediately\n if (conn.ip) {\n this.log.debug(`Using stored IP ${conn.ip} for ${device.productName}`);\n void this.initDevice(conn);\n }\n }\n\n // Periodic system info poll\n this.systemPollTimer = this.setInterval(() => {\n void this.pollAllSystemInfo();\n }, SYSTEM_POLL_MS);\n\n this.updateGlobalConnection();\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 // Also migrate from old adapter config if devices exist there.\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(prefix, {\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 }\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 conn.wsClient?.close();\n if (conn.pollTimer) {\n this.clearInterval(conn.pollTimer);\n }\n if (conn.reconnectTimer) {\n this.clearTimeout(conn.reconnectTimer);\n }\n }\n this.connections.clear();\n\n // Detach process-level last-line-of-defence handlers\n if (this.unhandledRejectionHandler) {\n process.off(\"unhandledRejection\", this.unhandledRejectionHandler);\n this.unhandledRejectionHandler = null;\n }\n if (this.uncaughtExceptionHandler) {\n process.off(\"uncaughtException\", this.uncaughtExceptionHandler);\n this.uncaughtExceptionHandler = null;\n }\n\n void this.setState(\"info.connection\", { val: false, ack: true });\n } finally {\n callback();\n }\n }\n\n /**\n * Handle state changes\n *\n * @param id State ID\n * @param state State value\n */\n private async onStateChange(id: string, state: ioBroker.State | null | undefined): Promise<void> {\n if (!state || state.ack || this.unloading) {\n return;\n }\n\n // Pairing button\n if (id.endsWith(\".startPairing\")) {\n if (state.val) {\n await this.startPairing();\n }\n return;\n }\n\n // Remove device button\n if (id.endsWith(\".remove\")) {\n if (state.val) {\n await this.removeDevice(id);\n }\n return;\n }\n\n // Find which device this state belongs to\n const conn = this.findConnectionForState(id);\n if (!conn || !conn.ip) {\n return;\n }\n\n const client = new HomeWizardClient(conn.ip, conn.config.token, { log: this.log });\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(`Invalid battery.mode value: '${String(state.val)}' \u2014 expected one of: zero, to_full, standby`);\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 }\n } catch (err) {\n this.log.warn(`Failed to set ${id}: ${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 = new HomeWizardClient(device.ip, \"\", { log: this.log });\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 = new HomeWizardClient(device.ip, result.token, { log: this.log });\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 previous.wsClient?.close();\n if (previous.pollTimer) {\n this.clearInterval(previous.pollTimer);\n }\n if (previous.reconnectTimer) {\n this.clearTimeout(previous.reconnectTimer);\n }\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);\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 = new HomeWizardClient(conn.ip, conn.config.token, { log: this.log });\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 const key = this.stateManager.devicePrefix(conn.config);\n\n const wsClient = new HomeWizardWebSocket(conn.ip, conn.config.token, {\n onMeasurement: (data: Measurement) => {\n // Skip updates for devices removed mid-flight (frame can race\n // delObjectAsync), and for adapter teardown.\n if (conn.removed || this.unloading) {\n return;\n }\n // Defensive .catch \u2014 Promise.all writes inside updateMeasurement may\n // reject on transient Redis hiccups; we want a debug-log, not an\n // unhandled rejection that bubbles to the process-level handler.\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 onConnected: () => {\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\n // (analog to logDeviceError) so chronic bouncing doesn't emit one\n // info per cycle \u2014 bouncing hardware is one phenomenon and one\n // restoration-info per hour is enough. 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 onDisconnected: (error?: Error) => {\n // Auth failures are not a connectivity-stability signal \u2014 they mean\n // the token is bad, not the WiFi. Counting them as short connections\n // would flip the device into unstable mode (faster reconnect spam,\n // misleading \"unstable\" log) on what is purely an auth issue.\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,\n // not user-actionable events. Per the gesch\u00E4rfte mcm-Linie\n // (reference_iobroker_logging_levels HART-block): diagnostische\n // Prefixe / interne Hysterese-State geh\u00F6ren auf debug, nicht 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 // Check if this was an auth failure (returns false \u2192 stop 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 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 log: this.log,\n });\n\n conn.wsClient = wsClient;\n wsClient.connect();\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 = new HomeWizardClient(conn.ip, conn.config.token, { log: this.log });\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 = new HomeWizardClient(conn.ip, conn.config.token, { log: this.log });\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 void this.setStateAsync(\"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 // Disconnect\n conn.wsClient?.close();\n if (conn.pollTimer) {\n this.clearInterval(conn.pollTimer);\n }\n if (conn.reconnectTimer) {\n this.clearTimeout(conn.reconnectTimer);\n }\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.7.
|
|
4
|
+
"version": "0.7.8",
|
|
5
5
|
"news": {
|
|
6
|
+
"0.7.8": {
|
|
7
|
+
"en": "Debug log now traces every HTTPS API call and device-state lifecycle — easier diagnostics for chronic bouncing or pairing/recovery issues.",
|
|
8
|
+
"de": "Debug-Log spurt jetzt jeden HTTPS-API-Call und den Device-State-Lifecycle — bessere Diagnose bei chronischem Bouncing oder Pairing/Recovery-Problemen.",
|
|
9
|
+
"ru": "Debug-лог теперь трассирует каждый HTTPS-API-вызов и жизненный цикл состояний устройства — лучше диагностировать хроническое прерывание и проблемы pairing/recovery.",
|
|
10
|
+
"pt": "Log de debug agora rastreia cada chamada HTTPS da API e o ciclo de vida dos estados do dispositivo — diagnóstico mais fácil para bouncing crônico ou problemas de pairing/recovery.",
|
|
11
|
+
"nl": "Debug-log traceert nu elke HTTPS-API-aanroep en de device-state-lifecycle — eenvoudiger diagnose bij chronisch bouncing of pairing/recovery-problemen.",
|
|
12
|
+
"fr": "Le log debug trace désormais chaque appel HTTPS de l'API et le cycle de vie des états — diagnostic facilité pour le bouncing chronique ou les problèmes de pairing/recovery.",
|
|
13
|
+
"it": "Il log di debug ora traccia ogni chiamata HTTPS dell'API e il ciclo di vita degli stati — diagnosi più facile per bouncing cronico o problemi di pairing/recovery.",
|
|
14
|
+
"es": "El log de debug ahora traza cada llamada HTTPS de la API y el ciclo de vida de los estados — diagnóstico más fácil para bouncing crónico o problemas de pairing/recovery.",
|
|
15
|
+
"pl": "Log debug śledzi teraz każde wywołanie HTTPS API i cykl życia stanów urządzenia — łatwiejsza diagnostyka chronicznego bouncingu lub problemów z pairing/recovery.",
|
|
16
|
+
"uk": "Debug-лог тепер трасує кожен HTTPS-API-виклик та життєвий цикл станів пристрою — простіша діагностика хронічного bouncing або проблем pairing/recovery.",
|
|
17
|
+
"zh-cn": "调试日志现在跟踪每个 HTTPS API 调用和设备状态生命周期 — 更容易诊断持续断线或配对/恢复问题。"
|
|
18
|
+
},
|
|
6
19
|
"0.7.7": {
|
|
7
20
|
"en": "Devices with chronic WiFi drop-outs no longer flood the log — max 1 warn/h per device, full timeline at debug level. Internal mode-switches moved to debug.",
|
|
8
21
|
"de": "Geräte mit dauerhaft schlechtem WLAN spammen das Log nicht mehr — max 1 Warnung pro Stunde pro Gerät, voller Verlauf im Debug. Interne Modus-Wechsel auf Debug verschoben.",
|
|
@@ -80,19 +93,6 @@
|
|
|
80
93
|
"pl": "Wewnętrzne wzmocnienie: ściślejsze sprawdzanie liczb, równoległe zapisy stanów, podział kodu, 38 nowych testów klienta HTTPS. Brak zmian dla użytkownika.",
|
|
81
94
|
"uk": "Внутрішнє зміцнення: строгіша перевірка чисел для сенсорів, паралельні записи станів, розділення коду для тестованості, 38 нових тестів HTTPS-клієнта. Без видимих змін.",
|
|
82
95
|
"zh-cn": "内部加固:传感器数值校验更严格、状态写入并行化、代码拆分以提升可测试性,HTTPS 客户端新增 38 个测试。对用户无可见变化。"
|
|
83
|
-
},
|
|
84
|
-
"0.7.1": {
|
|
85
|
-
"en": "Performance: state existence checks are cached after first creation — saves ~30 redis lookups per second on a P1 meter. WiFi signal unit corrected to dBm.",
|
|
86
|
-
"de": "Performance: Existenz-Prüfung für Datenpunkte wird nach dem ersten Anlegen gecacht — spart ca. 30 Redis-Lookups pro Sekunde bei einem P1-Meter. WLAN-Signalstärke jetzt in dBm.",
|
|
87
|
-
"ru": "Производительность: проверка существования датчиков кэшируется после первого создания — экономит около 30 redis-запросов в секунду на P1-счётчике. Единица WiFi-сигнала dBm.",
|
|
88
|
-
"pt": "Desempenho: verificação de existência de datapoints é cacheada após a primeira criação — poupa ~30 lookups redis por segundo num P1 Meter. Unidade do sinal WiFi em dBm.",
|
|
89
|
-
"nl": "Performance: bestaanscheck voor datapoints wordt na het eerste aanmaken gecachet — bespaart ~30 redis-lookups per seconde op een P1 Meter. WiFi-signaaleenheid nu dBm.",
|
|
90
|
-
"fr": "Performance : la vérification d'existence des datapoints est mise en cache après la première création — économise ~30 lookups redis par seconde sur un P1 Meter. Unité du signal WiFi en dBm.",
|
|
91
|
-
"it": "Performance: la verifica di esistenza dei datapoint viene memorizzata in cache dopo la prima creazione — risparmia ~30 lookup redis al secondo su un P1 Meter. Unità segnale WiFi ora dBm.",
|
|
92
|
-
"es": "Rendimiento: la comprobación de existencia de datapoints se guarda en caché tras la primera creación — ahorra ~30 lookups redis por segundo en un P1 Meter. Unidad de señal WiFi en dBm.",
|
|
93
|
-
"pl": "Wydajność: sprawdzanie istnienia datapointów jest cache'owane po pierwszym utworzeniu — oszczędza ~30 zapytań redis na sekundę przy P1 Meter. Jednostka sygnału WiFi to dBm.",
|
|
94
|
-
"uk": "Продуктивність: перевірка існування датапоінтів кешується після першого створення — економить ~30 redis-запитів на секунду на P1 Meter. Одиниця сигналу WiFi — dBm.",
|
|
95
|
-
"zh-cn": "性能:数据点存在检查在首次创建后会缓存——P1 Meter 每秒节省约 30 次 Redis 查询。WiFi 信号单位修正为 dBm。"
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
98
|
"titleLang": {
|