iobroker.homewizard 0.7.2 → 0.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,20 +11,18 @@
11
11
 
12
12
  <img src="https://raw.githubusercontent.com/krobipd/ioBroker.homewizard/main/admin/homewizard.svg" width="100" />
13
13
 
14
- Real-time energy monitoring from [HomeWizard](https://www.homewizard.com) Energy devices via API v2 with WebSocket push (~1 update/second).
14
+ Real-time energy monitoring for [HomeWizard](https://www.homewizard.com) Energy devices with API v2.
15
15
 
16
16
  ---
17
17
 
18
18
  ## Features
19
19
 
20
- - **WebSocket push** for real-time energy data (~1 update per second)
21
- - **Automatic device discovery** via mDNS (zero configuration)
22
- - **Hue-style pairing** — press the button on the device to connect
23
- - **Multi-device support** — manage all HomeWizard devices in one adapter instance
24
- - **Battery control** — manage HomeWizard Plug-In Batteries (mode, permissions)
25
- - **System control** — LED brightness, cloud toggle, reboot, identify
26
- - **REST fallback** — automatic polling when WebSocket is unavailable
27
- - **Multi-language UI** — state names, descriptions and dropdown labels follow the ioBroker system language (11 languages)
20
+ - **HomeWizard API v2** HTTPS + WebSocket, bearer-token authentication
21
+ - **mDNS pairing** — `_homewizard._tcp` discovery, press the device button to pair
22
+ - **WebSocket push** — measurements arrive ~1/s; REST polling takes over while the WebSocket reconnects
23
+ - **Plug-In Battery control** — charge/discharge mode and grid-feed permissions through the paired P1/kWh meter
24
+ - **Adaptive reconnect** — devices with weak WiFi switch to a faster reconnect interval and keep REST polling running so data keeps flowing
25
+ - **Encrypted device tokens** — stored per device object, no adapter restart on pairing or removal
28
26
 
29
27
  ---
30
28
 
@@ -39,12 +37,14 @@ Real-time energy monitoring from [HomeWizard](https://www.homewizard.com) Energy
39
37
 
40
38
  ## Supported Devices
41
39
 
42
- | Device | Product Type | WebSocket | Battery Control |
43
- |--------|-------------|-----------|-----------------|
44
- | P1 Meter | HWE-P1 | Yes | Yes (as controller) |
45
- | kWh Meter 1-Phase | HWE-KWH1 / SDM230 | Yes | Yes (as controller) |
46
- | kWh Meter 3-Phase | HWE-KWH3 / SDM630 | Yes | Yes (as controller) |
47
- | Plug-In Battery | HWE-BAT | Yes | Controlled via P1/kWh |
40
+ | Device | Product Type |
41
+ |--------|--------------|
42
+ | P1 Meter | HWE-P1 |
43
+ | kWh Meter 1-Phase | HWE-KWH1 (also sold as SDM230) |
44
+ | kWh Meter 3-Phase | HWE-KWH3 (also sold as SDM630) |
45
+ | Plug-In Battery | HWE-BAT |
46
+
47
+ The Plug-In Battery is paired separately and shows up as its own device. To control charge/discharge mode and grid-feed permissions, you write to the `battery.*` data points of the P1 or kWh meter — that's where HomeWizard exposes the battery commands.
48
48
 
49
49
  ---
50
50
 
@@ -138,15 +138,13 @@ homewizard.0.
138
138
  └── system/ — System settings
139
139
  ├── cloud_enabled — Cloud communication (bool, R/W)
140
140
  ├── status_led_brightness_pct — LED brightness 0-100 (number, R/W)
141
- ├── api_v1_enabled — Legacy API v1 (bool, R/W)
141
+ ├── api_v1_enabled — Toggle the device's deprecated v1 API (bool, R/W — leave off)
142
142
  ├── reboot — Reboot device (button)
143
143
  └── identify — Blink LED (button)
144
144
  ```
145
145
 
146
146
  > States are created dynamically based on what the device reports. Not all devices have all states. kWh meters additionally provide apparent/reactive current, apparent/reactive power, and power factor states.
147
147
 
148
- > State names, descriptions and dropdown labels (e.g. `tariff`, `battery.mode`) appear in the ioBroker system language. The adapter reads `system.config.language` at startup — change it under *Settings → Base settings → Language* and restart the adapter.
149
-
150
148
  ---
151
149
 
152
150
  ## Troubleshooting
@@ -157,9 +155,9 @@ homewizard.0.
157
155
  - Check that multicast/mDNS traffic is not blocked by your router/firewall
158
156
 
159
157
  ### WebSocket keeps disconnecting
160
- - Check WiFi signal strength (`info.wifi_rssi_db`)consider moving the device closer to the router
161
- - The adapter automatically detects unstable connections (e.g. P1 meter in a basement) and switches to faster reconnect (60s instead of 5 min) with persistent REST fallback
162
- - The adapter never gives up: reconnects with exponential backoff, falls back to REST polling, and periodically retries mDNS in case the IP changed
158
+ - Check `info.wifi_rssi_db` — anything above -75 dBm is fine, weaker than -85 dBm explains frequent drops
159
+ - For devices with weak WiFi the adapter switches to a faster reconnect interval (60 s instead of 5 min) and keeps REST polling in the background so you don't lose data
160
+ - IP changes are picked up via mDNS no manual reconfiguration needed
163
161
 
164
162
  ### Token invalid after factory reset
165
163
  - Set the device's `remove` data point to `true`, then pair again
@@ -167,6 +165,16 @@ homewizard.0.
167
165
  ---
168
166
 
169
167
  ## Changelog
168
+ <!--
169
+ Placeholder for the next version (at the beginning of the line):
170
+ ### **WORK IN PROGRESS**
171
+ -->
172
+ ### 0.7.4 (2026-05-09)
173
+ - Adapter log messages are now English only, in line with the ioBroker community standard. Localized state names, descriptions and dropdown labels (11 languages) are unchanged.
174
+
175
+ ### 0.7.3 (2026-05-07)
176
+ - Less log spam when a device stays offline for longer periods — the initial `device unreachable` warning is enough; mDNS recovery attempts and offline-retry status now log at debug level only.
177
+
170
178
  ### 0.7.2 (2026-05-06)
171
179
  - 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
180
 
@@ -175,18 +183,11 @@ homewizard.0.
175
183
  - 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
184
 
177
185
  ### 0.7.0 (2026-05-06)
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).
186
+ - Adapter texts now follow your ioBroker system language: datapoint names, descriptions, and dropdown values for `tariff` and `battery.mode` in 11 languages (EN, DE, RU, PT, NL, FR, IT, ES, PL, UK, ZH-CN).
179
187
  - Power-quality and Belgian capacity-tariff datapoints carry inline descriptions — hover in admin to see what each one means.
180
188
  - 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
189
  - Minimum requirements: Node.js 22 and ioBroker Admin 7.8.23.
182
190
 
183
- ### 0.6.7 (2026-05-01)
184
- - Internal cleanup. No user-facing changes.
185
- - Documentation: rewrote release notes for v0.6.0–v0.6.6 in user-friendly style across all languages.
186
-
187
- ### 0.6.6 (2026-04-28)
188
- - Internal cleanup. No user-facing changes.
189
-
190
191
  ### Support Development
191
192
 
192
193
  This adapter is free and open source. If you find it useful, consider buying me a coffee:
package/build/main.js CHANGED
@@ -26,7 +26,6 @@ var import_coerce = require("./lib/coerce");
26
26
  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
- var import_i18n_logs = require("./lib/i18n-logs");
30
29
  var import_main_helpers = require("./lib/main-helpers");
31
30
  var import_state_manager = require("./lib/state-manager");
32
31
  var import_websocket_client = require("./lib/websocket-client");
@@ -56,42 +55,27 @@ class HomeWizard extends utils.Adapter {
56
55
  discoveredDuringPairing = [];
57
56
  unhandledRejectionHandler = null;
58
57
  uncaughtExceptionHandler = null;
59
- /** ioBroker system language — read once in `onReady` from `system.config`. EN fallback. */
60
- systemLang = "en";
61
58
  /** @param options Adapter options */
62
59
  constructor(options = {}) {
63
60
  super({ ...options, name: "homewizard" });
64
61
  this.on("ready", () => {
65
- this.onReady().catch(
66
- (err) => this.log.error((0, import_i18n_logs.tLog)(this.systemLang, "onReadyFailed", { error: (0, import_coerce.errText)(err) }))
67
- );
62
+ this.onReady().catch((err) => this.log.error(`onReady failed: ${(0, import_coerce.errText)(err)}`));
68
63
  });
69
64
  this.on("stateChange", (id, state) => {
70
- this.onStateChange(id, state).catch(
71
- (err) => this.log.error((0, import_i18n_logs.tLog)(this.systemLang, "stateChangeFailed", { error: (0, import_coerce.errText)(err) }))
72
- );
65
+ this.onStateChange(id, state).catch((err) => this.log.error(`stateChange failed: ${(0, import_coerce.errText)(err)}`));
73
66
  });
74
67
  this.on("unload", (callback) => this.onUnload(callback));
75
68
  this.unhandledRejectionHandler = (reason) => {
76
- this.log.error((0, import_i18n_logs.tLog)(this.systemLang, "unhandledRejection", { error: (0, import_coerce.errText)(reason) }));
69
+ this.log.error(`Unhandled rejection: ${(0, import_coerce.errText)(reason)}`);
77
70
  };
78
71
  this.uncaughtExceptionHandler = (err) => {
79
- this.log.error((0, import_i18n_logs.tLog)(this.systemLang, "uncaughtException", { error: err.message }));
72
+ this.log.error(`Uncaught exception: ${err.message}`);
80
73
  };
81
74
  process.on("unhandledRejection", this.unhandledRejectionHandler);
82
75
  process.on("uncaughtException", this.uncaughtExceptionHandler);
83
76
  }
84
77
  /** Adapter started */
85
78
  async onReady() {
86
- var _a;
87
- try {
88
- const sysCfg = await this.getForeignObjectAsync("system.config");
89
- const lang = (_a = sysCfg == null ? void 0 : sysCfg.common) == null ? void 0 : _a.language;
90
- if (typeof lang === "string" && lang.length > 0) {
91
- this.systemLang = lang;
92
- }
93
- } catch {
94
- }
95
79
  this.stateManager = new import_state_manager.StateManager(this);
96
80
  await this.setStateAsync("startPairing", { val: false, ack: true });
97
81
  await this.setStateAsync("pairingIp", { val: "", ack: true });
@@ -106,7 +90,7 @@ class HomeWizard extends utils.Adapter {
106
90
  await this.subscribeStatesAsync("*.remove");
107
91
  const devices = await this.loadDevicesFromObjects();
108
92
  if (devices.length === 0) {
109
- this.log.info((0, import_i18n_logs.tLog)(this.systemLang, "noDevicesConfigured"));
93
+ this.log.info(`No devices configured \u2014 set 'startPairing' to true to add a device`);
110
94
  await this.setStateAsync("info.connection", { val: false, ack: true });
111
95
  }
112
96
  for (const device of devices) {
@@ -199,11 +183,7 @@ class HomeWizard extends utils.Adapter {
199
183
  }
200
184
  this.discoveredDuringPairing.push(discovered);
201
185
  this.log.info(
202
- (0, import_i18n_logs.tLog)(this.systemLang, "deviceFound", {
203
- name: discovered.name,
204
- type: discovered.productType,
205
- ip: discovered.ip
206
- })
186
+ `Found ${discovered.name} (${discovered.productType}) at ${discovered.ip} \u2014 press the button on the device to pair`
207
187
  );
208
188
  }
209
189
  /**
@@ -279,7 +259,7 @@ class HomeWizard extends utils.Adapter {
279
259
  const client = new import_homewizard_client.HomeWizardClient(conn.ip, conn.config.token);
280
260
  try {
281
261
  if (id.endsWith(".system.reboot")) {
282
- this.log.info((0, import_i18n_logs.tLog)(this.systemLang, "rebootingDevice", { name: conn.config.productName, ip: conn.ip }));
262
+ this.log.info(`Rebooting ${conn.config.productName} (${conn.ip})`);
283
263
  await client.reboot();
284
264
  } else if (id.endsWith(".system.identify")) {
285
265
  await client.identify();
@@ -297,7 +277,7 @@ class HomeWizard extends utils.Adapter {
297
277
  } else if (id.endsWith(".battery.mode")) {
298
278
  const mode = (0, import_coerce.validateBatteryMode)(String(state.val));
299
279
  if (!mode) {
300
- this.log.warn((0, import_i18n_logs.tLog)(this.systemLang, "invalidBatteryMode", { value: String(state.val) }));
280
+ this.log.warn(`Invalid battery.mode value: '${String(state.val)}' \u2014 expected one of: zero, to_full, standby`);
301
281
  return;
302
282
  }
303
283
  await client.setBatteries({ mode });
@@ -306,7 +286,7 @@ class HomeWizard extends utils.Adapter {
306
286
  const result = (0, import_coerce.parseBatteryPermissions)(String(state.val));
307
287
  if (!result.ok) {
308
288
  this.log.warn(
309
- (0, import_i18n_logs.tLog)(this.systemLang, "invalidPermissionsJson", { error: result.reason, value: result.sample })
289
+ `Invalid JSON for battery.permissions: ${result.reason} \u2014 expected array, got: ${result.sample}`
310
290
  );
311
291
  return;
312
292
  }
@@ -314,7 +294,7 @@ class HomeWizard extends utils.Adapter {
314
294
  await this.setStateAsync(id, { val: state.val, ack: true });
315
295
  }
316
296
  } catch (err) {
317
- this.log.warn((0, import_i18n_logs.tLog)(this.systemLang, "failedToSetState", { id, error: (0, import_coerce.errText)(err) }));
297
+ this.log.warn(`Failed to set ${id}: ${(0, import_coerce.errText)(err)}`);
318
298
  }
319
299
  }
320
300
  /** Start pairing mode — discover devices and attempt to pair */
@@ -331,7 +311,9 @@ class HomeWizard extends utils.Adapter {
331
311
  this.pairingManualIp = (ipState == null ? void 0 : ipState.val) ? String(ipState.val).trim() : "";
332
312
  await this.setStateAsync("pairingIp", { val: "", ack: true });
333
313
  if (this.pairingManualIp) {
334
- this.log.info((0, import_i18n_logs.tLog)(this.systemLang, "pairingEnabledManual", { ip: this.pairingManualIp }));
314
+ this.log.info(
315
+ `Pairing mode enabled for ${this.pairingManualIp} \u2014 press the button on your HomeWizard device now (60 seconds timeout)`
316
+ );
335
317
  this.discoveredDuringPairing.push({
336
318
  ip: this.pairingManualIp,
337
319
  productType: "unknown",
@@ -339,7 +321,9 @@ class HomeWizard extends utils.Adapter {
339
321
  name: this.pairingManualIp
340
322
  });
341
323
  } else {
342
- this.log.info((0, import_i18n_logs.tLog)(this.systemLang, "pairingEnabledMdns"));
324
+ this.log.info(
325
+ `Pairing mode enabled \u2014 searching for devices via mDNS, press the button on your HomeWizard device now (60 seconds timeout)`
326
+ );
343
327
  if (!this.discovery) {
344
328
  this.discovery = new import_discovery.HomeWizardDiscovery(this.log);
345
329
  }
@@ -352,7 +336,7 @@ class HomeWizard extends utils.Adapter {
352
336
  }, PAIRING_POLL_MS);
353
337
  this.pairingTimer = this.setTimeout(() => {
354
338
  this.stopPairing();
355
- this.log.info((0, import_i18n_logs.tLog)(this.systemLang, "pairingTimeout"));
339
+ this.log.info(`Pairing mode automatically disabled after 60 seconds timeout`);
356
340
  }, PAIRING_TIMEOUT_MS);
357
341
  }
358
342
  /** Poll all discovered devices to attempt pairing */
@@ -362,11 +346,7 @@ class HomeWizard extends utils.Adapter {
362
346
  const client = new import_homewizard_client.HomeWizardClient(device.ip);
363
347
  const result = await client.requestPairing();
364
348
  this.log.info(
365
- (0, import_i18n_logs.tLog)(this.systemLang, "pairingSuccess", {
366
- name: device.name,
367
- type: device.productType,
368
- ip: device.ip
369
- })
349
+ `Successfully paired with ${device.name} (${device.productType}) at ${device.ip} \u2014 connecting...`
370
350
  );
371
351
  const authedClient = new import_homewizard_client.HomeWizardClient(device.ip, result.token);
372
352
  const info = await authedClient.getDeviceInfo();
@@ -418,7 +398,7 @@ class HomeWizard extends utils.Adapter {
418
398
  if (this.discovery || this.isPairing) {
419
399
  return;
420
400
  }
421
- this.log.info((0, import_i18n_logs.tLog)(this.systemLang, "searchingNewIp"));
401
+ this.log.debug(`Device unreachable \u2014 searching for new IP via mDNS`);
422
402
  this.discovery = new import_discovery.HomeWizardDiscovery(this.log);
423
403
  this.discovery.start((discovered) => {
424
404
  for (const conn of this.connections.values()) {
@@ -428,13 +408,7 @@ class HomeWizard extends utils.Adapter {
428
408
  if (discovered.ip === conn.ip || conn.wsAuthenticated) {
429
409
  return;
430
410
  }
431
- this.log.info(
432
- (0, import_i18n_logs.tLog)(this.systemLang, "foundAtNewIp", {
433
- name: conn.config.productName,
434
- newIp: discovered.ip,
435
- oldIp: conn.ip
436
- })
437
- );
411
+ this.log.info(`${conn.config.productName}: found at new IP ${discovered.ip} (was ${conn.ip})`);
438
412
  conn.ip = discovered.ip;
439
413
  conn.config.ip = discovered.ip;
440
414
  conn.wsFailCount = 0;
@@ -457,11 +431,8 @@ class HomeWizard extends utils.Adapter {
457
431
  this.stopIpRecovery();
458
432
  for (const conn of this.connections.values()) {
459
433
  if (!conn.wsAuthenticated && conn.wsFailCount > 0) {
460
- this.log.warn(
461
- (0, import_i18n_logs.tLog)(this.systemLang, "deviceOfflineRetrying", {
462
- name: conn.config.productName,
463
- seconds: WS_RECONNECT_MAX_MS / 1e3
464
- })
434
+ this.log.debug(
435
+ `${conn.config.productName}: device offline \u2014 will keep retrying every ${WS_RECONNECT_MAX_MS / 1e3}s`
465
436
  );
466
437
  }
467
438
  }
@@ -537,9 +508,7 @@ class HomeWizard extends utils.Adapter {
537
508
  }
538
509
  if (conn.lastErrorCode) {
539
510
  this.log.info(
540
- (0, import_i18n_logs.tLog)(this.systemLang, this.isUnstable(conn) ? "connectionRestoredUnstable" : "connectionRestored", {
541
- name: conn.config.productName
542
- })
511
+ this.isUnstable(conn) ? `${conn.config.productName}: connection restored (unstable mode)` : `${conn.config.productName}: connection restored`
543
512
  );
544
513
  conn.lastErrorCode = "";
545
514
  }
@@ -560,9 +529,9 @@ class HomeWizard extends utils.Adapter {
560
529
  conn.recentDisconnects = 0;
561
530
  }
562
531
  if (transition === "becameUnstable") {
563
- this.log.info((0, import_i18n_logs.tLog)(this.systemLang, "unstableDetected", { name: conn.config.productName }));
532
+ this.log.info(`${conn.config.productName}: unstable connection detected \u2014 using faster reconnect`);
564
533
  } else if (transition === "stabilized") {
565
- this.log.info((0, import_i18n_logs.tLog)(this.systemLang, "connectionStabilized", { name: conn.config.productName }));
534
+ this.log.info(`${conn.config.productName}: connection stabilized \u2014 using normal reconnect`);
566
535
  }
567
536
  }
568
537
  conn.wsAuthenticated = false;
@@ -683,9 +652,7 @@ class HomeWizard extends utils.Adapter {
683
652
  return;
684
653
  }
685
654
  const key = this.stateManager.devicePrefix(conn.config);
686
- this.log.info(
687
- (0, import_i18n_logs.tLog)(this.systemLang, "removingDevice", { name: conn.config.productName, serial: conn.config.serial })
688
- );
655
+ this.log.info(`Removing device ${conn.config.productName} (${conn.config.serial})`);
689
656
  (_a = conn.wsClient) == null ? void 0 : _a.close();
690
657
  if (conn.pollTimer) {
691
658
  this.clearInterval(conn.pollTimer);
@@ -740,7 +707,7 @@ class HomeWizard extends utils.Adapter {
740
707
  if (conn.authFailCount < MAX_AUTH_FAILURES) {
741
708
  return true;
742
709
  }
743
- this.log.warn((0, import_i18n_logs.tLog)(this.systemLang, "tokenInvalid", { name: conn.config.productName }));
710
+ this.log.warn(`${conn.config.productName}: token invalid \u2014 re-pair device to fix`);
744
711
  if (cleanupTimers) {
745
712
  if (conn.pollTimer) {
746
713
  this.clearInterval(conn.pollTimer);
@@ -769,15 +736,9 @@ class HomeWizard extends utils.Adapter {
769
736
  if (isRepeat) {
770
737
  this.log.debug(`${conn.config.productName} ${context}: ${(0, import_coerce.errText)(err)}`);
771
738
  } else if (errorCode === "NETWORK") {
772
- this.log.warn((0, import_i18n_logs.tLog)(this.systemLang, "deviceUnreachable", { name: conn.config.productName }));
739
+ this.log.warn(`${conn.config.productName}: device unreachable \u2014 will keep retrying`);
773
740
  } else {
774
- this.log.warn(
775
- (0, import_i18n_logs.tLog)(this.systemLang, "deviceErrorContext", {
776
- name: conn.config.productName,
777
- context,
778
- error: (0, import_coerce.errText)(err)
779
- })
780
- );
741
+ this.log.warn(`${conn.config.productName} ${context}: ${(0, import_coerce.errText)(err)}`);
781
742
  }
782
743
  }
783
744
  }
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 {\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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",
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 {\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\n /** @param options Adapter options */\n public constructor(options: Partial<utils.AdapterOptions> = {}) {\n super({ ...options, name: \"homewizard\" });\n // Wrap async handlers with .catch() so a rejection can never become an\n // unhandled promise rejection (\u2192 SIGKILL \u2192 js-controller restart loop).\n this.on(\"ready\", () => {\n this.onReady().catch((err: unknown) => this.log.error(`onReady failed: ${errText(err)}`));\n });\n this.on(\"stateChange\", (id, state) => {\n this.onStateChange(id, state).catch((err: unknown) => this.log.error(`stateChange failed: ${errText(err)}`));\n });\n this.on(\"unload\", callback => this.onUnload(callback));\n\n // Last-line-of-defence against unhandled rejections / sync throws from\n // fire-and-forget paths. The per-handler wrappers cover documented async\n // paths; this catches anything that slips past during refactors.\n this.unhandledRejectionHandler = (reason: unknown) => {\n this.log.error(`Unhandled rejection: ${errText(reason)}`);\n };\n this.uncaughtExceptionHandler = (err: Error) => {\n this.log.error(`Uncaught exception: ${err.message}`);\n };\n process.on(\"unhandledRejection\", this.unhandledRejectionHandler);\n process.on(\"uncaughtException\", this.uncaughtExceptionHandler);\n }\n\n /** Adapter started */\n private async onReady(): Promise<void> {\n this.stateManager = new StateManager(this);\n\n // `pairingIp` is declared in io-package.json instanceObjects \u2014 just reset state.\n // Reset pairing states on start (in case previous run was killed mid-pairing)\n await this.setStateAsync(\"startPairing\", { val: false, ack: true });\n await this.setStateAsync(\"pairingIp\", { val: \"\", ack: true });\n\n // Subscribe to pairing button and writable device states\n await this.subscribeStatesAsync(\"startPairing\");\n await this.subscribeStatesAsync(\"*.system.reboot\");\n await this.subscribeStatesAsync(\"*.system.identify\");\n await this.subscribeStatesAsync(\"*.system.cloud_enabled\");\n await this.subscribeStatesAsync(\"*.system.status_led_brightness_pct\");\n await this.subscribeStatesAsync(\"*.system.api_v1_enabled\");\n await this.subscribeStatesAsync(\"*.battery.mode\");\n await this.subscribeStatesAsync(\"*.battery.permissions\");\n await this.subscribeStatesAsync(\"*.remove\");\n\n // Load devices from device objects (not from adapter config)\n const devices = await this.loadDevicesFromObjects();\n if (devices.length === 0) {\n this.log.info(`No devices configured \u2014 set 'startPairing' to true to add a device`);\n await this.setStateAsync(\"info.connection\", { val: false, ack: true });\n }\n\n // Create connection entries for all configured devices\n for (const device of devices) {\n const key = this.stateManager.devicePrefix(device);\n await this.stateManager.cleanupMovedStates(device);\n await this.stateManager.createDeviceStates(device);\n const conn = createDeviceConnection(device, device.ip || \"\");\n this.connections.set(key, conn);\n\n // If we have a stored IP, connect immediately\n if (conn.ip) {\n this.log.debug(`Using stored IP ${conn.ip} for ${device.productName}`);\n void this.initDevice(conn);\n }\n }\n\n // Periodic system info poll\n this.systemPollTimer = this.setInterval(() => {\n void this.pollAllSystemInfo();\n }, SYSTEM_POLL_MS);\n\n this.updateGlobalConnection();\n }\n\n /**\n * Load device configs from existing device objects\n * Tokens are stored encrypted in device object native\n */\n private async loadDevicesFromObjects(): Promise<DeviceConfig[]> {\n const devices: DeviceConfig[] = [];\n\n // Also migrate from old adapter config if devices exist there\n 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 `Found ${discovered.name} (${discovered.productType}) at ${discovered.ip} \u2014 press the button on the device to pair`,\n );\n }\n\n /**\n * Adapter stopping \u2014 MUST be synchronous\n *\n * @param callback Completion callback\n */\n private onUnload(callback: () => void): void {\n 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(`Rebooting ${conn.config.productName} (${conn.ip})`);\n await client.reboot();\n } else if (id.endsWith(\".system.identify\")) {\n await client.identify();\n } else if (id.endsWith(\".system.cloud_enabled\")) {\n await client.setSystem({ cloud_enabled: !!state.val });\n await this.setStateAsync(id, { val: state.val, ack: true });\n } else if (id.endsWith(\".system.status_led_brightness_pct\")) {\n await client.setSystem({\n status_led_brightness_pct: Number(state.val),\n });\n await this.setStateAsync(id, { val: state.val, ack: true });\n } else if (id.endsWith(\".system.api_v1_enabled\")) {\n await client.setSystem({ api_v1_enabled: !!state.val });\n await this.setStateAsync(id, { val: state.val, ack: true });\n } else if (id.endsWith(\".battery.mode\")) {\n const mode = validateBatteryMode(String(state.val));\n if (!mode) {\n this.log.warn(`Invalid battery.mode value: '${String(state.val)}' \u2014 expected one of: zero, to_full, standby`);\n return;\n }\n await client.setBatteries({ mode });\n await this.setStateAsync(id, { val: state.val, ack: true });\n } else if (id.endsWith(\".battery.permissions\")) {\n const result = parseBatteryPermissions(String(state.val));\n if (!result.ok) {\n this.log.warn(\n `Invalid JSON for battery.permissions: ${result.reason} \u2014 expected array, got: ${result.sample}`,\n );\n return;\n }\n await client.setBatteries({ permissions: result.perms });\n await this.setStateAsync(id, { val: state.val, ack: true });\n }\n } catch (err) {\n this.log.warn(`Failed to set ${id}: ${errText(err)}`);\n }\n }\n\n /** Start pairing mode \u2014 discover devices and attempt to pair */\n private async startPairing(): Promise<void> {\n if (this.isPairing) {\n this.log.debug(\"Pairing already active\");\n return;\n }\n\n // Reset startPairing immediately so it doesn't survive a restart\n await this.setStateAsync(\"startPairing\", { val: false, ack: true });\n\n this.isPairing = true;\n this.discoveredDuringPairing = [];\n\n // Stop IP recovery if running \u2014 pairing takes priority\n this.stopIpRecovery();\n\n // Check if manual IP is set, then clear pairingIp immediately\n const ipState = await this.getStateAsync(\"pairingIp\");\n this.pairingManualIp = ipState?.val ? String(ipState.val).trim() : \"\";\n await this.setStateAsync(\"pairingIp\", { val: \"\", ack: true });\n\n if (this.pairingManualIp) {\n this.log.info(\n `Pairing mode enabled for ${this.pairingManualIp} \u2014 press the button on your HomeWizard device now (60 seconds timeout)`,\n );\n // Add as discovered device immediately\n this.discoveredDuringPairing.push({\n ip: this.pairingManualIp,\n productType: \"unknown\",\n serial: \"unknown\",\n name: this.pairingManualIp,\n });\n } else {\n this.log.info(\n `Pairing mode enabled \u2014 searching for devices via mDNS, press the button on your HomeWizard device now (60 seconds timeout)`,\n );\n // Restart mDNS browser to trigger fresh query \u2014 already-cached devices\n // won't be re-announced otherwise and pairing would never find them\n if (!this.discovery) {\n this.discovery = new HomeWizardDiscovery(this.log);\n }\n this.discovery.start(discovered => {\n this.onDeviceDiscovered(discovered);\n });\n }\n\n // Poll discovered devices for pairing\n this.pairingPollTimer = this.setInterval(() => {\n void this.pollPairing();\n }, PAIRING_POLL_MS);\n\n // Timeout pairing\n this.pairingTimer = this.setTimeout(() => {\n this.stopPairing();\n this.log.info(`Pairing mode automatically disabled after 60 seconds timeout`);\n }, PAIRING_TIMEOUT_MS);\n }\n\n /** Poll all discovered devices to attempt pairing */\n private async pollPairing(): Promise<void> {\n for (const device of this.discoveredDuringPairing) {\n try {\n const client = new HomeWizardClient(device.ip);\n const result = await client.requestPairing();\n\n // Success! Button was pressed\n this.log.info(\n `Successfully paired with ${device.name} (${device.productType}) at ${device.ip} \u2014 connecting...`,\n );\n\n // Get device info\n const authedClient = new HomeWizardClient(device.ip, result.token);\n const info = await authedClient.getDeviceInfo();\n\n const deviceConfig: DeviceConfig = {\n token: result.token,\n productType: info.product_type,\n serial: info.serial,\n productName: info.product_name,\n ip: device.ip,\n };\n\n // Save to device object (no adapter restart!)\n await this.saveDeviceToObject(deviceConfig);\n await this.stateManager.createDeviceStates(deviceConfig);\n\n // 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 // Internal recovery \u2014 debug only. The initial disconnect already produced\n // one warn via logDeviceError; repeating that hourly while a device stays\n // offline is just spam.\n this.log.debug(`Device unreachable \u2014 searching for new IP via mDNS`);\n\n this.discovery = new HomeWizardDiscovery(this.log);\n this.discovery.start(discovered => {\n // Match against disconnected devices\n for (const conn of this.connections.values()) {\n if (conn.config.serial !== discovered.serial) {\n continue;\n }\n if (discovered.ip === conn.ip || conn.wsAuthenticated) {\n return; // Same IP or already connected\n }\n\n this.log.info(`${conn.config.productName}: found at new IP ${discovered.ip} (was ${conn.ip})`);\n\n // Update IP and persist \u2014 reset stability (new network conditions)\n conn.ip = discovered.ip;\n conn.config.ip = discovered.ip;\n conn.wsFailCount = 0;\n conn.recentDisconnects = 0;\n 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\n // backoff. Don't log per-device warns here: the initial disconnect already\n // produced a `deviceUnreachable` warn via logDeviceError; spamming the\n // user hourly while the device stays offline adds zero information. If\n // someone needs to see retry cadence they can enable debug logging.\n this.ipRecoveryTimer = this.setTimeout(() => {\n this.ipRecoveryTimer = undefined;\n this.stopIpRecovery();\n\n for (const conn of this.connections.values()) {\n if (!conn.wsAuthenticated && conn.wsFailCount > 0) {\n this.log.debug(\n `${conn.config.productName}: device offline \u2014 will keep retrying every ${WS_RECONNECT_MAX_MS / 1000}s`,\n );\n }\n }\n }, IP_RECOVERY_TIMEOUT_MS);\n }\n\n /** Stop mDNS IP recovery */\n private stopIpRecovery(): void {\n if (this.ipRecoveryTimer) {\n this.clearTimeout(this.ipRecoveryTimer);\n this.ipRecoveryTimer = undefined;\n }\n if (this.discovery && !this.isPairing) {\n this.discovery.stop();\n this.discovery = null;\n }\n }\n\n /**\n * Initialize a newly discovered device \u2014 fetch info and connect WebSocket\n *\n * @param conn Device connection with IP set\n */\n private async initDevice(conn: DeviceConnection): Promise<void> {\n 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 this.isUnstable(conn)\n ? `${conn.config.productName}: connection restored (unstable mode)`\n : `${conn.config.productName}: connection restored`,\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(`${conn.config.productName}: unstable connection detected \u2014 using faster reconnect`);\n } else if (transition === \"stabilized\") {\n this.log.info(`${conn.config.productName}: connection stabilized \u2014 using normal reconnect`);\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(`Removing device ${conn.config.productName} (${conn.config.serial})`);\n\n // Disconnect\n conn.wsClient?.close();\n if (conn.pollTimer) {\n this.clearInterval(conn.pollTimer);\n }\n if (conn.reconnectTimer) {\n this.clearTimeout(conn.reconnectTimer);\n }\n this.connections.delete(key);\n\n // Delete device object and all states (no adapter restart!)\n await this.stateManager.removeDevice(conn.config);\n\n this.updateGlobalConnection();\n }\n\n /**\n * Find connection for a state ID. Delegates to the pure helper so the\n * lookup math is unit-tested separately (`lib/main-helpers.test.ts`).\n *\n * @param stateId Full state ID\n */\n private findConnectionForState(stateId: string): DeviceConnection | undefined {\n return resolveConnectionForState(stateId, this.namespace, this.connections);\n }\n\n /**\n * Whether a device has unstable connectivity (frequent short-lived connections).\n * Unstable devices get faster reconnect and persistent REST fallback.\n *\n * @param conn Device connection\n */\n private isUnstable(conn: DeviceConnection): boolean {\n return conn.recentDisconnects >= UNSTABLE_DISCONNECT_THRESHOLD;\n }\n\n /**\n * Handle a possible auth failure on a device connection. Counts failures and,\n * once `MAX_AUTH_FAILURES` is reached, warns the user and (optionally) tears\n * down active timers and the WebSocket \u2014 stops bombarding the device with a\n * known-bad token.\n *\n * @param conn Device connection.\n * @param error The error from the failing call (any error type accepted).\n * @param cleanupTimers If `true`, clears poll/reconnect timers and closes the WS\n * on threshold reach. Used by REST-fallback paths where\n * the WS would otherwise keep retrying indefinitely. The\n * WS-disconnect path passes `false` because the caller\n * decides the next step itself.\n * @returns `true` if the caller should continue normal flow (no auth-stop),\n * `false` if the auth-stop fired and the caller should bail out.\n */\n private handleAuthFailure(conn: DeviceConnection, error: unknown, cleanupTimers: boolean): boolean {\n if (!(error instanceof HomeWizardApiError) || error.errorCode !== \"user:unauthorized\") {\n return true;\n }\n conn.authFailCount++;\n if (conn.authFailCount < MAX_AUTH_FAILURES) {\n return true;\n }\n this.log.warn(`${conn.config.productName}: token invalid \u2014 re-pair device to fix`);\n if (cleanupTimers) {\n if (conn.pollTimer) {\n this.clearInterval(conn.pollTimer);\n conn.pollTimer = undefined;\n }\n if (conn.reconnectTimer) {\n this.clearTimeout(conn.reconnectTimer);\n conn.reconnectTimer = undefined;\n }\n conn.wsClient?.close();\n }\n return false;\n }\n\n /**\n * Log device error with deduplication (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(`${conn.config.productName}: device unreachable \u2014 will keep retrying`);\n } else {\n this.log.warn(`${conn.config.productName} ${context}: ${errText(err)}`);\n }\n }\n}\n\nif (require.main !== module) {\n module.exports = (options: Partial<utils.AdapterOptions> | undefined) => new HomeWizard(options);\n} else {\n (() => new HomeWizard())();\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["resolveConnectionForState"]
7
7
  }
package/io-package.json CHANGED
@@ -1,8 +1,34 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "homewizard",
4
- "version": "0.7.2",
4
+ "version": "0.7.4",
5
5
  "news": {
6
+ "0.7.4": {
7
+ "en": "Adapter log messages are now English only, in line with the ioBroker community standard. Localized state names, descriptions and dropdown labels (11 languages) remain unchanged.",
8
+ "de": "Adapter-Logs sind jetzt nur noch auf Englisch, gemäß ioBroker-Community-Standard. Lokalisierte Datenpunkt-Namen, Beschreibungen und Dropdown-Labels (11 Sprachen) bleiben erhalten.",
9
+ "ru": "Сообщения журнала теперь только на английском, согласно стандарту сообщества ioBroker. Локализованные имена состояний, описания и подписи (11 языков) сохраняются.",
10
+ "pt": "As mensagens de log do adaptador agora são apenas em inglês, conforme o padrão da comunidade ioBroker. Nomes de estados, descrições e rótulos localizados (11 idiomas) permanecem inalterados.",
11
+ "nl": "Adapter-logberichten zijn nu alleen Engels, conform de ioBroker-communitystandaard. Gelokaliseerde statusnamen, beschrijvingen en dropdown-labels (11 talen) blijven ongewijzigd.",
12
+ "fr": "Les messages de log de l'adaptateur sont désormais uniquement en anglais, conformément au standard de la communauté ioBroker. Les noms d'états, descriptions et libellés localisés (11 langues) restent inchangés.",
13
+ "it": "I messaggi di log dell'adattatore sono ora solo in inglese, secondo lo standard della community ioBroker. I nomi degli stati, descrizioni ed etichette localizzati (11 lingue) rimangono invariati.",
14
+ "es": "Los mensajes de registro del adaptador ahora son solo en inglés, conforme al estándar de la comunidad ioBroker. Los nombres de estados, descripciones y etiquetas localizados (11 idiomas) permanecen sin cambios.",
15
+ "pl": "Komunikaty dziennika adaptera są teraz wyłącznie po angielsku, zgodnie ze standardem społeczności ioBroker. Zlokalizowane nazwy stanów, opisy i etykiety (11 języków) pozostają bez zmian.",
16
+ "uk": "Повідомлення журналу адаптера тепер лише англійською, відповідно до стандарту спільноти ioBroker. Локалізовані назви станів, описи та мітки (11 мов) залишаються без змін.",
17
+ "zh-cn": "适配器日志消息现在仅为英文,符合 ioBroker 社区标准。本地化的数据点名称、描述和下拉标签(11 种语言)保持不变。"
18
+ },
19
+ "0.7.3": {
20
+ "en": "Less log spam for devices that stay offline for longer periods. The initial unreachable warning is enough; hourly mDNS recovery attempts and offline-retry status now log at debug level only.",
21
+ "de": "Weniger Log-Spam für Geräte mit längerem Ausfall. Der erste Unerreichbarkeits-Warnhinweis reicht; stündliche mDNS-Recovery-Versuche und Offline-Retry-Status erscheinen jetzt nur noch im Debug-Log.",
22
+ "ru": "Меньше спама в логе при длительном отсутствии устройства. Первого предупреждения о недоступности достаточно; ежечасные попытки mDNS-восстановления и статусы повторных попыток теперь только в debug.",
23
+ "pt": "Menos spam no log quando um dispositivo permanece offline. O aviso inicial de inacessibilidade é suficiente; tentativas horárias de recuperação por mDNS e status de retry passam agora a debug.",
24
+ "nl": "Minder log-spam voor apparaten die langer offline blijven. De eerste onbereikbaarheidswaarschuwing volstaat; uurlijkse mDNS-recovery-pogingen en offline-retry-status nu alleen op debug.",
25
+ "fr": "Moins de spam dans les logs pour les appareils restant hors-ligne. Le premier avertissement suffit ; les tentatives mDNS horaires et le statut de réessai passent en debug.",
26
+ "it": "Meno spam nel log per dispositivi a lungo offline. Il primo avviso di non raggiungibilità è sufficiente; i tentativi orari di recovery mDNS e lo stato di retry passano a debug.",
27
+ "es": "Menos spam en el log para dispositivos que permanecen offline mucho tiempo. La advertencia inicial de inalcanzable es suficiente; los intentos horarios de mDNS y el estado de reintentos pasan a debug.",
28
+ "pl": "Mniej spamu w logu dla urządzeń długo offline. Pierwsze ostrzeżenie o niedostępności wystarczy; cogodzinne próby odzyskiwania mDNS i status ponawiania trafiają teraz do debug.",
29
+ "uk": "Менше спаму в журналі для пристроїв, що тривалий час офлайн. Першого попередження про недоступність достатньо; погодинні спроби mDNS-відновлення та статус повторних спроб тепер у debug.",
30
+ "zh-cn": "对长时间离线的设备减少日志噪音。首次「不可达」告警已足够;每小时的 mDNS 恢复尝试与离线重试状态现仅记录在 debug 级别。"
31
+ },
6
32
  "0.7.2": {
7
33
  "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
34
  "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.",
@@ -67,32 +93,6 @@
67
93
  "pl": "Wewnętrzne porządki. Brak zmian widocznych dla użytkownika.",
68
94
  "uk": "Внутрішнє прибирання. Без помітних для користувача змін.",
69
95
  "zh-cn": "内部清理。对用户无可见变化。"
70
- },
71
- "0.6.5": {
72
- "en": "Crash defense: process-level error handlers. Min js-controller restored to >=6.0.11.",
73
- "de": "Crash-Schutz: process-level Error-Handler. Min js-controller zurück auf >=6.0.11.",
74
- "ru": "Защита от сбоев: process-level обработчики ошибок. Мин. js-controller восстановлен на >=6.0.11.",
75
- "pt": "Proteção contra falhas: handlers de erro process-level. Mín. js-controller restaurado para >=6.0.11.",
76
- "nl": "Crashbescherming: process-level error-handlers. Min. js-controller hersteld op >=6.0.11.",
77
- "fr": "Défense contre les crashs : handlers d'erreur process-level. Min js-controller rétabli à >=6.0.11.",
78
- "it": "Difesa anti-crash: handler di errore process-level. Min js-controller ripristinato a >=6.0.11.",
79
- "es": "Defensa contra fallos: handlers de error process-level. Mín. js-controller restaurado a >=6.0.11.",
80
- "pl": "Ochrona przed crashem: handlery błędów process-level. Min. js-controller przywrócony do >=6.0.11.",
81
- "uk": "Захист від збоїв: process-level обробники помилок. Мін. js-controller повернено на >=6.0.11.",
82
- "zh-cn": "崩溃防护:process-level 错误处理器。最低 js-controller 恢复为 >=6.0.11。"
83
- },
84
- "0.6.4": {
85
- "en": "Internal hardening. No user-facing changes.",
86
- "de": "Interne Härtung. Keine User-sichtbaren Änderungen.",
87
- "ru": "Внутреннее усиление. Без видимых изменений для пользователя.",
88
- "pt": "Endurecimento interno. Sem alterações visíveis para o usuário.",
89
- "nl": "Interne verharding. Geen wijzigingen voor de gebruiker.",
90
- "fr": "Durcissement interne. Aucune modification visible pour l'utilisateur.",
91
- "it": "Hardening interno. Nessuna modifica visibile all'utente.",
92
- "es": "Endurecimiento interno. Sin cambios visibles para el usuario.",
93
- "pl": "Wewnętrzne wzmocnienie. Brak zmian widocznych dla użytkownika.",
94
- "uk": "Внутрішнє зміцнення. Без помітних для користувача змін.",
95
- "zh-cn": "内部加固。对用户无可见变化。"
96
96
  }
97
97
  },
98
98
  "titleLang": {
@@ -109,17 +109,17 @@
109
109
  "zh-cn": "HomeWizard Energy"
110
110
  },
111
111
  "desc": {
112
- "en": "Real-time energy monitoring from HomeWizard devices (P1 Meter, kWh Meter, Battery) via API v2 with WebSocket push",
113
- "de": "Echtzeit-Energieüberwachung von HomeWizard-Geräten (P1 Meter, kWh Meter, Batterie) via API v2 mit WebSocket-Push",
114
- "ru": "Мониторинг энергии в реальном времени от устройств HomeWizard (P1 Meter, kWh Meter, Battery) через API v2 с WebSocket push",
115
- "pt": "Monitoramento de energia em tempo real de dispositivos HomeWizard (P1 Meter, kWh Meter, Battery) via API v2 com WebSocket push",
116
- "nl": "Realtime energiemonitoring van HomeWizard-apparaten (P1 Meter, kWh Meter, Battery) via API v2 met WebSocket push",
117
- "fr": "Surveillance énergétique en temps réel des appareils HomeWizard (P1 Meter, kWh Meter, Battery) via API v2 avec WebSocket push",
118
- "it": "Monitoraggio energetico in tempo reale da dispositivi HomeWizard (P1 Meter, kWh Meter, Battery) via API v2 con WebSocket push",
119
- "es": "Monitoreo de energía en tiempo real de dispositivos HomeWizard (P1 Meter, kWh Meter, Battery) vía API v2 con WebSocket push",
120
- "pl": "Monitorowanie energii w czasie rzeczywistym z urządzeń HomeWizard (P1 Meter, kWh Meter, Battery) przez API v2 z WebSocket push",
121
- "uk": "Моніторинг енергії в реальному часі від пристроїв HomeWizard (P1 Meter, kWh Meter, Battery) через API v2 з WebSocket push",
122
- "zh-cn": "通过 API v2 WebSocket 推送实时监控 HomeWizard 设备(P1 Meter、kWh Meter、Battery)的能源数据"
112
+ "en": "Real-time energy monitoring for HomeWizard Energy devices with API v2",
113
+ "de": "Echtzeit-Energieüberwachung für HomeWizard-Energie-Geräte mit API v2",
114
+ "ru": "Мониторинг энергии в реальном времени для устройств HomeWizard Energy с API v2",
115
+ "pt": "Monitoramento de energia em tempo real para dispositivos HomeWizard Energy com API v2",
116
+ "nl": "Realtime energiemonitoring voor HomeWizard Energy-apparaten met API v2",
117
+ "fr": "Surveillance énergétique en temps réel pour les appareils HomeWizard Energy avec API v2",
118
+ "it": "Monitoraggio energetico in tempo reale per dispositivi HomeWizard Energy con API v2",
119
+ "es": "Monitoreo de energía en tiempo real para dispositivos HomeWizard Energy con API v2",
120
+ "pl": "Monitorowanie energii w czasie rzeczywistym dla urządzeń HomeWizard Energy z API v2",
121
+ "uk": "Моніторинг енергії в реальному часі для пристроїв HomeWizard Energy з API v2",
122
+ "zh-cn": "为支持 API v2 HomeWizard Energy 设备提供实时能源监控"
123
123
  },
124
124
  "authors": [
125
125
  "krobi <krobi@power-dreams.com>"
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "iobroker.homewizard",
3
- "version": "0.7.2",
4
- "description": "ioBroker adapter for HomeWizard Energy devices real-time energy monitoring via API v2 with WebSocket push",
3
+ "version": "0.7.4",
4
+ "description": "ioBroker adapter for HomeWizard Energy devices with API v2",
5
5
  "author": {
6
6
  "name": "krobi",
7
7
  "email": "krobi@power-dreams.com"
@@ -1,384 +0,0 @@
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 i18n_logs_exports = {};
20
- __export(i18n_logs_exports, {
21
- LOG_STRINGS: () => LOG_STRINGS,
22
- tLog: () => tLog
23
- });
24
- module.exports = __toCommonJS(i18n_logs_exports);
25
- const SUPPORTED_LANGS = ["en", "de", "ru", "pt", "nl", "fr", "it", "es", "pl", "uk", "zh-cn"];
26
- function fmt(template, params) {
27
- if (!params) {
28
- return template;
29
- }
30
- return template.replace(/\{(\w+)\}/g, (_, key) => {
31
- const v = params[key];
32
- if (v === null) {
33
- return "(none)";
34
- }
35
- if (v === void 0) {
36
- return `{${key}}`;
37
- }
38
- return String(v);
39
- });
40
- }
41
- const LOG_STRINGS = {
42
- // ──────── Adapter lifecycle / crash defense ────────
43
- onReadyFailed: {
44
- en: "onReady failed: {error}",
45
- de: "onReady fehlgeschlagen: {error}",
46
- ru: "onReady \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0441\u044F \u0441 \u043E\u0448\u0438\u0431\u043A\u043E\u0439: {error}",
47
- pt: "onReady falhou: {error}",
48
- nl: "onReady is mislukt: {error}",
49
- fr: "onReady a \xE9chou\xE9 : {error}",
50
- it: "onReady non riuscito: {error}",
51
- es: "onReady fall\xF3: {error}",
52
- pl: "onReady nie powi\xF3d\u0142 si\u0119: {error}",
53
- uk: "onReady \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0432\u0441\u044F \u0437 \u043F\u043E\u043C\u0438\u043B\u043A\u043E\u044E: {error}",
54
- "zh-cn": "onReady \u5931\u8D25\uFF1A{error}"
55
- },
56
- stateChangeFailed: {
57
- en: "stateChange failed: {error}",
58
- de: "stateChange fehlgeschlagen: {error}",
59
- ru: "stateChange \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0441\u044F \u0441 \u043E\u0448\u0438\u0431\u043A\u043E\u0439: {error}",
60
- pt: "stateChange falhou: {error}",
61
- nl: "stateChange is mislukt: {error}",
62
- fr: "stateChange a \xE9chou\xE9 : {error}",
63
- it: "stateChange non riuscito: {error}",
64
- es: "stateChange fall\xF3: {error}",
65
- pl: "stateChange nie powi\xF3d\u0142 si\u0119: {error}",
66
- uk: "stateChange \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0432\u0441\u044F \u0437 \u043F\u043E\u043C\u0438\u043B\u043A\u043E\u044E: {error}",
67
- "zh-cn": "stateChange \u5931\u8D25\uFF1A{error}"
68
- },
69
- unhandledRejection: {
70
- en: "Unhandled rejection: {error}",
71
- de: "Unbehandelte Promise-Rejection: {error}",
72
- ru: "\u041D\u0435\u043E\u0431\u0440\u0430\u0431\u043E\u0442\u0430\u043D\u043D\u044B\u0439 rejection: {error}",
73
- pt: "Rejei\xE7\xE3o n\xE3o tratada: {error}",
74
- nl: "Onafgehandelde rejection: {error}",
75
- fr: "Rejet non g\xE9r\xE9 : {error}",
76
- it: "Rejection non gestita: {error}",
77
- es: "Rechazo no manejado: {error}",
78
- pl: "Nieobs\u0142u\u017Cone odrzucenie: {error}",
79
- uk: "\u041D\u0435\u043E\u0431\u0440\u043E\u0431\u043B\u0435\u043D\u0438\u0439 rejection: {error}",
80
- "zh-cn": "\u672A\u5904\u7406\u7684 rejection\uFF1A{error}"
81
- },
82
- uncaughtException: {
83
- en: "Uncaught exception: {error}",
84
- de: "Nicht abgefangene Exception: {error}",
85
- ru: "\u041D\u0435\u043E\u0431\u0440\u0430\u0431\u043E\u0442\u0430\u043D\u043D\u043E\u0435 \u0438\u0441\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u0435: {error}",
86
- pt: "Exce\xE7\xE3o n\xE3o capturada: {error}",
87
- nl: "Niet-opgevangen exception: {error}",
88
- fr: "Exception non captur\xE9e : {error}",
89
- it: "Eccezione non catturata: {error}",
90
- es: "Excepci\xF3n no capturada: {error}",
91
- pl: "Nieprzechwycony wyj\u0105tek: {error}",
92
- uk: "\u041D\u0435\u043F\u0435\u0440\u0435\u0445\u043E\u043F\u043B\u0435\u043D\u0435 \u0432\u0438\u043A\u043B\u044E\u0447\u0435\u043D\u043D\u044F: {error}",
93
- "zh-cn": "\u672A\u6355\u83B7\u7684\u5F02\u5E38\uFF1A{error}"
94
- },
95
- // ──────── Pairing flow ────────
96
- noDevicesConfigured: {
97
- en: "No devices configured \u2014 set 'startPairing' to true to add a device",
98
- de: "Keine Ger\xE4te konfiguriert \u2014 'startPairing' auf true setzen, um ein Ger\xE4t hinzuzuf\xFCgen",
99
- ru: "\u041D\u0435\u0442 \u043D\u0430\u0441\u0442\u0440\u043E\u0435\u043D\u043D\u044B\u0445 \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432 \u2014 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u0435 'startPairing' \u0432 true, \u0447\u0442\u043E\u0431\u044B \u0434\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u043E",
100
- pt: "Nenhum dispositivo configurado \u2014 defina 'startPairing' como true para adicionar um dispositivo",
101
- nl: "Geen apparaten geconfigureerd \u2014 zet 'startPairing' op true om een apparaat toe te voegen",
102
- fr: "Aucun appareil configur\xE9 \u2014 d\xE9finissez 'startPairing' sur true pour ajouter un appareil",
103
- it: "Nessun dispositivo configurato \u2014 imposta 'startPairing' su true per aggiungere un dispositivo",
104
- es: "No hay dispositivos configurados \u2014 establece 'startPairing' en true para a\xF1adir uno",
105
- pl: "Brak skonfigurowanych urz\u0105dze\u0144 \u2014 ustaw 'startPairing' na true, aby doda\u0107 urz\u0105dzenie",
106
- uk: "\u041D\u0435\u043C\u0430\u0454 \u043D\u0430\u043B\u0430\u0448\u0442\u043E\u0432\u0430\u043D\u0438\u0445 \u043F\u0440\u0438\u0441\u0442\u0440\u043E\u0457\u0432 \u2014 \u0432\u0441\u0442\u0430\u043D\u043E\u0432\u0456\u0442\u044C 'startPairing' \u043D\u0430 true, \u0449\u043E\u0431 \u0434\u043E\u0434\u0430\u0442\u0438 \u043F\u0440\u0438\u0441\u0442\u0440\u0456\u0439",
107
- "zh-cn": "\u672A\u914D\u7F6E\u8BBE\u5907 \u2014 \u5C06 'startPairing' \u8BBE\u7F6E\u4E3A true \u4EE5\u6DFB\u52A0\u8BBE\u5907"
108
- },
109
- deviceFound: {
110
- en: "Found {name} ({type}) at {ip} \u2014 press the button on the device to pair",
111
- de: "Gefunden: {name} ({type}) unter {ip} \u2014 Knopf am Ger\xE4t dr\xFCcken, um zu koppeln",
112
- ru: "\u041D\u0430\u0439\u0434\u0435\u043D\u043E {name} ({type}) \u043F\u043E \u0430\u0434\u0440\u0435\u0441\u0443 {ip} \u2014 \u043D\u0430\u0436\u043C\u0438\u0442\u0435 \u043A\u043D\u043E\u043F\u043A\u0443 \u043D\u0430 \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u0435 \u0434\u043B\u044F \u0441\u043E\u043F\u0440\u044F\u0436\u0435\u043D\u0438\u044F",
113
- pt: "Encontrado {name} ({type}) em {ip} \u2014 pressione o bot\xE3o do dispositivo para emparelhar",
114
- nl: "Gevonden: {name} ({type}) op {ip} \u2014 druk op de knop op het apparaat om te koppelen",
115
- fr: "Trouv\xE9 : {name} ({type}) \xE0 {ip} \u2014 appuyez sur le bouton de l'appareil pour l'appairer",
116
- it: "Trovato {name} ({type}) a {ip} \u2014 premi il pulsante sul dispositivo per accoppiare",
117
- es: "Encontrado {name} ({type}) en {ip} \u2014 pulsa el bot\xF3n del dispositivo para emparejar",
118
- pl: "Znaleziono {name} ({type}) pod {ip} \u2014 naci\u015Bnij przycisk na urz\u0105dzeniu, aby sparowa\u0107",
119
- uk: "\u0417\u043D\u0430\u0439\u0434\u0435\u043D\u043E {name} ({type}) \u0437\u0430 \u0430\u0434\u0440\u0435\u0441\u043E\u044E {ip} \u2014 \u043D\u0430\u0442\u0438\u0441\u043D\u0456\u0442\u044C \u043A\u043D\u043E\u043F\u043A\u0443 \u043D\u0430 \u043F\u0440\u0438\u0441\u0442\u0440\u043E\u0457 \u0434\u043B\u044F \u0441\u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043D\u044F",
120
- "zh-cn": "\u5DF2\u627E\u5230 {name} ({type}) \u4F4D\u4E8E {ip} \u2014 \u6309\u8BBE\u5907\u4E0A\u7684\u6309\u94AE\u914D\u5BF9"
121
- },
122
- pairingEnabledManual: {
123
- en: "Pairing mode enabled for {ip} \u2014 press the button on your HomeWizard device now (60 seconds timeout)",
124
- de: "Pairing-Modus aktiv f\xFCr {ip} \u2014 jetzt Knopf am HomeWizard-Ger\xE4t dr\xFCcken (60 Sekunden Timeout)",
125
- ru: "\u0420\u0435\u0436\u0438\u043C \u0441\u043E\u043F\u0440\u044F\u0436\u0435\u043D\u0438\u044F \u0432\u043A\u043B\u044E\u0447\u0451\u043D \u0434\u043B\u044F {ip} \u2014 \u043D\u0430\u0436\u043C\u0438\u0442\u0435 \u043A\u043D\u043E\u043F\u043A\u0443 \u043D\u0430 \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u0435 HomeWizard (\u0442\u0430\u0439\u043C-\u0430\u0443\u0442 60 \u0441\u0435\u043A\u0443\u043D\u0434)",
126
- pt: "Modo de emparelhamento ativo para {ip} \u2014 pressione o bot\xE3o do dispositivo HomeWizard agora (60 segundos)",
127
- nl: "Koppelmodus actief voor {ip} \u2014 druk nu op de knop van uw HomeWizard-apparaat (60 seconden time-out)",
128
- fr: "Mode d'appairage activ\xE9 pour {ip} \u2014 appuyez sur le bouton de l'appareil HomeWizard (timeout 60 secondes)",
129
- it: "Modalit\xE0 di accoppiamento attiva per {ip} \u2014 premi ora il pulsante sul dispositivo HomeWizard (timeout 60 secondi)",
130
- es: "Modo de emparejamiento activo para {ip} \u2014 pulsa ahora el bot\xF3n del dispositivo HomeWizard (60 segundos)",
131
- pl: "Tryb parowania aktywny dla {ip} \u2014 naci\u015Bnij teraz przycisk na urz\u0105dzeniu HomeWizard (timeout 60 sekund)",
132
- uk: "\u0420\u0435\u0436\u0438\u043C \u0441\u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043D\u044F \u0430\u043A\u0442\u0438\u0432\u043D\u0438\u0439 \u0434\u043B\u044F {ip} \u2014 \u043D\u0430\u0442\u0438\u0441\u043D\u0456\u0442\u044C \u0437\u0430\u0440\u0430\u0437 \u043A\u043D\u043E\u043F\u043A\u0443 \u043D\u0430 \u043F\u0440\u0438\u0441\u0442\u0440\u043E\u0457 HomeWizard (\u0442\u0430\u0439\u043C-\u0430\u0443\u0442 60 \u0441\u0435\u043A\u0443\u043D\u0434)",
133
- "zh-cn": "\u5DF2\u4E3A {ip} \u542F\u7528\u914D\u5BF9\u6A21\u5F0F \u2014 \u73B0\u5728\u8BF7\u6309\u4E0B HomeWizard \u8BBE\u5907\u4E0A\u7684\u6309\u94AE\uFF0860 \u79D2\u8D85\u65F6\uFF09"
134
- },
135
- pairingEnabledMdns: {
136
- en: "Pairing mode enabled \u2014 searching for devices via mDNS, press the button on your HomeWizard device now (60 seconds timeout)",
137
- de: "Pairing-Modus aktiv \u2014 Ger\xE4te-Suche via mDNS, jetzt Knopf am HomeWizard-Ger\xE4t dr\xFCcken (60 Sekunden Timeout)",
138
- ru: "\u0420\u0435\u0436\u0438\u043C \u0441\u043E\u043F\u0440\u044F\u0436\u0435\u043D\u0438\u044F \u0432\u043A\u043B\u044E\u0447\u0451\u043D \u2014 \u043F\u043E\u0438\u0441\u043A \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432 \u0447\u0435\u0440\u0435\u0437 mDNS, \u043D\u0430\u0436\u043C\u0438\u0442\u0435 \u043A\u043D\u043E\u043F\u043A\u0443 \u043D\u0430 \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u0435 HomeWizard (60 \u0441\u0435\u043A\u0443\u043D\u0434)",
139
- pt: "Modo de emparelhamento ativo \u2014 procurando dispositivos via mDNS, pressione o bot\xE3o agora (60 segundos)",
140
- nl: "Koppelmodus actief \u2014 apparaten zoeken via mDNS, druk nu op de knop van uw HomeWizard-apparaat (60 seconden)",
141
- fr: "Mode d'appairage activ\xE9 \u2014 recherche d'appareils via mDNS, appuyez maintenant sur le bouton (timeout 60 secondes)",
142
- it: "Modalit\xE0 di accoppiamento attiva \u2014 ricerca dispositivi tramite mDNS, premi ora il pulsante (timeout 60 secondi)",
143
- es: "Modo de emparejamiento activo \u2014 buscando dispositivos v\xEDa mDNS, pulsa ahora el bot\xF3n (60 segundos)",
144
- pl: "Tryb parowania aktywny \u2014 wyszukiwanie urz\u0105dze\u0144 przez mDNS, naci\u015Bnij teraz przycisk (timeout 60 sekund)",
145
- uk: "\u0420\u0435\u0436\u0438\u043C \u0441\u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043D\u044F \u0430\u043A\u0442\u0438\u0432\u043D\u0438\u0439 \u2014 \u043F\u043E\u0448\u0443\u043A \u043F\u0440\u0438\u0441\u0442\u0440\u043E\u0457\u0432 \u0447\u0435\u0440\u0435\u0437 mDNS, \u043D\u0430\u0442\u0438\u0441\u043D\u0456\u0442\u044C \u0437\u0430\u0440\u0430\u0437 \u043A\u043D\u043E\u043F\u043A\u0443 (\u0442\u0430\u0439\u043C-\u0430\u0443\u0442 60 \u0441\u0435\u043A\u0443\u043D\u0434)",
146
- "zh-cn": "\u5DF2\u542F\u7528\u914D\u5BF9\u6A21\u5F0F \u2014 \u901A\u8FC7 mDNS \u641C\u7D22\u8BBE\u5907\uFF0C\u73B0\u5728\u8BF7\u6309\u4E0B\u8BBE\u5907\u4E0A\u7684\u6309\u94AE\uFF0860 \u79D2\u8D85\u65F6\uFF09"
147
- },
148
- pairingTimeout: {
149
- en: "Pairing mode automatically disabled after 60 seconds timeout",
150
- de: "Pairing-Modus nach 60 Sekunden automatisch beendet",
151
- ru: "\u0420\u0435\u0436\u0438\u043C \u0441\u043E\u043F\u0440\u044F\u0436\u0435\u043D\u0438\u044F \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438 \u043E\u0442\u043A\u043B\u044E\u0447\u0451\u043D \u043F\u043E\u0441\u043B\u0435 \u0442\u0430\u0439\u043C-\u0430\u0443\u0442\u0430 60 \u0441\u0435\u043A\u0443\u043D\u0434",
152
- pt: "Modo de emparelhamento desativado automaticamente ap\xF3s timeout de 60 segundos",
153
- nl: "Koppelmodus automatisch uitgeschakeld na time-out van 60 seconden",
154
- fr: "Mode d'appairage d\xE9sactiv\xE9 automatiquement apr\xE8s timeout de 60 secondes",
155
- it: "Modalit\xE0 di accoppiamento disattivata automaticamente dopo timeout di 60 secondi",
156
- es: "Modo de emparejamiento desactivado autom\xE1ticamente tras 60 segundos",
157
- pl: "Tryb parowania automatycznie wy\u0142\u0105czony po 60 sekundach",
158
- uk: "\u0420\u0435\u0436\u0438\u043C \u0441\u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043D\u044F \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u043D\u043E \u0432\u0438\u043C\u043A\u043D\u0435\u043D\u043E \u043F\u0456\u0441\u043B\u044F \u0442\u0430\u0439\u043C-\u0430\u0443\u0442\u0443 60 \u0441\u0435\u043A\u0443\u043D\u0434",
159
- "zh-cn": "60 \u79D2\u8D85\u65F6\u540E\u5DF2\u81EA\u52A8\u7981\u7528\u914D\u5BF9\u6A21\u5F0F"
160
- },
161
- pairingSuccess: {
162
- en: "Successfully paired with {name} ({type}) at {ip} \u2014 connecting...",
163
- de: "Erfolgreich gekoppelt mit {name} ({type}) unter {ip} \u2014 verbinde...",
164
- ru: "\u0423\u0441\u043F\u0435\u0448\u043D\u043E\u0435 \u0441\u043E\u043F\u0440\u044F\u0436\u0435\u043D\u0438\u0435 \u0441 {name} ({type}) \u043F\u043E \u0430\u0434\u0440\u0435\u0441\u0443 {ip} \u2014 \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u0435...",
165
- pt: "Emparelhamento bem-sucedido com {name} ({type}) em {ip} \u2014 conectando...",
166
- nl: "Succesvol gekoppeld met {name} ({type}) op {ip} \u2014 verbinden...",
167
- fr: "Appairage r\xE9ussi avec {name} ({type}) \xE0 {ip} \u2014 connexion...",
168
- it: "Accoppiamento riuscito con {name} ({type}) a {ip} \u2014 connessione in corso...",
169
- es: "Emparejamiento exitoso con {name} ({type}) en {ip} \u2014 conectando...",
170
- pl: "Pomy\u015Blnie sparowano z {name} ({type}) pod {ip} \u2014 \u0142\u0105czenie...",
171
- uk: "\u0423\u0441\u043F\u0456\u0448\u043D\u0435 \u0441\u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043D\u044F \u0437 {name} ({type}) \u0437\u0430 \u0430\u0434\u0440\u0435\u0441\u043E\u044E {ip} \u2014 \u043F\u0456\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u043D\u044F...",
172
- "zh-cn": "\u5DF2\u6210\u529F\u4E0E {name} ({type}) \u914D\u5BF9\uFF0C\u5730\u5740 {ip} \u2014 \u6B63\u5728\u8FDE\u63A5..."
173
- },
174
- // ──────── State writes ────────
175
- rebootingDevice: {
176
- en: "Rebooting {name} ({ip})",
177
- de: "Starte {name} ({ip}) neu",
178
- ru: "\u041F\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u043A\u0430 {name} ({ip})",
179
- pt: "Reiniciando {name} ({ip})",
180
- nl: "Opnieuw opstarten van {name} ({ip})",
181
- fr: "Red\xE9marrage de {name} ({ip})",
182
- it: "Riavvio di {name} ({ip})",
183
- es: "Reiniciando {name} ({ip})",
184
- pl: "Restartowanie {name} ({ip})",
185
- uk: "\u041F\u0435\u0440\u0435\u0437\u0430\u0432\u0430\u043D\u0442\u0430\u0436\u0435\u043D\u043D\u044F {name} ({ip})",
186
- "zh-cn": "\u6B63\u5728\u91CD\u542F {name} ({ip})"
187
- },
188
- failedToSetState: {
189
- en: "Failed to set {id}: {error}",
190
- de: "Setzen von {id} fehlgeschlagen: {error}",
191
- ru: "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C {id}: {error}",
192
- pt: "Falha ao definir {id}: {error}",
193
- nl: "Instellen van {id} is mislukt: {error}",
194
- fr: "\xC9chec de la d\xE9finition de {id} : {error}",
195
- it: "Impossibile impostare {id}: {error}",
196
- es: "Error al establecer {id}: {error}",
197
- pl: "Nie uda\u0142o si\u0119 ustawi\u0107 {id}: {error}",
198
- uk: "\u041D\u0435 \u0432\u0434\u0430\u043B\u043E\u0441\u044F \u0432\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u0438 {id}: {error}",
199
- "zh-cn": "\u65E0\u6CD5\u8BBE\u7F6E {id}\uFF1A{error}"
200
- },
201
- invalidPermissionsJson: {
202
- en: "Invalid JSON for battery.permissions: {error} \u2014 expected array, got: {value}",
203
- de: "Ung\xFCltiges JSON f\xFCr battery.permissions: {error} \u2014 Array erwartet, erhalten: {value}",
204
- ru: "\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 JSON \u0434\u043B\u044F battery.permissions: {error} \u2014 \u043E\u0436\u0438\u0434\u0430\u043B\u0441\u044F \u043C\u0430\u0441\u0441\u0438\u0432, \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043E: {value}",
205
- pt: "JSON inv\xE1lido para battery.permissions: {error} \u2014 esperado array, recebido: {value}",
206
- nl: "Ongeldige JSON voor battery.permissions: {error} \u2014 array verwacht, ontvangen: {value}",
207
- fr: "JSON invalide pour battery.permissions : {error} \u2014 tableau attendu, re\xE7u : {value}",
208
- it: "JSON non valido per battery.permissions: {error} \u2014 atteso array, ricevuto: {value}",
209
- es: "JSON inv\xE1lido para battery.permissions: {error} \u2014 se esperaba array, recibido: {value}",
210
- pl: "Nieprawid\u0142owy JSON dla battery.permissions: {error} \u2014 oczekiwano tablicy, otrzymano: {value}",
211
- uk: "\u041D\u0435\u0432\u0456\u0440\u043D\u0438\u0439 JSON \u0434\u043B\u044F battery.permissions: {error} \u2014 \u043E\u0447\u0456\u043A\u0443\u0432\u0430\u0432\u0441\u044F \u043C\u0430\u0441\u0438\u0432, \u043E\u0442\u0440\u0438\u043C\u0430\u043D\u043E: {value}",
212
- "zh-cn": "battery.permissions \u7684 JSON \u65E0\u6548\uFF1A{error} \u2014 \u671F\u671B\u6570\u7EC4\uFF0C\u6536\u5230\uFF1A{value}"
213
- },
214
- invalidBatteryMode: {
215
- en: "Invalid battery.mode value: '{value}' \u2014 expected one of: zero, to_full, standby",
216
- de: "Ung\xFCltiger battery.mode-Wert: '{value}' \u2014 erwartet: zero, to_full oder standby",
217
- ru: "\u041D\u0435\u0432\u0435\u0440\u043D\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 battery.mode: '{value}' \u2014 \u043E\u0436\u0438\u0434\u0430\u0435\u0442\u0441\u044F: zero, to_full \u0438\u043B\u0438 standby",
218
- pt: "Valor inv\xE1lido para battery.mode: '{value}' \u2014 esperado: zero, to_full ou standby",
219
- nl: "Ongeldige battery.mode-waarde: '{value}' \u2014 verwacht: zero, to_full of standby",
220
- fr: "Valeur battery.mode invalide : '{value}' \u2014 attendu : zero, to_full ou standby",
221
- it: "Valore battery.mode non valido: '{value}' \u2014 atteso: zero, to_full o standby",
222
- es: "Valor battery.mode inv\xE1lido: '{value}' \u2014 esperado: zero, to_full o standby",
223
- pl: "Nieprawid\u0142owa warto\u015B\u0107 battery.mode: '{value}' \u2014 oczekiwano: zero, to_full lub standby",
224
- uk: "\u041D\u0435\u0432\u0456\u0440\u043D\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u043D\u044F battery.mode: '{value}' \u2014 \u043E\u0447\u0456\u043A\u0443\u0454\u0442\u044C\u0441\u044F: zero, to_full \u0430\u0431\u043E standby",
225
- "zh-cn": "battery.mode \u503C\u65E0\u6548\uFF1A'{value}' \u2014 \u671F\u671B\uFF1Azero\u3001to_full \u6216 standby"
226
- },
227
- removingDevice: {
228
- en: "Removing device {name} ({serial})",
229
- de: "Entferne Ger\xE4t {name} ({serial})",
230
- ru: "\u0423\u0434\u0430\u043B\u0435\u043D\u0438\u0435 \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u0430 {name} ({serial})",
231
- pt: "Removendo dispositivo {name} ({serial})",
232
- nl: "Apparaat {name} ({serial}) verwijderen",
233
- fr: "Suppression de l'appareil {name} ({serial})",
234
- it: "Rimozione del dispositivo {name} ({serial})",
235
- es: "Eliminando dispositivo {name} ({serial})",
236
- pl: "Usuwanie urz\u0105dzenia {name} ({serial})",
237
- uk: "\u0412\u0438\u0434\u0430\u043B\u0435\u043D\u043D\u044F \u043F\u0440\u0438\u0441\u0442\u0440\u043E\u044E {name} ({serial})",
238
- "zh-cn": "\u6B63\u5728\u79FB\u9664\u8BBE\u5907 {name} ({serial})"
239
- },
240
- // ──────── Connection lifecycle ────────
241
- searchingNewIp: {
242
- en: "Device unreachable \u2014 searching for new IP via mDNS",
243
- de: "Ger\xE4t nicht erreichbar \u2014 suche neue IP via mDNS",
244
- ru: "\u0423\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u043E \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u043E \u2014 \u043F\u043E\u0438\u0441\u043A \u043D\u043E\u0432\u043E\u0433\u043E IP \u0447\u0435\u0440\u0435\u0437 mDNS",
245
- pt: "Dispositivo inacess\xEDvel \u2014 procurando novo IP via mDNS",
246
- nl: "Apparaat onbereikbaar \u2014 nieuwe IP zoeken via mDNS",
247
- fr: "Appareil injoignable \u2014 recherche d'une nouvelle IP via mDNS",
248
- it: "Dispositivo non raggiungibile \u2014 ricerca nuovo IP tramite mDNS",
249
- es: "Dispositivo inaccesible \u2014 buscando nueva IP v\xEDa mDNS",
250
- pl: "Urz\u0105dzenie nieosi\u0105galne \u2014 wyszukiwanie nowego IP przez mDNS",
251
- uk: "\u041F\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u0438\u0439 \u2014 \u043F\u043E\u0448\u0443\u043A \u043D\u043E\u0432\u043E\u0457 IP \u0447\u0435\u0440\u0435\u0437 mDNS",
252
- "zh-cn": "\u8BBE\u5907\u4E0D\u53EF\u8FBE \u2014 \u901A\u8FC7 mDNS \u641C\u7D22\u65B0 IP"
253
- },
254
- foundAtNewIp: {
255
- en: "{name}: found at new IP {newIp} (was {oldIp})",
256
- de: "{name}: unter neuer IP {newIp} gefunden (zuvor {oldIp})",
257
- ru: "{name}: \u043D\u0430\u0439\u0434\u0435\u043D\u043E \u043F\u043E \u043D\u043E\u0432\u043E\u043C\u0443 IP {newIp} (\u0431\u044B\u043B\u043E {oldIp})",
258
- pt: "{name}: encontrado em novo IP {newIp} (era {oldIp})",
259
- nl: "{name}: gevonden op nieuwe IP {newIp} (was {oldIp})",
260
- fr: "{name} : trouv\xE9 \xE0 la nouvelle IP {newIp} (auparavant {oldIp})",
261
- it: "{name}: trovato al nuovo IP {newIp} (era {oldIp})",
262
- es: "{name}: encontrado en nueva IP {newIp} (antes {oldIp})",
263
- pl: "{name}: znaleziono pod nowym IP {newIp} (by\u0142o {oldIp})",
264
- uk: "{name}: \u0437\u043D\u0430\u0439\u0434\u0435\u043D\u043E \u0437\u0430 \u043D\u043E\u0432\u043E\u044E IP {newIp} (\u0431\u0443\u043B\u043E {oldIp})",
265
- "zh-cn": "{name}\uFF1A\u5DF2\u5728\u65B0 IP {newIp} \u627E\u5230\uFF08\u4E4B\u524D\u4E3A {oldIp}\uFF09"
266
- },
267
- deviceOfflineRetrying: {
268
- en: "{name}: device offline \u2014 will keep retrying every {seconds}s",
269
- de: "{name}: Ger\xE4t offline \u2014 Versuche werden alle {seconds}s wiederholt",
270
- ru: "{name}: \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u043E \u043E\u0444\u043B\u0430\u0439\u043D \u2014 \u043F\u043E\u0432\u0442\u043E\u0440 \u043A\u0430\u0436\u0434\u044B\u0435 {seconds}\u0441",
271
- pt: "{name}: dispositivo offline \u2014 tentando novamente a cada {seconds}s",
272
- nl: "{name}: apparaat offline \u2014 opnieuw proberen elke {seconds}s",
273
- fr: "{name} : appareil hors-ligne \u2014 nouvelle tentative toutes les {seconds}s",
274
- it: "{name}: dispositivo offline \u2014 nuovi tentativi ogni {seconds}s",
275
- es: "{name}: dispositivo desconectado \u2014 reintentando cada {seconds}s",
276
- pl: "{name}: urz\u0105dzenie offline \u2014 ponawianie co {seconds}s",
277
- uk: "{name}: \u043F\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043E\u0444\u043B\u0430\u0439\u043D \u2014 \u043F\u043E\u0432\u0442\u043E\u0440\u043D\u0456 \u0441\u043F\u0440\u043E\u0431\u0438 \u043A\u043E\u0436\u043D\u0456 {seconds}\u0441",
278
- "zh-cn": "{name}\uFF1A\u8BBE\u5907\u79BB\u7EBF \u2014 \u5C06\u6BCF {seconds} \u79D2\u91CD\u8BD5\u4E00\u6B21"
279
- },
280
- deviceUnreachable: {
281
- en: "{name}: device unreachable \u2014 will keep retrying",
282
- de: "{name}: Ger\xE4t nicht erreichbar \u2014 Versuche werden fortgesetzt",
283
- ru: "{name}: \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u043E \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u043E \u2014 \u043F\u043E\u043F\u044B\u0442\u043A\u0438 \u0431\u0443\u0434\u0443\u0442 \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0435\u043D\u044B",
284
- pt: "{name}: dispositivo inacess\xEDvel \u2014 tentativas continuar\xE3o",
285
- nl: "{name}: apparaat onbereikbaar \u2014 pogingen worden voortgezet",
286
- fr: "{name} : appareil injoignable \u2014 les tentatives continuent",
287
- it: "{name}: dispositivo non raggiungibile \u2014 i tentativi continueranno",
288
- es: "{name}: dispositivo inaccesible \u2014 se seguir\xE1n intentando",
289
- pl: "{name}: urz\u0105dzenie nieosi\u0105galne \u2014 pr\xF3by b\u0119d\u0105 kontynuowane",
290
- uk: "{name}: \u043F\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u0438\u0439 \u2014 \u0441\u043F\u0440\u043E\u0431\u0438 \u0442\u0440\u0438\u0432\u0430\u0442\u0438\u043C\u0443\u0442\u044C",
291
- "zh-cn": "{name}\uFF1A\u8BBE\u5907\u4E0D\u53EF\u8FBE \u2014 \u5C06\u7EE7\u7EED\u91CD\u8BD5"
292
- },
293
- deviceErrorContext: {
294
- en: "{name} {context}: {error}",
295
- de: "{name} {context}: {error}",
296
- ru: "{name} {context}: {error}",
297
- pt: "{name} {context}: {error}",
298
- nl: "{name} {context}: {error}",
299
- fr: "{name} {context} : {error}",
300
- it: "{name} {context}: {error}",
301
- es: "{name} {context}: {error}",
302
- pl: "{name} {context}: {error}",
303
- uk: "{name} {context}: {error}",
304
- "zh-cn": "{name} {context}\uFF1A{error}"
305
- },
306
- connectionRestored: {
307
- en: "{name}: connection restored",
308
- de: "{name}: Verbindung wiederhergestellt",
309
- ru: "{name}: \u0441\u043E\u0435\u0434\u0438\u043D\u0435\u043D\u0438\u0435 \u0432\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043E",
310
- pt: "{name}: conex\xE3o restabelecida",
311
- nl: "{name}: verbinding hersteld",
312
- fr: "{name} : connexion r\xE9tablie",
313
- it: "{name}: connessione ripristinata",
314
- es: "{name}: conexi\xF3n restablecida",
315
- pl: "{name}: po\u0142\u0105czenie przywr\xF3cone",
316
- uk: "{name}: \u0437'\u0454\u0434\u043D\u0430\u043D\u043D\u044F \u0432\u0456\u0434\u043D\u043E\u0432\u043B\u0435\u043D\u043E",
317
- "zh-cn": "{name}\uFF1A\u8FDE\u63A5\u5DF2\u6062\u590D"
318
- },
319
- connectionRestoredUnstable: {
320
- en: "{name}: connection restored (unstable mode)",
321
- de: "{name}: Verbindung wiederhergestellt (Unstable-Modus)",
322
- ru: "{name}: \u0441\u043E\u0435\u0434\u0438\u043D\u0435\u043D\u0438\u0435 \u0432\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043E (\u043D\u0435\u0441\u0442\u0430\u0431\u0438\u043B\u044C\u043D\u044B\u0439 \u0440\u0435\u0436\u0438\u043C)",
323
- pt: "{name}: conex\xE3o restabelecida (modo inst\xE1vel)",
324
- nl: "{name}: verbinding hersteld (instabiele modus)",
325
- fr: "{name} : connexion r\xE9tablie (mode instable)",
326
- it: "{name}: connessione ripristinata (modalit\xE0 instabile)",
327
- es: "{name}: conexi\xF3n restablecida (modo inestable)",
328
- pl: "{name}: po\u0142\u0105czenie przywr\xF3cone (tryb niestabilny)",
329
- uk: "{name}: \u0437'\u0454\u0434\u043D\u0430\u043D\u043D\u044F \u0432\u0456\u0434\u043D\u043E\u0432\u043B\u0435\u043D\u043E (\u043D\u0435\u0441\u0442\u0430\u0431\u0456\u043B\u044C\u043D\u0438\u0439 \u0440\u0435\u0436\u0438\u043C)",
330
- "zh-cn": "{name}\uFF1A\u8FDE\u63A5\u5DF2\u6062\u590D\uFF08\u4E0D\u7A33\u5B9A\u6A21\u5F0F\uFF09"
331
- },
332
- unstableDetected: {
333
- en: "{name}: unstable connection detected \u2014 using faster reconnect",
334
- de: "{name}: instabile Verbindung erkannt \u2014 schnellerer Reconnect aktiv",
335
- ru: "{name}: \u043E\u0431\u043D\u0430\u0440\u0443\u0436\u0435\u043D\u043E \u043D\u0435\u0441\u0442\u0430\u0431\u0438\u043B\u044C\u043D\u043E\u0435 \u0441\u043E\u0435\u0434\u0438\u043D\u0435\u043D\u0438\u0435 \u2014 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0435\u0442\u0441\u044F \u0431\u044B\u0441\u0442\u0440\u043E\u0435 \u043F\u0435\u0440\u0435\u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u0435",
336
- pt: "{name}: conex\xE3o inst\xE1vel detectada \u2014 usando reconex\xE3o mais r\xE1pida",
337
- nl: "{name}: instabiele verbinding gedetecteerd \u2014 sneller opnieuw verbinden",
338
- fr: "{name} : connexion instable d\xE9tect\xE9e \u2014 reconnexion plus rapide activ\xE9e",
339
- it: "{name}: rilevata connessione instabile \u2014 riconnessione pi\xF9 veloce",
340
- es: "{name}: conexi\xF3n inestable detectada \u2014 reconexi\xF3n m\xE1s r\xE1pida activada",
341
- pl: "{name}: wykryto niestabilne po\u0142\u0105czenie \u2014 szybsze ponowne \u0142\u0105czenie",
342
- uk: "{name}: \u0432\u0438\u044F\u0432\u043B\u0435\u043D\u043E \u043D\u0435\u0441\u0442\u0430\u0431\u0456\u043B\u044C\u043D\u0435 \u0437'\u0454\u0434\u043D\u0430\u043D\u043D\u044F \u2014 \u0432\u0438\u043A\u043E\u0440\u0438\u0441\u0442\u043E\u0432\u0443\u0454\u0442\u044C\u0441\u044F \u0448\u0432\u0438\u0434\u0448\u0435 \u043F\u043E\u0432\u0442\u043E\u0440\u043D\u0435 \u043F\u0456\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u043D\u044F",
343
- "zh-cn": "{name}\uFF1A\u68C0\u6D4B\u5230\u4E0D\u7A33\u5B9A\u8FDE\u63A5 \u2014 \u4F7F\u7528\u66F4\u5FEB\u7684\u91CD\u8FDE"
344
- },
345
- connectionStabilized: {
346
- en: "{name}: connection stabilized \u2014 using normal reconnect",
347
- de: "{name}: Verbindung stabilisiert \u2014 normaler Reconnect aktiv",
348
- ru: "{name}: \u0441\u043E\u0435\u0434\u0438\u043D\u0435\u043D\u0438\u0435 \u0441\u0442\u0430\u0431\u0438\u043B\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u043D\u043E \u2014 \u043E\u0431\u044B\u0447\u043D\u043E\u0435 \u043F\u0435\u0440\u0435\u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u0435",
349
- pt: "{name}: conex\xE3o estabilizada \u2014 usando reconex\xE3o normal",
350
- nl: "{name}: verbinding gestabiliseerd \u2014 normale reconnect actief",
351
- fr: "{name} : connexion stabilis\xE9e \u2014 reconnexion normale active",
352
- it: "{name}: connessione stabilizzata \u2014 riconnessione normale",
353
- es: "{name}: conexi\xF3n estabilizada \u2014 reconexi\xF3n normal activa",
354
- pl: "{name}: po\u0142\u0105czenie ustabilizowane \u2014 normalne ponowne \u0142\u0105czenie",
355
- uk: "{name}: \u0437'\u0454\u0434\u043D\u0430\u043D\u043D\u044F \u0441\u0442\u0430\u0431\u0456\u043B\u0456\u0437\u043E\u0432\u0430\u043D\u043E \u2014 \u0437\u0432\u0438\u0447\u0430\u0439\u043D\u0435 \u043F\u043E\u0432\u0442\u043E\u0440\u043D\u0435 \u043F\u0456\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u043D\u044F",
356
- "zh-cn": "{name}\uFF1A\u8FDE\u63A5\u5DF2\u7A33\u5B9A \u2014 \u4F7F\u7528\u5E38\u89C4\u91CD\u8FDE"
357
- },
358
- tokenInvalid: {
359
- en: "{name}: token invalid \u2014 re-pair device to fix",
360
- de: "{name}: Token ung\xFCltig \u2014 Ger\xE4t neu koppeln, um den Fehler zu beheben",
361
- ru: "{name}: \u0442\u043E\u043A\u0435\u043D \u043D\u0435\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043B\u0435\u043D \u2014 \u0432\u044B\u043F\u043E\u043B\u043D\u0438\u0442\u0435 \u043F\u043E\u0432\u0442\u043E\u0440\u043D\u043E\u0435 \u0441\u043E\u043F\u0440\u044F\u0436\u0435\u043D\u0438\u0435 \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u0430",
362
- pt: "{name}: token inv\xE1lido \u2014 emparelhe o dispositivo novamente para corrigir",
363
- nl: "{name}: token ongeldig \u2014 apparaat opnieuw koppelen om dit op te lossen",
364
- fr: "{name} : token invalide \u2014 r\xE9appairez l'appareil pour corriger",
365
- it: "{name}: token non valido \u2014 riaccoppia il dispositivo per risolvere",
366
- es: "{name}: token inv\xE1lido \u2014 vuelve a emparejar el dispositivo para arreglarlo",
367
- pl: "{name}: token nieprawid\u0142owy \u2014 sparuj urz\u0105dzenie ponownie, aby naprawi\u0107",
368
- uk: "{name}: \u0442\u043E\u043A\u0435\u043D \u043D\u0435\u0434\u0456\u0439\u0441\u043D\u0438\u0439 \u2014 \u043F\u043E\u0432\u0442\u043E\u0440\u0456\u0442\u044C \u0441\u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043D\u044F \u043F\u0440\u0438\u0441\u0442\u0440\u043E\u044E",
369
- "zh-cn": "{name}\uFF1Atoken \u65E0\u6548 \u2014 \u8BF7\u91CD\u65B0\u914D\u5BF9\u8BBE\u5907\u4EE5\u4FEE\u590D"
370
- }
371
- };
372
- function tLog(lang, key, params) {
373
- var _a;
374
- const langKey = SUPPORTED_LANGS.includes(lang) ? lang : "en";
375
- const bundle = LOG_STRINGS[key];
376
- const template = (_a = bundle[langKey]) != null ? _a : bundle.en;
377
- return fmt(template, params);
378
- }
379
- // Annotate the CommonJS export names for ESM import in node:
380
- 0 && (module.exports = {
381
- LOG_STRINGS,
382
- tLog
383
- });
384
- //# sourceMappingURL=i18n-logs.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/lib/i18n-logs.ts"],
4
- "sourcesContent": ["/**\n * Localized log strings \u2014 info/warn/error end up in the ioBroker admin log,\n * which is user-facing. Translations cover all 11 ioBroker system languages\n * (en/de/ru/pt/nl/fr/it/es/pl/uk/zh-cn).\n *\n * The active language is read once in `main.onReady` from\n * `system.config.language` and stored on the adapter instance. A language\n * change in admin requires an adapter restart \u2014 acceptable, users don't\n * switch languages on the fly.\n *\n * Debug logs stay English (maintainer diagnostics, not user-visible at\n * default loglevel). Stack traces stay verbatim \u2014 code paths aren't\n * translatable.\n */\n\nconst SUPPORTED_LANGS = [\"en\", \"de\", \"ru\", \"pt\", \"nl\", \"fr\", \"it\", \"es\", \"pl\", \"uk\", \"zh-cn\"] as const;\ntype Lang = (typeof SUPPORTED_LANGS)[number];\n\n/**\n * Token substitution: `{name}` in the template is replaced with `params.name`.\n * `null` values render as `(none)`, missing tokens are kept as `{key}` so a\n * caller bug surfaces in the log instead of silently emitting an empty string.\n *\n * @param template Localized log string with `{key}` placeholders.\n * @param params Token values; `null` \u2192 `(none)`, `undefined` \u2192 token kept.\n */\nfunction fmt(template: string, params?: Record<string, string | number | null | undefined>): string {\n if (!params) {\n return template;\n }\n return template.replace(/\\{(\\w+)\\}/g, (_, key: string) => {\n const v = params[key];\n if (v === null) {\n return \"(none)\";\n }\n if (v === undefined) {\n return `{${key}}`;\n }\n return String(v);\n });\n}\n\n/**\n * All user-facing info/warn/error strings. Keys are descriptive identifiers,\n * values are bundles for the 11 supported ioBroker system languages. Tech\n * internals (module prefixes like `mDNS:`, raw `code` strings) are kept out\n * \u2014 user-facing logs should be readable without source-code context.\n */\nexport const LOG_STRINGS = {\n // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Adapter lifecycle / crash defense \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n onReadyFailed: {\n en: \"onReady failed: {error}\",\n de: \"onReady fehlgeschlagen: {error}\",\n ru: \"onReady \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0441\u044F \u0441 \u043E\u0448\u0438\u0431\u043A\u043E\u0439: {error}\",\n pt: \"onReady falhou: {error}\",\n nl: \"onReady is mislukt: {error}\",\n fr: \"onReady a \u00E9chou\u00E9 : {error}\",\n it: \"onReady non riuscito: {error}\",\n es: \"onReady fall\u00F3: {error}\",\n pl: \"onReady nie powi\u00F3d\u0142 si\u0119: {error}\",\n uk: \"onReady \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0432\u0441\u044F \u0437 \u043F\u043E\u043C\u0438\u043B\u043A\u043E\u044E: {error}\",\n \"zh-cn\": \"onReady \u5931\u8D25\uFF1A{error}\",\n },\n stateChangeFailed: {\n en: \"stateChange failed: {error}\",\n de: \"stateChange fehlgeschlagen: {error}\",\n ru: \"stateChange \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0441\u044F \u0441 \u043E\u0448\u0438\u0431\u043A\u043E\u0439: {error}\",\n pt: \"stateChange falhou: {error}\",\n nl: \"stateChange is mislukt: {error}\",\n fr: \"stateChange a \u00E9chou\u00E9 : {error}\",\n it: \"stateChange non riuscito: {error}\",\n es: \"stateChange fall\u00F3: {error}\",\n pl: \"stateChange nie powi\u00F3d\u0142 si\u0119: {error}\",\n uk: \"stateChange \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0432\u0441\u044F \u0437 \u043F\u043E\u043C\u0438\u043B\u043A\u043E\u044E: {error}\",\n \"zh-cn\": \"stateChange \u5931\u8D25\uFF1A{error}\",\n },\n unhandledRejection: {\n en: \"Unhandled rejection: {error}\",\n de: \"Unbehandelte Promise-Rejection: {error}\",\n ru: \"\u041D\u0435\u043E\u0431\u0440\u0430\u0431\u043E\u0442\u0430\u043D\u043D\u044B\u0439 rejection: {error}\",\n pt: \"Rejei\u00E7\u00E3o n\u00E3o tratada: {error}\",\n nl: \"Onafgehandelde rejection: {error}\",\n fr: \"Rejet non g\u00E9r\u00E9 : {error}\",\n it: \"Rejection non gestita: {error}\",\n es: \"Rechazo no manejado: {error}\",\n pl: \"Nieobs\u0142u\u017Cone odrzucenie: {error}\",\n uk: \"\u041D\u0435\u043E\u0431\u0440\u043E\u0431\u043B\u0435\u043D\u0438\u0439 rejection: {error}\",\n \"zh-cn\": \"\u672A\u5904\u7406\u7684 rejection\uFF1A{error}\",\n },\n uncaughtException: {\n en: \"Uncaught exception: {error}\",\n de: \"Nicht abgefangene Exception: {error}\",\n ru: \"\u041D\u0435\u043E\u0431\u0440\u0430\u0431\u043E\u0442\u0430\u043D\u043D\u043E\u0435 \u0438\u0441\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u0435: {error}\",\n pt: \"Exce\u00E7\u00E3o n\u00E3o capturada: {error}\",\n nl: \"Niet-opgevangen exception: {error}\",\n fr: \"Exception non captur\u00E9e : {error}\",\n it: \"Eccezione non catturata: {error}\",\n es: \"Excepci\u00F3n no capturada: {error}\",\n pl: \"Nieprzechwycony wyj\u0105tek: {error}\",\n uk: \"\u041D\u0435\u043F\u0435\u0440\u0435\u0445\u043E\u043F\u043B\u0435\u043D\u0435 \u0432\u0438\u043A\u043B\u044E\u0447\u0435\u043D\u043D\u044F: {error}\",\n \"zh-cn\": \"\u672A\u6355\u83B7\u7684\u5F02\u5E38\uFF1A{error}\",\n },\n\n // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Pairing flow \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n noDevicesConfigured: {\n en: \"No devices configured \u2014 set 'startPairing' to true to add a device\",\n de: \"Keine Ger\u00E4te konfiguriert \u2014 'startPairing' auf true setzen, um ein Ger\u00E4t hinzuzuf\u00FCgen\",\n ru: \"\u041D\u0435\u0442 \u043D\u0430\u0441\u0442\u0440\u043E\u0435\u043D\u043D\u044B\u0445 \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432 \u2014 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u0435 'startPairing' \u0432 true, \u0447\u0442\u043E\u0431\u044B \u0434\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u043E\",\n pt: \"Nenhum dispositivo configurado \u2014 defina 'startPairing' como true para adicionar um dispositivo\",\n nl: \"Geen apparaten geconfigureerd \u2014 zet 'startPairing' op true om een apparaat toe te voegen\",\n fr: \"Aucun appareil configur\u00E9 \u2014 d\u00E9finissez 'startPairing' sur true pour ajouter un appareil\",\n it: \"Nessun dispositivo configurato \u2014 imposta 'startPairing' su true per aggiungere un dispositivo\",\n es: \"No hay dispositivos configurados \u2014 establece 'startPairing' en true para a\u00F1adir uno\",\n pl: \"Brak skonfigurowanych urz\u0105dze\u0144 \u2014 ustaw 'startPairing' na true, aby doda\u0107 urz\u0105dzenie\",\n uk: \"\u041D\u0435\u043C\u0430\u0454 \u043D\u0430\u043B\u0430\u0448\u0442\u043E\u0432\u0430\u043D\u0438\u0445 \u043F\u0440\u0438\u0441\u0442\u0440\u043E\u0457\u0432 \u2014 \u0432\u0441\u0442\u0430\u043D\u043E\u0432\u0456\u0442\u044C 'startPairing' \u043D\u0430 true, \u0449\u043E\u0431 \u0434\u043E\u0434\u0430\u0442\u0438 \u043F\u0440\u0438\u0441\u0442\u0440\u0456\u0439\",\n \"zh-cn\": \"\u672A\u914D\u7F6E\u8BBE\u5907 \u2014 \u5C06 'startPairing' \u8BBE\u7F6E\u4E3A true \u4EE5\u6DFB\u52A0\u8BBE\u5907\",\n },\n deviceFound: {\n en: \"Found {name} ({type}) at {ip} \u2014 press the button on the device to pair\",\n de: \"Gefunden: {name} ({type}) unter {ip} \u2014 Knopf am Ger\u00E4t dr\u00FCcken, um zu koppeln\",\n ru: \"\u041D\u0430\u0439\u0434\u0435\u043D\u043E {name} ({type}) \u043F\u043E \u0430\u0434\u0440\u0435\u0441\u0443 {ip} \u2014 \u043D\u0430\u0436\u043C\u0438\u0442\u0435 \u043A\u043D\u043E\u043F\u043A\u0443 \u043D\u0430 \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u0435 \u0434\u043B\u044F \u0441\u043E\u043F\u0440\u044F\u0436\u0435\u043D\u0438\u044F\",\n pt: \"Encontrado {name} ({type}) em {ip} \u2014 pressione o bot\u00E3o do dispositivo para emparelhar\",\n nl: \"Gevonden: {name} ({type}) op {ip} \u2014 druk op de knop op het apparaat om te koppelen\",\n fr: \"Trouv\u00E9 : {name} ({type}) \u00E0 {ip} \u2014 appuyez sur le bouton de l'appareil pour l'appairer\",\n it: \"Trovato {name} ({type}) a {ip} \u2014 premi il pulsante sul dispositivo per accoppiare\",\n es: \"Encontrado {name} ({type}) en {ip} \u2014 pulsa el bot\u00F3n del dispositivo para emparejar\",\n pl: \"Znaleziono {name} ({type}) pod {ip} \u2014 naci\u015Bnij przycisk na urz\u0105dzeniu, aby sparowa\u0107\",\n uk: \"\u0417\u043D\u0430\u0439\u0434\u0435\u043D\u043E {name} ({type}) \u0437\u0430 \u0430\u0434\u0440\u0435\u0441\u043E\u044E {ip} \u2014 \u043D\u0430\u0442\u0438\u0441\u043D\u0456\u0442\u044C \u043A\u043D\u043E\u043F\u043A\u0443 \u043D\u0430 \u043F\u0440\u0438\u0441\u0442\u0440\u043E\u0457 \u0434\u043B\u044F \u0441\u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043D\u044F\",\n \"zh-cn\": \"\u5DF2\u627E\u5230 {name} ({type}) \u4F4D\u4E8E {ip} \u2014 \u6309\u8BBE\u5907\u4E0A\u7684\u6309\u94AE\u914D\u5BF9\",\n },\n pairingEnabledManual: {\n en: \"Pairing mode enabled for {ip} \u2014 press the button on your HomeWizard device now (60 seconds timeout)\",\n de: \"Pairing-Modus aktiv f\u00FCr {ip} \u2014 jetzt Knopf am HomeWizard-Ger\u00E4t dr\u00FCcken (60 Sekunden Timeout)\",\n ru: \"\u0420\u0435\u0436\u0438\u043C \u0441\u043E\u043F\u0440\u044F\u0436\u0435\u043D\u0438\u044F \u0432\u043A\u043B\u044E\u0447\u0451\u043D \u0434\u043B\u044F {ip} \u2014 \u043D\u0430\u0436\u043C\u0438\u0442\u0435 \u043A\u043D\u043E\u043F\u043A\u0443 \u043D\u0430 \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u0435 HomeWizard (\u0442\u0430\u0439\u043C-\u0430\u0443\u0442 60 \u0441\u0435\u043A\u0443\u043D\u0434)\",\n pt: \"Modo de emparelhamento ativo para {ip} \u2014 pressione o bot\u00E3o do dispositivo HomeWizard agora (60 segundos)\",\n nl: \"Koppelmodus actief voor {ip} \u2014 druk nu op de knop van uw HomeWizard-apparaat (60 seconden time-out)\",\n fr: \"Mode d'appairage activ\u00E9 pour {ip} \u2014 appuyez sur le bouton de l'appareil HomeWizard (timeout 60 secondes)\",\n it: \"Modalit\u00E0 di accoppiamento attiva per {ip} \u2014 premi ora il pulsante sul dispositivo HomeWizard (timeout 60 secondi)\",\n es: \"Modo de emparejamiento activo para {ip} \u2014 pulsa ahora el bot\u00F3n del dispositivo HomeWizard (60 segundos)\",\n pl: \"Tryb parowania aktywny dla {ip} \u2014 naci\u015Bnij teraz przycisk na urz\u0105dzeniu HomeWizard (timeout 60 sekund)\",\n uk: \"\u0420\u0435\u0436\u0438\u043C \u0441\u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043D\u044F \u0430\u043A\u0442\u0438\u0432\u043D\u0438\u0439 \u0434\u043B\u044F {ip} \u2014 \u043D\u0430\u0442\u0438\u0441\u043D\u0456\u0442\u044C \u0437\u0430\u0440\u0430\u0437 \u043A\u043D\u043E\u043F\u043A\u0443 \u043D\u0430 \u043F\u0440\u0438\u0441\u0442\u0440\u043E\u0457 HomeWizard (\u0442\u0430\u0439\u043C-\u0430\u0443\u0442 60 \u0441\u0435\u043A\u0443\u043D\u0434)\",\n \"zh-cn\": \"\u5DF2\u4E3A {ip} \u542F\u7528\u914D\u5BF9\u6A21\u5F0F \u2014 \u73B0\u5728\u8BF7\u6309\u4E0B HomeWizard \u8BBE\u5907\u4E0A\u7684\u6309\u94AE\uFF0860 \u79D2\u8D85\u65F6\uFF09\",\n },\n pairingEnabledMdns: {\n en: \"Pairing mode enabled \u2014 searching for devices via mDNS, press the button on your HomeWizard device now (60 seconds timeout)\",\n de: \"Pairing-Modus aktiv \u2014 Ger\u00E4te-Suche via mDNS, jetzt Knopf am HomeWizard-Ger\u00E4t dr\u00FCcken (60 Sekunden Timeout)\",\n ru: \"\u0420\u0435\u0436\u0438\u043C \u0441\u043E\u043F\u0440\u044F\u0436\u0435\u043D\u0438\u044F \u0432\u043A\u043B\u044E\u0447\u0451\u043D \u2014 \u043F\u043E\u0438\u0441\u043A \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432 \u0447\u0435\u0440\u0435\u0437 mDNS, \u043D\u0430\u0436\u043C\u0438\u0442\u0435 \u043A\u043D\u043E\u043F\u043A\u0443 \u043D\u0430 \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u0435 HomeWizard (60 \u0441\u0435\u043A\u0443\u043D\u0434)\",\n pt: \"Modo de emparelhamento ativo \u2014 procurando dispositivos via mDNS, pressione o bot\u00E3o agora (60 segundos)\",\n nl: \"Koppelmodus actief \u2014 apparaten zoeken via mDNS, druk nu op de knop van uw HomeWizard-apparaat (60 seconden)\",\n fr: \"Mode d'appairage activ\u00E9 \u2014 recherche d'appareils via mDNS, appuyez maintenant sur le bouton (timeout 60 secondes)\",\n it: \"Modalit\u00E0 di accoppiamento attiva \u2014 ricerca dispositivi tramite mDNS, premi ora il pulsante (timeout 60 secondi)\",\n es: \"Modo de emparejamiento activo \u2014 buscando dispositivos v\u00EDa mDNS, pulsa ahora el bot\u00F3n (60 segundos)\",\n pl: \"Tryb parowania aktywny \u2014 wyszukiwanie urz\u0105dze\u0144 przez mDNS, naci\u015Bnij teraz przycisk (timeout 60 sekund)\",\n uk: \"\u0420\u0435\u0436\u0438\u043C \u0441\u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043D\u044F \u0430\u043A\u0442\u0438\u0432\u043D\u0438\u0439 \u2014 \u043F\u043E\u0448\u0443\u043A \u043F\u0440\u0438\u0441\u0442\u0440\u043E\u0457\u0432 \u0447\u0435\u0440\u0435\u0437 mDNS, \u043D\u0430\u0442\u0438\u0441\u043D\u0456\u0442\u044C \u0437\u0430\u0440\u0430\u0437 \u043A\u043D\u043E\u043F\u043A\u0443 (\u0442\u0430\u0439\u043C-\u0430\u0443\u0442 60 \u0441\u0435\u043A\u0443\u043D\u0434)\",\n \"zh-cn\": \"\u5DF2\u542F\u7528\u914D\u5BF9\u6A21\u5F0F \u2014 \u901A\u8FC7 mDNS \u641C\u7D22\u8BBE\u5907\uFF0C\u73B0\u5728\u8BF7\u6309\u4E0B\u8BBE\u5907\u4E0A\u7684\u6309\u94AE\uFF0860 \u79D2\u8D85\u65F6\uFF09\",\n },\n pairingTimeout: {\n en: \"Pairing mode automatically disabled after 60 seconds timeout\",\n de: \"Pairing-Modus nach 60 Sekunden automatisch beendet\",\n ru: \"\u0420\u0435\u0436\u0438\u043C \u0441\u043E\u043F\u0440\u044F\u0436\u0435\u043D\u0438\u044F \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438 \u043E\u0442\u043A\u043B\u044E\u0447\u0451\u043D \u043F\u043E\u0441\u043B\u0435 \u0442\u0430\u0439\u043C-\u0430\u0443\u0442\u0430 60 \u0441\u0435\u043A\u0443\u043D\u0434\",\n pt: \"Modo de emparelhamento desativado automaticamente ap\u00F3s timeout de 60 segundos\",\n nl: \"Koppelmodus automatisch uitgeschakeld na time-out van 60 seconden\",\n fr: \"Mode d'appairage d\u00E9sactiv\u00E9 automatiquement apr\u00E8s timeout de 60 secondes\",\n it: \"Modalit\u00E0 di accoppiamento disattivata automaticamente dopo timeout di 60 secondi\",\n es: \"Modo de emparejamiento desactivado autom\u00E1ticamente tras 60 segundos\",\n pl: \"Tryb parowania automatycznie wy\u0142\u0105czony po 60 sekundach\",\n uk: \"\u0420\u0435\u0436\u0438\u043C \u0441\u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043D\u044F \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u043D\u043E \u0432\u0438\u043C\u043A\u043D\u0435\u043D\u043E \u043F\u0456\u0441\u043B\u044F \u0442\u0430\u0439\u043C-\u0430\u0443\u0442\u0443 60 \u0441\u0435\u043A\u0443\u043D\u0434\",\n \"zh-cn\": \"60 \u79D2\u8D85\u65F6\u540E\u5DF2\u81EA\u52A8\u7981\u7528\u914D\u5BF9\u6A21\u5F0F\",\n },\n pairingSuccess: {\n en: \"Successfully paired with {name} ({type}) at {ip} \u2014 connecting...\",\n de: \"Erfolgreich gekoppelt mit {name} ({type}) unter {ip} \u2014 verbinde...\",\n ru: \"\u0423\u0441\u043F\u0435\u0448\u043D\u043E\u0435 \u0441\u043E\u043F\u0440\u044F\u0436\u0435\u043D\u0438\u0435 \u0441 {name} ({type}) \u043F\u043E \u0430\u0434\u0440\u0435\u0441\u0443 {ip} \u2014 \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u0435...\",\n pt: \"Emparelhamento bem-sucedido com {name} ({type}) em {ip} \u2014 conectando...\",\n nl: \"Succesvol gekoppeld met {name} ({type}) op {ip} \u2014 verbinden...\",\n fr: \"Appairage r\u00E9ussi avec {name} ({type}) \u00E0 {ip} \u2014 connexion...\",\n it: \"Accoppiamento riuscito con {name} ({type}) a {ip} \u2014 connessione in corso...\",\n es: \"Emparejamiento exitoso con {name} ({type}) en {ip} \u2014 conectando...\",\n pl: \"Pomy\u015Blnie sparowano z {name} ({type}) pod {ip} \u2014 \u0142\u0105czenie...\",\n uk: \"\u0423\u0441\u043F\u0456\u0448\u043D\u0435 \u0441\u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043D\u044F \u0437 {name} ({type}) \u0437\u0430 \u0430\u0434\u0440\u0435\u0441\u043E\u044E {ip} \u2014 \u043F\u0456\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u043D\u044F...\",\n \"zh-cn\": \"\u5DF2\u6210\u529F\u4E0E {name} ({type}) \u914D\u5BF9\uFF0C\u5730\u5740 {ip} \u2014 \u6B63\u5728\u8FDE\u63A5...\",\n },\n\n // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 State writes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n rebootingDevice: {\n en: \"Rebooting {name} ({ip})\",\n de: \"Starte {name} ({ip}) neu\",\n ru: \"\u041F\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u043A\u0430 {name} ({ip})\",\n pt: \"Reiniciando {name} ({ip})\",\n nl: \"Opnieuw opstarten van {name} ({ip})\",\n fr: \"Red\u00E9marrage de {name} ({ip})\",\n it: \"Riavvio di {name} ({ip})\",\n es: \"Reiniciando {name} ({ip})\",\n pl: \"Restartowanie {name} ({ip})\",\n uk: \"\u041F\u0435\u0440\u0435\u0437\u0430\u0432\u0430\u043D\u0442\u0430\u0436\u0435\u043D\u043D\u044F {name} ({ip})\",\n \"zh-cn\": \"\u6B63\u5728\u91CD\u542F {name} ({ip})\",\n },\n failedToSetState: {\n en: \"Failed to set {id}: {error}\",\n de: \"Setzen von {id} fehlgeschlagen: {error}\",\n ru: \"\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C {id}: {error}\",\n pt: \"Falha ao definir {id}: {error}\",\n nl: \"Instellen van {id} is mislukt: {error}\",\n fr: \"\u00C9chec de la d\u00E9finition de {id} : {error}\",\n it: \"Impossibile impostare {id}: {error}\",\n es: \"Error al establecer {id}: {error}\",\n pl: \"Nie uda\u0142o si\u0119 ustawi\u0107 {id}: {error}\",\n uk: \"\u041D\u0435 \u0432\u0434\u0430\u043B\u043E\u0441\u044F \u0432\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u0438 {id}: {error}\",\n \"zh-cn\": \"\u65E0\u6CD5\u8BBE\u7F6E {id}\uFF1A{error}\",\n },\n invalidPermissionsJson: {\n en: \"Invalid JSON for battery.permissions: {error} \u2014 expected array, got: {value}\",\n de: \"Ung\u00FCltiges JSON f\u00FCr battery.permissions: {error} \u2014 Array erwartet, erhalten: {value}\",\n ru: \"\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 JSON \u0434\u043B\u044F battery.permissions: {error} \u2014 \u043E\u0436\u0438\u0434\u0430\u043B\u0441\u044F \u043C\u0430\u0441\u0441\u0438\u0432, \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043E: {value}\",\n pt: \"JSON inv\u00E1lido para battery.permissions: {error} \u2014 esperado array, recebido: {value}\",\n nl: \"Ongeldige JSON voor battery.permissions: {error} \u2014 array verwacht, ontvangen: {value}\",\n fr: \"JSON invalide pour battery.permissions : {error} \u2014 tableau attendu, re\u00E7u : {value}\",\n it: \"JSON non valido per battery.permissions: {error} \u2014 atteso array, ricevuto: {value}\",\n es: \"JSON inv\u00E1lido para battery.permissions: {error} \u2014 se esperaba array, recibido: {value}\",\n pl: \"Nieprawid\u0142owy JSON dla battery.permissions: {error} \u2014 oczekiwano tablicy, otrzymano: {value}\",\n uk: \"\u041D\u0435\u0432\u0456\u0440\u043D\u0438\u0439 JSON \u0434\u043B\u044F battery.permissions: {error} \u2014 \u043E\u0447\u0456\u043A\u0443\u0432\u0430\u0432\u0441\u044F \u043C\u0430\u0441\u0438\u0432, \u043E\u0442\u0440\u0438\u043C\u0430\u043D\u043E: {value}\",\n \"zh-cn\": \"battery.permissions \u7684 JSON \u65E0\u6548\uFF1A{error} \u2014 \u671F\u671B\u6570\u7EC4\uFF0C\u6536\u5230\uFF1A{value}\",\n },\n invalidBatteryMode: {\n en: \"Invalid battery.mode value: '{value}' \u2014 expected one of: zero, to_full, standby\",\n de: \"Ung\u00FCltiger battery.mode-Wert: '{value}' \u2014 erwartet: zero, to_full oder standby\",\n ru: \"\u041D\u0435\u0432\u0435\u0440\u043D\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 battery.mode: '{value}' \u2014 \u043E\u0436\u0438\u0434\u0430\u0435\u0442\u0441\u044F: zero, to_full \u0438\u043B\u0438 standby\",\n pt: \"Valor inv\u00E1lido para battery.mode: '{value}' \u2014 esperado: zero, to_full ou standby\",\n nl: \"Ongeldige battery.mode-waarde: '{value}' \u2014 verwacht: zero, to_full of standby\",\n fr: \"Valeur battery.mode invalide : '{value}' \u2014 attendu : zero, to_full ou standby\",\n it: \"Valore battery.mode non valido: '{value}' \u2014 atteso: zero, to_full o standby\",\n es: \"Valor battery.mode inv\u00E1lido: '{value}' \u2014 esperado: zero, to_full o standby\",\n pl: \"Nieprawid\u0142owa warto\u015B\u0107 battery.mode: '{value}' \u2014 oczekiwano: zero, to_full lub standby\",\n uk: \"\u041D\u0435\u0432\u0456\u0440\u043D\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u043D\u044F battery.mode: '{value}' \u2014 \u043E\u0447\u0456\u043A\u0443\u0454\u0442\u044C\u0441\u044F: zero, to_full \u0430\u0431\u043E standby\",\n \"zh-cn\": \"battery.mode \u503C\u65E0\u6548\uFF1A'{value}' \u2014 \u671F\u671B\uFF1Azero\u3001to_full \u6216 standby\",\n },\n removingDevice: {\n en: \"Removing device {name} ({serial})\",\n de: \"Entferne Ger\u00E4t {name} ({serial})\",\n ru: \"\u0423\u0434\u0430\u043B\u0435\u043D\u0438\u0435 \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u0430 {name} ({serial})\",\n pt: \"Removendo dispositivo {name} ({serial})\",\n nl: \"Apparaat {name} ({serial}) verwijderen\",\n fr: \"Suppression de l'appareil {name} ({serial})\",\n it: \"Rimozione del dispositivo {name} ({serial})\",\n es: \"Eliminando dispositivo {name} ({serial})\",\n pl: \"Usuwanie urz\u0105dzenia {name} ({serial})\",\n uk: \"\u0412\u0438\u0434\u0430\u043B\u0435\u043D\u043D\u044F \u043F\u0440\u0438\u0441\u0442\u0440\u043E\u044E {name} ({serial})\",\n \"zh-cn\": \"\u6B63\u5728\u79FB\u9664\u8BBE\u5907 {name} ({serial})\",\n },\n\n // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Connection lifecycle \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n searchingNewIp: {\n en: \"Device unreachable \u2014 searching for new IP via mDNS\",\n de: \"Ger\u00E4t nicht erreichbar \u2014 suche neue IP via mDNS\",\n ru: \"\u0423\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u043E \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u043E \u2014 \u043F\u043E\u0438\u0441\u043A \u043D\u043E\u0432\u043E\u0433\u043E IP \u0447\u0435\u0440\u0435\u0437 mDNS\",\n pt: \"Dispositivo inacess\u00EDvel \u2014 procurando novo IP via mDNS\",\n nl: \"Apparaat onbereikbaar \u2014 nieuwe IP zoeken via mDNS\",\n fr: \"Appareil injoignable \u2014 recherche d'une nouvelle IP via mDNS\",\n it: \"Dispositivo non raggiungibile \u2014 ricerca nuovo IP tramite mDNS\",\n es: \"Dispositivo inaccesible \u2014 buscando nueva IP v\u00EDa mDNS\",\n pl: \"Urz\u0105dzenie nieosi\u0105galne \u2014 wyszukiwanie nowego IP przez mDNS\",\n uk: \"\u041F\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u0438\u0439 \u2014 \u043F\u043E\u0448\u0443\u043A \u043D\u043E\u0432\u043E\u0457 IP \u0447\u0435\u0440\u0435\u0437 mDNS\",\n \"zh-cn\": \"\u8BBE\u5907\u4E0D\u53EF\u8FBE \u2014 \u901A\u8FC7 mDNS \u641C\u7D22\u65B0 IP\",\n },\n foundAtNewIp: {\n en: \"{name}: found at new IP {newIp} (was {oldIp})\",\n de: \"{name}: unter neuer IP {newIp} gefunden (zuvor {oldIp})\",\n ru: \"{name}: \u043D\u0430\u0439\u0434\u0435\u043D\u043E \u043F\u043E \u043D\u043E\u0432\u043E\u043C\u0443 IP {newIp} (\u0431\u044B\u043B\u043E {oldIp})\",\n pt: \"{name}: encontrado em novo IP {newIp} (era {oldIp})\",\n nl: \"{name}: gevonden op nieuwe IP {newIp} (was {oldIp})\",\n fr: \"{name} : trouv\u00E9 \u00E0 la nouvelle IP {newIp} (auparavant {oldIp})\",\n it: \"{name}: trovato al nuovo IP {newIp} (era {oldIp})\",\n es: \"{name}: encontrado en nueva IP {newIp} (antes {oldIp})\",\n pl: \"{name}: znaleziono pod nowym IP {newIp} (by\u0142o {oldIp})\",\n uk: \"{name}: \u0437\u043D\u0430\u0439\u0434\u0435\u043D\u043E \u0437\u0430 \u043D\u043E\u0432\u043E\u044E IP {newIp} (\u0431\u0443\u043B\u043E {oldIp})\",\n \"zh-cn\": \"{name}\uFF1A\u5DF2\u5728\u65B0 IP {newIp} \u627E\u5230\uFF08\u4E4B\u524D\u4E3A {oldIp}\uFF09\",\n },\n deviceOfflineRetrying: {\n en: \"{name}: device offline \u2014 will keep retrying every {seconds}s\",\n de: \"{name}: Ger\u00E4t offline \u2014 Versuche werden alle {seconds}s wiederholt\",\n ru: \"{name}: \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u043E \u043E\u0444\u043B\u0430\u0439\u043D \u2014 \u043F\u043E\u0432\u0442\u043E\u0440 \u043A\u0430\u0436\u0434\u044B\u0435 {seconds}\u0441\",\n pt: \"{name}: dispositivo offline \u2014 tentando novamente a cada {seconds}s\",\n nl: \"{name}: apparaat offline \u2014 opnieuw proberen elke {seconds}s\",\n fr: \"{name} : appareil hors-ligne \u2014 nouvelle tentative toutes les {seconds}s\",\n it: \"{name}: dispositivo offline \u2014 nuovi tentativi ogni {seconds}s\",\n es: \"{name}: dispositivo desconectado \u2014 reintentando cada {seconds}s\",\n pl: \"{name}: urz\u0105dzenie offline \u2014 ponawianie co {seconds}s\",\n uk: \"{name}: \u043F\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043E\u0444\u043B\u0430\u0439\u043D \u2014 \u043F\u043E\u0432\u0442\u043E\u0440\u043D\u0456 \u0441\u043F\u0440\u043E\u0431\u0438 \u043A\u043E\u0436\u043D\u0456 {seconds}\u0441\",\n \"zh-cn\": \"{name}\uFF1A\u8BBE\u5907\u79BB\u7EBF \u2014 \u5C06\u6BCF {seconds} \u79D2\u91CD\u8BD5\u4E00\u6B21\",\n },\n deviceUnreachable: {\n en: \"{name}: device unreachable \u2014 will keep retrying\",\n de: \"{name}: Ger\u00E4t nicht erreichbar \u2014 Versuche werden fortgesetzt\",\n ru: \"{name}: \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u043E \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u043E \u2014 \u043F\u043E\u043F\u044B\u0442\u043A\u0438 \u0431\u0443\u0434\u0443\u0442 \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0435\u043D\u044B\",\n pt: \"{name}: dispositivo inacess\u00EDvel \u2014 tentativas continuar\u00E3o\",\n nl: \"{name}: apparaat onbereikbaar \u2014 pogingen worden voortgezet\",\n fr: \"{name} : appareil injoignable \u2014 les tentatives continuent\",\n it: \"{name}: dispositivo non raggiungibile \u2014 i tentativi continueranno\",\n es: \"{name}: dispositivo inaccesible \u2014 se seguir\u00E1n intentando\",\n pl: \"{name}: urz\u0105dzenie nieosi\u0105galne \u2014 pr\u00F3by b\u0119d\u0105 kontynuowane\",\n uk: \"{name}: \u043F\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u0438\u0439 \u2014 \u0441\u043F\u0440\u043E\u0431\u0438 \u0442\u0440\u0438\u0432\u0430\u0442\u0438\u043C\u0443\u0442\u044C\",\n \"zh-cn\": \"{name}\uFF1A\u8BBE\u5907\u4E0D\u53EF\u8FBE \u2014 \u5C06\u7EE7\u7EED\u91CD\u8BD5\",\n },\n deviceErrorContext: {\n en: \"{name} {context}: {error}\",\n de: \"{name} {context}: {error}\",\n ru: \"{name} {context}: {error}\",\n pt: \"{name} {context}: {error}\",\n nl: \"{name} {context}: {error}\",\n fr: \"{name} {context} : {error}\",\n it: \"{name} {context}: {error}\",\n es: \"{name} {context}: {error}\",\n pl: \"{name} {context}: {error}\",\n uk: \"{name} {context}: {error}\",\n \"zh-cn\": \"{name} {context}\uFF1A{error}\",\n },\n connectionRestored: {\n en: \"{name}: connection restored\",\n de: \"{name}: Verbindung wiederhergestellt\",\n ru: \"{name}: \u0441\u043E\u0435\u0434\u0438\u043D\u0435\u043D\u0438\u0435 \u0432\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043E\",\n pt: \"{name}: conex\u00E3o restabelecida\",\n nl: \"{name}: verbinding hersteld\",\n fr: \"{name} : connexion r\u00E9tablie\",\n it: \"{name}: connessione ripristinata\",\n es: \"{name}: conexi\u00F3n restablecida\",\n pl: \"{name}: po\u0142\u0105czenie przywr\u00F3cone\",\n uk: \"{name}: \u0437'\u0454\u0434\u043D\u0430\u043D\u043D\u044F \u0432\u0456\u0434\u043D\u043E\u0432\u043B\u0435\u043D\u043E\",\n \"zh-cn\": \"{name}\uFF1A\u8FDE\u63A5\u5DF2\u6062\u590D\",\n },\n connectionRestoredUnstable: {\n en: \"{name}: connection restored (unstable mode)\",\n de: \"{name}: Verbindung wiederhergestellt (Unstable-Modus)\",\n ru: \"{name}: \u0441\u043E\u0435\u0434\u0438\u043D\u0435\u043D\u0438\u0435 \u0432\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043E (\u043D\u0435\u0441\u0442\u0430\u0431\u0438\u043B\u044C\u043D\u044B\u0439 \u0440\u0435\u0436\u0438\u043C)\",\n pt: \"{name}: conex\u00E3o restabelecida (modo inst\u00E1vel)\",\n nl: \"{name}: verbinding hersteld (instabiele modus)\",\n fr: \"{name} : connexion r\u00E9tablie (mode instable)\",\n it: \"{name}: connessione ripristinata (modalit\u00E0 instabile)\",\n es: \"{name}: conexi\u00F3n restablecida (modo inestable)\",\n pl: \"{name}: po\u0142\u0105czenie przywr\u00F3cone (tryb niestabilny)\",\n uk: \"{name}: \u0437'\u0454\u0434\u043D\u0430\u043D\u043D\u044F \u0432\u0456\u0434\u043D\u043E\u0432\u043B\u0435\u043D\u043E (\u043D\u0435\u0441\u0442\u0430\u0431\u0456\u043B\u044C\u043D\u0438\u0439 \u0440\u0435\u0436\u0438\u043C)\",\n \"zh-cn\": \"{name}\uFF1A\u8FDE\u63A5\u5DF2\u6062\u590D\uFF08\u4E0D\u7A33\u5B9A\u6A21\u5F0F\uFF09\",\n },\n unstableDetected: {\n en: \"{name}: unstable connection detected \u2014 using faster reconnect\",\n de: \"{name}: instabile Verbindung erkannt \u2014 schnellerer Reconnect aktiv\",\n ru: \"{name}: \u043E\u0431\u043D\u0430\u0440\u0443\u0436\u0435\u043D\u043E \u043D\u0435\u0441\u0442\u0430\u0431\u0438\u043B\u044C\u043D\u043E\u0435 \u0441\u043E\u0435\u0434\u0438\u043D\u0435\u043D\u0438\u0435 \u2014 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0435\u0442\u0441\u044F \u0431\u044B\u0441\u0442\u0440\u043E\u0435 \u043F\u0435\u0440\u0435\u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u0435\",\n pt: \"{name}: conex\u00E3o inst\u00E1vel detectada \u2014 usando reconex\u00E3o mais r\u00E1pida\",\n nl: \"{name}: instabiele verbinding gedetecteerd \u2014 sneller opnieuw verbinden\",\n fr: \"{name} : connexion instable d\u00E9tect\u00E9e \u2014 reconnexion plus rapide activ\u00E9e\",\n it: \"{name}: rilevata connessione instabile \u2014 riconnessione pi\u00F9 veloce\",\n es: \"{name}: conexi\u00F3n inestable detectada \u2014 reconexi\u00F3n m\u00E1s r\u00E1pida activada\",\n pl: \"{name}: wykryto niestabilne po\u0142\u0105czenie \u2014 szybsze ponowne \u0142\u0105czenie\",\n uk: \"{name}: \u0432\u0438\u044F\u0432\u043B\u0435\u043D\u043E \u043D\u0435\u0441\u0442\u0430\u0431\u0456\u043B\u044C\u043D\u0435 \u0437'\u0454\u0434\u043D\u0430\u043D\u043D\u044F \u2014 \u0432\u0438\u043A\u043E\u0440\u0438\u0441\u0442\u043E\u0432\u0443\u0454\u0442\u044C\u0441\u044F \u0448\u0432\u0438\u0434\u0448\u0435 \u043F\u043E\u0432\u0442\u043E\u0440\u043D\u0435 \u043F\u0456\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u043D\u044F\",\n \"zh-cn\": \"{name}\uFF1A\u68C0\u6D4B\u5230\u4E0D\u7A33\u5B9A\u8FDE\u63A5 \u2014 \u4F7F\u7528\u66F4\u5FEB\u7684\u91CD\u8FDE\",\n },\n connectionStabilized: {\n en: \"{name}: connection stabilized \u2014 using normal reconnect\",\n de: \"{name}: Verbindung stabilisiert \u2014 normaler Reconnect aktiv\",\n ru: \"{name}: \u0441\u043E\u0435\u0434\u0438\u043D\u0435\u043D\u0438\u0435 \u0441\u0442\u0430\u0431\u0438\u043B\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u043D\u043E \u2014 \u043E\u0431\u044B\u0447\u043D\u043E\u0435 \u043F\u0435\u0440\u0435\u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u0435\",\n pt: \"{name}: conex\u00E3o estabilizada \u2014 usando reconex\u00E3o normal\",\n nl: \"{name}: verbinding gestabiliseerd \u2014 normale reconnect actief\",\n fr: \"{name} : connexion stabilis\u00E9e \u2014 reconnexion normale active\",\n it: \"{name}: connessione stabilizzata \u2014 riconnessione normale\",\n es: \"{name}: conexi\u00F3n estabilizada \u2014 reconexi\u00F3n normal activa\",\n pl: \"{name}: po\u0142\u0105czenie ustabilizowane \u2014 normalne ponowne \u0142\u0105czenie\",\n uk: \"{name}: \u0437'\u0454\u0434\u043D\u0430\u043D\u043D\u044F \u0441\u0442\u0430\u0431\u0456\u043B\u0456\u0437\u043E\u0432\u0430\u043D\u043E \u2014 \u0437\u0432\u0438\u0447\u0430\u0439\u043D\u0435 \u043F\u043E\u0432\u0442\u043E\u0440\u043D\u0435 \u043F\u0456\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u043D\u044F\",\n \"zh-cn\": \"{name}\uFF1A\u8FDE\u63A5\u5DF2\u7A33\u5B9A \u2014 \u4F7F\u7528\u5E38\u89C4\u91CD\u8FDE\",\n },\n tokenInvalid: {\n en: \"{name}: token invalid \u2014 re-pair device to fix\",\n de: \"{name}: Token ung\u00FCltig \u2014 Ger\u00E4t neu koppeln, um den Fehler zu beheben\",\n ru: \"{name}: \u0442\u043E\u043A\u0435\u043D \u043D\u0435\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043B\u0435\u043D \u2014 \u0432\u044B\u043F\u043E\u043B\u043D\u0438\u0442\u0435 \u043F\u043E\u0432\u0442\u043E\u0440\u043D\u043E\u0435 \u0441\u043E\u043F\u0440\u044F\u0436\u0435\u043D\u0438\u0435 \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u0430\",\n pt: \"{name}: token inv\u00E1lido \u2014 emparelhe o dispositivo novamente para corrigir\",\n nl: \"{name}: token ongeldig \u2014 apparaat opnieuw koppelen om dit op te lossen\",\n fr: \"{name} : token invalide \u2014 r\u00E9appairez l'appareil pour corriger\",\n it: \"{name}: token non valido \u2014 riaccoppia il dispositivo per risolvere\",\n es: \"{name}: token inv\u00E1lido \u2014 vuelve a emparejar el dispositivo para arreglarlo\",\n pl: \"{name}: token nieprawid\u0142owy \u2014 sparuj urz\u0105dzenie ponownie, aby naprawi\u0107\",\n uk: \"{name}: \u0442\u043E\u043A\u0435\u043D \u043D\u0435\u0434\u0456\u0439\u0441\u043D\u0438\u0439 \u2014 \u043F\u043E\u0432\u0442\u043E\u0440\u0456\u0442\u044C \u0441\u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043D\u044F \u043F\u0440\u0438\u0441\u0442\u0440\u043E\u044E\",\n \"zh-cn\": \"{name}\uFF1Atoken \u65E0\u6548 \u2014 \u8BF7\u91CD\u65B0\u914D\u5BF9\u8BBE\u5907\u4EE5\u4FEE\u590D\",\n },\n} as const;\n\n/**\n * Look up a log string in the requested language with EN fallback.\n *\n * @param lang ioBroker system language (`'en'`, `'de'`, \u2026) \u2014 any string\n * accepted, falls back to `en` for unknown values.\n * @param key Translation key from {@link LOG_STRINGS}.\n * @param params Token values for `{name}` placeholders.\n */\nexport function tLog(\n lang: string,\n key: keyof typeof LOG_STRINGS,\n params?: Record<string, string | number | null | undefined>,\n): string {\n const langKey = (SUPPORTED_LANGS as readonly string[]).includes(lang) ? (lang as Lang) : \"en\";\n const bundle = LOG_STRINGS[key];\n const template = bundle[langKey] ?? bundle.en;\n return fmt(template, params);\n}\n"],
5
- "mappings": "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",
6
- "names": []
7
- }