iobroker.homewizard 0.7.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -94,7 +94,7 @@ homewizard.0.
94
94
  │ ├── productType — Product type (string)
95
95
  │ ├── firmware — Firmware version (string)
96
96
  │ ├── connected — WebSocket connection status (bool)
97
- │ ├── wifi_rssi_db — WiFi signal strength (number, dB)
97
+ │ ├── wifi_rssi_db — WiFi signal strength (number, dBm)
98
98
  │ └── uptime_s — Device uptime (number, s)
99
99
  ├── measurement/ — Measurement data
100
100
  │ ├── power_w — Total power (number, W)
@@ -167,16 +167,18 @@ homewizard.0.
167
167
  ---
168
168
 
169
169
  ## Changelog
170
+ ### 0.7.2 (2026-05-06)
171
+ - Internal hardening: stricter number parsing for sensor inputs, parallel state writes, code split for testability, 38 new tests covering the HTTPS client. No user-facing changes.
172
+
173
+ ### 0.7.1 (2026-05-06)
174
+ - WiFi signal strength is now reported in dBm (was incorrectly labelled `dB`).
175
+ - Faster state updates: existence checks for datapoints are cached after first creation, saving ~30 Redis lookups per second on a P1 Meter pushing 1 measurement/second.
176
+
170
177
  ### 0.7.0 (2026-05-06)
171
- - Multi-language: state names, descriptions, dropdown labels and user-facing logs follow the ioBroker system language across 11 languages (EN, DE, RU, PT, NL, FR, IT, ES, PL, UK, ZH-CN).
172
- - `tariff` and `battery.mode` show translated dropdown labels in admin/vis instead of raw values; power-quality and Belgian capacity-tariff states now show inline descriptions (admin tooltips).
173
- - Baseline bumped to Node 22 and ioBroker Admin 7.8.23 (May-2026 platform requirement).
174
- - Robustness: `errText` helper consolidates 5 inline catch-site duplicates across `main.ts`.
175
- - `battery.permissions` parses JSON in a guarded path with explicit array check — malformed input now becomes a clean warning instead of a cryptic stack trace.
176
- - `battery.mode` whitelist-validates before the API call (`zero` / `to_full` / `standby`); other values are warned and dropped.
177
- - `handleAuthFailure` helper extracted; duplicate auth-stop branch in WS-disconnect and REST-fallback paths consolidated.
178
- - Deploy workflow runs on Node 24 (Node 22 + npm@latest installer triggers `MODULE_NOT_FOUND` for `promise-retry`).
179
- - New `coerce.test.ts` with 22 tests covering `errText`, `validateBatteryMode`, `parseBatteryPermissions` and the existing coerce helpers.
178
+ - Adapter texts now follow your ioBroker system language: datapoint names, descriptions, dropdown values for `tariff` and `battery.mode`, and user-visible logs in 11 languages (EN, DE, RU, PT, NL, FR, IT, ES, PL, UK, ZH-CN).
179
+ - Power-quality and Belgian capacity-tariff datapoints carry inline descriptions — hover in admin to see what each one means.
180
+ - Battery inputs are checked up-front: an unknown `battery.mode` or malformed `battery.permissions` JSON gives a clear warning instead of a cryptic error.
181
+ - Minimum requirements: Node.js 22 and ioBroker Admin 7.8.23.
180
182
 
181
183
  ### 0.6.7 (2026-05-01)
182
184
  - Internal cleanup. No user-facing changes.
@@ -185,20 +187,6 @@ homewizard.0.
185
187
  ### 0.6.6 (2026-04-28)
186
188
  - Internal cleanup. No user-facing changes.
187
189
 
188
- ### 0.6.5 (2026-04-26)
189
- - Crash defense: process-level error handlers.
190
- - Min `js-controller` restored to `>=6.0.11` (was incorrectly `>=7.0.0`).
191
-
192
- ### 0.6.4 (2026-04-23)
193
- - Internal hardening. No user-facing changes.
194
-
195
- Older entries are in [CHANGELOG_OLD.md](CHANGELOG_OLD.md).
196
-
197
- ## Support
198
-
199
- - [ioBroker Forum](https://forum.iobroker.net/)
200
- - [GitHub Issues](https://github.com/krobipd/ioBroker.homewizard/issues)
201
-
202
190
  ### Support Development
203
191
 
204
192
  This adapter is free and open source. If you find it useful, consider buying me a coffee:
@@ -208,6 +196,8 @@ This adapter is free and open source. If you find it useful, consider buying me
208
196
 
209
197
  ---
210
198
 
199
+ Older entries are in [CHANGELOG_OLD.md](CHANGELOG_OLD.md).
200
+
211
201
  ## License
212
202
 
213
203
  MIT License
@@ -28,11 +28,12 @@ __export(coerce_exports, {
28
28
  validateBatteryMode: () => validateBatteryMode
29
29
  });
30
30
  module.exports = __toCommonJS(coerce_exports);
31
+ const DECIMAL_NUMBER_RE = /^-?\d+(\.\d+)?$/;
31
32
  function coerceFiniteNumber(value) {
32
33
  if (typeof value === "number") {
33
34
  return Number.isFinite(value) ? value : null;
34
35
  }
35
- if (typeof value === "string" && value.length > 0) {
36
+ if (typeof value === "string" && DECIMAL_NUMBER_RE.test(value)) {
36
37
  const n = Number(value);
37
38
  return Number.isFinite(n) ? n : null;
38
39
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/coerce.ts"],
4
- "sourcesContent": ["/**\n * Boundary coercion helpers for external API data (REST + WebSocket).\n * HomeWizard API v2 is well-documented but field types still drift in practice\n * (firmware bugs, future additions, null values). These helpers guard against\n * NaN/Infinity/non-string values reaching ioBroker states.\n */\n\n/**\n * Coerce to a finite number or null.\n * Accepts numbers directly; parses numeric strings; rejects NaN/Infinity/other.\n *\n * @param value Unknown external value\n */\nexport function coerceFiniteNumber(value: unknown): number | null {\n if (typeof value === \"number\") {\n return Number.isFinite(value) ? value : null;\n }\n if (typeof value === \"string\" && value.length > 0) {\n const n = Number(value);\n return Number.isFinite(n) ? n : null;\n }\n return null;\n}\n\n/**\n * Coerce to a non-empty string, or null.\n *\n * @param value Unknown external value\n */\nexport function coerceString(value: unknown): string | null {\n if (typeof value === \"string\" && value.length > 0) {\n return value;\n }\n return null;\n}\n\n/**\n * Coerce to a boolean (only `true`/`false` accepted \u2014 no truthy/falsy JS rules).\n *\n * @param value Unknown external value\n */\nexport function coerceBoolean(value: unknown): boolean | null {\n if (typeof value === \"boolean\") {\n return value;\n }\n return null;\n}\n\n/**\n * Guard for plain objects (not arrays, not null).\n *\n * @param value Unknown external value\n */\nexport function isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/** Allowed values for `battery.mode` per HomeWizard API v2. */\nexport const BATTERY_MODES = [\"zero\", \"to_full\", \"standby\"] as const;\nexport type BatteryMode = (typeof BATTERY_MODES)[number];\n\n/**\n * Validate user input for `battery.mode` against the API-allowed enum. Returns\n * the typed mode on success, or `null` if the input is not in the whitelist.\n *\n * @param value Raw user input (`String(state.val)`).\n */\nexport function validateBatteryMode(value: unknown): BatteryMode | null {\n return typeof value === \"string\" && (BATTERY_MODES as readonly string[]).includes(value)\n ? (value as BatteryMode)\n : null;\n}\n\n/** Outcome of {@link parseBatteryPermissions} \u2014 either a parsed array or a diagnostic. */\nexport type BatteryPermissionsResult = { ok: true; perms: string[] } | { ok: false; reason: string; sample: string };\n\n/**\n * Parse a JSON string for `battery.permissions`. Expected shape: `string[]`.\n * Wraps `JSON.parse` so a malformed user input becomes a typed warning instead\n * of a thrown exception, and rejects non-array results explicitly.\n *\n * @param raw Raw user input (`String(state.val)`).\n */\nexport function parseBatteryPermissions(raw: string): BatteryPermissionsResult {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n return { ok: false, reason: errText(err), sample: raw.slice(0, 200) };\n }\n if (!Array.isArray(parsed)) {\n return { ok: false, reason: \"expected JSON array\", sample: raw.slice(0, 200) };\n }\n // permissions are documented as string array \u2014 coerce defensively.\n const perms: string[] = [];\n for (const item of parsed) {\n if (typeof item !== \"string\") {\n return {\n ok: false,\n reason: `non-string entry: ${typeof item}`,\n sample: raw.slice(0, 200),\n };\n }\n perms.push(item);\n }\n return { ok: true, perms };\n}\n\n/**\n * Extract a log-friendly message from a thrown / rejected value. Centralizes the\n * `err instanceof Error ? err.message : String(err)` pattern that otherwise\n * gets repeated at every catch-site. Plain objects are JSON-stringified so a\n * `[object Object]` log is avoided when adapters throw bag-of-fields.\n *\n * @param err Caught value of unknown shape (Error, string, undefined, ...).\n */\nexport function errText(err: unknown): string {\n if (err instanceof Error) {\n return err.message;\n }\n if (err === null) {\n return \"null\";\n }\n if (err === undefined) {\n return \"undefined\";\n }\n if (typeof err === \"string\") {\n return err;\n }\n if (typeof err === \"number\" || typeof err === \"boolean\" || typeof err === \"bigint\") {\n return String(err);\n }\n // Plain objects + symbols would otherwise stringify to \"[object Object]\" / fail.\n // Prefer JSON for the common case so the log is at least diagnosable.\n try {\n return JSON.stringify(err);\n } catch {\n return Object.prototype.toString.call(err);\n }\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaO,SAAS,mBAAmB,OAA+B;AAChE,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;AAAA,EAC1C;AACA,MAAI,OAAO,UAAU,YAAY,MAAM,SAAS,GAAG;AACjD,UAAM,IAAI,OAAO,KAAK;AACtB,WAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAAA,EAClC;AACA,SAAO;AACT;AAOO,SAAS,aAAa,OAA+B;AAC1D,MAAI,OAAO,UAAU,YAAY,MAAM,SAAS,GAAG;AACjD,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAOO,SAAS,cAAc,OAAgC;AAC5D,MAAI,OAAO,UAAU,WAAW;AAC9B,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAOO,SAAS,cAAc,OAAkD;AAC9E,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAGO,MAAM,gBAAgB,CAAC,QAAQ,WAAW,SAAS;AASnD,SAAS,oBAAoB,OAAoC;AACtE,SAAO,OAAO,UAAU,YAAa,cAAoC,SAAS,KAAK,IAClF,QACD;AACN;AAYO,SAAS,wBAAwB,KAAuC;AAC7E,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,GAAG;AAAA,EACzB,SAAS,KAAK;AACZ,WAAO,EAAE,IAAI,OAAO,QAAQ,QAAQ,GAAG,GAAG,QAAQ,IAAI,MAAM,GAAG,GAAG,EAAE;AAAA,EACtE;AACA,MAAI,CAAC,MAAM,QAAQ,MAAM,GAAG;AAC1B,WAAO,EAAE,IAAI,OAAO,QAAQ,uBAAuB,QAAQ,IAAI,MAAM,GAAG,GAAG,EAAE;AAAA,EAC/E;AAEA,QAAM,QAAkB,CAAC;AACzB,aAAW,QAAQ,QAAQ;AACzB,QAAI,OAAO,SAAS,UAAU;AAC5B,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,QAAQ,qBAAqB,OAAO,IAAI;AAAA,QACxC,QAAQ,IAAI,MAAM,GAAG,GAAG;AAAA,MAC1B;AAAA,IACF;AACA,UAAM,KAAK,IAAI;AAAA,EACjB;AACA,SAAO,EAAE,IAAI,MAAM,MAAM;AAC3B;AAUO,SAAS,QAAQ,KAAsB;AAC5C,MAAI,eAAe,OAAO;AACxB,WAAO,IAAI;AAAA,EACb;AACA,MAAI,QAAQ,MAAM;AAChB,WAAO;AAAA,EACT;AACA,MAAI,QAAQ,QAAW;AACrB,WAAO;AAAA,EACT;AACA,MAAI,OAAO,QAAQ,UAAU;AAC3B,WAAO;AAAA,EACT;AACA,MAAI,OAAO,QAAQ,YAAY,OAAO,QAAQ,aAAa,OAAO,QAAQ,UAAU;AAClF,WAAO,OAAO,GAAG;AAAA,EACnB;AAGA,MAAI;AACF,WAAO,KAAK,UAAU,GAAG;AAAA,EAC3B,QAAQ;AACN,WAAO,OAAO,UAAU,SAAS,KAAK,GAAG;AAAA,EAC3C;AACF;",
4
+ "sourcesContent": ["/**\n * Boundary coercion helpers for external API data (REST + WebSocket).\n * HomeWizard API v2 is well-documented but field types still drift in practice\n * (firmware bugs, future additions, null values). These helpers guard against\n * NaN/Infinity/non-string values reaching ioBroker states.\n */\n\n// Strict decimal regex \u2014 only optional minus sign + digits + optional fractional part.\n// Rejects HEX (`0x...`), exponential (`1e3`), Infinity, NaN, leading/trailing whitespace.\n// hassemu (E8 in v1.9.0) hardened the same coerce-helper this way; consistency item D8.\nconst DECIMAL_NUMBER_RE = /^-?\\d+(\\.\\d+)?$/;\n\n/**\n * Coerce to a finite number or null.\n * Accepts numbers directly; parses strict decimal strings; rejects NaN, Infinity,\n * HEX (`0x...`) and exponential notation (`1e3`).\n *\n * @param value Unknown external value\n */\nexport function coerceFiniteNumber(value: unknown): number | null {\n if (typeof value === \"number\") {\n return Number.isFinite(value) ? value : null;\n }\n if (typeof value === \"string\" && DECIMAL_NUMBER_RE.test(value)) {\n const n = Number(value);\n return Number.isFinite(n) ? n : null;\n }\n return null;\n}\n\n/**\n * Coerce to a non-empty string, or null.\n *\n * @param value Unknown external value\n */\nexport function coerceString(value: unknown): string | null {\n if (typeof value === \"string\" && value.length > 0) {\n return value;\n }\n return null;\n}\n\n/**\n * Coerce to a boolean (only `true`/`false` accepted \u2014 no truthy/falsy JS rules).\n *\n * @param value Unknown external value\n */\nexport function coerceBoolean(value: unknown): boolean | null {\n if (typeof value === \"boolean\") {\n return value;\n }\n return null;\n}\n\n/**\n * Guard for plain objects (not arrays, not null).\n *\n * @param value Unknown external value\n */\nexport function isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/** Allowed values for `battery.mode` per HomeWizard API v2. */\nexport const BATTERY_MODES = [\"zero\", \"to_full\", \"standby\"] as const;\nexport type BatteryMode = (typeof BATTERY_MODES)[number];\n\n/**\n * Validate user input for `battery.mode` against the API-allowed enum. Returns\n * the typed mode on success, or `null` if the input is not in the whitelist.\n *\n * @param value Raw user input (`String(state.val)`).\n */\nexport function validateBatteryMode(value: unknown): BatteryMode | null {\n return typeof value === \"string\" && (BATTERY_MODES as readonly string[]).includes(value)\n ? (value as BatteryMode)\n : null;\n}\n\n/** Outcome of {@link parseBatteryPermissions} \u2014 either a parsed array or a diagnostic. */\nexport type BatteryPermissionsResult = { ok: true; perms: string[] } | { ok: false; reason: string; sample: string };\n\n/**\n * Parse a JSON string for `battery.permissions`. Expected shape: `string[]`.\n * Wraps `JSON.parse` so a malformed user input becomes a typed warning instead\n * of a thrown exception, and rejects non-array results explicitly.\n *\n * @param raw Raw user input (`String(state.val)`).\n */\nexport function parseBatteryPermissions(raw: string): BatteryPermissionsResult {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n return { ok: false, reason: errText(err), sample: raw.slice(0, 200) };\n }\n if (!Array.isArray(parsed)) {\n return { ok: false, reason: \"expected JSON array\", sample: raw.slice(0, 200) };\n }\n // permissions are documented as string array \u2014 coerce defensively.\n const perms: string[] = [];\n for (const item of parsed) {\n if (typeof item !== \"string\") {\n return {\n ok: false,\n reason: `non-string entry: ${typeof item}`,\n sample: raw.slice(0, 200),\n };\n }\n perms.push(item);\n }\n return { ok: true, perms };\n}\n\n/**\n * Extract a log-friendly message from a thrown / rejected value. Centralizes the\n * `err instanceof Error ? err.message : String(err)` pattern that otherwise\n * gets repeated at every catch-site. Plain objects are JSON-stringified so a\n * `[object Object]` log is avoided when adapters throw bag-of-fields.\n *\n * @param err Caught value of unknown shape (Error, string, undefined, ...).\n */\nexport function errText(err: unknown): string {\n if (err instanceof Error) {\n return err.message;\n }\n if (err === null) {\n return \"null\";\n }\n if (err === undefined) {\n return \"undefined\";\n }\n if (typeof err === \"string\") {\n return err;\n }\n if (typeof err === \"number\" || typeof err === \"boolean\" || typeof err === \"bigint\") {\n return String(err);\n }\n // Plain objects + symbols would otherwise stringify to \"[object Object]\" / fail.\n // Prefer JSON for the common case so the log is at least diagnosable.\n try {\n return JSON.stringify(err);\n } catch {\n return Object.prototype.toString.call(err);\n }\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUA,MAAM,oBAAoB;AASnB,SAAS,mBAAmB,OAA+B;AAChE,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;AAAA,EAC1C;AACA,MAAI,OAAO,UAAU,YAAY,kBAAkB,KAAK,KAAK,GAAG;AAC9D,UAAM,IAAI,OAAO,KAAK;AACtB,WAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAAA,EAClC;AACA,SAAO;AACT;AAOO,SAAS,aAAa,OAA+B;AAC1D,MAAI,OAAO,UAAU,YAAY,MAAM,SAAS,GAAG;AACjD,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAOO,SAAS,cAAc,OAAgC;AAC5D,MAAI,OAAO,UAAU,WAAW;AAC9B,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAOO,SAAS,cAAc,OAAkD;AAC9E,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAGO,MAAM,gBAAgB,CAAC,QAAQ,WAAW,SAAS;AASnD,SAAS,oBAAoB,OAAoC;AACtE,SAAO,OAAO,UAAU,YAAa,cAAoC,SAAS,KAAK,IAClF,QACD;AACN;AAYO,SAAS,wBAAwB,KAAuC;AAC7E,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,GAAG;AAAA,EACzB,SAAS,KAAK;AACZ,WAAO,EAAE,IAAI,OAAO,QAAQ,QAAQ,GAAG,GAAG,QAAQ,IAAI,MAAM,GAAG,GAAG,EAAE;AAAA,EACtE;AACA,MAAI,CAAC,MAAM,QAAQ,MAAM,GAAG;AAC1B,WAAO,EAAE,IAAI,OAAO,QAAQ,uBAAuB,QAAQ,IAAI,MAAM,GAAG,GAAG,EAAE;AAAA,EAC/E;AAEA,QAAM,QAAkB,CAAC;AACzB,aAAW,QAAQ,QAAQ;AACzB,QAAI,OAAO,SAAS,UAAU;AAC5B,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,QAAQ,qBAAqB,OAAO,IAAI;AAAA,QACxC,QAAQ,IAAI,MAAM,GAAG,GAAG;AAAA,MAC1B;AAAA,IACF;AACA,UAAM,KAAK,IAAI;AAAA,EACjB;AACA,SAAO,EAAE,IAAI,MAAM,MAAM;AAC3B;AAUO,SAAS,QAAQ,KAAsB;AAC5C,MAAI,eAAe,OAAO;AACxB,WAAO,IAAI;AAAA,EACb;AACA,MAAI,QAAQ,MAAM;AAChB,WAAO;AAAA,EACT;AACA,MAAI,QAAQ,QAAW;AACrB,WAAO;AAAA,EACT;AACA,MAAI,OAAO,QAAQ,UAAU;AAC3B,WAAO;AAAA,EACT;AACA,MAAI,OAAO,QAAQ,YAAY,OAAO,QAAQ,aAAa,OAAO,QAAQ,UAAU;AAClF,WAAO,OAAO,GAAG;AAAA,EACnB;AAGA,MAAI;AACF,WAAO,KAAK,UAAU,GAAG;AAAA,EAC3B,QAAQ;AACN,WAAO,OAAO,UAAU,SAAS,KAAK,GAAG;AAAA,EAC3C;AACF;",
6
6
  "names": []
7
7
  }
@@ -37,13 +37,22 @@ var import_cacert = require("./cacert");
37
37
  class HomeWizardClient {
38
38
  ip;
39
39
  token;
40
+ agent;
41
+ /** Override target port — only used by tests against a local stub-server. */
42
+ port;
40
43
  /**
41
- * @param ip Device IP address
42
- * @param token Bearer token (empty string for pairing requests)
44
+ * @param ip Device IP address
45
+ * @param token Bearer token (empty string for pairing requests)
46
+ * @param options Optional overrides — primarily for unit tests against a local TLS stub.
47
+ * @param options.agent HTTPS agent to use; defaults to {@link HW_AGENT} (with HomeWizard CA pinning).
48
+ * @param options.port Target port; defaults to 443.
43
49
  */
44
- constructor(ip, token = "") {
50
+ constructor(ip, token = "", options = {}) {
51
+ var _a, _b;
45
52
  this.ip = ip;
46
53
  this.token = token;
54
+ this.agent = (_a = options.agent) != null ? _a : import_cacert.HW_AGENT;
55
+ this.port = (_b = options.port) != null ? _b : 443;
47
56
  }
48
57
  /** Get device info (GET /api) */
49
58
  async getDeviceInfo() {
@@ -112,11 +121,11 @@ class HomeWizardClient {
112
121
  const req = https.request(
113
122
  {
114
123
  hostname: this.ip,
115
- port: 443,
124
+ port: this.port,
116
125
  path,
117
126
  method,
118
127
  headers,
119
- agent: import_cacert.HW_AGENT,
128
+ agent: this.agent,
120
129
  timeout: 1e4
121
130
  },
122
131
  (res) => {
@@ -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\n /**\n * @param ip Device IP address\n * @param token Bearer token (empty string for pairing requests)\n */\n constructor(ip: string, token: string = \"\") {\n this.ip = ip;\n this.token = token;\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 return this.request<PairingResponse>(\"POST\", \"/api/user\", {\n name: \"local/iobroker\",\n });\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 const req = https.request(\n {\n hostname: this.ip,\n port: 443,\n path,\n method,\n headers,\n agent: HW_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 if (!res.statusCode || res.statusCode >= 400) {\n const error = new HomeWizardApiError(res.statusCode ?? 0, data, `${method} ${path}`);\n reject(error);\n return;\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\", reject);\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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",
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, token: string = \"\", options: { agent?: https.Agent; port?: number } = {}) {\n this.ip = ip;\n this.token = token;\n this.agent = options.agent ?? HW_AGENT;\n this.port = options.port ?? 443;\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 return this.request<PairingResponse>(\"POST\", \"/api/user\", {\n name: \"local/iobroker\",\n });\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 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 if (!res.statusCode || res.statusCode >= 400) {\n const error = new HomeWizardApiError(res.statusCode ?? 0, data, `${method} ${path}`);\n reject(error);\n return;\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\", reject);\n req.on(\"timeout\", () => {\n req.destroy(new Error(`Timeout: ${method} ${path}`));\n });\n\n if (bodyStr) {\n req.write(bodyStr);\n }\n req.end();\n });\n }\n}\n\n/** API error with status code and parsed error body */\nexport class HomeWizardApiError extends Error {\n readonly statusCode: number;\n readonly errorCode: string;\n\n /**\n * @param statusCode HTTP status code\n * @param body Response body\n * @param context Request context for error message\n */\n constructor(statusCode: number, body: string, context: string) {\n let errorCode = \"unknown\";\n let description = body;\n try {\n const parsed = JSON.parse(body);\n errorCode = parsed.error?.code ?? parsed.error ?? \"unknown\";\n description = parsed.error?.description ?? parsed.error?.code ?? body;\n } catch {\n // body is not JSON\n }\n super(`${context}: HTTP ${statusCode} \u2014 ${description}`);\n this.name = \"HomeWizardApiError\";\n this.statusCode = statusCode;\n this.errorCode = errorCode;\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var main_helpers_exports = {};
20
+ __export(main_helpers_exports, {
21
+ computeReconnectDelay: () => computeReconnectDelay,
22
+ decideUnstableTransition: () => decideUnstableTransition,
23
+ findConnectionForState: () => findConnectionForState,
24
+ pickRestPollInterval: () => pickRestPollInterval,
25
+ shouldStartIpRecovery: () => shouldStartIpRecovery,
26
+ stripNamespace: () => stripNamespace
27
+ });
28
+ module.exports = __toCommonJS(main_helpers_exports);
29
+ function decideUnstableTransition(prevDisconnects, durationMs, stableThresholdMs, unstableThreshold) {
30
+ if (durationMs < stableThresholdMs) {
31
+ const next = prevDisconnects + 1;
32
+ return next === unstableThreshold ? "becameUnstable" : "noChange";
33
+ }
34
+ return prevDisconnects >= unstableThreshold ? "stabilized" : "noChange";
35
+ }
36
+ function computeReconnectDelay(failCount, baseMs, maxMs) {
37
+ if (failCount <= 0) {
38
+ return baseMs;
39
+ }
40
+ return Math.min(baseMs * Math.pow(2, failCount - 1), maxMs);
41
+ }
42
+ function shouldStartIpRecovery(failCount, beforeMdns, retryEvery) {
43
+ if (failCount < beforeMdns) {
44
+ return false;
45
+ }
46
+ return (failCount - beforeMdns) % retryEvery === 0;
47
+ }
48
+ function pickRestPollInterval(unstable, stableIntervalMs, unstableIntervalMs) {
49
+ return unstable ? unstableIntervalMs : stableIntervalMs;
50
+ }
51
+ function stripNamespace(stateId, namespace) {
52
+ const prefix = `${namespace}.`;
53
+ return stateId.startsWith(prefix) ? stateId.slice(prefix.length) : stateId;
54
+ }
55
+ function findConnectionForState(stateId, namespace, connections) {
56
+ const localId = stripNamespace(stateId, namespace);
57
+ for (const [prefix, conn] of connections) {
58
+ if (localId.startsWith(`${prefix}.`)) {
59
+ return conn;
60
+ }
61
+ }
62
+ return void 0;
63
+ }
64
+ // Annotate the CommonJS export names for ESM import in node:
65
+ 0 && (module.exports = {
66
+ computeReconnectDelay,
67
+ decideUnstableTransition,
68
+ findConnectionForState,
69
+ pickRestPollInterval,
70
+ shouldStartIpRecovery,
71
+ stripNamespace
72
+ });
73
+ //# sourceMappingURL=main-helpers.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/lib/main-helpers.ts"],
4
+ "sourcesContent": ["/**\n * Pure decision helpers for the adapter lifecycle. Extracted from `main.ts` so\n * the math/branching can be unit-tested without spinning up an adapter mock.\n *\n * Nothing in here touches `this.adapter` / `this.log` / timers \u2014 caller wires\n * the result back to the actual side-effects.\n */\n\nimport type { DeviceConnection } from \"./types\";\n\n/** Outcome of {@link decideUnstableTransition}. */\nexport type UnstableTransition = \"becameUnstable\" | \"stabilized\" | \"noChange\";\n\n/**\n * Decide whether a device just crossed into \"unstable\" mode (too many short\n * connections in a row) or back to \"stable\". Pure function over the connection\n * counters \u2014 caller updates `recentDisconnects` and emits the info-log.\n *\n * @param prevDisconnects `conn.recentDisconnects` before this disconnect.\n * @param durationMs How long the connection lived this time (`now - lastConnectedAt`).\n * @param stableThresholdMs `STABLE_THRESHOLD_MS` constant.\n * @param unstableThreshold `UNSTABLE_DISCONNECT_THRESHOLD` constant.\n */\nexport function decideUnstableTransition(\n prevDisconnects: number,\n durationMs: number,\n stableThresholdMs: number,\n unstableThreshold: number,\n): UnstableTransition {\n if (durationMs < stableThresholdMs) {\n // Disconnect happened within the stable window \u2192 counter goes up.\n const next = prevDisconnects + 1;\n return next === unstableThreshold ? \"becameUnstable\" : \"noChange\";\n }\n // Connection survived the stable window \u2192 counter resets.\n return prevDisconnects >= unstableThreshold ? \"stabilized\" : \"noChange\";\n}\n\n/**\n * Compute exponential-backoff delay for the next WebSocket reconnect attempt.\n *\n * @param failCount Consecutive failures (`conn.wsFailCount`, already incremented for this attempt).\n * @param baseMs `WS_RECONNECT_BASE_MS`.\n * @param maxMs `WS_RECONNECT_MAX_MS` (stable) or `WS_RECONNECT_MAX_UNSTABLE_MS`.\n */\nexport function computeReconnectDelay(failCount: number, baseMs: number, maxMs: number): number {\n if (failCount <= 0) {\n return baseMs;\n }\n return Math.min(baseMs * Math.pow(2, failCount - 1), maxMs);\n}\n\n/**\n * Decide whether mDNS IP-recovery should kick off on this WS-failure tick.\n * After `beforeMdns` failures, recovery runs once; thereafter it retries every\n * `retryEvery` failures (~hourly given the 5-minute cap).\n *\n * @param failCount Consecutive failures (post-increment).\n * @param beforeMdns `WS_FAILURES_BEFORE_MDNS`.\n * @param retryEvery `MDNS_RETRY_EVERY`.\n */\nexport function shouldStartIpRecovery(failCount: number, beforeMdns: number, retryEvery: number): boolean {\n if (failCount < beforeMdns) {\n return false;\n }\n return (failCount - beforeMdns) % retryEvery === 0;\n}\n\n/**\n * Pick the REST-fallback poll interval based on connection stability. Unstable\n * devices poll faster to keep the data flowing while WS reconnect is still\n * trying.\n *\n * @param unstable Whether the device is currently in unstable mode.\n * @param stableIntervalMs `REST_POLL_MS`.\n * @param unstableIntervalMs `REST_POLL_UNSTABLE_MS`.\n */\nexport function pickRestPollInterval(unstable: boolean, stableIntervalMs: number, unstableIntervalMs: number): number {\n return unstable ? unstableIntervalMs : stableIntervalMs;\n}\n\n/**\n * Strip the adapter namespace prefix from a state-ID. Only strips when the\n * prefix matches at the start \u2014 defensive against unexpected IDs.\n *\n * @param stateId Full state-ID (`<namespace>.<localId>`).\n * @param namespace Adapter namespace (e.g. `homewizard.0`).\n */\nexport function stripNamespace(stateId: string, namespace: string): string {\n const prefix = `${namespace}.`;\n return stateId.startsWith(prefix) ? stateId.slice(prefix.length) : stateId;\n}\n\n/**\n * Find the device connection that owns a given state-ID. Pure linear lookup\n * over the connection map \u2014 fine for the typical 1\u201310 paired devices.\n *\n * @param stateId Full state-ID written by the user.\n * @param namespace Adapter namespace.\n * @param connections Iterable of `(prefix, connection)` pairs (`map.entries()`-shape).\n */\nexport function findConnectionForState<T extends DeviceConnection>(\n stateId: string,\n namespace: string,\n connections: Iterable<[string, T]>,\n): T | undefined {\n const localId = stripNamespace(stateId, namespace);\n for (const [prefix, conn] of connections) {\n if (localId.startsWith(`${prefix}.`)) {\n return conn;\n }\n }\n return undefined;\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAuBO,SAAS,yBACd,iBACA,YACA,mBACA,mBACoB;AACpB,MAAI,aAAa,mBAAmB;AAElC,UAAM,OAAO,kBAAkB;AAC/B,WAAO,SAAS,oBAAoB,mBAAmB;AAAA,EACzD;AAEA,SAAO,mBAAmB,oBAAoB,eAAe;AAC/D;AASO,SAAS,sBAAsB,WAAmB,QAAgB,OAAuB;AAC9F,MAAI,aAAa,GAAG;AAClB,WAAO;AAAA,EACT;AACA,SAAO,KAAK,IAAI,SAAS,KAAK,IAAI,GAAG,YAAY,CAAC,GAAG,KAAK;AAC5D;AAWO,SAAS,sBAAsB,WAAmB,YAAoB,YAA6B;AACxG,MAAI,YAAY,YAAY;AAC1B,WAAO;AAAA,EACT;AACA,UAAQ,YAAY,cAAc,eAAe;AACnD;AAWO,SAAS,qBAAqB,UAAmB,kBAA0B,oBAAoC;AACpH,SAAO,WAAW,qBAAqB;AACzC;AASO,SAAS,eAAe,SAAiB,WAA2B;AACzE,QAAM,SAAS,GAAG,SAAS;AAC3B,SAAO,QAAQ,WAAW,MAAM,IAAI,QAAQ,MAAM,OAAO,MAAM,IAAI;AACrE;AAUO,SAAS,uBACd,SACA,WACA,aACe;AACf,QAAM,UAAU,eAAe,SAAS,SAAS;AACjD,aAAW,CAAC,QAAQ,IAAI,KAAK,aAAa;AACxC,QAAI,QAAQ,WAAW,GAAG,MAAM,GAAG,GAAG;AACpC,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;",
6
+ "names": []
7
+ }
@@ -415,6 +415,14 @@ function batteryModeStates() {
415
415
  }
416
416
  class StateManager {
417
417
  adapter;
418
+ /**
419
+ * Cache of state / channel IDs that have already passed
420
+ * `setObjectNotExistsAsync`. Skips repeat DB lookups on the hot path —
421
+ * a P1 meter pushes ~1 measurement/s with up to ~30 active fields, which
422
+ * otherwise meant ~30 Redis lookups per second just to ask „does it
423
+ * exist". On `removeDevice(prefix)` all `prefix.*` IDs are dropped.
424
+ */
425
+ createdIds = /* @__PURE__ */ new Set();
418
426
  /** @param adapter The ioBroker adapter instance */
419
427
  constructor(adapter) {
420
428
  this.adapter = adapter;
@@ -445,7 +453,7 @@ class StateManager {
445
453
  await this.createState(`${prefix}.info.productType`, (0, import_i18n_states.tName)("productType"), "string", "text", false);
446
454
  await this.createState(`${prefix}.info.firmware`, (0, import_i18n_states.tName)("firmware"), "string", "text", false);
447
455
  await this.createState(`${prefix}.info.connected`, (0, import_i18n_states.tName)("connected"), "boolean", "indicator.reachable", false);
448
- await this.createState(`${prefix}.info.wifi_rssi_db`, (0, import_i18n_states.tName)("wifiRssi"), "number", "value", false, "dB");
456
+ await this.createState(`${prefix}.info.wifi_rssi_db`, (0, import_i18n_states.tName)("wifiRssi"), "number", "value", false, "dBm");
449
457
  await this.createState(`${prefix}.info.uptime_s`, (0, import_i18n_states.tName)("uptime"), "number", "value", false, "s");
450
458
  await this.createButton(`${prefix}.remove`, (0, import_i18n_states.tName)("removeDevice"), (0, import_i18n_states.tDesc)("removeDeviceDesc"));
451
459
  await this.adapter.setStateAsync(`${prefix}.info.productName`, {
@@ -469,12 +477,9 @@ class StateManager {
469
477
  }
470
478
  const prefix = this.devicePrefix(config);
471
479
  const mPrefix = `${prefix}.measurement`;
472
- await this.adapter.setObjectNotExistsAsync(mPrefix, {
473
- type: "channel",
474
- common: { name: asName((0, import_i18n_states.tName)("measurement")) },
475
- native: {}
476
- });
480
+ await this.ensureChannel(mPrefix, asName((0, import_i18n_states.tName)("measurement")));
477
481
  const record = data;
482
+ const writes = [];
478
483
  for (const def of MEASUREMENT_STATE_DEFS) {
479
484
  const raw = record[def.key];
480
485
  let coerced = null;
@@ -484,22 +489,24 @@ class StateManager {
484
489
  coerced = (0, import_coerce.coerceString)(raw);
485
490
  }
486
491
  if (coerced !== null) {
487
- await this.ensureAndSet(
488
- `${mPrefix}.${def.id}`,
489
- (0, import_i18n_states.tName)(def.nameKey),
490
- def.type,
491
- def.role,
492
- coerced,
493
- def.unit,
494
- void 0,
495
- def.descKey ? (0, import_i18n_states.tDesc)(def.descKey) : void 0,
496
- def.key === "tariff" ? tariffStates() : void 0
492
+ writes.push(
493
+ this.ensureAndSet(
494
+ `${mPrefix}.${def.id}`,
495
+ (0, import_i18n_states.tName)(def.nameKey),
496
+ def.type,
497
+ def.role,
498
+ coerced,
499
+ def.unit,
500
+ void 0,
501
+ def.descKey ? (0, import_i18n_states.tDesc)(def.descKey) : void 0,
502
+ def.key === "tariff" ? tariffStates() : void 0
503
+ )
497
504
  );
498
505
  }
499
506
  }
507
+ await Promise.all(writes);
500
508
  const external = record.external;
501
509
  if (Array.isArray(external) && external.length > 0) {
502
- let extChannelEnsured = false;
503
510
  for (const rawExt of external) {
504
511
  if (!(0, import_coerce.isPlainObject)(rawExt)) {
505
512
  continue;
@@ -512,36 +519,24 @@ class StateManager {
512
519
  const value = (0, import_coerce.coerceFiniteNumber)(rawExt.value);
513
520
  const unit = (0, import_coerce.coerceString)(rawExt.unit);
514
521
  const timestamp = (0, import_coerce.coerceString)(rawExt.timestamp);
515
- if (!extChannelEnsured) {
516
- await this.adapter.setObjectNotExistsAsync(`${mPrefix}.external`, {
517
- type: "channel",
518
- common: { name: asName((0, import_i18n_states.tName)("externalMeters")) },
519
- native: {}
520
- });
521
- extChannelEnsured = true;
522
- }
522
+ await this.ensureChannel(`${mPrefix}.external`, asName((0, import_i18n_states.tName)("externalMeters")));
523
523
  const extId = `${mPrefix}.external.${sanitize(type)}_${sanitize(uniqueId)}`;
524
- await this.adapter.setObjectNotExistsAsync(extId, {
525
- type: "channel",
526
- common: { name: type },
527
- native: {}
528
- });
524
+ await this.ensureChannel(extId, type);
525
+ const extWrites = [];
529
526
  if (value !== null) {
530
- await this.ensureAndSet(
531
- `${extId}.value`,
532
- (0, import_i18n_states.tName)("externalValue"),
533
- "number",
534
- "value",
535
- value,
536
- unit != null ? unit : void 0
527
+ extWrites.push(
528
+ this.ensureAndSet(`${extId}.value`, (0, import_i18n_states.tName)("externalValue"), "number", "value", value, unit != null ? unit : void 0)
537
529
  );
538
530
  }
539
531
  if (unit) {
540
- await this.ensureAndSet(`${extId}.unit`, (0, import_i18n_states.tName)("externalUnit"), "string", "text", unit);
532
+ extWrites.push(this.ensureAndSet(`${extId}.unit`, (0, import_i18n_states.tName)("externalUnit"), "string", "text", unit));
541
533
  }
542
534
  if (timestamp) {
543
- await this.ensureAndSet(`${extId}.timestamp`, (0, import_i18n_states.tName)("externalTimestamp"), "string", "date", timestamp);
535
+ extWrites.push(
536
+ this.ensureAndSet(`${extId}.timestamp`, (0, import_i18n_states.tName)("externalTimestamp"), "string", "date", timestamp)
537
+ );
544
538
  }
539
+ await Promise.all(extWrites);
545
540
  }
546
541
  }
547
542
  }
@@ -559,17 +554,13 @@ class StateManager {
559
554
  const record = system;
560
555
  const rssi = (0, import_coerce.coerceFiniteNumber)(record.wifi_rssi_db);
561
556
  if (rssi !== null) {
562
- await this.ensureAndSet(`${prefix}.info.wifi_rssi_db`, (0, import_i18n_states.tName)("wifiRssi"), "number", "value", rssi, "dB");
557
+ await this.ensureAndSet(`${prefix}.info.wifi_rssi_db`, (0, import_i18n_states.tName)("wifiRssi"), "number", "value", rssi, "dBm");
563
558
  }
564
559
  const uptime = (0, import_coerce.coerceFiniteNumber)(record.uptime_s);
565
560
  if (uptime !== null) {
566
561
  await this.ensureAndSet(`${prefix}.info.uptime_s`, (0, import_i18n_states.tName)("uptime"), "number", "value", uptime, "s");
567
562
  }
568
- await this.adapter.setObjectNotExistsAsync(`${prefix}.system`, {
569
- type: "channel",
570
- common: { name: asName((0, import_i18n_states.tName)("systemSettings")) },
571
- native: {}
572
- });
563
+ await this.ensureChannel(`${prefix}.system`, asName((0, import_i18n_states.tName)("systemSettings")));
573
564
  const cloudEnabled = (0, import_coerce.coerceBoolean)(record.cloud_enabled);
574
565
  if (cloudEnabled !== null) {
575
566
  await this.ensureAndSet(
@@ -621,11 +612,7 @@ class StateManager {
621
612
  }
622
613
  const prefix = this.devicePrefix(config);
623
614
  const record = battery;
624
- await this.adapter.setObjectNotExistsAsync(`${prefix}.battery`, {
625
- type: "channel",
626
- common: { name: asName((0, import_i18n_states.tName)("batteryControl")) },
627
- native: {}
628
- });
615
+ await this.ensureChannel(`${prefix}.battery`, asName((0, import_i18n_states.tName)("batteryControl")));
629
616
  const mode = (0, import_coerce.coerceString)(record.mode);
630
617
  if (mode) {
631
618
  await this.ensureAndSet(
@@ -705,6 +692,11 @@ class StateManager {
705
692
  async removeDevice(config) {
706
693
  const prefix = this.devicePrefix(config);
707
694
  await this.adapter.delObjectAsync(prefix, { recursive: true });
695
+ for (const id of this.createdIds) {
696
+ if (id === prefix || id.startsWith(`${prefix}.`)) {
697
+ this.createdIds.delete(id);
698
+ }
699
+ }
708
700
  }
709
701
  /**
710
702
  * Remove measurement states from old locations (pre-v0.4.0: device root instead of measurement/ channel)
@@ -733,6 +725,24 @@ class StateManager {
733
725
  devicePrefix(config) {
734
726
  return `${sanitize(config.productType)}_${sanitize(config.serial)}`;
735
727
  }
728
+ /**
729
+ * Ensure a channel object exists. Skips the DB lookup once `id` is in the
730
+ * cache — channels are static after first creation per device.
731
+ *
732
+ * @param id Full channel ID (`<prefix>.<channelName>`).
733
+ * @param name Display name (translation object or device-supplied string).
734
+ */
735
+ async ensureChannel(id, name) {
736
+ if (this.createdIds.has(id)) {
737
+ return;
738
+ }
739
+ await this.adapter.setObjectNotExistsAsync(id, {
740
+ type: "channel",
741
+ common: { name },
742
+ native: {}
743
+ });
744
+ this.createdIds.add(id);
745
+ }
736
746
  /**
737
747
  * Create a state if it doesn't exist
738
748
  *
@@ -746,6 +756,9 @@ class StateManager {
746
756
  * @param states Optional `common.states` map
747
757
  */
748
758
  async createState(id, name, type, role, write, unit, desc, states) {
759
+ if (this.createdIds.has(id)) {
760
+ return;
761
+ }
749
762
  const common = {
750
763
  name: typeof name === "string" ? name : asName(name),
751
764
  type,
@@ -767,6 +780,7 @@ class StateManager {
767
780
  common,
768
781
  native: {}
769
782
  });
783
+ this.createdIds.add(id);
770
784
  }
771
785
  /**
772
786
  * Create a button state (read: false, write: true) with initial value false
@@ -776,6 +790,9 @@ class StateManager {
776
790
  * @param desc Optional translation object for `common.desc`
777
791
  */
778
792
  async createButton(id, name, desc) {
793
+ if (this.createdIds.has(id)) {
794
+ return;
795
+ }
779
796
  const common = {
780
797
  name: asName(name),
781
798
  type: "boolean",
@@ -792,6 +809,7 @@ class StateManager {
792
809
  native: {}
793
810
  });
794
811
  await this.adapter.setStateAsync(id, { val: false, ack: true });
812
+ this.createdIds.add(id);
795
813
  }
796
814
  /**
797
815
  * Ensure state exists and set value
@@ -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 { StateName, STATE_DESCS, STATE_NAMES } from \"./i18n-states\";\nimport { tDesc, tLabel, 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/** Build a `common.states` map where the values are translation objects (admin v6+). */\nfunction tariffStates(): Record<string, string> {\n return {\n 1: tLabel(\"tariff1\") as unknown as string,\n 2: tLabel(\"tariff2\") as unknown as string,\n 3: tLabel(\"tariff3\") as unknown as string,\n 4: tLabel(\"tariff4\") as unknown as string,\n };\n}\nfunction batteryModeStates(): Record<string, string> {\n return {\n zero: tLabel(\"modeZero\") as unknown as string,\n to_full: tLabel(\"modeToFull\") as unknown as string,\n standby: tLabel(\"modeStandby\") as unknown as string,\n };\n}\n\n/** Manages ioBroker state creation and updates for HomeWizard devices */\nexport class StateManager {\n private readonly adapter: utils.AdapterInstance;\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, \"dB\");\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\n await this.adapter.setObjectNotExistsAsync(mPrefix, {\n type: \"channel\",\n common: { name: asName(tName(\"measurement\")) },\n native: {},\n });\n\n // Main measurement values \u2014 coerce per declared type\n const record = data;\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 await 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() : undefined,\n );\n }\n }\n\n // External meters (P1 gas/water/heat)\n const external = record.external;\n if (Array.isArray(external) && external.length > 0) {\n let extChannelEnsured = false;\n\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 if (!extChannelEnsured) {\n await this.adapter.setObjectNotExistsAsync(`${mPrefix}.external`, {\n type: \"channel\",\n common: { name: asName(tName(\"externalMeters\")) },\n native: {},\n });\n extChannelEnsured = true;\n }\n\n const extId = `${mPrefix}.external.${sanitize(type)}_${sanitize(uniqueId)}`;\n // External meter channel keeps the device-supplied type (e.g. \"gas_meter\") as channel name \u2014\n // identifies the physical meter, not localizable.\n await this.adapter.setObjectNotExistsAsync(extId, {\n type: \"channel\",\n common: { name: type },\n native: {},\n });\n if (value !== null) {\n await this.ensureAndSet(\n `${extId}.value`,\n tName(\"externalValue\"),\n \"number\",\n \"value\",\n value,\n unit ?? undefined,\n );\n }\n if (unit) {\n await this.ensureAndSet(`${extId}.unit`, tName(\"externalUnit\"), \"string\", \"text\", unit);\n }\n if (timestamp) {\n await this.ensureAndSet(`${extId}.timestamp`, tName(\"externalTimestamp\"), \"string\", \"date\", timestamp);\n }\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, \"dB\");\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\n await this.adapter.setObjectNotExistsAsync(`${prefix}.system`, {\n type: \"channel\",\n common: { name: asName(tName(\"systemSettings\")) },\n native: {},\n });\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.adapter.setObjectNotExistsAsync(`${prefix}.battery`, {\n type: \"channel\",\n common: { name: asName(tName(\"batteryControl\")) },\n native: {},\n });\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(),\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 }\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 * 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 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 }\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 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 }\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 { StateName, STATE_DESCS, STATE_NAMES } from \"./i18n-states\";\nimport { tDesc, tLabel, 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/** Build a `common.states` map where the values are translation objects (admin v6+). */\nfunction tariffStates(): Record<string, string> {\n return {\n 1: tLabel(\"tariff1\") as unknown as string,\n 2: tLabel(\"tariff2\") as unknown as string,\n 3: tLabel(\"tariff3\") as unknown as string,\n 4: tLabel(\"tariff4\") as unknown as string,\n };\n}\nfunction batteryModeStates(): Record<string, string> {\n return {\n zero: tLabel(\"modeZero\") as unknown as string,\n to_full: tLabel(\"modeToFull\") as unknown as string,\n standby: tLabel(\"modeStandby\") as unknown as string,\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() : 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(),\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 this.createdIds.add(id);\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
@@ -27,6 +27,7 @@ var import_connection_utils = require("./lib/connection-utils");
27
27
  var import_discovery = require("./lib/discovery");
28
28
  var import_homewizard_client = require("./lib/homewizard-client");
29
29
  var import_i18n_logs = require("./lib/i18n-logs");
30
+ var import_main_helpers = require("./lib/main-helpers");
30
31
  var import_state_manager = require("./lib/state-manager");
31
32
  var import_websocket_client = require("./lib/websocket-client");
32
33
  const PAIRING_TIMEOUT_MS = 6e4;
@@ -509,7 +510,7 @@ class HomeWizard extends utils.Adapter {
509
510
  if (conn.authFailCount >= MAX_AUTH_FAILURES) {
510
511
  return;
511
512
  }
512
- if (conn.wsFailCount >= WS_FAILURES_BEFORE_MDNS && (conn.wsFailCount - WS_FAILURES_BEFORE_MDNS) % MDNS_RETRY_EVERY === 0) {
513
+ if ((0, import_main_helpers.shouldStartIpRecovery)(conn.wsFailCount, WS_FAILURES_BEFORE_MDNS, MDNS_RETRY_EVERY)) {
513
514
  this.startIpRecovery();
514
515
  }
515
516
  const key = this.stateManager.devicePrefix(conn.config);
@@ -547,17 +548,22 @@ class HomeWizard extends utils.Adapter {
547
548
  onDisconnected: (error) => {
548
549
  if (conn.lastConnectedAt > 0) {
549
550
  const duration = Date.now() - conn.lastConnectedAt;
551
+ const transition = (0, import_main_helpers.decideUnstableTransition)(
552
+ conn.recentDisconnects,
553
+ duration,
554
+ STABLE_THRESHOLD_MS,
555
+ import_connection_utils.UNSTABLE_DISCONNECT_THRESHOLD
556
+ );
550
557
  if (duration < STABLE_THRESHOLD_MS) {
551
558
  conn.recentDisconnects++;
552
- if (conn.recentDisconnects === import_connection_utils.UNSTABLE_DISCONNECT_THRESHOLD) {
553
- this.log.info((0, import_i18n_logs.tLog)(this.systemLang, "unstableDetected", { name: conn.config.productName }));
554
- }
555
559
  } else {
556
- if (conn.recentDisconnects >= import_connection_utils.UNSTABLE_DISCONNECT_THRESHOLD) {
557
- this.log.info((0, import_i18n_logs.tLog)(this.systemLang, "connectionStabilized", { name: conn.config.productName }));
558
- }
559
560
  conn.recentDisconnects = 0;
560
561
  }
562
+ if (transition === "becameUnstable") {
563
+ this.log.info((0, import_i18n_logs.tLog)(this.systemLang, "unstableDetected", { name: conn.config.productName }));
564
+ } else if (transition === "stabilized") {
565
+ this.log.info((0, import_i18n_logs.tLog)(this.systemLang, "connectionStabilized", { name: conn.config.productName }));
566
+ }
561
567
  }
562
568
  conn.wsAuthenticated = false;
563
569
  conn.wsClient = null;
@@ -577,7 +583,7 @@ class HomeWizard extends utils.Adapter {
577
583
  this.startRestFallback(conn);
578
584
  conn.wsFailCount++;
579
585
  const maxDelay = this.isUnstable(conn) ? WS_RECONNECT_MAX_UNSTABLE_MS : WS_RECONNECT_MAX_MS;
580
- const delay = Math.min(WS_RECONNECT_BASE_MS * Math.pow(2, conn.wsFailCount - 1), maxDelay);
586
+ const delay = (0, import_main_helpers.computeReconnectDelay)(conn.wsFailCount, WS_RECONNECT_BASE_MS, maxDelay);
581
587
  this.log.debug(`${key}: WS reconnect in ${delay / 1e3}s (attempt ${conn.wsFailCount})`);
582
588
  conn.reconnectTimer = this.setTimeout(() => {
583
589
  conn.reconnectTimer = void 0;
@@ -601,7 +607,7 @@ class HomeWizard extends utils.Adapter {
601
607
  return;
602
608
  }
603
609
  const unstable = this.isUnstable(conn);
604
- const interval = unstable ? REST_POLL_UNSTABLE_MS : REST_POLL_MS;
610
+ const interval = (0, import_main_helpers.pickRestPollInterval)(unstable, REST_POLL_MS, REST_POLL_UNSTABLE_MS);
605
611
  const client = new import_homewizard_client.HomeWizardClient(conn.ip, conn.config.token);
606
612
  conn.pollTimer = this.setInterval(async () => {
607
613
  try {
@@ -692,19 +698,13 @@ class HomeWizard extends utils.Adapter {
692
698
  this.updateGlobalConnection();
693
699
  }
694
700
  /**
695
- * Find connection for a state ID
701
+ * Find connection for a state ID. Delegates to the pure helper so the
702
+ * lookup math is unit-tested separately (`lib/main-helpers.test.ts`).
696
703
  *
697
704
  * @param stateId Full state ID
698
705
  */
699
706
  findConnectionForState(stateId) {
700
- const localId = stateId.replace(`${this.namespace}.`, "");
701
- for (const conn of this.connections.values()) {
702
- const prefix = this.stateManager.devicePrefix(conn.config);
703
- if (localId.startsWith(`${prefix}.`)) {
704
- return conn;
705
- }
706
- }
707
- return void 0;
707
+ return (0, import_main_helpers.findConnectionForState)(stateId, this.namespace, this.connections);
708
708
  }
709
709
  /**
710
710
  * Whether a device has unstable connectivity (frequent short-lived connections).
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, 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 { tLog } from \"./lib/i18n-logs\";\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\nclass HomeWizard extends utils.Adapter {\n private stateManager!: StateManager;\n private discovery: HomeWizardDiscovery | null = null;\n private readonly connections = new Map<string, DeviceConnection>();\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 /** ioBroker system language \u2014 read once in `onReady` from `system.config`. EN fallback. */\n private systemLang: string = \"en\";\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) =>\n this.log.error(tLog(this.systemLang, \"onReadyFailed\", { error: errText(err) })),\n );\n });\n this.on(\"stateChange\", (id, state) => {\n this.onStateChange(id, state).catch((err: unknown) =>\n this.log.error(tLog(this.systemLang, \"stateChangeFailed\", { error: errText(err) })),\n );\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(tLog(this.systemLang, \"unhandledRejection\", { error: errText(reason) }));\n };\n this.uncaughtExceptionHandler = (err: Error) => {\n this.log.error(tLog(this.systemLang, \"uncaughtException\", { error: 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 // Read ioBroker system language for user-facing logs + state names. EN fallback for unknown values.\n try {\n const sysCfg = await this.getForeignObjectAsync(\"system.config\");\n const lang = sysCfg?.common?.language;\n if (typeof lang === \"string\" && lang.length > 0) {\n this.systemLang = lang;\n }\n } catch {\n // EN fallback already in place\n }\n\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(tLog(this.systemLang, \"noDevicesConfigured\"));\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 const oldDevices: DeviceConfig[] = ((this.config as Record<string, unknown>).devices 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\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 const token = this.decrypt(native.encryptedToken);\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 tLog(this.systemLang, \"deviceFound\", {\n name: discovered.name,\n type: discovered.productType,\n ip: discovered.ip,\n }),\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 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) {\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(tLog(this.systemLang, \"rebootingDevice\", { name: conn.config.productName, ip: 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(tLog(this.systemLang, \"invalidBatteryMode\", { value: String(state.val) }));\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 tLog(this.systemLang, \"invalidPermissionsJson\", { error: result.reason, value: 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(tLog(this.systemLang, \"failedToSetState\", { id, error: 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 this.log.info(tLog(this.systemLang, \"pairingEnabledManual\", { ip: this.pairingManualIp }));\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(tLog(this.systemLang, \"pairingEnabledMdns\"));\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(tLog(this.systemLang, \"pairingTimeout\"));\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 tLog(this.systemLang, \"pairingSuccess\", {\n name: device.name,\n type: device.productType,\n ip: device.ip,\n }),\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 // Create connection and connect\n const key = this.stateManager.devicePrefix(deviceConfig);\n const conn = createDeviceConnection(deviceConfig, device.ip);\n this.connections.set(key, conn);\n void this.initDevice(conn);\n\n // Remove from discovery list\n this.discoveredDuringPairing = this.discoveredDuringPairing.filter(d => d.serial !== info.serial);\n\n this.stopPairing();\n this.updateGlobalConnection();\n return;\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 this.log.info(tLog(this.systemLang, \"searchingNewIp\"));\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\n this.log.info(\n tLog(this.systemLang, \"foundAtNewIp\", {\n name: conn.config.productName,\n newIp: discovered.ip,\n oldIp: conn.ip,\n }),\n );\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 void this.saveDeviceToObject(conn.config);\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 backoff\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.warn(\n tLog(this.systemLang, \"deviceOfflineRetrying\", {\n name: conn.config.productName,\n seconds: WS_RECONNECT_MAX_MS / 1000,\n }),\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 try {\n const client = new HomeWizardClient(conn.ip, conn.config.token);\n const info = await client.getDeviceInfo();\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 this.logDeviceError(conn, \"init\", err);\n }\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 // After repeated failures, try mDNS periodically to find a new IP\n if (\n conn.wsFailCount >= WS_FAILURES_BEFORE_MDNS &&\n (conn.wsFailCount - WS_FAILURES_BEFORE_MDNS) % MDNS_RETRY_EVERY === 0\n ) {\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 void this.stateManager.updateMeasurement(conn.config, data);\n },\n onConnected: () => {\n conn.wsAuthenticated = true;\n conn.wsFailCount = 0;\n conn.authFailCount = 0;\n conn.lastConnectedAt = Date.now();\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\n if (conn.lastErrorCode) {\n this.log.info(\n tLog(this.systemLang, this.isUnstable(conn) ? \"connectionRestoredUnstable\" : \"connectionRestored\", {\n name: conn.config.productName,\n }),\n );\n conn.lastErrorCode = \"\";\n }\n\n this.log.debug(`WebSocket connected to ${conn.config.productName} (${conn.ip})`);\n },\n onDisconnected: (error?: Error) => {\n // Track connection stability\n if (conn.lastConnectedAt > 0) {\n const duration = Date.now() - conn.lastConnectedAt;\n if (duration < STABLE_THRESHOLD_MS) {\n conn.recentDisconnects++;\n if (conn.recentDisconnects === UNSTABLE_DISCONNECT_THRESHOLD) {\n this.log.info(tLog(this.systemLang, \"unstableDetected\", { name: conn.config.productName }));\n }\n } else {\n // Was stable \u2014 reset counter\n if (conn.recentDisconnects >= UNSTABLE_DISCONNECT_THRESHOLD) {\n this.log.info(tLog(this.systemLang, \"connectionStabilized\", { name: conn.config.productName }));\n }\n conn.recentDisconnects = 0;\n }\n }\n\n conn.wsAuthenticated = false;\n conn.wsClient = null;\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 = Math.min(WS_RECONNECT_BASE_MS * Math.pow(2, conn.wsFailCount - 1), 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 = unstable ? REST_POLL_UNSTABLE_MS : REST_POLL_MS;\n const client = new HomeWizardClient(conn.ip, conn.config.token);\n\n conn.pollTimer = this.setInterval(async () => {\n try {\n const data = await client.getMeasurement();\n await this.stateManager.updateMeasurement(conn.config, data);\n } catch (err) {\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 */\n private async pollAllSystemInfo(): Promise<void> {\n for (const conn of this.connections.values()) {\n if (conn.ip && conn.wsAuthenticated) {\n await this.pollSystemInfo(conn);\n }\n }\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) {\n return;\n }\n\n try {\n const client = new HomeWizardClient(conn.ip, conn.config.token);\n const system = await client.getSystem();\n await this.stateManager.updateSystem(conn.config, system);\n\n // Also poll battery if device supports it\n try {\n const battery = await client.getBatteries();\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 {\n // Device may not support batteries \u2014 that's fine\n }\n } catch (err) {\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(\n tLog(this.systemLang, \"removingDevice\", { name: conn.config.productName, serial: conn.config.serial }),\n );\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\n *\n * @param stateId Full state ID\n */\n private findConnectionForState(stateId: string): DeviceConnection | undefined {\n const localId = stateId.replace(`${this.namespace}.`, \"\");\n for (const conn of this.connections.values()) {\n const prefix = this.stateManager.devicePrefix(conn.config);\n if (localId.startsWith(`${prefix}.`)) {\n return conn;\n }\n }\n return undefined;\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(tLog(this.systemLang, \"tokenInvalid\", { name: conn.config.productName }));\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 (based on error category, not context).\n * First occurrence of a new error category logs as warn, repeats as debug.\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 } else if (errorCode === \"NETWORK\") {\n this.log.warn(tLog(this.systemLang, \"deviceUnreachable\", { name: conn.config.productName }));\n } else {\n this.log.warn(\n tLog(this.systemLang, \"deviceErrorContext\", {\n name: conn.config.productName,\n context,\n error: errText(err),\n }),\n );\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
- "names": []
4
+ "sourcesContent": ["import * as utils from \"@iobroker/adapter-core\";\nimport { errText, 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 { tLog } from \"./lib/i18n-logs\";\nimport {\n computeReconnectDelay,\n decideUnstableTransition,\n findConnectionForState as resolveConnectionForState,\n pickRestPollInterval,\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\nclass HomeWizard extends utils.Adapter {\n private stateManager!: StateManager;\n private discovery: HomeWizardDiscovery | null = null;\n private readonly connections = new Map<string, DeviceConnection>();\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 /** ioBroker system language \u2014 read once in `onReady` from `system.config`. EN fallback. */\n private systemLang: string = \"en\";\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) =>\n this.log.error(tLog(this.systemLang, \"onReadyFailed\", { error: errText(err) })),\n );\n });\n this.on(\"stateChange\", (id, state) => {\n this.onStateChange(id, state).catch((err: unknown) =>\n this.log.error(tLog(this.systemLang, \"stateChangeFailed\", { error: errText(err) })),\n );\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(tLog(this.systemLang, \"unhandledRejection\", { error: errText(reason) }));\n };\n this.uncaughtExceptionHandler = (err: Error) => {\n this.log.error(tLog(this.systemLang, \"uncaughtException\", { error: 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 // Read ioBroker system language for user-facing logs + state names. EN fallback for unknown values.\n try {\n const sysCfg = await this.getForeignObjectAsync(\"system.config\");\n const lang = sysCfg?.common?.language;\n if (typeof lang === \"string\" && lang.length > 0) {\n this.systemLang = lang;\n }\n } catch {\n // EN fallback already in place\n }\n\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(tLog(this.systemLang, \"noDevicesConfigured\"));\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 const oldDevices: DeviceConfig[] = ((this.config as Record<string, unknown>).devices 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\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 const token = this.decrypt(native.encryptedToken);\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 tLog(this.systemLang, \"deviceFound\", {\n name: discovered.name,\n type: discovered.productType,\n ip: discovered.ip,\n }),\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 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) {\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(tLog(this.systemLang, \"rebootingDevice\", { name: conn.config.productName, ip: 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(tLog(this.systemLang, \"invalidBatteryMode\", { value: String(state.val) }));\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 tLog(this.systemLang, \"invalidPermissionsJson\", { error: result.reason, value: 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(tLog(this.systemLang, \"failedToSetState\", { id, error: 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 this.log.info(tLog(this.systemLang, \"pairingEnabledManual\", { ip: this.pairingManualIp }));\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(tLog(this.systemLang, \"pairingEnabledMdns\"));\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(tLog(this.systemLang, \"pairingTimeout\"));\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 tLog(this.systemLang, \"pairingSuccess\", {\n name: device.name,\n type: device.productType,\n ip: device.ip,\n }),\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 // Create connection and connect\n const key = this.stateManager.devicePrefix(deviceConfig);\n const conn = createDeviceConnection(deviceConfig, device.ip);\n this.connections.set(key, conn);\n void this.initDevice(conn);\n\n // Remove from discovery list\n this.discoveredDuringPairing = this.discoveredDuringPairing.filter(d => d.serial !== info.serial);\n\n this.stopPairing();\n this.updateGlobalConnection();\n return;\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 this.log.info(tLog(this.systemLang, \"searchingNewIp\"));\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\n this.log.info(\n tLog(this.systemLang, \"foundAtNewIp\", {\n name: conn.config.productName,\n newIp: discovered.ip,\n oldIp: conn.ip,\n }),\n );\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 void this.saveDeviceToObject(conn.config);\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 backoff\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.warn(\n tLog(this.systemLang, \"deviceOfflineRetrying\", {\n name: conn.config.productName,\n seconds: WS_RECONNECT_MAX_MS / 1000,\n }),\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 try {\n const client = new HomeWizardClient(conn.ip, conn.config.token);\n const info = await client.getDeviceInfo();\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 this.logDeviceError(conn, \"init\", err);\n }\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 // 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 void this.stateManager.updateMeasurement(conn.config, data);\n },\n onConnected: () => {\n conn.wsAuthenticated = true;\n conn.wsFailCount = 0;\n conn.authFailCount = 0;\n conn.lastConnectedAt = Date.now();\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\n if (conn.lastErrorCode) {\n this.log.info(\n tLog(this.systemLang, this.isUnstable(conn) ? \"connectionRestoredUnstable\" : \"connectionRestored\", {\n name: conn.config.productName,\n }),\n );\n conn.lastErrorCode = \"\";\n }\n\n this.log.debug(`WebSocket connected to ${conn.config.productName} (${conn.ip})`);\n },\n onDisconnected: (error?: Error) => {\n // Track connection stability \u2014 pure decision in main-helpers, side-effects here\n if (conn.lastConnectedAt > 0) {\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 if (transition === \"becameUnstable\") {\n this.log.info(tLog(this.systemLang, \"unstableDetected\", { name: conn.config.productName }));\n } else if (transition === \"stabilized\") {\n this.log.info(tLog(this.systemLang, \"connectionStabilized\", { name: conn.config.productName }));\n }\n }\n\n conn.wsAuthenticated = false;\n conn.wsClient = null;\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 try {\n const data = await client.getMeasurement();\n await this.stateManager.updateMeasurement(conn.config, data);\n } catch (err) {\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 */\n private async pollAllSystemInfo(): Promise<void> {\n for (const conn of this.connections.values()) {\n if (conn.ip && conn.wsAuthenticated) {\n await this.pollSystemInfo(conn);\n }\n }\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) {\n return;\n }\n\n try {\n const client = new HomeWizardClient(conn.ip, conn.config.token);\n const system = await client.getSystem();\n await this.stateManager.updateSystem(conn.config, system);\n\n // Also poll battery if device supports it\n try {\n const battery = await client.getBatteries();\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 {\n // Device may not support batteries \u2014 that's fine\n }\n } catch (err) {\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(\n tLog(this.systemLang, \"removingDevice\", { name: conn.config.productName, serial: conn.config.serial }),\n );\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(tLog(this.systemLang, \"tokenInvalid\", { name: conn.config.productName }));\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 (based on error category, not context).\n * First occurrence of a new error category logs as warn, repeats as debug.\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 } else if (errorCode === \"NETWORK\") {\n this.log.warn(tLog(this.systemLang, \"deviceUnreachable\", { name: conn.config.productName }));\n } else {\n this.log.warn(\n tLog(this.systemLang, \"deviceErrorContext\", {\n name: conn.config.productName,\n context,\n error: errText(err),\n }),\n );\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
+ "names": ["resolveConnectionForState"]
7
7
  }
package/io-package.json CHANGED
@@ -1,20 +1,46 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "homewizard",
4
- "version": "0.7.0",
4
+ "version": "0.7.2",
5
5
  "news": {
6
+ "0.7.2": {
7
+ "en": "Internal robustness: stricter number parsing for sensor inputs, parallel state writes, code split for testability, 38 new tests covering the HTTPS client. No user-facing changes.",
8
+ "de": "Interne Härtung: strengere Zahlenprüfung für Sensorwerte, parallele State-Writes, Code-Split für bessere Testbarkeit, 38 neue Tests für den HTTPS-Client. Keine User-sichtbaren Änderungen.",
9
+ "ru": "Внутреннее усиление: строгая проверка чисел для сенсорных значений, параллельная запись состояний, разделение кода ради тестируемости, 38 новых тестов для HTTPS-клиента. Без видимых изменений.",
10
+ "pt": "Endurecimento interno: análise de números mais rigorosa, gravação de estados em paralelo, código dividido para testes, 38 novos testes do cliente HTTPS. Sem mudanças visíveis.",
11
+ "nl": "Interne verharding: strengere getalcontrole voor sensorwaarden, parallelle state-writes, code-splitsing voor testbaarheid, 38 nieuwe tests voor de HTTPS-client. Geen wijzigingen voor de gebruiker.",
12
+ "fr": "Durcissement interne : analyse de nombres plus stricte, écritures d'état en parallèle, code scindé pour les tests, 38 nouveaux tests du client HTTPS. Aucune modification visible.",
13
+ "it": "Hardening interno: parsing numerico più rigoroso per i sensori, scritture di stato in parallelo, codice diviso per la testabilità, 38 nuovi test per il client HTTPS. Nessuna modifica visibile.",
14
+ "es": "Endurecimiento interno: análisis de números más estricto, escrituras de estado en paralelo, código dividido para tests, 38 nuevas pruebas del cliente HTTPS. Sin cambios visibles.",
15
+ "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.",
16
+ "uk": "Внутрішнє зміцнення: строгіша перевірка чисел для сенсорів, паралельні записи станів, розділення коду для тестованості, 38 нових тестів HTTPS-клієнта. Без видимих змін.",
17
+ "zh-cn": "内部加固:传感器数值校验更严格、状态写入并行化、代码拆分以提升可测试性,HTTPS 客户端新增 38 个测试。对用户无可见变化。"
18
+ },
19
+ "0.7.1": {
20
+ "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.",
21
+ "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.",
22
+ "ru": "Производительность: проверка существования датчиков кэшируется после первого создания — экономит около 30 redis-запросов в секунду на P1-счётчике. Единица WiFi-сигнала dBm.",
23
+ "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.",
24
+ "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.",
25
+ "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.",
26
+ "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.",
27
+ "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.",
28
+ "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.",
29
+ "uk": "Продуктивність: перевірка існування датапоінтів кешується після першого створення — економить ~30 redis-запитів на секунду на P1 Meter. Одиниця сигналу WiFi — dBm.",
30
+ "zh-cn": "性能:数据点存在检查在首次创建后会缓存——P1 Meter 每秒节省约 30 次 Redis 查询。WiFi 信号单位修正为 dBm。"
31
+ },
6
32
  "0.7.0": {
7
- "en": "Multi-language across 11 languages (state names, descriptions, dropdown labels, user logs). errText helper, guarded battery.permissions JSON, whitelist-validated battery.mode. Baseline Node 22 + Admin 7.8.23.",
8
- "de": "Mehrsprachigkeit in 11 Sprachen (Zustandsnamen, Beschreibungen, Dropdown-Labels, User-Logs). errText-Helper, abgesichertes battery.permissions JSON, whitelist-validierter battery.mode. Baseline Node 22 + Admin 7.8.23.",
9
- "ru": "Многоязычность на 11 языках (имена состояний, описания, метки списков, пользовательские логи). Helper errText, защищённый JSON в battery.permissions, whitelist для battery.mode. Baseline Node 22 + Admin 7.8.23.",
10
- "pt": "Multilíngue em 11 idiomas (nomes de estados, descrições, rótulos de dropdown, logs de utilizador). Helper errText, JSON protegido em battery.permissions, whitelist para battery.mode. Baseline Node 22 + Admin 7.8.23.",
11
- "nl": "Meertalig in 11 talen (statusnamen, beschrijvingen, dropdown-labels, gebruikerslogs). errText-helper, beveiligd battery.permissions JSON, whitelist voor battery.mode. Baseline Node 22 + Admin 7.8.23.",
12
- "fr": "Multilingue en 11 langues (noms d'états, descriptions, libellés déroulants, logs utilisateur). Helper errText, JSON protégé dans battery.permissions, whitelist pour battery.mode. Baseline Node 22 + Admin 7.8.23.",
13
- "it": "Multilingua in 11 lingue (nomi degli stati, descrizioni, etichette dei menu, log utente). Helper errText, JSON protetto in battery.permissions, whitelist per battery.mode. Baseline Node 22 + Admin 7.8.23.",
14
- "es": "Multilingüe en 11 idiomas (nombres de estados, descripciones, etiquetas de menús, logs de usuario). Helper errText, JSON protegido en battery.permissions, whitelist para battery.mode. Baseline Node 22 + Admin 7.8.23.",
15
- "pl": "Wiele języków w 11 językach (nazwy stanów, opisy, etykiety menu, logi użytkownika). Helper errText, zabezpieczony JSON w battery.permissions, whitelista dla battery.mode. Baseline Node 22 + Admin 7.8.23.",
16
- "uk": "Багатомовність 11 мовами (імена станів, описи, мітки меню, користувацькі логи). Хелпер errText, захищений JSON в battery.permissions, whitelist для battery.mode. Baseline Node 22 + Admin 7.8.23.",
17
- "zh-cn": "11 种语言多语言支持(状态名、描述、下拉标签、用户日志)。errText helper、battery.permissions JSON 防错、battery.mode 白名单校验。基线 Node 22 + Admin 7.8.23。"
33
+ "en": "Multi-language: state names, descriptions, dropdown values (tariff, battery.mode) and user logs in your ioBroker system language (11 languages). Battery inputs validated. Min Node 22, Admin 7.8.23.",
34
+ "de": "Mehrsprachig: Datenpunkt-Namen, -Beschreibungen, Dropdown-Werte (tariff, battery.mode) und User-Logs in der ioBroker-Systemsprache (11 Sprachen). Batterie-Eingaben werden geprüft. Min Node 22, Admin 7.8.23.",
35
+ "ru": "Многоязычность: имена и описания датчиков, значения списков (tariff, battery.mode) и пользовательские логи в системном языке ioBroker (11 языков). Проверка ввода для батареи. Мин. Node 22, Admin 7.8.23.",
36
+ "pt": "Multilíngue: nomes e descrições de datapoints, valores de dropdown (tariff, battery.mode) e logs no idioma do sistema ioBroker (11 idiomas). Validação de entradas de bateria. Mín. Node 22, Admin 7.8.23.",
37
+ "nl": "Meertalig: datapoint-namen, beschrijvingen, dropdown-waarden (tariff, battery.mode) en gebruikerslogs in de ioBroker-systeemtaal (11 talen). Batterij-invoer wordt gecontroleerd. Min. Node 22, Admin 7.8.23.",
38
+ "fr": "Multilingue : noms et descriptions des datapoints, valeurs déroulantes (tariff, battery.mode) et logs dans la langue système ioBroker (11 langues). Saisies batterie validées. Min Node 22, Admin 7.8.23.",
39
+ "it": "Multilingua: nomi e descrizioni dei datapoint, valori dei menu (tariff, battery.mode) e log utente nella lingua di sistema ioBroker (11 lingue). Input batteria validati. Min Node 22, Admin 7.8.23.",
40
+ "es": "Multilingüe: nombres y descripciones de datapoints, valores de menús (tariff, battery.mode) y logs en el idioma del sistema ioBroker (11 idiomas). Entradas de batería validadas. Mín. Node 22, Admin 7.8.23.",
41
+ "pl": "Wiele języków: nazwy i opisy datapointów, wartości menu (tariff, battery.mode) i logi w języku systemu ioBroker (11 języków). Walidacja wprowadzania baterii. Min Node 22, Admin 7.8.23.",
42
+ "uk": "Багатомовність: імена та описи датапоінтів, значення меню (tariff, battery.mode) та логи мовою системи ioBroker (11 мов). Перевірка вводу для батареї. Мін. Node 22, Admin 7.8.23.",
43
+ "zh-cn": "多语言:数据点名称、描述、下拉值(tariff、battery.mode)和用户日志使用 ioBroker 系统语言(11 种)。电池输入会被校验。最低 Node 22Admin 7.8.23。"
18
44
  },
19
45
  "0.6.7": {
20
46
  "en": "Documentation: rewrote release notes in user-friendly style across all languages.",
@@ -67,32 +93,6 @@
67
93
  "pl": "Wewnętrzne wzmocnienie. Brak zmian widocznych dla użytkownika.",
68
94
  "uk": "Внутрішнє зміцнення. Без помітних для користувача змін.",
69
95
  "zh-cn": "内部加固。对用户无可见变化。"
70
- },
71
- "0.6.3": {
72
- "en": "WebSocket and REST input hardening. Stops endless reconnect when the device token is invalid.",
73
- "de": "WebSocket- und REST-Input-Härtung. Beendet Endlos-Reconnect bei ungültigem Geräte-Token.",
74
- "ru": "Усиление input WebSocket и REST. Прекращает бесконечный reconnect при недействительном токене устройства.",
75
- "pt": "Endurecimento de entrada WebSocket e REST. Para a reconexão infinita quando o token do dispositivo é inválido.",
76
- "nl": "WebSocket- en REST-input-verharding. Stopt eindeloze reconnect bij ongeldig apparaat-token.",
77
- "fr": "Durcissement des entrées WebSocket et REST. Arrête la reconnexion infinie en cas de token d'appareil invalide.",
78
- "it": "Hardening input WebSocket e REST. Ferma riconnessione infinita su token dispositivo non valido.",
79
- "es": "Endurecimiento de entrada WebSocket y REST. Detiene la reconexión infinita con token de dispositivo inválido.",
80
- "pl": "Wzmocnienie input WebSocket i REST. Zatrzymuje nieskończony reconnect przy nieprawidłowym tokenie urządzenia.",
81
- "uk": "Зміцнення input WebSocket та REST. Припиняє нескінченний reconnect при недійсному токені пристрою.",
82
- "zh-cn": "WebSocket 和 REST 输入加固。设备 token 无效时停止无限重连。"
83
- },
84
- "0.6.2": {
85
- "en": "Fix: hanging promise on response stream errors. Safer adapter shutdown.",
86
- "de": "Fix: hängender Promise bei Response-Stream-Fehlern. Sichereres Adapter-Shutdown.",
87
- "ru": "Fix: зависший promise при ошибках response-stream. Более безопасное завершение работы адаптера.",
88
- "pt": "Fix: promise pendurada em erros de response stream. Encerramento mais seguro do adaptador.",
89
- "nl": "Fix: hangende promise bij response-stream-fouten. Veiliger afsluiten van de adapter.",
90
- "fr": "Fix : promesse bloquée sur erreurs de response stream. Arrêt plus sûr de l'adaptateur.",
91
- "it": "Fix: promise sospesa su errori response stream. Spegnimento più sicuro dell'adattatore.",
92
- "es": "Fix: promise colgada en errores de response stream. Apagado más seguro del adaptador.",
93
- "pl": "Fix: zawieszony promise przy błędach response stream. Bezpieczniejsze zamykanie adaptera.",
94
- "uk": "Fix: завислий promise при помилках response-stream. Безпечніше завершення роботи адаптера.",
95
- "zh-cn": "修复:response stream 错误时的挂起 promise。更安全的适配器关闭。"
96
96
  }
97
97
  },
98
98
  "titleLang": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.homewizard",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "description": "ioBroker adapter for HomeWizard Energy devices — real-time energy monitoring via API v2 with WebSocket push",
5
5
  "author": {
6
6
  "name": "krobi",