iobroker.govee-smart 2.2.0 → 2.3.1

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
@@ -124,6 +124,15 @@ This adapter's MQTT authentication and BLE-over-LAN (ptReal) protocol implementa
124
124
  ---
125
125
 
126
126
  ## Changelog
127
+ ### 2.3.1 (2026-05-04)
128
+
129
+ - Smoke-Tests für GoveeCloudClient + GoveeMqttClient — Initial-State-Checks für getFailureReason, token, connected, plus Setter-Smoke-Tests (637 → 637+9 Tests). Volle Pfade über https/mqtt-Mocks kommen separat.
130
+
131
+ ### 2.3.0 (2026-05-04)
132
+
133
+ - App-Version-Drift-Monitor: täglicher iTunes-Lookup vergleicht die im Adapter hinterlegte Govee-App-Version mit der aktuellen iOS-Version. Bei Drift > 2 Minor wird gewarnt — Govees undokumentierte Endpoints rejecten gelegentlich zu alte Clients.
134
+ - Code-Hygiene: `onStateChange`-Handler in eigene Methoden für Diagnostics-Export und Generic-Capability-Routing aufgeteilt. Magic-Numbers durch `timing-constants.ts` ersetzt. Lifecycle-Flags besser dokumentiert.
135
+
127
136
  ### 2.2.0 (2026-05-04)
128
137
 
129
138
  - 2FA-Verifizierung triggert keinen Restart mehr. MQTT-Pushes typsicher, Sensor-Datenpunkte im richtigen Kanal (`sensor/`, `events/` statt `control/`).
@@ -144,20 +153,6 @@ This adapter's MQTT authentication and BLE-over-LAN (ptReal) protocol implementa
144
153
  - Verification warning shortened. The full step-by-step instructions live in the Wiki, the log only states the action.
145
154
  - "MQTT connected to AWS IoT" → "MQTT connected". "OpenAPI MQTT" → "Cloud-events" in user-facing logs.
146
155
 
147
- ### 2.1.2 (2026-05-02)
148
-
149
- - The verification message no longer claims your account has 2FA when it doesn't. Govee asks for a one-time check the first time it sees this client — same dialog, but the wording matches reality now.
150
- - Adapters upgrading from v2.1.0 had stored MQTT credentials as plain text by mistake. The corrupted leftover bytes are now cleared on first start, so the verification flow only runs once.
151
- - New device added to the catalogue: H61D5 (LED Strip).
152
-
153
- ### 2.1.1 (2026-05-02)
154
-
155
- - Security fix: in v2.1.0 your saved MQTT login (token + certificate) was accidentally stored unencrypted. Now actually encrypted at rest as intended.
156
- - Diagnostics datapoints renamed from `info.diagnostics_*` to `diag.export` / `diag.result` / `diag.tier`. Old datapoints are removed on first start — adjust scripts that referenced the old names.
157
- - The `diag.export` JSON now also shows failed Cloud calls (with status code) and recent log lines for the device, so a single JSON dump is enough for a bug report.
158
- - 2-Factor verification warning no longer repeats on every reconnect attempt. You'll see it once when Govee actually wants a code, not every minute while the adapter retries.
159
- - The MQTT connection is no longer dropped every few hours when the access token rotates — refreshed in the background. No more spurious 2FA warning after the adapter has been running a while.
160
-
161
156
  Older entries are in [CHANGELOG_OLD.md](CHANGELOG_OLD.md).
162
157
 
163
158
  ## Support
package/build/main.js CHANGED
@@ -37,6 +37,9 @@ var import_segment_wizard = require("./lib/segment-wizard");
37
37
  var import_sku_cache = require("./lib/sku-cache");
38
38
  var import_state_manager = require("./lib/state-manager");
39
39
  var import_types = require("./lib/types");
40
+ var import_timing_constants = require("./lib/timing-constants");
41
+ var import_govee_constants = require("./lib/govee-constants");
42
+ var import_http_client = require("./lib/http-client");
40
43
  const FULL_LIMITS = { perMinute: 8, perDay: 9e3 };
41
44
  const VERIFICATION_REQUEST_THROTTLE_MS = 3e4;
42
45
  const STATE_TO_COMMAND = {
@@ -77,18 +80,27 @@ class GoveeAdapter extends utils.Adapter {
77
80
  * einen unnötigen setStateAsync macht (H4).
78
81
  */
79
82
  lastConnectionState = null;
80
- /**
81
- * True nach dem ersten erfolgreichen App-API-Poll. checkAllReady wartet
82
- * darauf wenn Sensor-Devices angemeldet sind (H2).
83
- */
83
+ // === Lifecycle-Flags (Adapter-Boot-Sequenz) ===
84
+ // checkAllReady() prüft alle 5 Voraussetzungen gleichzeitig sie laufen
85
+ // parallel ab, kein lineares STATE_MACHINE-Pattern weil Channels
86
+ // unabhängig connecten.
87
+ /** LAN-Scan-Initial-Wait abgeschlossen (3s nach Start). */
88
+ lanScanDone = false;
89
+ /** State-Tree-Erstellung für alle Cached/Cloud-Devices fertig. */
90
+ statesReady = false;
91
+ /** Cloud-Init-Phase abgeschlossen (mit oder ohne Erfolg). */
92
+ cloudInitDone = false;
93
+ /** True nach dem ersten erfolgreichen App-API-Poll (für Sensoren mit Werten). */
84
94
  appApiInitialPollDone = false;
95
+ /** Verhindert Mehrfach-Ready-Log innerhalb derselben Adapter-Session. */
96
+ readyLogged = false;
97
+ /** Cloud war mindestens einmal connected — für „restored"-Log nach Down. */
98
+ cloudWasConnected = false;
99
+ /** Tägliches Interval für App-Version-Drift-Check gegen App-Store. */
100
+ appVersionCheckTimer;
101
+ // === Sub-Komponenten ===
85
102
  skuCache = null;
86
103
  localSnapshots = null;
87
- cloudWasConnected = false;
88
- readyLogged = false;
89
- cloudInitDone = false;
90
- lanScanDone = false;
91
- statesReady = false;
92
104
  stateCreationQueue = [];
93
105
  lanScanTimer;
94
106
  cleanupTimer;
@@ -340,8 +352,8 @@ class GoveeAdapter extends utils.Adapter {
340
352
  }
341
353
  }).catch((e) => this.log.debug(`pollAppApi failed: ${(0, import_types.errMessage)(e)}`));
342
354
  };
343
- this.appApiPollTimer = this.setInterval(triggerAppApiPoll, 2 * 60 * 1e3);
344
- this.appApiInitialTimer = this.setTimeout(triggerAppApiPoll, 5e3);
355
+ this.appApiPollTimer = this.setInterval(triggerAppApiPoll, import_timing_constants.APP_API_POLL_INTERVAL_MS);
356
+ this.appApiInitialTimer = this.setTimeout(triggerAppApiPoll, import_timing_constants.APP_API_INITIAL_DELAY_MS);
345
357
  if (!cachedOk) {
346
358
  const result = await this.cloudInitWithTimeout();
347
359
  this.cloudWasConnected = result.ok;
@@ -385,6 +397,18 @@ class GoveeAdapter extends utils.Adapter {
385
397
  this.cleanupTimer = this.setTimeout(() => {
386
398
  this.reapStaleDevices().catch((e) => this.log.debug(`Device cleanup failed: ${(0, import_types.errMessage)(e)}`));
387
399
  }, 3e4);
400
+ this.appVersionCheckTimer = this.setInterval(
401
+ () => {
402
+ this.checkAppVersionDrift().catch((e) => this.log.debug(`App version check error: ${(0, import_types.errMessage)(e)}`));
403
+ },
404
+ 24 * 60 * 60 * 1e3
405
+ );
406
+ this.setTimeout(
407
+ () => {
408
+ this.checkAppVersionDrift().catch((e) => this.log.debug(`App version check error: ${(0, import_types.errMessage)(e)}`));
409
+ },
410
+ 2 * 60 * 1e3
411
+ );
388
412
  this.updateConnectionState();
389
413
  this.checkAllReady();
390
414
  this.readyTimer = this.setTimeout(() => {
@@ -405,7 +429,7 @@ class GoveeAdapter extends utils.Adapter {
405
429
  }
406
430
  const loadPromise = this.deviceManager.loadFromCloud();
407
431
  const timeoutPromise = new Promise((resolve) => {
408
- this.cloudInitTimer = this.setTimeout(() => resolve({ ok: false, reason: "transient" }), 6e4);
432
+ this.cloudInitTimer = this.setTimeout(() => resolve({ ok: false, reason: "transient" }), import_timing_constants.READY_TIMEOUT_MS);
409
433
  });
410
434
  try {
411
435
  return await Promise.race([loadPromise, timeoutPromise]);
@@ -499,6 +523,10 @@ class GoveeAdapter extends utils.Adapter {
499
523
  this.clearTimeout(this.cloudInitTimer);
500
524
  this.cloudInitTimer = void 0;
501
525
  }
526
+ if (this.appVersionCheckTimer) {
527
+ this.clearInterval(this.appVersionCheckTimer);
528
+ this.appVersionCheckTimer = void 0;
529
+ }
502
530
  (_a = this.cloudRetry) == null ? void 0 : _a.dispose();
503
531
  (_b = this.segmentWizard) == null ? void 0 : _b.dispose();
504
532
  (_c = this.lanClient) == null ? void 0 : _c.stop();
@@ -535,7 +563,7 @@ class GoveeAdapter extends utils.Adapter {
535
563
  * @param state New state value
536
564
  */
537
565
  async onStateChange(id, state) {
538
- var _a, _b, _c, _d, _e;
566
+ var _a;
539
567
  if (!state || state.ack || !this.deviceManager || !this.stateManager) {
540
568
  return;
541
569
  }
@@ -591,40 +619,12 @@ class GoveeAdapter extends utils.Adapter {
591
619
  return;
592
620
  }
593
621
  if (stateSuffix === "diag.export" && val) {
594
- const deviceKey = `${device.sku}:${device.deviceId}`;
595
- const now = Date.now();
596
- const last = (_a = this.diagnosticsLastRun.get(deviceKey)) != null ? _a : 0;
597
- if (now - last < 2e3) {
598
- this.log.debug(`Diagnostics export throttled for ${device.name} \u2014 last run ${now - last}ms ago`);
599
- await this.setStateAsync(id, { val: false, ack: true });
600
- return;
601
- }
602
- this.diagnosticsLastRun.set(deviceKey, now);
603
- const diag = this.deviceManager.generateDiagnostics(device, (_b = this.version) != null ? _b : "unknown");
604
- const resultId = `${this.namespace}.${prefix}.diag.result`;
605
- await this.setStateAsync(resultId, {
606
- val: JSON.stringify(diag, null, 2),
607
- ack: true
608
- });
609
- await this.setStateAsync(id, { val: false, ack: true });
610
- this.log.info(`Diagnostics exported for ${device.name} (${device.sku})`);
622
+ await this.handleDiagnosticsExport(device, prefix, id);
611
623
  return;
612
624
  }
613
625
  const command = this.stateToCommand(stateSuffix);
614
626
  if (!command) {
615
- const obj = await this.getObjectAsync(id);
616
- const capType = (_c = obj == null ? void 0 : obj.native) == null ? void 0 : _c.capabilityType;
617
- const capInstance = (_d = obj == null ? void 0 : obj.native) == null ? void 0 : _d.capabilityInstance;
618
- if (typeof capType === "string" && typeof capInstance === "string") {
619
- try {
620
- await this.deviceManager.sendCapabilityCommand(device, capType, capInstance, val);
621
- await this.setStateAsync(id, { val, ack: true });
622
- } catch (err) {
623
- this.log.warn(`Command failed for ${device.name}: ${(0, import_types.errMessage)(err)}`);
624
- }
625
- } else {
626
- this.log.debug(`Unknown writable state: ${stateSuffix}`);
627
- }
627
+ await this.handleGenericCapabilityCommand(device, id, stateSuffix, val);
628
628
  return;
629
629
  }
630
630
  if ((command === "lightScene" || command === "diyScene" || command === "snapshot") && (val === "0" || val === 0)) {
@@ -635,7 +635,7 @@ class GoveeAdapter extends utils.Adapter {
635
635
  const level = typeof val === "number" ? val : parseInt(String(val), 10);
636
636
  if (!isNaN(level)) {
637
637
  device.sceneSpeed = level;
638
- (_e = this.deviceManager) == null ? void 0 : _e.persistDeviceToCache(device);
638
+ (_a = this.deviceManager) == null ? void 0 : _a.persistDeviceToCache(device);
639
639
  }
640
640
  await this.setStateAsync(id, { val, ack: true });
641
641
  return;
@@ -988,6 +988,51 @@ class GoveeAdapter extends utils.Adapter {
988
988
  * each retired device — those reaping APIs come in with the pruning patch,
989
989
  * not before (Memory `feedback_kein_phantom_schema`).
990
990
  */
991
+ /**
992
+ * App-Version-Drift-Check gegen iTunes-Lookup.
993
+ *
994
+ * Govee's app2.govee.com-Endpoints rejecten manchmal sehr alte
995
+ * User-Agent-Strings. Daily-Check fragt iTunes nach der aktuellen
996
+ * iOS-App-Version + vergleicht mit `GOVEE_APP_VERSION`. Bei Drift > 2
997
+ * minor versions: warn-Log + state `info.appVersionDrift` schreiben.
998
+ *
999
+ * Failures (5xx, network) werden silent debug-geloggt — kein User-Impact.
1000
+ */
1001
+ async checkAppVersionDrift() {
1002
+ var _a, _b, _c, _d, _e, _f;
1003
+ try {
1004
+ const resp = await (0, import_http_client.httpsRequest)({
1005
+ method: "GET",
1006
+ url: "https://itunes.apple.com/lookup?bundleId=com.ihoment.GoVeeSensor",
1007
+ headers: { "User-Agent": "ioBroker.govee-smart" },
1008
+ timeout: 1e4
1009
+ });
1010
+ const liveVersion = (_b = (_a = resp.results) == null ? void 0 : _a[0]) == null ? void 0 : _b.version;
1011
+ if (typeof liveVersion !== "string" || liveVersion.length === 0) {
1012
+ return;
1013
+ }
1014
+ const localParts = import_govee_constants.GOVEE_APP_VERSION.split(".").map(Number);
1015
+ const liveParts = liveVersion.split(".").map(Number);
1016
+ const localMajor = (_c = localParts[0]) != null ? _c : 0;
1017
+ const localMinor = (_d = localParts[1]) != null ? _d : 0;
1018
+ const liveMajor = (_e = liveParts[0]) != null ? _e : 0;
1019
+ const liveMinor = (_f = liveParts[1]) != null ? _f : 0;
1020
+ const liveTotal = liveMajor * 100 + liveMinor;
1021
+ const localTotal = localMajor * 100 + localMinor;
1022
+ const driftMinor = liveTotal - localTotal;
1023
+ const driftMessage = driftMinor === 0 ? `current (live=${liveVersion}, local=${import_govee_constants.GOVEE_APP_VERSION})` : driftMinor <= 2 ? `minor drift (live=${liveVersion}, local=${import_govee_constants.GOVEE_APP_VERSION})` : `STALE (live=${liveVersion}, local=${import_govee_constants.GOVEE_APP_VERSION}) \u2014 bump GOVEE_APP_VERSION`;
1024
+ await this.setStateAsync("info.appVersionDrift", { val: driftMessage, ack: true }).catch(() => void 0);
1025
+ if (driftMinor > 2) {
1026
+ this.log.warn(
1027
+ `Govee app version drift: live ${liveVersion} vs local ${import_govee_constants.GOVEE_APP_VERSION} \u2014 undocumented endpoints may start failing. Run sync-govee-app-version.py + release a new adapter version.`
1028
+ );
1029
+ } else {
1030
+ this.log.debug(`App version: ${driftMessage}`);
1031
+ }
1032
+ } catch (e) {
1033
+ this.log.debug(`App version check failed: ${(0, import_types.errMessage)(e)}`);
1034
+ }
1035
+ }
991
1036
  async reapStaleDevices() {
992
1037
  if (!this.stateManager || !this.deviceManager) {
993
1038
  return;
@@ -1727,6 +1772,67 @@ class GoveeAdapter extends utils.Adapter {
1727
1772
  colorRgb: "",
1728
1773
  colorTemperature: ""
1729
1774
  };
1775
+ /**
1776
+ * Diagnostics-Export-Button-Handler. Throttled auf 2s pro Device damit
1777
+ * wiederholte/Skript-Trigger keinen Burst von JSON-Serialisierungen
1778
+ * erzeugen.
1779
+ *
1780
+ * @param device Govee device
1781
+ * @param prefix Device state prefix
1782
+ * @param triggerStateId The state-id that triggered the export (so we can ack)
1783
+ */
1784
+ async handleDiagnosticsExport(device, prefix, triggerStateId) {
1785
+ var _a, _b;
1786
+ if (!this.deviceManager) {
1787
+ return;
1788
+ }
1789
+ const deviceKey = `${device.sku}:${device.deviceId}`;
1790
+ const now = Date.now();
1791
+ const last = (_a = this.diagnosticsLastRun.get(deviceKey)) != null ? _a : 0;
1792
+ if (now - last < 2e3) {
1793
+ this.log.debug(`Diagnostics export throttled for ${device.name} \u2014 last run ${now - last}ms ago`);
1794
+ await this.setStateAsync(triggerStateId, { val: false, ack: true });
1795
+ return;
1796
+ }
1797
+ this.diagnosticsLastRun.set(deviceKey, now);
1798
+ const diag = this.deviceManager.generateDiagnostics(device, (_b = this.version) != null ? _b : "unknown");
1799
+ const resultId = `${this.namespace}.${prefix}.diag.result`;
1800
+ await this.setStateAsync(resultId, {
1801
+ val: JSON.stringify(diag, null, 2),
1802
+ ack: true
1803
+ });
1804
+ await this.setStateAsync(triggerStateId, { val: false, ack: true });
1805
+ this.log.info(`Diagnostics exported for ${device.name} (${device.sku})`);
1806
+ }
1807
+ /**
1808
+ * Generischer Capability-Routing-Pfad für States die nicht im STATE_TO_COMMAND
1809
+ * Mapping sind. Liest `native.capabilityType`/`capabilityInstance` aus dem
1810
+ * State-Object und schickt das via Cloud-API.
1811
+ *
1812
+ * @param device Target device
1813
+ * @param id Full state-id (für ack)
1814
+ * @param stateSuffix State-Pfad innerhalb Device (für debug-log)
1815
+ * @param val Value zum Senden
1816
+ */
1817
+ async handleGenericCapabilityCommand(device, id, stateSuffix, val) {
1818
+ var _a, _b;
1819
+ if (!this.deviceManager) {
1820
+ return;
1821
+ }
1822
+ const obj = await this.getObjectAsync(id);
1823
+ const capType = (_a = obj == null ? void 0 : obj.native) == null ? void 0 : _a.capabilityType;
1824
+ const capInstance = (_b = obj == null ? void 0 : obj.native) == null ? void 0 : _b.capabilityInstance;
1825
+ if (typeof capType === "string" && typeof capInstance === "string") {
1826
+ try {
1827
+ await this.deviceManager.sendCapabilityCommand(device, capType, capInstance, val);
1828
+ await this.setStateAsync(id, { val, ack: true });
1829
+ } catch (err) {
1830
+ this.log.warn(`Command failed for ${device.name}: ${(0, import_types.errMessage)(err)}`);
1831
+ }
1832
+ } else {
1833
+ this.log.debug(`Unknown writable state: ${stateSuffix}`);
1834
+ }
1835
+ }
1730
1836
  /**
1731
1837
  * Reset related dropdown states when switching between scenes/snapshots/colors.
1732
1838
  * Each mode-switch resets all OTHER mode dropdowns to "---" (0).
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 {\n buildDeviceStateDefs,\n getDefaultLanStates,\n mapCloudStateValue,\n planCloudCapabilityWrites,\n} from \"./lib/capability-mapper\";\nimport { getDeviceTier, initDeviceRegistry } from \"./lib/device-registry\";\nimport { DeviceManager, resolveSegmentCount, SEGMENT_HARD_MAX } from \"./lib/device-manager\";\nimport { GoveeApiClient } from \"./lib/govee-api-client\";\nimport { GoveeCloudClient } from \"./lib/govee-cloud-client\";\nimport { GoveeLanClient } from \"./lib/govee-lan-client\";\nimport { GoveeMqttClient } from \"./lib/govee-mqtt-client\";\nimport { GoveeOpenapiMqttClient } from \"./lib/govee-openapi-mqtt-client\";\nimport { LocalSnapshotStore, type LocalSnapshot, type SnapshotSegment } from \"./lib/local-snapshots\";\nimport { CloudRetryLoop, type CloudRetryHost } from \"./lib/cloud-retry\";\nimport { RateLimiter } from \"./lib/rate-limiter\";\nimport { SegmentWizard, wizardIdleText, type WizardHost, type WizardResult } from \"./lib/segment-wizard\";\nimport { SkuCache } from \"./lib/sku-cache\";\nimport { StateManager } from \"./lib/state-manager\";\nimport {\n hexToRgb,\n errMessage,\n parseSegmentList,\n resolveStatesValue,\n rgbIntToHex,\n rgbToHex,\n type AdapterConfig,\n type CloudLoadResult,\n type CloudStateCapability,\n type DeviceState,\n type GoveeDevice,\n type PersistedMqttCredentials,\n} from \"./lib/types\";\n\n/**\n * Rate limit defaults \u2014 full Cloud API budget (8/min, 9000/day). v2 no\n * longer halves this with govee-appliances because that adapter is\n * deprecated and won't run alongside govee-smart.\n */\nconst FULL_LIMITS = { perMinute: 8, perDay: 9000 };\n\n/**\n * Minimum gap between two `mqttAuth: requestCode` calls. Govee returns 200\n * for every request and queues another email \u2014 without a throttle, double-clicking\n * the button in admin would spam the user's inbox. 30 s is short enough that a\n * legitimate retry after a failed delivery still feels responsive.\n */\nconst VERIFICATION_REQUEST_THROTTLE_MS = 30_000;\n\n/**\n * State-suffix \u2192 command-name lookup for writable states. Segment indices\n * are dynamic and handled by regex in stateToCommand \u2014 everything else is\n * a straight string mapping.\n */\nconst STATE_TO_COMMAND: Readonly<Record<string, string>> = {\n \"control.power\": \"power\",\n \"control.brightness\": \"brightness\",\n \"control.colorRgb\": \"colorRgb\",\n \"control.colorTemperature\": \"colorTemperature\",\n \"control.scene\": \"scene\",\n \"control.gradient_toggle\": \"gradientToggle\",\n \"scenes.light_scene\": \"lightScene\",\n \"scenes.diy_scene\": \"diyScene\",\n \"scenes.scene_speed\": \"sceneSpeed\",\n \"music.music_mode\": \"music\",\n \"music.music_sensitivity\": \"music\",\n \"music.music_auto_color\": \"music\",\n \"snapshots.snapshot_cloud\": \"snapshot\",\n \"segments.command\": \"segmentBatch\",\n};\n\nclass GoveeAdapter extends utils.Adapter {\n private deviceManager: DeviceManager | null = null;\n private stateManager: StateManager | null = null;\n private lanClient: GoveeLanClient | null = null;\n private mqttClient: GoveeMqttClient | null = null;\n private openapiMqttClient: GoveeOpenapiMqttClient | null = null;\n private cloudClient: GoveeCloudClient | null = null;\n private rateLimiter: RateLimiter | null = null;\n /** Repeating timer for the App-API poll (sensor-state pull). */\n private appApiPollTimer: ioBroker.Interval | undefined;\n /**\n * One-shot timer for the FIRST app-api poll (5s nach start) \u2014 Handle\n * damit onUnload das wegr\u00E4umen kann bevor es ins Leere feuert.\n */\n private appApiInitialTimer: ioBroker.Timeout | undefined;\n /** One-shot timer for cloud-init 60s safety timeout \u2014 gleiches Pattern. */\n private cloudInitTimer: ioBroker.Timeout | undefined;\n /**\n * Letzter info.connection-Wert \u2014 Cache damit nicht jeder device-update\n * einen unn\u00F6tigen setStateAsync macht (H4).\n */\n private lastConnectionState: boolean | null = null;\n /**\n * True nach dem ersten erfolgreichen App-API-Poll. checkAllReady wartet\n * darauf wenn Sensor-Devices angemeldet sind (H2).\n */\n private appApiInitialPollDone = false;\n private skuCache: SkuCache | null = null;\n private localSnapshots: LocalSnapshotStore | null = null;\n private cloudWasConnected = false;\n private readyLogged = false;\n private cloudInitDone = false;\n private lanScanDone = false;\n private statesReady = false;\n private stateCreationQueue: Promise<void>[] = [];\n private lanScanTimer: ioBroker.Timeout | undefined;\n private cleanupTimer: ioBroker.Timeout | undefined;\n private readyTimer: ioBroker.Timeout | undefined;\n private cloudRetry: CloudRetryLoop | null = null;\n private segmentWizard: SegmentWizard | null = null;\n private unhandledRejectionHandler: ((reason: unknown) => void) | null = null;\n private uncaughtExceptionHandler: ((err: Error) => void) | null = null;\n /** Per-device timestamp of the last diagnostics export \u2014 throttle gate */\n private diagnosticsLastRun = new Map<string, number>();\n /** Cached admin language from system.config \u2014 used for wizard UI text */\n private adminLanguage = \"en\";\n /** Last time `requestCode` was triggered via onMessage \u2014 guards against double-click email spam. */\n private lastVerificationRequestMs = 0;\n\n /** @param options Adapter options */\n public constructor(options: Partial<utils.AdapterOptions> = {}) {\n super({ ...options, name: \"govee-smart\" });\n // Per ioBroker rule: async handlers registered on events MUST .catch,\n // otherwise rejections become unhandled \u2192 SIGKILL code 6 \u2192 restart loop.\n this.on(\"ready\", () =>\n this.onReady().catch(e =>\n this.log.error(`onReady crashed: ${e instanceof Error ? (e.stack ?? e.message) : String(e)}`),\n ),\n );\n this.on(\"stateChange\", (id, state) =>\n this.onStateChange(id, state).catch(e => this.log.warn(`onStateChange crashed for ${id}: ${errMessage(e)}`)),\n );\n this.on(\"message\", obj => this.onMessage(obj));\n this.on(\"unload\", callback => this.onUnload(callback));\n // Last-line-of-defence against unhandled rejections / sync throws from\n // fire-and-forget paths. The per-handler .catch() wrappers cover the\n // direct entry points; this catches whatever slips past them so the\n // adapter logs the cause instead of triggering js-controller SIGKILL.\n this.unhandledRejectionHandler = (reason: unknown) => {\n this.log.error(\n `Unhandled rejection: ${reason instanceof Error ? (reason.stack ?? reason.message) : String(reason)}`,\n );\n };\n this.uncaughtExceptionHandler = (err: Error) => {\n this.log.error(`Uncaught exception: ${err.stack ?? err.message}`);\n };\n process.on(\"unhandledRejection\", this.unhandledRejectionHandler);\n process.on(\"uncaughtException\", this.uncaughtExceptionHandler);\n }\n\n /** Adapter started \u2014 initialize all channels */\n private async onReady(): Promise<void> {\n const config = this.config as unknown as AdapterConfig;\n\n // info channel + states are declared as instanceObjects in\n // io-package.json, so js-controller materialises them on install /\n // upgrade. We only initialise the runtime values here.\n await this.setStateAsync(\"info.connection\", { val: false, ack: true });\n await this.setStateAsync(\"info.mqttConnected\", { val: false, ack: true });\n await this.setStateAsync(\"info.cloudConnected\", { val: false, ack: true });\n await this.setStateAsync(\"info.openapiMqttConnected\", {\n val: false,\n ack: true,\n });\n await this.setStateAsync(\"info.refresh_cloud_data\", {\n val: false,\n ack: true,\n });\n // Load admin language from system.config so wizard prose matches the\n // user's Admin UI. Falls back to English on any lookup failure.\n try {\n const sysConf = await this.getForeignObjectAsync(\"system.config\");\n const lang = (sysConf?.common as { language?: string } | undefined)?.language;\n if (typeof lang === \"string\" && lang.length > 0) {\n this.adminLanguage = lang;\n }\n } catch {\n // Keep default \"en\"\n }\n await this.setStateAsync(\"info.wizardStatus\", {\n val: wizardIdleText(this.adminLanguage),\n ack: true,\n });\n\n this.stateManager = new StateManager(this);\n // General groups online state (reflects Cloud connection)\n await this.stateManager.createGroupsOnlineState(false);\n this.deviceManager = new DeviceManager(this.log, this);\n const dataDir = utils.getAbsoluteInstanceDataDir(this);\n\n // Load device registry from devices.json in the adapter package root.\n // Status filter: verified+reported active by default; seed-status entries\n // require the experimentalQuirks config toggle.\n initDeviceRegistry({\n experimental: config.experimentalQuirks === true,\n log: this.log,\n });\n this.skuCache = new SkuCache(dataDir, this.log);\n this.localSnapshots = new LocalSnapshotStore(dataDir, this.log);\n this.deviceManager.setSkuCache(this.skuCache);\n\n // API client for undocumented scene/music/DIY libraries (always available)\n const apiClient = new GoveeApiClient();\n apiClient.setEmail(config.goveeEmail);\n this.deviceManager.setApiClient(apiClient);\n\n this.deviceManager.setCallbacks(\n (device, state) => this.onDeviceStateUpdate(device, state),\n devices => this.onDeviceListChanged(devices),\n );\n\n // Update info.ip when LAN IP changes\n this.deviceManager.onLanIpChanged = (device, ip) => {\n const prefix = this.stateManager!.devicePrefix(device);\n this.setStateAsync(`${prefix}.info.ip`, { val: ip, ack: true }).catch(() => {});\n };\n\n // Sync individual segment states after batch command\n this.deviceManager.onSegmentBatchUpdate = (device, batch) => {\n const prefix = this.stateManager!.devicePrefix(device);\n for (const idx of batch.segments) {\n if (batch.color !== undefined) {\n const hex = rgbIntToHex(batch.color);\n this.setStateAsync(`${prefix}.segments.${idx}.color`, {\n val: hex,\n ack: true,\n }).catch(() => {});\n }\n if (batch.brightness !== undefined) {\n this.setStateAsync(`${prefix}.segments.${idx}.brightness`, {\n val: batch.brightness,\n ack: true,\n }).catch(() => {});\n }\n }\n };\n\n // Sync per-segment states from MQTT BLE status push (AA A5 packets)\n this.deviceManager.onMqttSegmentUpdate = (device, segments) => {\n const prefix = this.stateManager!.devicePrefix(device);\n for (const seg of segments) {\n this.setStateAsync(`${prefix}.segments.${seg.index}.color`, {\n val: rgbToHex(seg.r, seg.g, seg.b),\n ack: true,\n }).catch(() => {});\n this.setStateAsync(`${prefix}.segments.${seg.index}.brightness`, {\n val: seg.brightness,\n ack: true,\n }).catch(() => {});\n }\n };\n\n // When MQTT reveals more segments than the Cloud advertised, rebuild\n // the device's state tree so the extra segments get their datapoints.\n this.deviceManager.onSegmentCountGrown = device => {\n if (!this.stateManager) {\n return;\n }\n this.stateManager.createSegmentStates(device).catch(e => {\n this.log.warn(`Failed to rebuild segment tree for ${device.name} after count growth: ${errMessage(e)}`);\n });\n };\n\n // Log startup with configured channels\n const startChannels: string[] = [\"LAN\"];\n if (config.apiKey) {\n startChannels.push(\"Cloud\");\n }\n if (config.goveeEmail && config.goveePassword) {\n startChannels.push(\"MQTT\");\n }\n this.log.info(`Starting (${startChannels.join(\", \")})`);\n\n // --- LAN (always active) ---\n this.lanClient = new GoveeLanClient(this.log, this);\n this.deviceManager.setLanClient(this.lanClient);\n\n this.lanClient.start(\n lanDevice => {\n this.deviceManager!.handleLanDiscovery(lanDevice);\n // Poll status only when MQTT is unavailable. With an active MQTT\n // subscription Govee pushes state changes authoritatively, so the\n // LAN devStatus request would be duplicate traffic.\n if (!this.mqttClient?.connected) {\n this.lanClient!.requestStatus(lanDevice.ip);\n }\n },\n (sourceIp, status) => {\n this.deviceManager!.handleLanStatus(sourceIp, status);\n },\n 30_000,\n config.networkInterface || \"\",\n );\n\n // Wait for first LAN scan responses (UDP multicast, devices respond within 1-2s)\n this.lanScanTimer = this.setTimeout(() => {\n this.lanScanDone = true;\n this.checkAllReady();\n }, 3_000);\n\n // --- MQTT (if account credentials provided) ---\n // Initialize MQTT before Cloud so scene library can load on first cycle\n if (config.goveeEmail && config.goveePassword) {\n this.mqttClient = new GoveeMqttClient(config.goveeEmail, config.goveePassword, this.log, this);\n\n // Forward every parsed MQTT op.command into the diagnostics ring buffer\n // so diag.export contains the recent packets per device.\n this.mqttClient.setPacketHook((deviceId, topic, hex) => {\n this.deviceManager?.getDiagnostics().addMqttPacket(deviceId, topic, hex);\n });\n\n // 2FA: forward optional code from settings into the next login attempt;\n // clear the field automatically once Govee has accepted it.\n this.mqttClient.setVerificationCode(config.mqttVerificationCode ?? \"\");\n this.mqttClient.setOnVerificationConsumed(() => {\n this.clearVerificationCodeSetting().catch(e => {\n this.log.warn(`Could not clear mqttVerificationCode: ${errMessage(e)}`);\n });\n });\n this.mqttClient.setOnVerificationFailed(reason => {\n // On 'failed' (455 / 454+code-was-sent) blank the code so the user\n // doesn't keep retrying with a stale value. On 'pending' (454 + no\n // code) we leave the field as-is \u2014 the user is about to fill it.\n if (reason === \"failed\") {\n this.clearVerificationCodeSetting().catch(() => {});\n }\n });\n\n // Re-use cached MQTT credentials across restarts. Stored in the\n // info.mqttCredentials state (NOT in adapter native): writing to\n // system.adapter.X.0 native triggers a js-controller adapter\n // restart, which would loop endlessly on every login. States are\n // restart-safe.\n //\n // One-shot: clean up legacy v2.1.0/v2.1.1/v2.1.2 native fields\n // that contained plaintext credentials. Best-effort.\n await this.cleanupLegacyMqttNativeOnce();\n const cachedCreds = await this.loadPersistedCredsFromState();\n if (cachedCreds) {\n this.mqttClient.setPersistedCredentials(cachedCreds);\n }\n this.mqttClient.setOnCredentialsRefresh(creds => {\n this.persistCredsToState(creds).catch(e => {\n this.log.warn(`Could not persist MQTT credentials: ${errMessage(e)}`);\n });\n });\n\n await this.mqttClient.connect(\n update => this.deviceManager!.handleMqttStatus(update),\n connected => {\n this.setStateAsync(\"info.mqttConnected\", {\n val: connected,\n ack: true,\n }).catch(() => {});\n if (connected) {\n this.checkAllReady();\n }\n this.updateConnectionState();\n },\n // Forward every fresh bearer token \u2014 fires on initial login and on\n // each reconnect-login, so the API client never runs with a stale one.\n token => apiClient.setBearerToken(token),\n );\n }\n\n // --- Device data: Cache first, Cloud only on cache miss ---\n const cachedOk = this.deviceManager.loadFromCache();\n\n if (config.apiKey) {\n this.cloudClient = new GoveeCloudClient(config.apiKey, this.log);\n // Capture the most recent Cloud response per (deviceId, endpoint) for\n // diagnostics \u2014 bounded by the DiagnosticsCollector's response slot cap.\n this.cloudClient.setResponseHook((deviceId, endpoint, body) => {\n this.deviceManager?.getDiagnostics().setApiResponse(deviceId, endpoint, body);\n });\n this.deviceManager.setCloudClient(this.cloudClient);\n\n // Bridge synthetic capabilities (App-API, OpenAPI-MQTT events) into the\n // same setState pipeline as polled Cloud state. Keeps mapCloudStateValue\n // as the single source of truth for value coercion + state-id resolution.\n this.deviceManager.setOnCloudCapabilities((device, caps) => {\n this.applyCloudCapabilities(device, caps).catch(e =>\n this.log.warn(`applyCloudCapabilities failed for ${device.sku}: ${errMessage(e)}`),\n );\n });\n\n this.rateLimiter = new RateLimiter(this.log, this, FULL_LIMITS.perMinute, FULL_LIMITS.perDay);\n this.rateLimiter.start();\n this.deviceManager.setRateLimiter(this.rateLimiter);\n\n // OpenAPI-MQTT \u2014 push channel for appliance/sensor events\n // (lackWater, iceFull, bodyAppeared etc.). API key is enough; no\n // separate credentials required. Connection runs in parallel to\n // the AWS-IoT MQTT used for status push of regular devices.\n this.openapiMqttClient = new GoveeOpenapiMqttClient(config.apiKey, this.log, this);\n this.openapiMqttClient.connect(\n event => this.deviceManager?.handleOpenApiEvent(event),\n connected => {\n this.setStateAsync(\"info.openapiMqttConnected\", {\n val: connected,\n ack: true,\n }).catch(() => {});\n },\n );\n\n // App-API poll \u2014 every 2 minutes, pulls state for sensors like H5179\n // where OpenAPI v2 /device/state returns empty. Bearer token comes\n // from the AWS-IoT MQTT login, so a no-op until that succeeds.\n const triggerAppApiPoll = (): void => {\n this.deviceManager\n ?.pollAppApi()\n .then(() => {\n // H2 \u2014 Mark initial-poll-done und re-check Ready damit der\n // Adapter \u201Eready\" loggen kann sobald Sensor-Werte da sind.\n if (!this.appApiInitialPollDone) {\n this.appApiInitialPollDone = true;\n this.checkAllReady();\n }\n })\n .catch(e => this.log.debug(`pollAppApi failed: ${errMessage(e)}`));\n };\n this.appApiPollTimer = this.setInterval(triggerAppApiPoll, 2 * 60 * 1000);\n // Initial poll 5s nach Start \u2014 gibt MQTT Zeit f\u00FCr den Bearer-Login.\n // Ohne diesen Sofort-Poll bleiben Sensoren wie H5179 die ersten 2\n // Minuten nach Start offline (Online-Signal kommt nur via App-API).\n // Handle in Member-Variable damit onUnload den Timer cleart bevor\n // er auf disposed deviceManager feuert.\n this.appApiInitialTimer = this.setTimeout(triggerAppApiPoll, 5000);\n\n if (!cachedOk) {\n // No cache \u2014 first start, fetch from Cloud with 60s hard-timeout.\n // If Cloud hangs/fails, we don't want to block adapter startup indefinitely.\n const result = await this.cloudInitWithTimeout();\n this.cloudWasConnected = result.ok;\n this.ensureCloudRetry().setConnected(result.ok);\n this.setStateAsync(\"info.cloudConnected\", {\n val: result.ok,\n ack: true,\n }).catch(() => {});\n this.stateManager?.updateGroupsOnline(result.ok).catch(() => {});\n\n if (result.ok) {\n await this.loadCloudStates();\n } else {\n this.handleCloudFailure(result);\n }\n } else {\n this.log.info(\"Using cached device data \u2014 no Cloud calls needed\");\n this.cloudWasConnected = true;\n this.ensureCloudRetry().setConnected(true);\n this.setStateAsync(\"info.cloudConnected\", {\n val: true,\n ack: true,\n }).catch(() => {});\n this.stateManager?.updateGroupsOnline(true).catch(() => {});\n }\n // Load group membership from undocumented API (needs bearer token + device map)\n await this.deviceManager.loadGroupMembers();\n\n this.cloudInitDone = true;\n }\n\n // Wait for all state creation from cache/cloud load to complete.\n // Drain-loop: a callback that fires during the await (e.g. a late LAN\n // discovery) can push fresh promises into the queue \u2014 we need to await\n // those too before flipping statesReady, otherwise the initial state\n // tree would be incomplete on very fast startups.\n while (this.stateCreationQueue.length > 0) {\n const pending = this.stateCreationQueue;\n this.stateCreationQueue = [];\n await Promise.all(pending);\n }\n this.statesReady = true;\n\n // Subscribe to all writable device and group states\n await this.subscribeStatesAsync(\"devices.*\");\n await this.subscribeStatesAsync(\"groups.*\");\n await this.subscribeStatesAsync(\"info.refresh_cloud_data\");\n\n // Cleanup stale devices after initial discovery (30s delay for LAN scan).\n // Reaps devices from every adapter-level map that was keyed on them so the\n // process doesn't leak memory across Cloud-side device turnover.\n this.cleanupTimer = this.setTimeout(() => {\n this.reapStaleDevices().catch(e => this.log.debug(`Device cleanup failed: ${errMessage(e)}`));\n }, 30_000);\n\n this.updateConnectionState();\n\n // Check if all channels are ready \u2014 may already be true if MQTT connected fast\n this.checkAllReady();\n // Safety timeout: log ready even if a channel takes too long.\n // 60s deckt normalen MQTT-Connect + 1 Reconnect-Attempt ab.\n this.readyTimer = this.setTimeout(() => {\n if (!this.readyLogged) {\n this.readyLogged = true;\n this.logDeviceSummary();\n }\n }, 60_000);\n }\n\n /**\n * Initial Cloud-Load mit 60-Sekunden-Hardtimeout.\n * Blockiert nicht l\u00E4nger \u2014 wenn Cloud h\u00E4ngt, geht Adapter mit LAN+MQTT weiter,\n * und der Retry-Loop probiert's passend zum Fehlergrund erneut.\n */\n private async cloudInitWithTimeout(): Promise<CloudLoadResult> {\n if (!this.deviceManager) {\n return { ok: false, reason: \"transient\" };\n }\n const loadPromise = this.deviceManager.loadFromCloud();\n const timeoutPromise = new Promise<CloudLoadResult>(resolve => {\n this.cloudInitTimer = this.setTimeout(() => resolve({ ok: false, reason: \"transient\" }), 60_000);\n });\n try {\n return await Promise.race([loadPromise, timeoutPromise]);\n } catch {\n return { ok: false, reason: \"transient\" };\n }\n }\n\n /** Build the host object for {@link CloudRetryLoop}. */\n private buildCloudRetryHost(): CloudRetryHost {\n return {\n log: this.log,\n setTimeout: (cb, ms) => this.setTimeout(cb, ms),\n clearTimeout: h => this.clearTimeout(h as ioBroker.Timeout),\n loadFromCloud: () => this.cloudInitWithTimeout(),\n onCloudRestored: async () => {\n this.cloudWasConnected = true;\n this.setStateAsync(\"info.cloudConnected\", {\n val: true,\n ack: true,\n }).catch(() => {});\n this.stateManager?.updateGroupsOnline(true).catch(() => {});\n await this.loadCloudStates();\n },\n };\n }\n\n /** Lazy-initialise the retry loop on first use. */\n private ensureCloudRetry(): CloudRetryLoop {\n if (!this.cloudRetry) {\n this.cloudRetry = new CloudRetryLoop(this.buildCloudRetryHost());\n this.cloudRetry.setConnected(this.cloudWasConnected);\n }\n return this.cloudRetry;\n }\n\n /**\n * React to a Cloud-load outcome \u2014 delegates to {@link CloudRetryLoop}.\n *\n * @param result CloudLoadResult from initial load or retry attempt\n */\n private handleCloudFailure(result: CloudLoadResult): void {\n this.ensureCloudRetry().handleResult(result);\n }\n\n /**\n * React to the user writing `info.refresh_cloud_data = true`. Performs one\n * full Cloud reload cycle so newly created scenes/snapshots from the Govee\n * Home app show up without an adapter restart.\n */\n private async handleManualCloudRefresh(): Promise<void> {\n if (!this.deviceManager || !this.cloudClient) {\n this.log.info(\"Refresh cloud data: no Cloud client configured (API key missing) \u2014 nothing to do\");\n return;\n }\n this.log.info(\"Refresh cloud data: re-fetching scenes and snapshots for all devices\");\n try {\n const changed = await this.deviceManager.refreshSceneData();\n if (changed) {\n await this.loadCloudStates();\n }\n // G2 \u2014 kein \u201Edone\"-Log: User hat den Button gedr\u00FCckt, sieht das\n // Ergebnis am Adapter-Verhalten. \u201Edone\" auf info-level w\u00E4re bei\n // Erfolg redundant (Fehler-Pfad direkt darunter ist warn).\n } catch (e) {\n this.log.warn(`Refresh cloud data failed: ${errMessage(e)}`);\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.lanScanTimer) {\n this.clearTimeout(this.lanScanTimer);\n }\n if (this.cleanupTimer) {\n this.clearTimeout(this.cleanupTimer);\n }\n if (this.readyTimer) {\n this.clearTimeout(this.readyTimer);\n }\n if (this.appApiPollTimer) {\n this.clearInterval(this.appApiPollTimer);\n this.appApiPollTimer = undefined;\n }\n if (this.appApiInitialTimer) {\n this.clearTimeout(this.appApiInitialTimer);\n this.appApiInitialTimer = undefined;\n }\n if (this.cloudInitTimer) {\n this.clearTimeout(this.cloudInitTimer);\n this.cloudInitTimer = undefined;\n }\n this.cloudRetry?.dispose();\n this.segmentWizard?.dispose();\n this.lanClient?.stop();\n this.mqttClient?.disconnect();\n this.openapiMqttClient?.disconnect();\n this.rateLimiter?.stop();\n // Remove process-level handlers so an adapter restart doesn't stack them.\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 // onUnload MUST be synchronous \u2014 don't await, but silence potential\n // promise rejection during teardown to avoid \"unhandled rejection\" warnings.\n this.setState(\"info.connection\", { val: false, ack: true }).catch(() => {});\n this.setState(\"info.mqttConnected\", { val: false, ack: true }).catch(() => {});\n this.setState(\"info.openapiMqttConnected\", {\n val: false,\n ack: true,\n }).catch(() => {});\n this.setState(\"info.cloudConnected\", { val: false, ack: true }).catch(() => {});\n } catch {\n // ignore\n }\n callback();\n }\n\n /**\n * Handle state changes from user (write operations).\n *\n * @param id State ID\n * @param state New state value\n */\n private async onStateChange(id: string, state: ioBroker.State | null | undefined): Promise<void> {\n if (!state || state.ack || !this.deviceManager || !this.stateManager) {\n return;\n }\n\n // Global refresh button \u2014 triggers one fresh cloud fetch across all\n // devices and re-builds the state tree. Handy after creating a new\n // snapshot in the Govee Home app without restarting the adapter.\n if (id === `${this.namespace}.info.refresh_cloud_data` && state.val) {\n await this.handleManualCloudRefresh();\n await this.setStateAsync(id, { val: false, ack: true });\n return;\n }\n\n // Find which device this state belongs to\n const localId = id.replace(`${this.namespace}.`, \"\");\n if (!localId.startsWith(\"devices.\") && !localId.startsWith(\"groups.\")) {\n return;\n }\n\n const device = this.findDeviceForState(localId);\n if (!device) {\n return;\n }\n\n // Determine command from state suffix after device prefix\n const prefix = this.stateManager.devicePrefix(device);\n const stateSuffix = localId.slice(prefix.length + 1);\n\n // Resolve dropdown input \u2014 accept Number, numeric String, or label\n // (case-insensitive) against the state's common.states map. Returns\n // the canonical key as String so the rest of the handler sees the\n // same shape it always saw (e.g. \"1\"). Non-dropdown states are\n // passed through unchanged.\n const resolved = await this.resolveDropdownInput(id, state.val);\n if (!resolved.ok) {\n this.log.warn(`Unknown dropdown value for ${id}: ${String(state.val)} \u2014 ignoring`);\n return;\n }\n const val = resolved.val;\n\n // Group fan-out: route commands to each member device\n if (device.sku === \"BaseGroup\" && device.groupMembers) {\n await this.handleGroupFanOut(device, stateSuffix, val);\n await this.setStateAsync(id, { val, ack: true });\n if (stateSuffix === \"scenes.light_scene\" || stateSuffix === \"music.music_mode\") {\n await this.resetRelatedDropdowns(prefix, stateSuffix === \"scenes.light_scene\" ? \"lightScene\" : \"music\");\n }\n return;\n }\n\n // Handle local snapshot commands (no Cloud/MQTT needed)\n if (stateSuffix === \"snapshots.snapshot_save\" && typeof val === \"string\" && val.trim()) {\n await this.handleSnapshotSave(device, val.trim());\n await this.setStateAsync(id, { val: \"\", ack: true });\n return;\n }\n if (stateSuffix === \"snapshots.snapshot_local\") {\n if (val !== \"0\" && val !== 0) {\n await this.handleSnapshotRestore(device, val);\n await this.resetRelatedDropdowns(prefix, \"snapshotLocal\");\n }\n await this.setStateAsync(id, { val, ack: true });\n return;\n }\n if (stateSuffix === \"snapshots.snapshot_delete\" && typeof val === \"string\" && val.trim()) {\n this.handleSnapshotDelete(device, val.trim());\n await this.setStateAsync(id, { val: \"\", ack: true });\n return;\n }\n\n // Manual segments toggle/list \u2014 handler owns the ack because a parse\n // error rewrites manual_mode to false, and an outer ack with the\n // raw value would resurrect the rejected entry.\n if (stateSuffix === \"segments.manual_mode\" || stateSuffix === \"segments.manual_list\") {\n await this.handleManualSegmentsChange(device, stateSuffix, val);\n return;\n }\n\n // Diagnostics export button \u2014 throttled to 2 s per device so a repeated\n // or scripted trigger can't produce a burst of JSON serialisations.\n if (stateSuffix === \"diag.export\" && val) {\n const deviceKey = `${device.sku}:${device.deviceId}`;\n const now = Date.now();\n const last = this.diagnosticsLastRun.get(deviceKey) ?? 0;\n if (now - last < 2000) {\n this.log.debug(`Diagnostics export throttled for ${device.name} \u2014 last run ${now - last}ms ago`);\n await this.setStateAsync(id, { val: false, ack: true });\n return;\n }\n this.diagnosticsLastRun.set(deviceKey, now);\n const diag = this.deviceManager.generateDiagnostics(device, this.version ?? \"unknown\");\n const resultId = `${this.namespace}.${prefix}.diag.result`;\n await this.setStateAsync(resultId, {\n val: JSON.stringify(diag, null, 2),\n ack: true,\n });\n await this.setStateAsync(id, { val: false, ack: true });\n this.log.info(`Diagnostics exported for ${device.name} (${device.sku})`);\n return;\n }\n\n const command = this.stateToCommand(stateSuffix);\n\n if (!command) {\n // Try generic capability routing via state object metadata\n const obj = await this.getObjectAsync(id);\n const capType = obj?.native?.capabilityType;\n const capInstance = obj?.native?.capabilityInstance;\n if (typeof capType === \"string\" && typeof capInstance === \"string\") {\n try {\n await this.deviceManager.sendCapabilityCommand(device, capType, capInstance, val);\n await this.setStateAsync(id, { val, ack: true });\n } catch (err) {\n this.log.warn(`Command failed for ${device.name}: ${errMessage(err)}`);\n }\n } else {\n this.log.debug(`Unknown writable state: ${stateSuffix}`);\n }\n return;\n }\n\n // Dropdown reset to \"---\" (value 0) \u2014 acknowledge without sending command\n if ((command === \"lightScene\" || command === \"diyScene\" || command === \"snapshot\") && (val === \"0\" || val === 0)) {\n await this.setStateAsync(id, { val, ack: true });\n return;\n }\n\n // Scene speed: store on device, applied on next scene activation.\n // Persist to SKU cache so the user's choice survives a restart.\n if (command === \"sceneSpeed\") {\n const level = typeof val === \"number\" ? val : parseInt(String(val), 10);\n if (!isNaN(level)) {\n device.sceneSpeed = level;\n this.deviceManager?.persistDeviceToCache(device);\n }\n await this.setStateAsync(id, { val, ack: true });\n return;\n }\n\n try {\n // Music mode: combine all music states into one STRUCT command\n if (command === \"music\") {\n // music_mode \"---\" (value 0) \u2014 acknowledge without sending command\n if (stateSuffix === \"music.music_mode\" && (val === \"0\" || val === 0)) {\n await this.setStateAsync(id, { val, ack: true });\n return;\n }\n await this.sendMusicCommand(device, prefix, stateSuffix, val);\n await this.setStateAsync(id, { val, ack: true });\n // Reset scene/snapshot dropdowns when activating music mode\n if (stateSuffix === \"music.music_mode\") {\n await this.resetRelatedDropdowns(prefix, \"music\");\n }\n return;\n }\n\n await this.deviceManager.sendCommand(device, command, val);\n // Optimistic ack\n await this.setStateAsync(id, { val, ack: true });\n // Reset related dropdowns when switching modes.\n // Power-off is a special case \u2014 the device is off, so no mode is\n // active anymore; reset every mode dropdown so the UI reflects the\n // reality (scene/music/snapshot selections are now just history).\n if (command === \"power\" && val === false) {\n await this.resetModeDropdowns(prefix, \"\");\n } else {\n await this.resetRelatedDropdowns(prefix, command);\n }\n } catch (err) {\n this.log.warn(`Command failed for ${device.name}: ${errMessage(err)}`);\n }\n }\n\n /**\n * Resolve a dropdown-state input value against the state's common.states\n * map. Returns the canonical key (always String form) so a user can write\n * either the index (\"1\"), the index as a number (1) or the label name\n * (\"Aurora\", case-insensitive) \u2014 all three land at the same canonical\n * value for the rest of the handler.\n *\n * Non-dropdown states (no common.states), reset sentinels (0/\"0\"/\"\") and\n * non-string/number inputs are passed through unchanged. A dropdown input\n * that doesn't match any key or label returns ok=false so the caller can\n * warn and skip the command.\n *\n * @param id Full state id\n * @param raw Raw input value as provided by the user/script\n */\n private async resolveDropdownInput(\n id: string,\n raw: ioBroker.StateValue,\n ): Promise<{ val: ioBroker.StateValue; ok: boolean }> {\n if (raw === null || raw === undefined) {\n return { val: raw, ok: true };\n }\n // Reset sentinels \u2014 let the existing branch handle them.\n if (raw === 0 || raw === \"0\" || raw === \"\") {\n return { val: raw, ok: true };\n }\n // Only dropdown candidates have common.states; non-dropdown inputs\n // can't be resolved here so they pass through.\n if (typeof raw !== \"number\" && typeof raw !== \"string\") {\n return { val: raw, ok: true };\n }\n const obj = await this.getObjectAsync(id);\n const states = obj?.common?.states;\n if (!states || typeof states !== \"object\") {\n return { val: raw, ok: true };\n }\n const resolved = resolveStatesValue(raw, states as Record<string, string>);\n if (resolved) {\n return { val: resolved.key, ok: true };\n }\n return { val: raw, ok: false };\n }\n\n /**\n * Build and send a music_setting STRUCT command.\n * Reads sibling music state values and combines them into one API call.\n *\n * @param device Target device\n * @param prefix Device state prefix\n * @param changedSuffix Which music state was changed\n * @param newValue New value for the changed state\n */\n private async sendMusicCommand(\n device: GoveeDevice,\n prefix: string,\n changedSuffix: string,\n newValue: ioBroker.StateValue,\n ): Promise<void> {\n const musicBase = `${this.namespace}.${prefix}.music`;\n\n // Read current sibling values\n const modeState = await this.getStateAsync(`${musicBase}.music_mode`);\n const sensState = await this.getStateAsync(`${musicBase}.music_sensitivity`);\n const autoState = await this.getStateAsync(`${musicBase}.music_auto_color`);\n\n // Apply the changed value, use siblings for the rest\n const musicMode =\n changedSuffix === \"music.music_mode\" ? parseInt(String(newValue), 10) : parseInt(String(modeState?.val ?? 0), 10);\n const sensitivity =\n changedSuffix === \"music.music_sensitivity\" ? (newValue as number) : ((sensState?.val as number) ?? 100);\n const autoColor = changedSuffix === \"music.music_auto_color\" ? (newValue ? 1 : 0) : autoState?.val ? 1 : 0;\n\n if (!musicMode || musicMode === 0) {\n this.log.debug(\"Music mode not selected, skipping command\");\n return;\n }\n\n // LAN first: send via ptReal BLE if device is on LAN\n if (device.lanIp && this.lanClient) {\n // Read current color for RGB-modes (Spectrum=1, Rolling=2)\n let r = 0,\n g = 0,\n b = 0;\n if (musicMode === 1 || musicMode === 2) {\n const colorState = await this.getStateAsync(`${this.namespace}.${prefix}.control.colorRgb`);\n if (colorState?.val && typeof colorState.val === \"string\") {\n ({ r, g, b } = hexToRgb(colorState.val));\n }\n }\n this.lanClient.setMusicMode(device.lanIp, musicMode, r, g, b);\n return;\n }\n\n // Cloud fallback\n const structValue: Record<string, unknown> = {\n musicMode,\n sensitivity,\n autoColor,\n };\n\n await this.deviceManager!.sendCapabilityCommand(\n device,\n \"devices.capabilities.music_setting\",\n \"musicMode\",\n structValue,\n );\n }\n\n /**\n * Called by device-manager when a device state changes\n *\n * @param device Updated device\n * @param state Changed state values\n */\n private onDeviceStateUpdate(device: GoveeDevice, state: Partial<DeviceState>): void {\n if (this.stateManager) {\n this.stateManager.updateDeviceState(device, state).catch(() => {});\n }\n this.updateConnectionState();\n\n // Update group reachability when member online status changes\n if (state.online !== undefined) {\n this.updateGroupReachability();\n }\n\n // Mirror power-off to mode-dropdown reset. Covers MQTT/LAN-initiated\n // power changes (Govee app or physical remote) so the UI stays honest:\n // a device that's off can't be \"playing Aurora-A\" anymore.\n // L11 \u2014 defensive auch 0 als false akzeptieren (Govee schickt Power\n // theoretisch als boolean, aber MQTT-Boundary k\u00F6nnte 0 durchschleusen).\n const powerOff = state.power === false || (state.power as unknown) === 0;\n if (powerOff && this.stateManager) {\n const prefix = this.stateManager.devicePrefix(device);\n this.resetModeDropdowns(prefix, \"\").catch(() => undefined);\n }\n }\n\n /**\n * Fan out a group command to all member devices.\n * Basic controls (power, brightness, color) are sent directly.\n * Scenes/music are matched by name across members.\n *\n * @param group BaseGroup device\n * @param stateSuffix State path suffix (e.g. \"control.power\")\n * @param value Command value\n */\n private async handleGroupFanOut(group: GoveeDevice, stateSuffix: string, value: ioBroker.StateValue): Promise<void> {\n if (!this.deviceManager || !group.groupMembers) {\n return;\n }\n\n const devices = this.deviceManager.getDevices();\n const members = this.resolveGroupMembers(group, devices).filter(d => d.state.online);\n\n if (members.length === 0) {\n this.log.debug(`Group \"${group.name}\": no reachable members for fan-out`);\n return;\n }\n\n const command = this.stateToCommand(stateSuffix);\n if (!command) {\n return;\n }\n\n // Dropdown reset \u2014 no command needed\n if ((command === \"lightScene\" || command === \"music\") && (value === \"0\" || value === 0)) {\n return;\n }\n\n for (const member of members) {\n try {\n if (command === \"lightScene\") {\n await this.fanOutScene(group, member, value);\n } else if (command === \"music\") {\n await this.fanOutMusic(group, member, stateSuffix, value);\n } else {\n await this.deviceManager.sendCommand(member, command, value);\n }\n } catch (err) {\n this.log.debug(`Group fan-out to ${member.name}: ${errMessage(err)}`);\n }\n }\n }\n\n /**\n * Fan out a scene command: match group scene name to member scene index.\n *\n * @param group BaseGroup device\n * @param member Target member device\n * @param value Dropdown index value\n */\n private async fanOutScene(group: GoveeDevice, member: GoveeDevice, value: ioBroker.StateValue): Promise<void> {\n if (!this.deviceManager || !this.stateManager) {\n return;\n }\n\n // Get group scene name from dropdown value (1-based index)\n const groupPrefix = this.stateManager.devicePrefix(group);\n const obj = await this.getObjectAsync(`${this.namespace}.${groupPrefix}.scenes.light_scene`);\n const groupStates = obj?.common?.states as Record<string, string> | undefined;\n const sceneName = groupStates?.[String(value)];\n if (!sceneName) {\n return;\n }\n\n // Find the same scene name in the member's scene list (1-based)\n const memberIdx = member.scenes.findIndex(s => s.name === sceneName);\n if (memberIdx >= 0) {\n await this.deviceManager.sendCommand(member, \"lightScene\", memberIdx + 1);\n }\n }\n\n /**\n * Fan out a music command: match group music name to member music index.\n *\n * @param group BaseGroup device\n * @param member Target member device\n * @param stateSuffix Music state path suffix\n * @param value Command value\n */\n private async fanOutMusic(\n group: GoveeDevice,\n member: GoveeDevice,\n stateSuffix: string,\n value: ioBroker.StateValue,\n ): Promise<void> {\n if (!this.deviceManager || !this.stateManager) {\n return;\n }\n\n // For sensitivity/auto_color, forward directly \u2014 these are numeric values\n if (stateSuffix !== \"music.music_mode\") {\n await this.sendMusicCommand(member, this.stateManager.devicePrefix(member), stateSuffix, value);\n return;\n }\n\n // Get group music name from dropdown value (1-based index)\n const groupPrefix = this.stateManager.devicePrefix(group);\n const obj = await this.getObjectAsync(`${this.namespace}.${groupPrefix}.music.music_mode`);\n const groupStates = obj?.common?.states as Record<string, string> | undefined;\n const musicName = groupStates?.[String(value)];\n if (!musicName) {\n return;\n }\n\n // Find the same music name in the member's music library (1-based)\n const memberIdx = member.musicLibrary.findIndex(m => m.name === musicName);\n if (memberIdx >= 0) {\n // Build the music command struct for the member\n const memberPrefix = this.stateManager.devicePrefix(member);\n // Temporarily write the music mode value to trigger the member's music command\n await this.sendMusicCommand(member, memberPrefix, \"music.music_mode\", memberIdx + 1);\n }\n }\n\n /**\n * Resolve group member references to actual device objects.\n *\n * @param group BaseGroup device with groupMembers\n * @param devices Full device list to search\n */\n private resolveGroupMembers(group: GoveeDevice, devices: GoveeDevice[]): GoveeDevice[] {\n if (!group.groupMembers) {\n return [];\n }\n return group.groupMembers\n .map(m => devices.find(d => d.sku === m.sku && d.deviceId === m.deviceId))\n .filter((d): d is GoveeDevice => d !== undefined);\n }\n\n /**\n * Recalculate info.membersUnreachable for all groups.\n * Called when any device's online status changes.\n */\n private updateGroupReachability(): void {\n if (!this.deviceManager || !this.stateManager) {\n return;\n }\n const devices = this.deviceManager.getDevices();\n for (const group of devices) {\n if (group.sku !== \"BaseGroup\" || !group.groupMembers) {\n continue;\n }\n const memberDevices = this.resolveGroupMembers(group, devices);\n this.stateManager.updateGroupMembersUnreachable(group, memberDevices).catch(() => {});\n }\n }\n\n /**\n * Rebuild state definitions for one device and feed them into StateManager.\n * Used both from the full-list callback and from targeted refreshes\n * (e.g. after a local snapshot was added or removed \u2014 no reason to rebuild\n * the entire tree for every device then).\n *\n * @param device Target device\n * @param allDevices Full device list (needed to resolve group members)\n */\n private refreshDeviceStates(device: GoveeDevice, allDevices: GoveeDevice[]): void {\n if (!this.stateManager) {\n return;\n }\n const localSnaps = this.localSnapshots?.getSnapshots(device.sku, device.deviceId);\n let memberDevices: GoveeDevice[] | undefined;\n if (device.sku === \"BaseGroup\" && device.groupMembers) {\n memberDevices = this.resolveGroupMembers(device, allDevices);\n }\n const stateDefs = buildDeviceStateDefs(device, localSnaps, memberDevices);\n const p = this.stateManager\n .createDeviceStates(device, stateDefs)\n .then(async () => {\n // v2.1.0 \u2192 v2.1.1 layout migration: drop legacy info.diagnostics_*\n // before publishing the new diag.tier value. Idempotent.\n await this.stateManager?.migrateLegacyDiagnostics(device);\n await this.stateManager?.updateDeviceTier(device, getDeviceTier(device.sku));\n })\n .catch(e => {\n this.log.error(`createDeviceStates failed for ${device.name}: ${errMessage(e)}`);\n });\n // Until ready, collect so onReady can await the whole initial batch.\n // After ready, fire-and-forget \u2014 the queue would otherwise keep growing\n // with resolved promises for the lifetime of the adapter.\n if (!this.statesReady) {\n this.stateCreationQueue.push(p);\n } else {\n void p;\n }\n }\n\n /**\n * Called by device-manager when the device list changes\n *\n * @param devices Current list of all devices\n */\n private onDeviceListChanged(devices: GoveeDevice[]): void {\n if (!this.stateManager) {\n return;\n }\n\n for (const device of devices) {\n this.refreshDeviceStates(device, devices);\n }\n\n this.updateConnectionState();\n // Cache sync happens once after the initial setup completes (see\n // checkAllReady) \u2014 triggering here would fire on every device update\n // and spam the log.\n\n // Keep adapter-level per-device maps (diagnosticsLastRun, ...) aligned\n // with the new device list so removed devices don't leave orphan keys.\n // Skip during the initial boot phase \u2014 the startup cleanupTimer handles\n // that pass with proper LAN-scan-settled timing.\n if (this.statesReady) {\n this.reapStaleDevices().catch(() => undefined);\n }\n }\n\n /**\n * Update global `info.connection` \u2014 der ioBroker-IDC-Indikator.\n *\n * Semantik:\n * - Mit Devices: `connected = true` wenn MIND. ein Device online ist.\n * Wenn alle offline \u2192 false (User sieht: kein Device antwortet).\n * - Ohne Devices: `connected = true` wenn der LAN-Stack l\u00E4uft. Sonst\n * false (z.B. EADDRINUSE oder bind-Fehler).\n *\n * H4 (geplant f\u00FCr Phase H): nur bei tats\u00E4chlichem Wechsel schreiben\n * (cache lastConnectedValue). Aktuell l\u00E4uft updateConnectionState bei\n * jedem device-state-update \u2014 fire-and-forget setStateAsync, nur leichter\n * Overhead.\n */\n private updateConnectionState(): void {\n const devices = this.deviceManager?.getDevices() ?? [];\n const hasDevices = devices.length > 0;\n const anyOnline = devices.some(d => d.state.online);\n const lanRunning = this.lanClient !== null;\n const connected = hasDevices ? anyOnline : lanRunning;\n if (connected !== this.lastConnectionState) {\n this.lastConnectionState = connected;\n this.setStateAsync(\"info.connection\", { val: connected, ack: true }).catch(() => {});\n }\n }\n\n /**\n * Delete ioBroker objects for devices no longer present and drop the same\n * devices from adapter-level maps. Called after the initial-discovery\n * window and every time the device list changes.\n *\n * Scope of \"stale\" today: cleanupDevices compares the ioBroker object tree\n * against the live device-manager registry \u2014 it deletes objects that\n * outlive their entry in `DeviceManager.devices`. In v2.0 that registry is\n * monotonically growing within a single adapter lifetime (entries only\n * leave via cache pruning across restarts), so this primarily catches\n * tree leftovers from a previous adapter version after upgrade. The\n * adapter-level `diagnosticsLastRun` map is also reaped so it can't outlive\n * its devices either.\n *\n * A future stale-pruning step that explicitly retires devices from the\n * device-manager registry should also drop the device from\n * `deviceManager.devices` and call `getDiagnostics().forget(deviceId)` for\n * each retired device \u2014 those reaping APIs come in with the pruning patch,\n * not before (Memory `feedback_kein_phantom_schema`).\n */\n private async reapStaleDevices(): Promise<void> {\n if (!this.stateManager || !this.deviceManager) {\n return;\n }\n const currentDevices = this.deviceManager.getDevices();\n await this.stateManager.cleanupDevices(currentDevices);\n\n // Diagnostics-buffer f\u00FCr entfernte Devices droppen damit Logs/Packets/\n // Responses l\u00E4ngst entfernter Ger\u00E4te nicht im Speicher bleiben.\n const liveDeviceIds = new Set(currentDevices.map(d => d.deviceId));\n this.deviceManager.getDiagnostics().pruneOrphans(liveDeviceIds);\n\n const liveKeys = new Set(currentDevices.map(d => `${d.sku}:${d.deviceId}`));\n for (const key of this.diagnosticsLastRun.keys()) {\n if (!liveKeys.has(key)) {\n this.diagnosticsLastRun.delete(key);\n }\n }\n }\n\n /**\n * Check if all configured channels are initialized and log ready message.\n * Called from MQTT onConnection callback and end of onReady.\n */\n private checkAllReady(): void {\n if (this.readyLogged) {\n return;\n }\n // Wait for first LAN scan (always active)\n if (!this.lanScanDone) {\n return;\n }\n // Wait for initial state creation to complete\n if (!this.statesReady) {\n return;\n }\n // Wait for Cloud init if configured\n if (this.cloudClient && !this.cloudInitDone) {\n return;\n }\n // Wait for MQTT connection if configured\n if (this.mqttClient && !this.mqttClient.connected) {\n return;\n }\n // H1 \u2014 Wait for OpenAPI-MQTT (Cloud-events for sensors/appliances)\n // wenn konfiguriert. Vorher fehlte das \u2014 Adapter war \"ready\" obwohl\n // Sensor-Events-Channel nicht connected war.\n if (this.openapiMqttClient && !this.openapiMqttClient.connected) {\n return;\n }\n // H2 \u2014 Wait for first App-API-Poll wenn ein Sensor-Device angemeldet\n // ist. App-API liefert die Sensor-Werte (H5179 Temp/Humidity/Battery).\n // Ohne diesen Check w\u00E4re der Adapter \"ready\" mit leeren Sensor-Werten\n // f\u00FCr ~2 Minuten.\n if (this.deviceManager?.hasDeviceNeedingAppApi() && !this.appApiInitialPollDone) {\n return;\n }\n this.readyLogged = true;\n this.logDeviceSummary();\n // Persist any learned changes from the initial load (e.g. resolveSegmentCount\n // collapsing Cloud's 15 to the real 10 on H70D1). One-shot on first ready;\n // subsequent mutations persist themselves (MQTT bumps, wizard, manual-mode).\n this.deviceManager?.saveDevicesToCache();\n }\n\n /**\n * Log final ready message with device/group/channel summary.\n */\n private logDeviceSummary(): void {\n if (!this.deviceManager) {\n return;\n }\n const all = this.deviceManager.getDevices();\n const devices = all.filter(d => d.sku !== \"BaseGroup\");\n const groups = all.filter(d => d.sku === \"BaseGroup\");\n\n // H5 \u2014 Ready-Log expliziter: Channel-Status (LAN+Cloud+MQTT+Cloud-events)\n // plus Devices-Online-Count plus Sensor-Initial-Poll-Marker. User soll\n // EINEN Blick auf den Log werfen und sehen was wirklich operational ist.\n const channels: string[] = [\"LAN\"];\n if (this.cloudWasConnected) {\n channels.push(\"Cloud\");\n }\n if (this.mqttClient?.connected) {\n channels.push(\"MQTT\");\n }\n if (this.openapiMqttClient?.connected) {\n channels.push(\"Cloud-events\");\n }\n\n // Device-summary mit Online-Count\n const lightDevices = devices.filter(d => d.type === \"devices.types.light\");\n const onlineDevices = devices.filter(d => d.state.online === true);\n const parts: string[] = [];\n if (devices.length > 0) {\n const onlineLights = lightDevices.filter(d => d.state.online === true).length;\n const totalLights = lightDevices.length;\n if (totalLights > 0) {\n parts.push(\n totalLights === onlineLights\n ? `${totalLights} light${totalLights > 1 ? \"s\" : \"\"} online`\n : `${totalLights} light${totalLights > 1 ? \"s\" : \"\"} (${onlineLights} online, ${totalLights - onlineLights} offline)`,\n );\n }\n const sensors = devices.length - lightDevices.length;\n if (sensors > 0) {\n const onlineSensors = onlineDevices.filter(d => d.type !== \"devices.types.light\").length;\n parts.push(`${sensors} sensor${sensors > 1 ? \"s\" : \"\"} (${onlineSensors} with data)`);\n }\n }\n if (groups.length > 0) {\n parts.push(`${groups.length} group${groups.length > 1 ? \"s\" : \"\"}`);\n }\n const summary = parts.length > 0 ? parts.join(\", \") : \"no devices found\";\n this.log.info(`Govee adapter ready \u2014 ${summary} \u2014 channels: ${channels.join(\"+\")}`);\n\n // Surface configured-but-not-connected channels with a concrete reason.\n // Truthful \u2014 never claim \"still pending\" when the channel actually failed.\n if (this.cloudClient && !this.cloudWasConnected) {\n const reason = this.cloudClient.getFailureReason();\n this.log.warn(reason ? `Cloud not connected: ${reason}` : \"Cloud not connected \u2014 see earlier errors\");\n }\n if (this.mqttClient && !this.mqttClient.connected) {\n const reason = this.mqttClient.getFailureReason();\n this.log.warn(reason ? `MQTT not connected: ${reason}` : \"MQTT not connected \u2014 see earlier errors\");\n }\n }\n\n /**\n * Load current state for all Cloud devices and populate state values.\n * Called once after initial Cloud device list load.\n */\n private async loadCloudStates(): Promise<void> {\n if (!this.cloudClient || !this.deviceManager || !this.stateManager) {\n return;\n }\n\n const devices = this.deviceManager.getDevices();\n // LAN-first: never overwrite LAN states with Cloud values\n const lanStateIds = new Set(getDefaultLanStates().map(s => s.id));\n let loaded = 0;\n\n for (const device of devices) {\n if (!device.channels.cloud || device.capabilities.length === 0) {\n continue;\n }\n\n try {\n const caps = await this.cloudClient.getDeviceState(device.sku, device.deviceId);\n const prefix = this.stateManager.devicePrefix(device);\n\n const writes: Promise<unknown>[] = [];\n for (const cap of caps) {\n const mapped = mapCloudStateValue(cap);\n if (!mapped) {\n continue;\n }\n // Skip LAN-covered states for LAN-capable devices\n if (device.lanIp && lanStateIds.has(mapped.stateId)) {\n continue;\n }\n const statePath = this.stateManager.resolveStatePath(prefix, mapped.stateId);\n // Fire-and-forget \u2014 States are created before loadCloudStates runs;\n // a rejection here means the state was deleted out-of-band and\n // can be safely ignored.\n writes.push(\n this.setStateAsync(statePath, {\n val: mapped.value,\n ack: true,\n }).catch(() => undefined),\n );\n }\n await Promise.all(writes);\n loaded++;\n } catch {\n this.log.debug(`Could not load Cloud state for ${device.name} (${device.sku})`);\n }\n }\n\n if (loaded > 0) {\n this.log.debug(`Cloud states loaded for ${loaded} devices`);\n }\n }\n\n /**\n * Apply a list of synthesized Cloud-state capabilities to a single\n * device \u2014 the App-API poll and OpenAPI-MQTT events both use this path\n * so their values flow through the same `mapCloudStateValue` pipeline\n * that polled Cloud states use.\n *\n * @param device Target Govee device\n * @param caps Capabilities to apply\n */\n private async applyCloudCapabilities(device: GoveeDevice, caps: CloudStateCapability[]): Promise<void> {\n if (!this.stateManager) {\n return;\n }\n const lanStateIds = new Set(getDefaultLanStates().map(s => s.id));\n const prefix = this.stateManager.devicePrefix(device);\n const planned = planCloudCapabilityWrites(caps, Boolean(device.lanIp), lanStateIds);\n // App-API and OpenAPI-MQTT deliver state IDs (battery, temperature,\n // humidity, lackWater, \u2026) that the Cloud-capability pipeline doesn't\n // declare for sensor/appliance SKUs \u2014 the state objects therefore\n // don't exist yet on first write. ensureSyntheticStateObject creates\n // them lazily with the right channel + role + unit.\n for (const mapped of planned) {\n await this.stateManager.ensureSyntheticStateObject(prefix, mapped.stateId);\n }\n const writes = planned.map(mapped => {\n const statePath = this.stateManager!.resolveStatePath(prefix, mapped.stateId);\n return this.setStateAsync(statePath, {\n val: mapped.value,\n ack: true,\n }).catch(() => undefined);\n });\n await Promise.all(writes);\n }\n\n /**\n * Find device for a state ID\n *\n * @param localId Local state ID without namespace prefix\n */\n private findDeviceForState(localId: string): GoveeDevice | undefined {\n if (!this.deviceManager || !this.stateManager) {\n return undefined;\n }\n\n for (const device of this.deviceManager.getDevices()) {\n const prefix = this.stateManager.devicePrefix(device);\n if (localId.startsWith(`${prefix}.`)) {\n return device;\n }\n }\n return undefined;\n }\n\n /**\n * Map state suffix to command name.\n *\n * Simple suffixes live in a lookup table, segment indices need regex\n * extraction because they're dynamic. The three music states all route\n * to the same \"music\" command \u2014 the handler reads sibling values.\n *\n * @param suffix State ID suffix (e.g. \"power\", \"brightness\")\n */\n private stateToCommand(suffix: string): string | null {\n const direct = STATE_TO_COMMAND[suffix];\n if (direct) {\n return direct;\n }\n const segColorMatch = /^segments\\.(\\d+)\\.color$/.exec(suffix);\n if (segColorMatch) {\n return `segmentColor:${segColorMatch[1]}`;\n }\n const segBrightMatch = /^segments\\.(\\d+)\\.brightness$/.exec(suffix);\n if (segBrightMatch) {\n return `segmentBrightness:${segBrightMatch[1]}`;\n }\n return null;\n }\n\n /**\n * Central entry point for manual-segment updates. Sets the device flags,\n * rebuilds the segment tree (which writes manual_mode + manual_list with\n * ack=true), and persists to cache. Both the user state-change handler\n * and the wizard route their final decisions here.\n *\n * @param device Target device\n * @param mode Whether manual mode should be active\n * @param indices Physical indices when mode=true, ignored otherwise\n */\n private async applyManualSegments(device: GoveeDevice, mode: boolean, indices?: number[]): Promise<void> {\n if (!this.stateManager) {\n return;\n }\n device.manualMode = mode;\n device.manualSegments = mode && Array.isArray(indices) && indices.length > 0 ? indices.slice() : undefined;\n await this.stateManager.createSegmentStates(device);\n this.deviceManager?.persistDeviceToCache(device);\n }\n\n /**\n * React to manual-segments state changes \u2014 parses list, forwards to\n * {@link applyManualSegments}. On parse error disables manual mode so the\n * rejected value doesn't survive in the state tree.\n *\n * @param device Target device\n * @param suffix State suffix (either \"segments.manual_mode\" or \"segments.manual_list\")\n * @param newValue Written value\n */\n private async handleManualSegmentsChange(device: GoveeDevice, suffix: string, newValue: unknown): Promise<void> {\n // Peer value that wasn't just written comes from the device object\n // (kept in sync via createSegmentStates), not a separate state read.\n const modeVal = suffix === \"segments.manual_mode\" ? Boolean(newValue) : device.manualMode === true;\n const listVal =\n suffix === \"segments.manual_list\"\n ? typeof newValue === \"string\"\n ? newValue\n : \"\"\n : Array.isArray(device.manualSegments)\n ? device.manualSegments.join(\",\")\n : \"\";\n\n if (!modeVal) {\n this.log.info(`${device.name}: manual segments disabled \u2014 strip treated as contiguous`);\n await this.applyManualSegments(device, false);\n return;\n }\n\n // Upper bound: cap at the real length if known, otherwise the protocol limit.\n // Real length can still grow via MQTT discovery, so SEGMENT_HARD_MAX is the\n // absolute safety net.\n const maxIndex =\n typeof device.segmentCount === \"number\" && device.segmentCount > 0 ? device.segmentCount - 1 : SEGMENT_HARD_MAX;\n const parsed = parseSegmentList(listVal, maxIndex);\n if (parsed.error) {\n this.log.warn(`${device.name}: manual_list invalid (${parsed.error}) \u2014 disabling manual mode`);\n await this.applyManualSegments(device, false);\n return;\n }\n\n this.log.debug(`${device.name}: manual segments active \u2014 ${parsed.indices.length} physical indices (${listVal})`);\n await this.applyManualSegments(device, true, parsed.indices);\n }\n\n // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Segment-Detection-Wizard \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Handle incoming sendTo messages (from jsonConfig).\n *\n * @param obj ioBroker message object\n */\n private onMessage(obj: ioBroker.Message): void {\n if (!obj?.command) {\n return;\n }\n // Never let a rejection bubble up from the event handler \u2014 the ioBroker\n // event emitter doesn't catch it, which would crash the adapter.\n this.handleMessage(obj).catch(e => {\n this.log.warn(`onMessage handler crashed for ${obj.command}: ${errMessage(e)}`);\n this.sendMessageResponse(obj, {\n error: e instanceof Error ? e.message : String(e),\n });\n });\n }\n\n private async handleMessage(obj: ioBroker.Message): Promise<void> {\n try {\n if (obj.command === \"getSegmentDevices\") {\n const devices = this.deviceManager?.getDevices() ?? [];\n const list = devices\n .filter(d => d.sku !== \"BaseGroup\" && d.state?.online === true && resolveSegmentCount(d) > 0)\n .map(d => {\n const count = resolveSegmentCount(d);\n return {\n value: this.deviceKeyFor(d),\n label: `${d.name} (${d.sku}, bisher ${count} Segmente)`,\n };\n });\n // selectSendTo expects the array directly, not wrapped in an object\n this.sendMessageResponse(obj, list);\n return;\n }\n if (obj.command === \"segmentWizard\") {\n const payload = (obj.message ?? {}) as {\n action?: string;\n device?: string;\n };\n const response = await this.runWizardStep(payload.action ?? \"\", payload.device ?? \"\");\n this.sendMessageResponse(obj, response);\n return;\n }\n if (obj.command === \"mqttAuth\") {\n const payload = (obj.message ?? {}) as { action?: string };\n const response = await this.runMqttAuthAction(payload.action ?? \"\");\n this.sendMessageResponse(obj, response);\n return;\n }\n } catch (e) {\n this.log.warn(`onMessage failed for ${obj.command}: ${errMessage(e)}`);\n this.sendMessageResponse(obj, {\n error: e instanceof Error ? e.message : String(e),\n });\n }\n }\n\n /**\n * Handle the `mqttAuth` onMessage commands triggered by the admin UI.\n *\n * Two actions:\n * - `test` \u2014 try a one-shot login with the current settings (incl. any code) and\n * report a single-line plaintext result the admin can show as a toast.\n * - `requestCode` \u2014 POST to /account/rest/account/v1/verification so Govee emails a fresh\n * code. 30-second in-memory throttle prevents double-click email spam.\n *\n * @param action Action name from the jsonConfig sendTo button\n */\n private async runMqttAuthAction(action: string): Promise<{ result: string }> {\n const config = this.config as unknown as AdapterConfig;\n if (!config.goveeEmail || !config.goveePassword) {\n return { result: \"Email + Passwort in den Adapter-Einstellungen n\u00F6tig.\" };\n }\n\n if (action === \"test\") {\n // One-shot client: don't reuse this.mqttClient \u2014 we don't want this\n // probe to take over its reconnect timer or auth-failure counter.\n const probe = new GoveeMqttClient(config.goveeEmail, config.goveePassword, this.log, this);\n probe.setVerificationCode(config.mqttVerificationCode ?? \"\");\n try {\n let connected = false;\n await probe.connect(\n () => {},\n isConnected => {\n connected = isConnected;\n },\n );\n probe.disconnect();\n return {\n result: connected\n ? \"Login erfolgreich \u2014 Echtzeit-Status-Updates aktiv.\"\n : \"Login angenommen, MQTT-Verbindung steht aber noch nicht \u2014 Adapter neu starten.\",\n };\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n if (/Verification required/i.test(msg)) {\n return {\n result:\n \"Govee verlangt 2-Faktor-Best\u00E4tigung. Bitte 'Verifizierungs-Code anfordern' klicken, Code aus der E-Mail eintragen und Speichern.\",\n };\n }\n if (/Verification code invalid/i.test(msg)) {\n return {\n result: \"2-Faktor-Code ung\u00FCltig oder abgelaufen \u2014 bitte einen neuen Code anfordern.\",\n };\n }\n if (/email not registered/i.test(msg)) {\n return { result: \"Diese E-Mail ist bei Govee nicht registriert.\" };\n }\n if (/Login failed/i.test(msg)) {\n return { result: \"Passwort wurde von Govee abgelehnt.\" };\n }\n if (/Rate limited/i.test(msg)) {\n return { result: \"Govee meldet Rate-Limit \u2014 bitte sp\u00E4ter erneut versuchen.\" };\n }\n if (/Account temporarily locked/i.test(msg)) {\n return { result: \"Govee-Account vor\u00FCbergehend gesperrt \u2014 Govee Home App \u00F6ffnen und Status pr\u00FCfen.\" };\n }\n return { result: `Login fehlgeschlagen: ${msg}` };\n }\n }\n\n if (action === \"requestCode\") {\n const now = Date.now();\n if (now - this.lastVerificationRequestMs < VERIFICATION_REQUEST_THROTTLE_MS) {\n const wait = Math.ceil((VERIFICATION_REQUEST_THROTTLE_MS - (now - this.lastVerificationRequestMs)) / 1000);\n return { result: `Bitte ${wait}s warten \u2014 gerade wurde schon ein Code angefordert.` };\n }\n this.lastVerificationRequestMs = now;\n const probe = new GoveeMqttClient(config.goveeEmail, config.goveePassword, this.log, this);\n try {\n await probe.requestVerificationCode();\n return { result: \"Code wurde an deine Govee-E-Mail-Adresse gesendet (Spam-Ordner pr\u00FCfen).\" };\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n return { result: `Govee hat den Code-Versand abgelehnt: ${msg}` };\n }\n }\n\n return { result: `Unbekannte Aktion '${action}'.` };\n }\n\n /**\n * Helper: clear `mqttVerificationCode` in adapter native after a successful\n * login or a 455-fail.\n *\n * Idempotent: liest erst den aktuellen Wert, schreibt nur wenn dirty.\n * Verhindert den Adapter-Restart der durch jeden\n * `extendForeignObjectAsync(system.adapter.X, native:...)`-Call ausgel\u00F6st\n * wird (Memory v2.1.3-Bug). Vorher gab es nach jedem 2FA-Login einen\n * unn\u00F6tigen Restart.\n */\n private async clearVerificationCodeSetting(): Promise<void> {\n try {\n const obj = await this.getForeignObjectAsync(`system.adapter.${this.namespace}`);\n const native = (obj?.native ?? {}) as Record<string, unknown>;\n // Skip wenn das Feld eh schon leer (oder undefined) \u2014 kein dirty write,\n // kein Restart-Trigger.\n if (typeof native.mqttVerificationCode !== \"string\" || native.mqttVerificationCode === \"\") {\n return;\n }\n await this.extendForeignObjectAsync(`system.adapter.${this.namespace}`, {\n native: { mqttVerificationCode: \"\" },\n });\n } catch (e) {\n this.log.warn(`Could not clear mqttVerificationCode: ${errMessage(e)}`);\n }\n }\n\n /**\n * Read persisted MQTT credentials from `info.mqttCredentials`. The\n * sensitive fields (bearer + cert + pass) are encrypted with the\n * system secret on save and decrypted here. Returns null if no\n * credentials are stored or the JSON is unparseable.\n *\n * State-based persistence (since v2.1.3) \u2014 writes to a state instead\n * of `system.adapter.X.native` so saving doesn't trigger an adapter\n * restart. The earlier native-based design caused an endless\n * login \u2192 save \u2192 restart \u2192 login loop.\n */\n private async loadPersistedCredsFromState(): Promise<PersistedMqttCredentials | null> {\n try {\n const s = await this.getStateAsync(\"info.mqttCredentials\");\n const raw = typeof s?.val === \"string\" ? s.val : \"\";\n if (!raw) {\n return null;\n }\n const obj = JSON.parse(raw) as {\n bearerToken?: unknown;\n iotEndpoint?: unknown;\n p12Cert?: unknown;\n p12Pass?: unknown;\n accountId?: unknown;\n accountTopic?: unknown;\n tokenExpiresAt?: unknown;\n };\n // typeof-Guards \u2014 JSON.parse liefert raw, this.decrypt() wirft auf\n // non-string-Input. Defensive: wenn das State-Blob durch ein Tool\n // editiert wurde und falsche Typen drin hat, returnen wir null.\n const safeStr = (v: unknown): string => (typeof v === \"string\" ? v : \"\");\n const bearerToken = this.decrypt(safeStr(obj.bearerToken));\n const p12Cert = this.decrypt(safeStr(obj.p12Cert));\n const p12Pass = this.decrypt(safeStr(obj.p12Pass));\n const iotEndpoint = safeStr(obj.iotEndpoint);\n const accountId = safeStr(obj.accountId);\n const accountTopic = safeStr(obj.accountTopic);\n const tokenExpiresAt = typeof obj.tokenExpiresAt === \"number\" ? obj.tokenExpiresAt : 0;\n if (!bearerToken || !iotEndpoint || !p12Cert || !accountId || !accountTopic || !tokenExpiresAt) {\n return null;\n }\n return { bearerToken, iotEndpoint, p12Cert, p12Pass, accountId, accountTopic, tokenExpiresAt };\n } catch {\n return null;\n }\n }\n\n /**\n * Persist freshly-issued MQTT credentials into `info.mqttCredentials`.\n * Sensitive fields go through `this.encrypt()` so the JSON blob is\n * useless without the system secret. State writes do NOT trigger an\n * adapter restart.\n *\n * @param creds The freshly-issued MQTT bundle from a successful login\n */\n private async persistCredsToState(creds: PersistedMqttCredentials): Promise<void> {\n const blob = JSON.stringify({\n bearerToken: this.encrypt(creds.bearerToken),\n iotEndpoint: creds.iotEndpoint,\n p12Cert: this.encrypt(creds.p12Cert),\n p12Pass: this.encrypt(creds.p12Pass),\n accountId: creds.accountId,\n accountTopic: creds.accountTopic,\n tokenExpiresAt: creds.tokenExpiresAt,\n });\n await this.setStateAsync(\"info.mqttCredentials\", { val: blob, ack: true });\n }\n\n /**\n * One-shot cleanup of legacy v2.1.0/v2.1.1/v2.1.2 plaintext credentials\n * sitting in `system.adapter.X.native`.\n *\n * Doppelte Idempotenz:\n * 1. State-Marker `info.legacyMqttCleaned=true` wird NACH erfolgreichem\n * Wipe gesetzt. Bei sp\u00E4teren Starts wird die Funktion \u00FCber den Marker\n * sofort verlassen \u2014 auch wenn das native irgendwie wieder dirty wird.\n * 2. Nur wenn KEINER der Legacy-Marker gesetzt ist UND das native dirty,\n * wird der einmalige extendForeignObjectAsync (Restart-Trigger) gemacht.\n *\n * Dieser Aufruf triggert genau einmal pro Adapter-Lifetime einen\n * Restart \u2014 das ist unvermeidlich, weil die Plaintext-Bytes weg m\u00FCssen.\n * Aber: nach erfolgreichem Cleanup bleibt der Marker, kein erneuter\n * Restart bei flaky writes.\n */\n private async cleanupLegacyMqttNativeOnce(): Promise<void> {\n try {\n // Marker-State zuerst pr\u00FCfen \u2014 wenn schon gecleant, sofort raus.\n const markerState = await this.getStateAsync(\"info.legacyMqttCleaned\");\n if (markerState?.val === true) {\n return;\n }\n const obj = await this.getForeignObjectAsync(`system.adapter.${this.namespace}`);\n const native = (obj?.native ?? {}) as Record<string, unknown>;\n const legacy = [\n \"mqttBearerToken\",\n \"mqttIotEndpoint\",\n \"mqttP12Cert\",\n \"mqttP12Pass\",\n \"mqttAccountId\",\n \"mqttAccountTopic\",\n \"mqttTokenExpiresAt\",\n ];\n const dirty = legacy.some(k => k in native && native[k] !== \"\" && native[k] !== 0);\n if (!dirty) {\n // Native ist clean (z.B. neue Installation oder schon migriert\n // ohne Marker). Marker setzen damit n\u00E4chster Start gar nicht erst\n // den Native-Read macht.\n await this.setStateAsync(\"info.legacyMqttCleaned\", { val: true, ack: true }).catch(() => undefined);\n return;\n }\n this.log.info(\"Removing legacy plaintext MQTT credentials from native (one-time migration)\");\n const wipe: Record<string, unknown> = {};\n for (const k of legacy) {\n wipe[k] = k === \"mqttTokenExpiresAt\" ? 0 : \"\";\n }\n await this.extendForeignObjectAsync(`system.adapter.${this.namespace}`, { native: wipe });\n // Marker setzen NACHDEM der Wipe erfolgreich war \u2014 bei einem etwaigen\n // Crash zwischen extend und setState l\u00E4uft der Cleanup beim n\u00E4chsten\n // Start nochmal (idempotent: native ist dann eh schon clean).\n await this.setStateAsync(\"info.legacyMqttCleaned\", { val: true, ack: true }).catch(() => undefined);\n } catch (e) {\n this.log.debug(`legacy MQTT cleanup skipped: ${errMessage(e)}`);\n }\n }\n\n private sendMessageResponse(obj: ioBroker.Message, data: unknown): void {\n if (obj.callback && obj.from) {\n this.sendTo(obj.from, obj.command, data as Record<string, unknown>, obj.callback);\n }\n }\n\n /**\n * Stable device key for wizard session tracking.\n *\n * @param device Target device\n */\n private deviceKeyFor(device: GoveeDevice): string {\n return `${device.sku}:${device.deviceId}`;\n }\n\n private findDeviceByKey(key: string): GoveeDevice | undefined {\n const devices = this.deviceManager?.getDevices() ?? [];\n return devices.find(d => this.deviceKeyFor(d) === key);\n }\n\n /** Construct the host object passed into SegmentWizard. */\n private buildWizardHost(): WizardHost {\n return {\n log: this.log,\n getState: id => this.getStateAsync(id),\n sendCommand: async (device, command, value) => {\n await this.deviceManager?.sendCommand(device, command, value);\n },\n flashSegmentAtomic: (device, idx) => {\n if (!device.lanIp || !this.lanClient) {\n return Promise.resolve(false);\n }\n this.lanClient.flashSingleSegment(device.lanIp, idx);\n return Promise.resolve(true);\n },\n restoreStripAtomic: (device, total, color, brightness) => {\n if (!device.lanIp || !this.lanClient) {\n return Promise.resolve(false);\n }\n const r = (color >> 16) & 0xff;\n const g = (color >> 8) & 0xff;\n const b = color & 0xff;\n this.lanClient.restoreAllSegments(device.lanIp, total, r, g, b, brightness);\n return Promise.resolve(true);\n },\n findDevice: key => this.findDeviceByKey(key),\n namespace: this.namespace,\n devicePrefix: device => this.stateManager?.devicePrefix(device) ?? \"\",\n setTimeout: (cb, ms) => this.setTimeout(cb, ms),\n clearTimeout: h => this.clearTimeout(h as ioBroker.Timeout),\n applyWizardResult: (device, result) => this.applyWizardResult(device, result),\n getLanguage: () => this.adminLanguage,\n };\n }\n\n /**\n * Apply a finished wizard's measurement: set the real segment count, then\n * route through {@link applyManualSegments} so the same state-tree rebuild\n * and cache-persist path runs for both wizard results and user edits.\n *\n * @param device Target device\n * @param result Wizard's measurement\n */\n private async applyWizardResult(device: GoveeDevice, result: WizardResult): Promise<void> {\n device.segmentCount = result.segmentCount;\n if (result.hasGaps) {\n const parsed = parseSegmentList(result.manualList, result.segmentCount - 1);\n await this.applyManualSegments(device, true, parsed.error ? undefined : parsed.indices);\n } else {\n await this.applyManualSegments(device, false);\n }\n this.log.debug(\n `applyWizardResult: ${device.sku} \u2192 segmentCount=${result.segmentCount}, ` +\n `manualMode=${device.manualMode}, list=\"${result.manualList}\"`,\n );\n }\n\n /**\n * Execute one wizard step (start/yes/no/abort). Delegates to\n * {@link SegmentWizard} \u2014 see `lib/segment-wizard.ts`.\n *\n * @param action \"start\" | \"yes\" | \"no\" | \"abort\"\n * @param deviceKey device identifier (only required for \"start\")\n */\n private async runWizardStep(action: string, deviceKey: string): Promise<Record<string, unknown>> {\n if (!this.segmentWizard) {\n this.segmentWizard = new SegmentWizard(this.buildWizardHost());\n }\n const response = await this.segmentWizard.runStep(action, deviceKey);\n // Mirror the current wizard status into a plain state so admin's\n // `type: \"state\"` component can show it live via state subscription.\n const statusText = this.segmentWizard.getStatusText();\n await this.setStateAsync(\"info.wizardStatus\", {\n val: statusText,\n ack: true,\n });\n return response;\n }\n\n /**\n * Save current device state as a local snapshot.\n *\n * @param device Target device\n * @param name Snapshot name\n */\n private async handleSnapshotSave(device: GoveeDevice, name: string): Promise<void> {\n if (!this.localSnapshots || !this.stateManager) {\n return;\n }\n\n const prefix = this.stateManager.devicePrefix(device);\n const ns = this.namespace;\n\n // Read device-level state in parallel\n const [powerState, brightState, colorState, ctState] = await Promise.all([\n this.getStateAsync(`${ns}.${prefix}.control.power`),\n this.getStateAsync(`${ns}.${prefix}.control.brightness`),\n this.getStateAsync(`${ns}.${prefix}.control.colorRgb`),\n this.getStateAsync(`${ns}.${prefix}.control.colorTemperature`),\n ]);\n\n // Read per-segment states in parallel \u2014 20 segments \u00D7 2 reads used to run\n // sequentially (~80ms), parallel completes in a single round-trip.\n let segments: SnapshotSegment[] | undefined;\n const segCount = device.segmentCount ?? 0;\n if (segCount > 0) {\n const segReads: Promise<[ioBroker.State | null | undefined, ioBroker.State | null | undefined]>[] = [];\n for (let i = 0; i < segCount; i++) {\n segReads.push(\n Promise.all([\n this.getStateAsync(`${ns}.${prefix}.segments.${i}.color`),\n this.getStateAsync(`${ns}.${prefix}.segments.${i}.brightness`),\n ]),\n );\n }\n const segResults = await Promise.all(segReads);\n segments = segResults.map(([segColor, segBright]) => ({\n color: typeof segColor?.val === \"string\" ? segColor.val : \"#000000\",\n brightness: typeof segBright?.val === \"number\" ? segBright.val : 100,\n }));\n }\n\n const snapshot: LocalSnapshot = {\n name,\n power: powerState?.val === true,\n brightness: typeof brightState?.val === \"number\" ? brightState.val : 0,\n colorRgb: typeof colorState?.val === \"string\" ? colorState.val : \"#000000\",\n colorTemperature: typeof ctState?.val === \"number\" ? ctState.val : 0,\n segments,\n savedAt: Date.now(),\n };\n\n this.localSnapshots.saveSnapshot(device.sku, device.deviceId, snapshot);\n this.log.info(`Local snapshot saved: \"${name}\" for ${device.name}`);\n\n // Targeted refresh \u2014 only this device's snapshot_local dropdown changed.\n this.refreshDeviceStates(device, this.deviceManager!.getDevices());\n }\n\n /**\n * Restore a local snapshot by index.\n *\n * @param device Target device\n * @param val Dropdown index value\n */\n private async handleSnapshotRestore(device: GoveeDevice, val: ioBroker.StateValue): Promise<void> {\n if (!this.localSnapshots || !this.deviceManager) {\n return;\n }\n\n const idx = parseInt(String(val), 10);\n if (idx < 1) {\n return;\n }\n\n const snaps = this.localSnapshots.getSnapshots(device.sku, device.deviceId);\n const snap = snaps[idx - 1];\n if (!snap) {\n this.log.warn(`Local snapshot index ${idx} not found for ${device.name}`);\n return;\n }\n\n this.log.info(`Restoring local snapshot \"${snap.name}\" for ${device.name}`);\n\n // Send each state via LAN \u2192 Cloud routing\n await this.deviceManager.sendCommand(device, \"power\", snap.power);\n if (snap.power) {\n await this.deviceManager.sendCommand(device, \"brightness\", snap.brightness);\n if (snap.colorTemperature > 0) {\n await this.deviceManager.sendCommand(device, \"colorTemperature\", snap.colorTemperature);\n } else {\n await this.deviceManager.sendCommand(device, \"colorRgb\", snap.colorRgb);\n }\n\n // Restore per-segment states via ptReal\n if (snap.segments && snap.segments.length > 0) {\n for (let i = 0; i < snap.segments.length; i++) {\n const seg = snap.segments[i];\n await this.deviceManager.sendCommand(device, `segmentColor:${i}`, seg.color);\n await this.deviceManager.sendCommand(device, `segmentBrightness:${i}`, seg.brightness);\n }\n }\n }\n }\n\n /**\n * Delete a local snapshot by name.\n *\n * @param device Target device\n * @param name Snapshot name to delete\n */\n private handleSnapshotDelete(device: GoveeDevice, name: string): void {\n if (!this.localSnapshots) {\n return;\n }\n\n if (this.localSnapshots.deleteSnapshot(device.sku, device.deviceId, name)) {\n this.log.info(`Local snapshot deleted: \"${name}\" for ${device.name}`);\n // Targeted refresh \u2014 only this device's snapshot_local dropdown changed.\n this.refreshDeviceStates(device, this.deviceManager!.getDevices());\n } else {\n this.log.warn(`Local snapshot \"${name}\" not found for ${device.name}`);\n }\n }\n\n /** Dropdowns whose value is a mode-selection \u2014 reset to \"---\" (0) when the mode stops. */\n private static readonly MODE_DROPDOWNS = [\n \"scenes.light_scene\",\n \"scenes.diy_scene\",\n \"snapshots.snapshot_cloud\",\n \"snapshots.snapshot_local\",\n \"music.music_mode\",\n ];\n\n /** Map command \u2192 its own dropdown path (excluded from reset when that mode is the one that was just activated). */\n private static readonly COMMAND_DROPDOWN: Record<string, string> = {\n lightScene: \"scenes.light_scene\",\n diyScene: \"scenes.diy_scene\",\n snapshot: \"snapshots.snapshot_cloud\",\n snapshotLocal: \"snapshots.snapshot_local\",\n music: \"music.music_mode\",\n colorRgb: \"\",\n colorTemperature: \"\",\n };\n\n /**\n * Reset related dropdown states when switching between scenes/snapshots/colors.\n * Each mode-switch resets all OTHER mode dropdowns to \"---\" (0).\n *\n * @param prefix Device state prefix\n * @param activeCommand The command that was just executed\n */\n private async resetRelatedDropdowns(prefix: string, activeCommand: string): Promise<void> {\n if (!(activeCommand in GoveeAdapter.COMMAND_DROPDOWN)) {\n return;\n }\n const ownDropdown = GoveeAdapter.COMMAND_DROPDOWN[activeCommand];\n await this.resetModeDropdowns(prefix, ownDropdown);\n }\n\n /**\n * Reset every mode dropdown except `keep` (empty = reset all). Used both for\n * mode-switches (keep the new mode's own dropdown) and for power-off\n * (reset everything \u2014 a device that's off has no active mode).\n *\n * @param prefix Device state prefix\n * @param keep Dropdown path to leave untouched (e.g. \"music.music_mode\"), or \"\" to reset all\n */\n private async resetModeDropdowns(prefix: string, keep: string): Promise<void> {\n await Promise.all(\n GoveeAdapter.MODE_DROPDOWNS.filter(d => d !== keep).map(async dropdown => {\n const stateId = `${this.namespace}.${prefix}.${dropdown}`;\n const current = await this.getStateAsync(stateId);\n if (current?.val && current.val !== \"0\" && current.val !== 0) {\n await this.setStateAsync(stateId, { val: \"0\", ack: true });\n }\n }),\n );\n }\n}\n\nif (require.main !== module) {\n module.exports = (options: Partial<utils.AdapterOptions> | undefined) => new GoveeAdapter(options);\n} else {\n (() => new GoveeAdapter())();\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import * as utils from \"@iobroker/adapter-core\";\nimport {\n buildDeviceStateDefs,\n getDefaultLanStates,\n mapCloudStateValue,\n planCloudCapabilityWrites,\n} from \"./lib/capability-mapper\";\nimport { getDeviceTier, initDeviceRegistry } from \"./lib/device-registry\";\nimport { DeviceManager, resolveSegmentCount, SEGMENT_HARD_MAX } from \"./lib/device-manager\";\nimport { GoveeApiClient } from \"./lib/govee-api-client\";\nimport { GoveeCloudClient } from \"./lib/govee-cloud-client\";\nimport { GoveeLanClient } from \"./lib/govee-lan-client\";\nimport { GoveeMqttClient } from \"./lib/govee-mqtt-client\";\nimport { GoveeOpenapiMqttClient } from \"./lib/govee-openapi-mqtt-client\";\nimport { LocalSnapshotStore, type LocalSnapshot, type SnapshotSegment } from \"./lib/local-snapshots\";\nimport { CloudRetryLoop, type CloudRetryHost } from \"./lib/cloud-retry\";\nimport { RateLimiter } from \"./lib/rate-limiter\";\nimport { SegmentWizard, wizardIdleText, type WizardHost, type WizardResult } from \"./lib/segment-wizard\";\nimport { SkuCache } from \"./lib/sku-cache\";\nimport { StateManager } from \"./lib/state-manager\";\nimport {\n hexToRgb,\n errMessage,\n parseSegmentList,\n resolveStatesValue,\n rgbIntToHex,\n rgbToHex,\n type AdapterConfig,\n type CloudLoadResult,\n type CloudStateCapability,\n type DeviceState,\n type GoveeDevice,\n type PersistedMqttCredentials,\n} from \"./lib/types\";\nimport { APP_API_INITIAL_DELAY_MS, APP_API_POLL_INTERVAL_MS, READY_TIMEOUT_MS } from \"./lib/timing-constants\";\nimport { GOVEE_APP_VERSION } from \"./lib/govee-constants\";\nimport { httpsRequest } from \"./lib/http-client\";\n\n/**\n * Rate limit defaults \u2014 full Cloud API budget (8/min, 9000/day). v2 no\n * longer halves this with govee-appliances because that adapter is\n * deprecated and won't run alongside govee-smart.\n */\nconst FULL_LIMITS = { perMinute: 8, perDay: 9000 };\n\n/**\n * Minimum gap between two `mqttAuth: requestCode` calls. Govee returns 200\n * for every request and queues another email \u2014 without a throttle, double-clicking\n * the button in admin would spam the user's inbox. 30 s is short enough that a\n * legitimate retry after a failed delivery still feels responsive.\n */\nconst VERIFICATION_REQUEST_THROTTLE_MS = 30_000;\n\n/**\n * State-suffix \u2192 command-name lookup for writable states. Segment indices\n * are dynamic and handled by regex in stateToCommand \u2014 everything else is\n * a straight string mapping.\n */\nconst STATE_TO_COMMAND: Readonly<Record<string, string>> = {\n \"control.power\": \"power\",\n \"control.brightness\": \"brightness\",\n \"control.colorRgb\": \"colorRgb\",\n \"control.colorTemperature\": \"colorTemperature\",\n \"control.scene\": \"scene\",\n \"control.gradient_toggle\": \"gradientToggle\",\n \"scenes.light_scene\": \"lightScene\",\n \"scenes.diy_scene\": \"diyScene\",\n \"scenes.scene_speed\": \"sceneSpeed\",\n \"music.music_mode\": \"music\",\n \"music.music_sensitivity\": \"music\",\n \"music.music_auto_color\": \"music\",\n \"snapshots.snapshot_cloud\": \"snapshot\",\n \"segments.command\": \"segmentBatch\",\n};\n\nclass GoveeAdapter extends utils.Adapter {\n private deviceManager: DeviceManager | null = null;\n private stateManager: StateManager | null = null;\n private lanClient: GoveeLanClient | null = null;\n private mqttClient: GoveeMqttClient | null = null;\n private openapiMqttClient: GoveeOpenapiMqttClient | null = null;\n private cloudClient: GoveeCloudClient | null = null;\n private rateLimiter: RateLimiter | null = null;\n /** Repeating timer for the App-API poll (sensor-state pull). */\n private appApiPollTimer: ioBroker.Interval | undefined;\n /**\n * One-shot timer for the FIRST app-api poll (5s nach start) \u2014 Handle\n * damit onUnload das wegr\u00E4umen kann bevor es ins Leere feuert.\n */\n private appApiInitialTimer: ioBroker.Timeout | undefined;\n /** One-shot timer for cloud-init 60s safety timeout \u2014 gleiches Pattern. */\n private cloudInitTimer: ioBroker.Timeout | undefined;\n /**\n * Letzter info.connection-Wert \u2014 Cache damit nicht jeder device-update\n * einen unn\u00F6tigen setStateAsync macht (H4).\n */\n private lastConnectionState: boolean | null = null;\n // === Lifecycle-Flags (Adapter-Boot-Sequenz) ===\n // checkAllReady() pr\u00FCft alle 5 Voraussetzungen gleichzeitig \u2014 sie laufen\n // parallel ab, kein lineares STATE_MACHINE-Pattern weil Channels\n // unabh\u00E4ngig connecten.\n /** LAN-Scan-Initial-Wait abgeschlossen (3s nach Start). */\n private lanScanDone = false;\n /** State-Tree-Erstellung f\u00FCr alle Cached/Cloud-Devices fertig. */\n private statesReady = false;\n /** Cloud-Init-Phase abgeschlossen (mit oder ohne Erfolg). */\n private cloudInitDone = false;\n /** True nach dem ersten erfolgreichen App-API-Poll (f\u00FCr Sensoren mit Werten). */\n private appApiInitialPollDone = false;\n /** Verhindert Mehrfach-Ready-Log innerhalb derselben Adapter-Session. */\n private readyLogged = false;\n /** Cloud war mindestens einmal connected \u2014 f\u00FCr \u201Erestored\"-Log nach Down. */\n private cloudWasConnected = false;\n /** T\u00E4gliches Interval f\u00FCr App-Version-Drift-Check gegen App-Store. */\n private appVersionCheckTimer: ioBroker.Interval | undefined;\n // === Sub-Komponenten ===\n private skuCache: SkuCache | null = null;\n private localSnapshots: LocalSnapshotStore | null = null;\n private stateCreationQueue: Promise<void>[] = [];\n private lanScanTimer: ioBroker.Timeout | undefined;\n private cleanupTimer: ioBroker.Timeout | undefined;\n private readyTimer: ioBroker.Timeout | undefined;\n private cloudRetry: CloudRetryLoop | null = null;\n private segmentWizard: SegmentWizard | null = null;\n private unhandledRejectionHandler: ((reason: unknown) => void) | null = null;\n private uncaughtExceptionHandler: ((err: Error) => void) | null = null;\n /** Per-device timestamp of the last diagnostics export \u2014 throttle gate */\n private diagnosticsLastRun = new Map<string, number>();\n /** Cached admin language from system.config \u2014 used for wizard UI text */\n private adminLanguage = \"en\";\n /** Last time `requestCode` was triggered via onMessage \u2014 guards against double-click email spam. */\n private lastVerificationRequestMs = 0;\n\n /** @param options Adapter options */\n public constructor(options: Partial<utils.AdapterOptions> = {}) {\n super({ ...options, name: \"govee-smart\" });\n // Per ioBroker rule: async handlers registered on events MUST .catch,\n // otherwise rejections become unhandled \u2192 SIGKILL code 6 \u2192 restart loop.\n this.on(\"ready\", () =>\n this.onReady().catch(e =>\n this.log.error(`onReady crashed: ${e instanceof Error ? (e.stack ?? e.message) : String(e)}`),\n ),\n );\n this.on(\"stateChange\", (id, state) =>\n this.onStateChange(id, state).catch(e => this.log.warn(`onStateChange crashed for ${id}: ${errMessage(e)}`)),\n );\n this.on(\"message\", obj => this.onMessage(obj));\n this.on(\"unload\", callback => this.onUnload(callback));\n // Last-line-of-defence against unhandled rejections / sync throws from\n // fire-and-forget paths. The per-handler .catch() wrappers cover the\n // direct entry points; this catches whatever slips past them so the\n // adapter logs the cause instead of triggering js-controller SIGKILL.\n this.unhandledRejectionHandler = (reason: unknown) => {\n this.log.error(\n `Unhandled rejection: ${reason instanceof Error ? (reason.stack ?? reason.message) : String(reason)}`,\n );\n };\n this.uncaughtExceptionHandler = (err: Error) => {\n this.log.error(`Uncaught exception: ${err.stack ?? err.message}`);\n };\n process.on(\"unhandledRejection\", this.unhandledRejectionHandler);\n process.on(\"uncaughtException\", this.uncaughtExceptionHandler);\n }\n\n /** Adapter started \u2014 initialize all channels */\n private async onReady(): Promise<void> {\n const config = this.config as unknown as AdapterConfig;\n\n // info channel + states are declared as instanceObjects in\n // io-package.json, so js-controller materialises them on install /\n // upgrade. We only initialise the runtime values here.\n await this.setStateAsync(\"info.connection\", { val: false, ack: true });\n await this.setStateAsync(\"info.mqttConnected\", { val: false, ack: true });\n await this.setStateAsync(\"info.cloudConnected\", { val: false, ack: true });\n await this.setStateAsync(\"info.openapiMqttConnected\", {\n val: false,\n ack: true,\n });\n await this.setStateAsync(\"info.refresh_cloud_data\", {\n val: false,\n ack: true,\n });\n // Load admin language from system.config so wizard prose matches the\n // user's Admin UI. Falls back to English on any lookup failure.\n try {\n const sysConf = await this.getForeignObjectAsync(\"system.config\");\n const lang = (sysConf?.common as { language?: string } | undefined)?.language;\n if (typeof lang === \"string\" && lang.length > 0) {\n this.adminLanguage = lang;\n }\n } catch {\n // Keep default \"en\"\n }\n await this.setStateAsync(\"info.wizardStatus\", {\n val: wizardIdleText(this.adminLanguage),\n ack: true,\n });\n\n this.stateManager = new StateManager(this);\n // General groups online state (reflects Cloud connection)\n await this.stateManager.createGroupsOnlineState(false);\n this.deviceManager = new DeviceManager(this.log, this);\n const dataDir = utils.getAbsoluteInstanceDataDir(this);\n\n // Load device registry from devices.json in the adapter package root.\n // Status filter: verified+reported active by default; seed-status entries\n // require the experimentalQuirks config toggle.\n initDeviceRegistry({\n experimental: config.experimentalQuirks === true,\n log: this.log,\n });\n this.skuCache = new SkuCache(dataDir, this.log);\n this.localSnapshots = new LocalSnapshotStore(dataDir, this.log);\n this.deviceManager.setSkuCache(this.skuCache);\n\n // API client for undocumented scene/music/DIY libraries (always available)\n const apiClient = new GoveeApiClient();\n apiClient.setEmail(config.goveeEmail);\n this.deviceManager.setApiClient(apiClient);\n\n this.deviceManager.setCallbacks(\n (device, state) => this.onDeviceStateUpdate(device, state),\n devices => this.onDeviceListChanged(devices),\n );\n\n // Update info.ip when LAN IP changes\n this.deviceManager.onLanIpChanged = (device, ip) => {\n const prefix = this.stateManager!.devicePrefix(device);\n this.setStateAsync(`${prefix}.info.ip`, { val: ip, ack: true }).catch(() => {});\n };\n\n // Sync individual segment states after batch command\n this.deviceManager.onSegmentBatchUpdate = (device, batch) => {\n const prefix = this.stateManager!.devicePrefix(device);\n for (const idx of batch.segments) {\n if (batch.color !== undefined) {\n const hex = rgbIntToHex(batch.color);\n this.setStateAsync(`${prefix}.segments.${idx}.color`, {\n val: hex,\n ack: true,\n }).catch(() => {});\n }\n if (batch.brightness !== undefined) {\n this.setStateAsync(`${prefix}.segments.${idx}.brightness`, {\n val: batch.brightness,\n ack: true,\n }).catch(() => {});\n }\n }\n };\n\n // Sync per-segment states from MQTT BLE status push (AA A5 packets)\n this.deviceManager.onMqttSegmentUpdate = (device, segments) => {\n const prefix = this.stateManager!.devicePrefix(device);\n for (const seg of segments) {\n this.setStateAsync(`${prefix}.segments.${seg.index}.color`, {\n val: rgbToHex(seg.r, seg.g, seg.b),\n ack: true,\n }).catch(() => {});\n this.setStateAsync(`${prefix}.segments.${seg.index}.brightness`, {\n val: seg.brightness,\n ack: true,\n }).catch(() => {});\n }\n };\n\n // When MQTT reveals more segments than the Cloud advertised, rebuild\n // the device's state tree so the extra segments get their datapoints.\n this.deviceManager.onSegmentCountGrown = device => {\n if (!this.stateManager) {\n return;\n }\n this.stateManager.createSegmentStates(device).catch(e => {\n this.log.warn(`Failed to rebuild segment tree for ${device.name} after count growth: ${errMessage(e)}`);\n });\n };\n\n // Log startup with configured channels\n const startChannels: string[] = [\"LAN\"];\n if (config.apiKey) {\n startChannels.push(\"Cloud\");\n }\n if (config.goveeEmail && config.goveePassword) {\n startChannels.push(\"MQTT\");\n }\n this.log.info(`Starting (${startChannels.join(\", \")})`);\n\n // --- LAN (always active) ---\n this.lanClient = new GoveeLanClient(this.log, this);\n this.deviceManager.setLanClient(this.lanClient);\n\n this.lanClient.start(\n lanDevice => {\n this.deviceManager!.handleLanDiscovery(lanDevice);\n // Poll status only when MQTT is unavailable. With an active MQTT\n // subscription Govee pushes state changes authoritatively, so the\n // LAN devStatus request would be duplicate traffic.\n if (!this.mqttClient?.connected) {\n this.lanClient!.requestStatus(lanDevice.ip);\n }\n },\n (sourceIp, status) => {\n this.deviceManager!.handleLanStatus(sourceIp, status);\n },\n 30_000,\n config.networkInterface || \"\",\n );\n\n // Wait for first LAN scan responses (UDP multicast, devices respond within 1-2s)\n this.lanScanTimer = this.setTimeout(() => {\n this.lanScanDone = true;\n this.checkAllReady();\n }, 3_000);\n\n // --- MQTT (if account credentials provided) ---\n // Initialize MQTT before Cloud so scene library can load on first cycle\n if (config.goveeEmail && config.goveePassword) {\n this.mqttClient = new GoveeMqttClient(config.goveeEmail, config.goveePassword, this.log, this);\n\n // Forward every parsed MQTT op.command into the diagnostics ring buffer\n // so diag.export contains the recent packets per device.\n this.mqttClient.setPacketHook((deviceId, topic, hex) => {\n this.deviceManager?.getDiagnostics().addMqttPacket(deviceId, topic, hex);\n });\n\n // 2FA: forward optional code from settings into the next login attempt;\n // clear the field automatically once Govee has accepted it.\n this.mqttClient.setVerificationCode(config.mqttVerificationCode ?? \"\");\n this.mqttClient.setOnVerificationConsumed(() => {\n this.clearVerificationCodeSetting().catch(e => {\n this.log.warn(`Could not clear mqttVerificationCode: ${errMessage(e)}`);\n });\n });\n this.mqttClient.setOnVerificationFailed(reason => {\n // On 'failed' (455 / 454+code-was-sent) blank the code so the user\n // doesn't keep retrying with a stale value. On 'pending' (454 + no\n // code) we leave the field as-is \u2014 the user is about to fill it.\n if (reason === \"failed\") {\n this.clearVerificationCodeSetting().catch(() => {});\n }\n });\n\n // Re-use cached MQTT credentials across restarts. Stored in the\n // info.mqttCredentials state (NOT in adapter native): writing to\n // system.adapter.X.0 native triggers a js-controller adapter\n // restart, which would loop endlessly on every login. States are\n // restart-safe.\n //\n // One-shot: clean up legacy v2.1.0/v2.1.1/v2.1.2 native fields\n // that contained plaintext credentials. Best-effort.\n await this.cleanupLegacyMqttNativeOnce();\n const cachedCreds = await this.loadPersistedCredsFromState();\n if (cachedCreds) {\n this.mqttClient.setPersistedCredentials(cachedCreds);\n }\n this.mqttClient.setOnCredentialsRefresh(creds => {\n this.persistCredsToState(creds).catch(e => {\n this.log.warn(`Could not persist MQTT credentials: ${errMessage(e)}`);\n });\n });\n\n await this.mqttClient.connect(\n update => this.deviceManager!.handleMqttStatus(update),\n connected => {\n this.setStateAsync(\"info.mqttConnected\", {\n val: connected,\n ack: true,\n }).catch(() => {});\n if (connected) {\n this.checkAllReady();\n }\n this.updateConnectionState();\n },\n // Forward every fresh bearer token \u2014 fires on initial login and on\n // each reconnect-login, so the API client never runs with a stale one.\n token => apiClient.setBearerToken(token),\n );\n }\n\n // --- Device data: Cache first, Cloud only on cache miss ---\n const cachedOk = this.deviceManager.loadFromCache();\n\n if (config.apiKey) {\n this.cloudClient = new GoveeCloudClient(config.apiKey, this.log);\n // Capture the most recent Cloud response per (deviceId, endpoint) for\n // diagnostics \u2014 bounded by the DiagnosticsCollector's response slot cap.\n this.cloudClient.setResponseHook((deviceId, endpoint, body) => {\n this.deviceManager?.getDiagnostics().setApiResponse(deviceId, endpoint, body);\n });\n this.deviceManager.setCloudClient(this.cloudClient);\n\n // Bridge synthetic capabilities (App-API, OpenAPI-MQTT events) into the\n // same setState pipeline as polled Cloud state. Keeps mapCloudStateValue\n // as the single source of truth for value coercion + state-id resolution.\n this.deviceManager.setOnCloudCapabilities((device, caps) => {\n this.applyCloudCapabilities(device, caps).catch(e =>\n this.log.warn(`applyCloudCapabilities failed for ${device.sku}: ${errMessage(e)}`),\n );\n });\n\n this.rateLimiter = new RateLimiter(this.log, this, FULL_LIMITS.perMinute, FULL_LIMITS.perDay);\n this.rateLimiter.start();\n this.deviceManager.setRateLimiter(this.rateLimiter);\n\n // OpenAPI-MQTT \u2014 push channel for appliance/sensor events\n // (lackWater, iceFull, bodyAppeared etc.). API key is enough; no\n // separate credentials required. Connection runs in parallel to\n // the AWS-IoT MQTT used for status push of regular devices.\n this.openapiMqttClient = new GoveeOpenapiMqttClient(config.apiKey, this.log, this);\n this.openapiMqttClient.connect(\n event => this.deviceManager?.handleOpenApiEvent(event),\n connected => {\n this.setStateAsync(\"info.openapiMqttConnected\", {\n val: connected,\n ack: true,\n }).catch(() => {});\n },\n );\n\n // App-API poll \u2014 every 2 minutes, pulls state for sensors like H5179\n // where OpenAPI v2 /device/state returns empty. Bearer token comes\n // from the AWS-IoT MQTT login, so a no-op until that succeeds.\n const triggerAppApiPoll = (): void => {\n this.deviceManager\n ?.pollAppApi()\n .then(() => {\n // H2 \u2014 Mark initial-poll-done und re-check Ready damit der\n // Adapter \u201Eready\" loggen kann sobald Sensor-Werte da sind.\n if (!this.appApiInitialPollDone) {\n this.appApiInitialPollDone = true;\n this.checkAllReady();\n }\n })\n .catch(e => this.log.debug(`pollAppApi failed: ${errMessage(e)}`));\n };\n this.appApiPollTimer = this.setInterval(triggerAppApiPoll, APP_API_POLL_INTERVAL_MS);\n // Initial poll: gibt MQTT Zeit f\u00FCr den Bearer-Login. Ohne diesen\n // Sofort-Poll bleiben Sensoren wie H5179 die ersten 2 Minuten nach\n // Start offline (Online-Signal kommt nur via App-API). Handle in\n // Member-Variable damit onUnload den Timer cleart.\n this.appApiInitialTimer = this.setTimeout(triggerAppApiPoll, APP_API_INITIAL_DELAY_MS);\n\n if (!cachedOk) {\n // No cache \u2014 first start, fetch from Cloud with 60s hard-timeout.\n // If Cloud hangs/fails, we don't want to block adapter startup indefinitely.\n const result = await this.cloudInitWithTimeout();\n this.cloudWasConnected = result.ok;\n this.ensureCloudRetry().setConnected(result.ok);\n this.setStateAsync(\"info.cloudConnected\", {\n val: result.ok,\n ack: true,\n }).catch(() => {});\n this.stateManager?.updateGroupsOnline(result.ok).catch(() => {});\n\n if (result.ok) {\n await this.loadCloudStates();\n } else {\n this.handleCloudFailure(result);\n }\n } else {\n this.log.info(\"Using cached device data \u2014 no Cloud calls needed\");\n this.cloudWasConnected = true;\n this.ensureCloudRetry().setConnected(true);\n this.setStateAsync(\"info.cloudConnected\", {\n val: true,\n ack: true,\n }).catch(() => {});\n this.stateManager?.updateGroupsOnline(true).catch(() => {});\n }\n // Load group membership from undocumented API (needs bearer token + device map)\n await this.deviceManager.loadGroupMembers();\n\n this.cloudInitDone = true;\n }\n\n // Wait for all state creation from cache/cloud load to complete.\n // Drain-loop: a callback that fires during the await (e.g. a late LAN\n // discovery) can push fresh promises into the queue \u2014 we need to await\n // those too before flipping statesReady, otherwise the initial state\n // tree would be incomplete on very fast startups.\n while (this.stateCreationQueue.length > 0) {\n const pending = this.stateCreationQueue;\n this.stateCreationQueue = [];\n await Promise.all(pending);\n }\n this.statesReady = true;\n\n // Subscribe to all writable device and group states\n await this.subscribeStatesAsync(\"devices.*\");\n await this.subscribeStatesAsync(\"groups.*\");\n await this.subscribeStatesAsync(\"info.refresh_cloud_data\");\n\n // Cleanup stale devices after initial discovery (30s delay for LAN scan).\n // Reaps devices from every adapter-level map that was keyed on them so the\n // process doesn't leak memory across Cloud-side device turnover.\n this.cleanupTimer = this.setTimeout(() => {\n this.reapStaleDevices().catch(e => this.log.debug(`Device cleanup failed: ${errMessage(e)}`));\n }, 30_000);\n\n // App-Version-Drift-Monitor \u2014 daily check + initial nach 2 min wenn der\n // Adapter-Start ohne MQTT-Login durchgeschlagen ist (z.B. LAN-only).\n this.appVersionCheckTimer = this.setInterval(\n () => {\n this.checkAppVersionDrift().catch(e => this.log.debug(`App version check error: ${errMessage(e)}`));\n },\n 24 * 60 * 60 * 1000,\n );\n this.setTimeout(\n () => {\n this.checkAppVersionDrift().catch(e => this.log.debug(`App version check error: ${errMessage(e)}`));\n },\n 2 * 60 * 1000,\n );\n\n this.updateConnectionState();\n\n // Check if all channels are ready \u2014 may already be true if MQTT connected fast\n this.checkAllReady();\n // Safety timeout: log ready even if a channel takes too long.\n // 60s deckt normalen MQTT-Connect + 1 Reconnect-Attempt ab.\n this.readyTimer = this.setTimeout(() => {\n if (!this.readyLogged) {\n // Safety-Timeout: log ready trotzdem auch wenn ein Channel zu lange\n // braucht. READY_TIMEOUT_MS deckt normalen MQTT-Connect + 1 Reconnect.\n this.readyLogged = true;\n this.logDeviceSummary();\n }\n }, 60_000);\n }\n\n /**\n * Initial Cloud-Load mit 60-Sekunden-Hardtimeout.\n * Blockiert nicht l\u00E4nger \u2014 wenn Cloud h\u00E4ngt, geht Adapter mit LAN+MQTT weiter,\n * und der Retry-Loop probiert's passend zum Fehlergrund erneut.\n */\n private async cloudInitWithTimeout(): Promise<CloudLoadResult> {\n if (!this.deviceManager) {\n return { ok: false, reason: \"transient\" };\n }\n const loadPromise = this.deviceManager.loadFromCloud();\n const timeoutPromise = new Promise<CloudLoadResult>(resolve => {\n this.cloudInitTimer = this.setTimeout(() => resolve({ ok: false, reason: \"transient\" }), READY_TIMEOUT_MS);\n });\n try {\n return await Promise.race([loadPromise, timeoutPromise]);\n } catch {\n return { ok: false, reason: \"transient\" };\n }\n }\n\n /** Build the host object for {@link CloudRetryLoop}. */\n private buildCloudRetryHost(): CloudRetryHost {\n return {\n log: this.log,\n setTimeout: (cb, ms) => this.setTimeout(cb, ms),\n clearTimeout: h => this.clearTimeout(h as ioBroker.Timeout),\n loadFromCloud: () => this.cloudInitWithTimeout(),\n onCloudRestored: async () => {\n this.cloudWasConnected = true;\n this.setStateAsync(\"info.cloudConnected\", {\n val: true,\n ack: true,\n }).catch(() => {});\n this.stateManager?.updateGroupsOnline(true).catch(() => {});\n await this.loadCloudStates();\n },\n };\n }\n\n /** Lazy-initialise the retry loop on first use. */\n private ensureCloudRetry(): CloudRetryLoop {\n if (!this.cloudRetry) {\n this.cloudRetry = new CloudRetryLoop(this.buildCloudRetryHost());\n this.cloudRetry.setConnected(this.cloudWasConnected);\n }\n return this.cloudRetry;\n }\n\n /**\n * React to a Cloud-load outcome \u2014 delegates to {@link CloudRetryLoop}.\n *\n * @param result CloudLoadResult from initial load or retry attempt\n */\n private handleCloudFailure(result: CloudLoadResult): void {\n this.ensureCloudRetry().handleResult(result);\n }\n\n /**\n * React to the user writing `info.refresh_cloud_data = true`. Performs one\n * full Cloud reload cycle so newly created scenes/snapshots from the Govee\n * Home app show up without an adapter restart.\n */\n private async handleManualCloudRefresh(): Promise<void> {\n if (!this.deviceManager || !this.cloudClient) {\n this.log.info(\"Refresh cloud data: no Cloud client configured (API key missing) \u2014 nothing to do\");\n return;\n }\n this.log.info(\"Refresh cloud data: re-fetching scenes and snapshots for all devices\");\n try {\n const changed = await this.deviceManager.refreshSceneData();\n if (changed) {\n await this.loadCloudStates();\n }\n // G2 \u2014 kein \u201Edone\"-Log: User hat den Button gedr\u00FCckt, sieht das\n // Ergebnis am Adapter-Verhalten. \u201Edone\" auf info-level w\u00E4re bei\n // Erfolg redundant (Fehler-Pfad direkt darunter ist warn).\n } catch (e) {\n this.log.warn(`Refresh cloud data failed: ${errMessage(e)}`);\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.lanScanTimer) {\n this.clearTimeout(this.lanScanTimer);\n }\n if (this.cleanupTimer) {\n this.clearTimeout(this.cleanupTimer);\n }\n if (this.readyTimer) {\n this.clearTimeout(this.readyTimer);\n }\n if (this.appApiPollTimer) {\n this.clearInterval(this.appApiPollTimer);\n this.appApiPollTimer = undefined;\n }\n if (this.appApiInitialTimer) {\n this.clearTimeout(this.appApiInitialTimer);\n this.appApiInitialTimer = undefined;\n }\n if (this.cloudInitTimer) {\n this.clearTimeout(this.cloudInitTimer);\n this.cloudInitTimer = undefined;\n }\n if (this.appVersionCheckTimer) {\n this.clearInterval(this.appVersionCheckTimer);\n this.appVersionCheckTimer = undefined;\n }\n this.cloudRetry?.dispose();\n this.segmentWizard?.dispose();\n this.lanClient?.stop();\n this.mqttClient?.disconnect();\n this.openapiMqttClient?.disconnect();\n this.rateLimiter?.stop();\n // Remove process-level handlers so an adapter restart doesn't stack them.\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 // onUnload MUST be synchronous \u2014 don't await, but silence potential\n // promise rejection during teardown to avoid \"unhandled rejection\" warnings.\n this.setState(\"info.connection\", { val: false, ack: true }).catch(() => {});\n this.setState(\"info.mqttConnected\", { val: false, ack: true }).catch(() => {});\n this.setState(\"info.openapiMqttConnected\", {\n val: false,\n ack: true,\n }).catch(() => {});\n this.setState(\"info.cloudConnected\", { val: false, ack: true }).catch(() => {});\n } catch {\n // ignore\n }\n callback();\n }\n\n /**\n * Handle state changes from user (write operations).\n *\n * @param id State ID\n * @param state New state value\n */\n private async onStateChange(id: string, state: ioBroker.State | null | undefined): Promise<void> {\n if (!state || state.ack || !this.deviceManager || !this.stateManager) {\n return;\n }\n\n // Global refresh button \u2014 triggers one fresh cloud fetch across all\n // devices and re-builds the state tree. Handy after creating a new\n // snapshot in the Govee Home app without restarting the adapter.\n if (id === `${this.namespace}.info.refresh_cloud_data` && state.val) {\n await this.handleManualCloudRefresh();\n await this.setStateAsync(id, { val: false, ack: true });\n return;\n }\n\n // Find which device this state belongs to\n const localId = id.replace(`${this.namespace}.`, \"\");\n if (!localId.startsWith(\"devices.\") && !localId.startsWith(\"groups.\")) {\n return;\n }\n\n const device = this.findDeviceForState(localId);\n if (!device) {\n return;\n }\n\n // Determine command from state suffix after device prefix\n const prefix = this.stateManager.devicePrefix(device);\n const stateSuffix = localId.slice(prefix.length + 1);\n\n // Resolve dropdown input \u2014 accept Number, numeric String, or label\n // (case-insensitive) against the state's common.states map. Returns\n // the canonical key as String so the rest of the handler sees the\n // same shape it always saw (e.g. \"1\"). Non-dropdown states are\n // passed through unchanged.\n const resolved = await this.resolveDropdownInput(id, state.val);\n if (!resolved.ok) {\n this.log.warn(`Unknown dropdown value for ${id}: ${String(state.val)} \u2014 ignoring`);\n return;\n }\n const val = resolved.val;\n\n // Group fan-out: route commands to each member device\n if (device.sku === \"BaseGroup\" && device.groupMembers) {\n await this.handleGroupFanOut(device, stateSuffix, val);\n await this.setStateAsync(id, { val, ack: true });\n if (stateSuffix === \"scenes.light_scene\" || stateSuffix === \"music.music_mode\") {\n await this.resetRelatedDropdowns(prefix, stateSuffix === \"scenes.light_scene\" ? \"lightScene\" : \"music\");\n }\n return;\n }\n\n // Handle local snapshot commands (no Cloud/MQTT needed)\n if (stateSuffix === \"snapshots.snapshot_save\" && typeof val === \"string\" && val.trim()) {\n await this.handleSnapshotSave(device, val.trim());\n await this.setStateAsync(id, { val: \"\", ack: true });\n return;\n }\n if (stateSuffix === \"snapshots.snapshot_local\") {\n if (val !== \"0\" && val !== 0) {\n await this.handleSnapshotRestore(device, val);\n await this.resetRelatedDropdowns(prefix, \"snapshotLocal\");\n }\n await this.setStateAsync(id, { val, ack: true });\n return;\n }\n if (stateSuffix === \"snapshots.snapshot_delete\" && typeof val === \"string\" && val.trim()) {\n this.handleSnapshotDelete(device, val.trim());\n await this.setStateAsync(id, { val: \"\", ack: true });\n return;\n }\n\n // Manual segments toggle/list \u2014 handler owns the ack because a parse\n // error rewrites manual_mode to false, and an outer ack with the\n // raw value would resurrect the rejected entry.\n if (stateSuffix === \"segments.manual_mode\" || stateSuffix === \"segments.manual_list\") {\n await this.handleManualSegmentsChange(device, stateSuffix, val);\n return;\n }\n\n if (stateSuffix === \"diag.export\" && val) {\n await this.handleDiagnosticsExport(device, prefix, id);\n return;\n }\n\n const command = this.stateToCommand(stateSuffix);\n\n if (!command) {\n await this.handleGenericCapabilityCommand(device, id, stateSuffix, val);\n return;\n }\n\n // Dropdown reset to \"---\" (value 0) \u2014 acknowledge without sending command\n if ((command === \"lightScene\" || command === \"diyScene\" || command === \"snapshot\") && (val === \"0\" || val === 0)) {\n await this.setStateAsync(id, { val, ack: true });\n return;\n }\n\n // Scene speed: store on device, applied on next scene activation.\n // Persist to SKU cache so the user's choice survives a restart.\n if (command === \"sceneSpeed\") {\n const level = typeof val === \"number\" ? val : parseInt(String(val), 10);\n if (!isNaN(level)) {\n device.sceneSpeed = level;\n this.deviceManager?.persistDeviceToCache(device);\n }\n await this.setStateAsync(id, { val, ack: true });\n return;\n }\n\n try {\n // Music mode: combine all music states into one STRUCT command\n if (command === \"music\") {\n // music_mode \"---\" (value 0) \u2014 acknowledge without sending command\n if (stateSuffix === \"music.music_mode\" && (val === \"0\" || val === 0)) {\n await this.setStateAsync(id, { val, ack: true });\n return;\n }\n await this.sendMusicCommand(device, prefix, stateSuffix, val);\n await this.setStateAsync(id, { val, ack: true });\n // Reset scene/snapshot dropdowns when activating music mode\n if (stateSuffix === \"music.music_mode\") {\n await this.resetRelatedDropdowns(prefix, \"music\");\n }\n return;\n }\n\n await this.deviceManager.sendCommand(device, command, val);\n // Optimistic ack\n await this.setStateAsync(id, { val, ack: true });\n // Reset related dropdowns when switching modes.\n // Power-off is a special case \u2014 the device is off, so no mode is\n // active anymore; reset every mode dropdown so the UI reflects the\n // reality (scene/music/snapshot selections are now just history).\n if (command === \"power\" && val === false) {\n await this.resetModeDropdowns(prefix, \"\");\n } else {\n await this.resetRelatedDropdowns(prefix, command);\n }\n } catch (err) {\n this.log.warn(`Command failed for ${device.name}: ${errMessage(err)}`);\n }\n }\n\n /**\n * Resolve a dropdown-state input value against the state's common.states\n * map. Returns the canonical key (always String form) so a user can write\n * either the index (\"1\"), the index as a number (1) or the label name\n * (\"Aurora\", case-insensitive) \u2014 all three land at the same canonical\n * value for the rest of the handler.\n *\n * Non-dropdown states (no common.states), reset sentinels (0/\"0\"/\"\") and\n * non-string/number inputs are passed through unchanged. A dropdown input\n * that doesn't match any key or label returns ok=false so the caller can\n * warn and skip the command.\n *\n * @param id Full state id\n * @param raw Raw input value as provided by the user/script\n */\n private async resolveDropdownInput(\n id: string,\n raw: ioBroker.StateValue,\n ): Promise<{ val: ioBroker.StateValue; ok: boolean }> {\n if (raw === null || raw === undefined) {\n return { val: raw, ok: true };\n }\n // Reset sentinels \u2014 let the existing branch handle them.\n if (raw === 0 || raw === \"0\" || raw === \"\") {\n return { val: raw, ok: true };\n }\n // Only dropdown candidates have common.states; non-dropdown inputs\n // can't be resolved here so they pass through.\n if (typeof raw !== \"number\" && typeof raw !== \"string\") {\n return { val: raw, ok: true };\n }\n const obj = await this.getObjectAsync(id);\n const states = obj?.common?.states;\n if (!states || typeof states !== \"object\") {\n return { val: raw, ok: true };\n }\n const resolved = resolveStatesValue(raw, states as Record<string, string>);\n if (resolved) {\n return { val: resolved.key, ok: true };\n }\n return { val: raw, ok: false };\n }\n\n /**\n * Build and send a music_setting STRUCT command.\n * Reads sibling music state values and combines them into one API call.\n *\n * @param device Target device\n * @param prefix Device state prefix\n * @param changedSuffix Which music state was changed\n * @param newValue New value for the changed state\n */\n private async sendMusicCommand(\n device: GoveeDevice,\n prefix: string,\n changedSuffix: string,\n newValue: ioBroker.StateValue,\n ): Promise<void> {\n const musicBase = `${this.namespace}.${prefix}.music`;\n\n // Read current sibling values\n const modeState = await this.getStateAsync(`${musicBase}.music_mode`);\n const sensState = await this.getStateAsync(`${musicBase}.music_sensitivity`);\n const autoState = await this.getStateAsync(`${musicBase}.music_auto_color`);\n\n // Apply the changed value, use siblings for the rest\n const musicMode =\n changedSuffix === \"music.music_mode\" ? parseInt(String(newValue), 10) : parseInt(String(modeState?.val ?? 0), 10);\n const sensitivity =\n changedSuffix === \"music.music_sensitivity\" ? (newValue as number) : ((sensState?.val as number) ?? 100);\n const autoColor = changedSuffix === \"music.music_auto_color\" ? (newValue ? 1 : 0) : autoState?.val ? 1 : 0;\n\n if (!musicMode || musicMode === 0) {\n this.log.debug(\"Music mode not selected, skipping command\");\n return;\n }\n\n // LAN first: send via ptReal BLE if device is on LAN\n if (device.lanIp && this.lanClient) {\n // Read current color for RGB-modes (Spectrum=1, Rolling=2)\n let r = 0,\n g = 0,\n b = 0;\n if (musicMode === 1 || musicMode === 2) {\n const colorState = await this.getStateAsync(`${this.namespace}.${prefix}.control.colorRgb`);\n if (colorState?.val && typeof colorState.val === \"string\") {\n ({ r, g, b } = hexToRgb(colorState.val));\n }\n }\n this.lanClient.setMusicMode(device.lanIp, musicMode, r, g, b);\n return;\n }\n\n // Cloud fallback\n const structValue: Record<string, unknown> = {\n musicMode,\n sensitivity,\n autoColor,\n };\n\n await this.deviceManager!.sendCapabilityCommand(\n device,\n \"devices.capabilities.music_setting\",\n \"musicMode\",\n structValue,\n );\n }\n\n /**\n * Called by device-manager when a device state changes\n *\n * @param device Updated device\n * @param state Changed state values\n */\n private onDeviceStateUpdate(device: GoveeDevice, state: Partial<DeviceState>): void {\n if (this.stateManager) {\n this.stateManager.updateDeviceState(device, state).catch(() => {});\n }\n this.updateConnectionState();\n\n // Update group reachability when member online status changes\n if (state.online !== undefined) {\n this.updateGroupReachability();\n }\n\n // Mirror power-off to mode-dropdown reset. Covers MQTT/LAN-initiated\n // power changes (Govee app or physical remote) so the UI stays honest:\n // a device that's off can't be \"playing Aurora-A\" anymore.\n // L11 \u2014 defensive auch 0 als false akzeptieren (Govee schickt Power\n // theoretisch als boolean, aber MQTT-Boundary k\u00F6nnte 0 durchschleusen).\n const powerOff = state.power === false || (state.power as unknown) === 0;\n if (powerOff && this.stateManager) {\n const prefix = this.stateManager.devicePrefix(device);\n this.resetModeDropdowns(prefix, \"\").catch(() => undefined);\n }\n }\n\n /**\n * Fan out a group command to all member devices.\n * Basic controls (power, brightness, color) are sent directly.\n * Scenes/music are matched by name across members.\n *\n * @param group BaseGroup device\n * @param stateSuffix State path suffix (e.g. \"control.power\")\n * @param value Command value\n */\n private async handleGroupFanOut(group: GoveeDevice, stateSuffix: string, value: ioBroker.StateValue): Promise<void> {\n if (!this.deviceManager || !group.groupMembers) {\n return;\n }\n\n const devices = this.deviceManager.getDevices();\n const members = this.resolveGroupMembers(group, devices).filter(d => d.state.online);\n\n if (members.length === 0) {\n this.log.debug(`Group \"${group.name}\": no reachable members for fan-out`);\n return;\n }\n\n const command = this.stateToCommand(stateSuffix);\n if (!command) {\n return;\n }\n\n // Dropdown reset \u2014 no command needed\n if ((command === \"lightScene\" || command === \"music\") && (value === \"0\" || value === 0)) {\n return;\n }\n\n for (const member of members) {\n try {\n if (command === \"lightScene\") {\n await this.fanOutScene(group, member, value);\n } else if (command === \"music\") {\n await this.fanOutMusic(group, member, stateSuffix, value);\n } else {\n await this.deviceManager.sendCommand(member, command, value);\n }\n } catch (err) {\n this.log.debug(`Group fan-out to ${member.name}: ${errMessage(err)}`);\n }\n }\n }\n\n /**\n * Fan out a scene command: match group scene name to member scene index.\n *\n * @param group BaseGroup device\n * @param member Target member device\n * @param value Dropdown index value\n */\n private async fanOutScene(group: GoveeDevice, member: GoveeDevice, value: ioBroker.StateValue): Promise<void> {\n if (!this.deviceManager || !this.stateManager) {\n return;\n }\n\n // Get group scene name from dropdown value (1-based index)\n const groupPrefix = this.stateManager.devicePrefix(group);\n const obj = await this.getObjectAsync(`${this.namespace}.${groupPrefix}.scenes.light_scene`);\n const groupStates = obj?.common?.states as Record<string, string> | undefined;\n const sceneName = groupStates?.[String(value)];\n if (!sceneName) {\n return;\n }\n\n // Find the same scene name in the member's scene list (1-based)\n const memberIdx = member.scenes.findIndex(s => s.name === sceneName);\n if (memberIdx >= 0) {\n await this.deviceManager.sendCommand(member, \"lightScene\", memberIdx + 1);\n }\n }\n\n /**\n * Fan out a music command: match group music name to member music index.\n *\n * @param group BaseGroup device\n * @param member Target member device\n * @param stateSuffix Music state path suffix\n * @param value Command value\n */\n private async fanOutMusic(\n group: GoveeDevice,\n member: GoveeDevice,\n stateSuffix: string,\n value: ioBroker.StateValue,\n ): Promise<void> {\n if (!this.deviceManager || !this.stateManager) {\n return;\n }\n\n // For sensitivity/auto_color, forward directly \u2014 these are numeric values\n if (stateSuffix !== \"music.music_mode\") {\n await this.sendMusicCommand(member, this.stateManager.devicePrefix(member), stateSuffix, value);\n return;\n }\n\n // Get group music name from dropdown value (1-based index)\n const groupPrefix = this.stateManager.devicePrefix(group);\n const obj = await this.getObjectAsync(`${this.namespace}.${groupPrefix}.music.music_mode`);\n const groupStates = obj?.common?.states as Record<string, string> | undefined;\n const musicName = groupStates?.[String(value)];\n if (!musicName) {\n return;\n }\n\n // Find the same music name in the member's music library (1-based)\n const memberIdx = member.musicLibrary.findIndex(m => m.name === musicName);\n if (memberIdx >= 0) {\n // Build the music command struct for the member\n const memberPrefix = this.stateManager.devicePrefix(member);\n // Temporarily write the music mode value to trigger the member's music command\n await this.sendMusicCommand(member, memberPrefix, \"music.music_mode\", memberIdx + 1);\n }\n }\n\n /**\n * Resolve group member references to actual device objects.\n *\n * @param group BaseGroup device with groupMembers\n * @param devices Full device list to search\n */\n private resolveGroupMembers(group: GoveeDevice, devices: GoveeDevice[]): GoveeDevice[] {\n if (!group.groupMembers) {\n return [];\n }\n return group.groupMembers\n .map(m => devices.find(d => d.sku === m.sku && d.deviceId === m.deviceId))\n .filter((d): d is GoveeDevice => d !== undefined);\n }\n\n /**\n * Recalculate info.membersUnreachable for all groups.\n * Called when any device's online status changes.\n */\n private updateGroupReachability(): void {\n if (!this.deviceManager || !this.stateManager) {\n return;\n }\n const devices = this.deviceManager.getDevices();\n for (const group of devices) {\n if (group.sku !== \"BaseGroup\" || !group.groupMembers) {\n continue;\n }\n const memberDevices = this.resolveGroupMembers(group, devices);\n this.stateManager.updateGroupMembersUnreachable(group, memberDevices).catch(() => {});\n }\n }\n\n /**\n * Rebuild state definitions for one device and feed them into StateManager.\n * Used both from the full-list callback and from targeted refreshes\n * (e.g. after a local snapshot was added or removed \u2014 no reason to rebuild\n * the entire tree for every device then).\n *\n * @param device Target device\n * @param allDevices Full device list (needed to resolve group members)\n */\n private refreshDeviceStates(device: GoveeDevice, allDevices: GoveeDevice[]): void {\n if (!this.stateManager) {\n return;\n }\n const localSnaps = this.localSnapshots?.getSnapshots(device.sku, device.deviceId);\n let memberDevices: GoveeDevice[] | undefined;\n if (device.sku === \"BaseGroup\" && device.groupMembers) {\n memberDevices = this.resolveGroupMembers(device, allDevices);\n }\n const stateDefs = buildDeviceStateDefs(device, localSnaps, memberDevices);\n const p = this.stateManager\n .createDeviceStates(device, stateDefs)\n .then(async () => {\n // v2.1.0 \u2192 v2.1.1 layout migration: drop legacy info.diagnostics_*\n // before publishing the new diag.tier value. Idempotent.\n await this.stateManager?.migrateLegacyDiagnostics(device);\n await this.stateManager?.updateDeviceTier(device, getDeviceTier(device.sku));\n })\n .catch(e => {\n this.log.error(`createDeviceStates failed for ${device.name}: ${errMessage(e)}`);\n });\n // Until ready, collect so onReady can await the whole initial batch.\n // After ready, fire-and-forget \u2014 the queue would otherwise keep growing\n // with resolved promises for the lifetime of the adapter.\n if (!this.statesReady) {\n this.stateCreationQueue.push(p);\n } else {\n void p;\n }\n }\n\n /**\n * Called by device-manager when the device list changes\n *\n * @param devices Current list of all devices\n */\n private onDeviceListChanged(devices: GoveeDevice[]): void {\n if (!this.stateManager) {\n return;\n }\n\n for (const device of devices) {\n this.refreshDeviceStates(device, devices);\n }\n\n this.updateConnectionState();\n // Cache sync happens once after the initial setup completes (see\n // checkAllReady) \u2014 triggering here would fire on every device update\n // and spam the log.\n\n // Keep adapter-level per-device maps (diagnosticsLastRun, ...) aligned\n // with the new device list so removed devices don't leave orphan keys.\n // Skip during the initial boot phase \u2014 the startup cleanupTimer handles\n // that pass with proper LAN-scan-settled timing.\n if (this.statesReady) {\n this.reapStaleDevices().catch(() => undefined);\n }\n }\n\n /**\n * Update global `info.connection` \u2014 der ioBroker-IDC-Indikator.\n *\n * Semantik:\n * - Mit Devices: `connected = true` wenn MIND. ein Device online ist.\n * Wenn alle offline \u2192 false (User sieht: kein Device antwortet).\n * - Ohne Devices: `connected = true` wenn der LAN-Stack l\u00E4uft. Sonst\n * false (z.B. EADDRINUSE oder bind-Fehler).\n *\n * H4 (geplant f\u00FCr Phase H): nur bei tats\u00E4chlichem Wechsel schreiben\n * (cache lastConnectedValue). Aktuell l\u00E4uft updateConnectionState bei\n * jedem device-state-update \u2014 fire-and-forget setStateAsync, nur leichter\n * Overhead.\n */\n private updateConnectionState(): void {\n const devices = this.deviceManager?.getDevices() ?? [];\n const hasDevices = devices.length > 0;\n const anyOnline = devices.some(d => d.state.online);\n const lanRunning = this.lanClient !== null;\n const connected = hasDevices ? anyOnline : lanRunning;\n if (connected !== this.lastConnectionState) {\n this.lastConnectionState = connected;\n this.setStateAsync(\"info.connection\", { val: connected, ack: true }).catch(() => {});\n }\n }\n\n /**\n * Delete ioBroker objects for devices no longer present and drop the same\n * devices from adapter-level maps. Called after the initial-discovery\n * window and every time the device list changes.\n *\n * Scope of \"stale\" today: cleanupDevices compares the ioBroker object tree\n * against the live device-manager registry \u2014 it deletes objects that\n * outlive their entry in `DeviceManager.devices`. In v2.0 that registry is\n * monotonically growing within a single adapter lifetime (entries only\n * leave via cache pruning across restarts), so this primarily catches\n * tree leftovers from a previous adapter version after upgrade. The\n * adapter-level `diagnosticsLastRun` map is also reaped so it can't outlive\n * its devices either.\n *\n * A future stale-pruning step that explicitly retires devices from the\n * device-manager registry should also drop the device from\n * `deviceManager.devices` and call `getDiagnostics().forget(deviceId)` for\n * each retired device \u2014 those reaping APIs come in with the pruning patch,\n * not before (Memory `feedback_kein_phantom_schema`).\n */\n /**\n * App-Version-Drift-Check gegen iTunes-Lookup.\n *\n * Govee's app2.govee.com-Endpoints rejecten manchmal sehr alte\n * User-Agent-Strings. Daily-Check fragt iTunes nach der aktuellen\n * iOS-App-Version + vergleicht mit `GOVEE_APP_VERSION`. Bei Drift > 2\n * minor versions: warn-Log + state `info.appVersionDrift` schreiben.\n *\n * Failures (5xx, network) werden silent debug-geloggt \u2014 kein User-Impact.\n */\n private async checkAppVersionDrift(): Promise<void> {\n try {\n const resp = await httpsRequest<{ resultCount?: number; results?: Array<{ version?: string }> }>({\n method: \"GET\",\n url: \"https://itunes.apple.com/lookup?bundleId=com.ihoment.GoVeeSensor\",\n headers: { \"User-Agent\": \"ioBroker.govee-smart\" },\n timeout: 10_000,\n });\n const liveVersion = resp.results?.[0]?.version;\n if (typeof liveVersion !== \"string\" || liveVersion.length === 0) {\n return;\n }\n const localParts = GOVEE_APP_VERSION.split(\".\").map(Number);\n const liveParts = liveVersion.split(\".\").map(Number);\n // Major + Minor vergleichen \u2014 patch-Differenz ist OK, Govee toleriert\n // bis ~2 minor.\n const localMajor = localParts[0] ?? 0;\n const localMinor = localParts[1] ?? 0;\n const liveMajor = liveParts[0] ?? 0;\n const liveMinor = liveParts[1] ?? 0;\n const liveTotal = liveMajor * 100 + liveMinor;\n const localTotal = localMajor * 100 + localMinor;\n const driftMinor = liveTotal - localTotal;\n const driftMessage =\n driftMinor === 0\n ? `current (live=${liveVersion}, local=${GOVEE_APP_VERSION})`\n : driftMinor <= 2\n ? `minor drift (live=${liveVersion}, local=${GOVEE_APP_VERSION})`\n : `STALE (live=${liveVersion}, local=${GOVEE_APP_VERSION}) \u2014 bump GOVEE_APP_VERSION`;\n await this.setStateAsync(\"info.appVersionDrift\", { val: driftMessage, ack: true }).catch(() => undefined);\n if (driftMinor > 2) {\n this.log.warn(\n `Govee app version drift: live ${liveVersion} vs local ${GOVEE_APP_VERSION} \u2014 undocumented endpoints may start failing. Run sync-govee-app-version.py + release a new adapter version.`,\n );\n } else {\n this.log.debug(`App version: ${driftMessage}`);\n }\n } catch (e) {\n this.log.debug(`App version check failed: ${errMessage(e)}`);\n }\n }\n\n private async reapStaleDevices(): Promise<void> {\n if (!this.stateManager || !this.deviceManager) {\n return;\n }\n const currentDevices = this.deviceManager.getDevices();\n await this.stateManager.cleanupDevices(currentDevices);\n\n // Diagnostics-buffer f\u00FCr entfernte Devices droppen damit Logs/Packets/\n // Responses l\u00E4ngst entfernter Ger\u00E4te nicht im Speicher bleiben.\n const liveDeviceIds = new Set(currentDevices.map(d => d.deviceId));\n this.deviceManager.getDiagnostics().pruneOrphans(liveDeviceIds);\n\n const liveKeys = new Set(currentDevices.map(d => `${d.sku}:${d.deviceId}`));\n for (const key of this.diagnosticsLastRun.keys()) {\n if (!liveKeys.has(key)) {\n this.diagnosticsLastRun.delete(key);\n }\n }\n }\n\n /**\n * Check if all configured channels are initialized and log ready message.\n * Called from MQTT onConnection callback and end of onReady.\n */\n private checkAllReady(): void {\n if (this.readyLogged) {\n return;\n }\n // Wait for first LAN scan (always active)\n if (!this.lanScanDone) {\n return;\n }\n // Wait for initial state creation to complete\n if (!this.statesReady) {\n return;\n }\n // Wait for Cloud init if configured\n if (this.cloudClient && !this.cloudInitDone) {\n return;\n }\n // Wait for MQTT connection if configured\n if (this.mqttClient && !this.mqttClient.connected) {\n return;\n }\n // H1 \u2014 Wait for OpenAPI-MQTT (Cloud-events for sensors/appliances)\n // wenn konfiguriert. Vorher fehlte das \u2014 Adapter war \"ready\" obwohl\n // Sensor-Events-Channel nicht connected war.\n if (this.openapiMqttClient && !this.openapiMqttClient.connected) {\n return;\n }\n // H2 \u2014 Wait for first App-API-Poll wenn ein Sensor-Device angemeldet\n // ist. App-API liefert die Sensor-Werte (H5179 Temp/Humidity/Battery).\n // Ohne diesen Check w\u00E4re der Adapter \"ready\" mit leeren Sensor-Werten\n // f\u00FCr ~2 Minuten.\n if (this.deviceManager?.hasDeviceNeedingAppApi() && !this.appApiInitialPollDone) {\n return;\n }\n this.readyLogged = true;\n this.logDeviceSummary();\n // Persist any learned changes from the initial load (e.g. resolveSegmentCount\n // collapsing Cloud's 15 to the real 10 on H70D1). One-shot on first ready;\n // subsequent mutations persist themselves (MQTT bumps, wizard, manual-mode).\n this.deviceManager?.saveDevicesToCache();\n }\n\n /**\n * Log final ready message with device/group/channel summary.\n */\n private logDeviceSummary(): void {\n if (!this.deviceManager) {\n return;\n }\n const all = this.deviceManager.getDevices();\n const devices = all.filter(d => d.sku !== \"BaseGroup\");\n const groups = all.filter(d => d.sku === \"BaseGroup\");\n\n // H5 \u2014 Ready-Log expliziter: Channel-Status (LAN+Cloud+MQTT+Cloud-events)\n // plus Devices-Online-Count plus Sensor-Initial-Poll-Marker. User soll\n // EINEN Blick auf den Log werfen und sehen was wirklich operational ist.\n const channels: string[] = [\"LAN\"];\n if (this.cloudWasConnected) {\n channels.push(\"Cloud\");\n }\n if (this.mqttClient?.connected) {\n channels.push(\"MQTT\");\n }\n if (this.openapiMqttClient?.connected) {\n channels.push(\"Cloud-events\");\n }\n\n // Device-summary mit Online-Count\n const lightDevices = devices.filter(d => d.type === \"devices.types.light\");\n const onlineDevices = devices.filter(d => d.state.online === true);\n const parts: string[] = [];\n if (devices.length > 0) {\n const onlineLights = lightDevices.filter(d => d.state.online === true).length;\n const totalLights = lightDevices.length;\n if (totalLights > 0) {\n parts.push(\n totalLights === onlineLights\n ? `${totalLights} light${totalLights > 1 ? \"s\" : \"\"} online`\n : `${totalLights} light${totalLights > 1 ? \"s\" : \"\"} (${onlineLights} online, ${totalLights - onlineLights} offline)`,\n );\n }\n const sensors = devices.length - lightDevices.length;\n if (sensors > 0) {\n const onlineSensors = onlineDevices.filter(d => d.type !== \"devices.types.light\").length;\n parts.push(`${sensors} sensor${sensors > 1 ? \"s\" : \"\"} (${onlineSensors} with data)`);\n }\n }\n if (groups.length > 0) {\n parts.push(`${groups.length} group${groups.length > 1 ? \"s\" : \"\"}`);\n }\n const summary = parts.length > 0 ? parts.join(\", \") : \"no devices found\";\n this.log.info(`Govee adapter ready \u2014 ${summary} \u2014 channels: ${channels.join(\"+\")}`);\n\n // Surface configured-but-not-connected channels with a concrete reason.\n // Truthful \u2014 never claim \"still pending\" when the channel actually failed.\n if (this.cloudClient && !this.cloudWasConnected) {\n const reason = this.cloudClient.getFailureReason();\n this.log.warn(reason ? `Cloud not connected: ${reason}` : \"Cloud not connected \u2014 see earlier errors\");\n }\n if (this.mqttClient && !this.mqttClient.connected) {\n const reason = this.mqttClient.getFailureReason();\n this.log.warn(reason ? `MQTT not connected: ${reason}` : \"MQTT not connected \u2014 see earlier errors\");\n }\n }\n\n /**\n * Load current state for all Cloud devices and populate state values.\n * Called once after initial Cloud device list load.\n */\n private async loadCloudStates(): Promise<void> {\n if (!this.cloudClient || !this.deviceManager || !this.stateManager) {\n return;\n }\n\n const devices = this.deviceManager.getDevices();\n // LAN-first: never overwrite LAN states with Cloud values\n const lanStateIds = new Set(getDefaultLanStates().map(s => s.id));\n let loaded = 0;\n\n for (const device of devices) {\n if (!device.channels.cloud || device.capabilities.length === 0) {\n continue;\n }\n\n try {\n const caps = await this.cloudClient.getDeviceState(device.sku, device.deviceId);\n const prefix = this.stateManager.devicePrefix(device);\n\n const writes: Promise<unknown>[] = [];\n for (const cap of caps) {\n const mapped = mapCloudStateValue(cap);\n if (!mapped) {\n continue;\n }\n // Skip LAN-covered states for LAN-capable devices\n if (device.lanIp && lanStateIds.has(mapped.stateId)) {\n continue;\n }\n const statePath = this.stateManager.resolveStatePath(prefix, mapped.stateId);\n // Fire-and-forget \u2014 States are created before loadCloudStates runs;\n // a rejection here means the state was deleted out-of-band and\n // can be safely ignored.\n writes.push(\n this.setStateAsync(statePath, {\n val: mapped.value,\n ack: true,\n }).catch(() => undefined),\n );\n }\n await Promise.all(writes);\n loaded++;\n } catch {\n this.log.debug(`Could not load Cloud state for ${device.name} (${device.sku})`);\n }\n }\n\n if (loaded > 0) {\n this.log.debug(`Cloud states loaded for ${loaded} devices`);\n }\n }\n\n /**\n * Apply a list of synthesized Cloud-state capabilities to a single\n * device \u2014 the App-API poll and OpenAPI-MQTT events both use this path\n * so their values flow through the same `mapCloudStateValue` pipeline\n * that polled Cloud states use.\n *\n * @param device Target Govee device\n * @param caps Capabilities to apply\n */\n private async applyCloudCapabilities(device: GoveeDevice, caps: CloudStateCapability[]): Promise<void> {\n if (!this.stateManager) {\n return;\n }\n const lanStateIds = new Set(getDefaultLanStates().map(s => s.id));\n const prefix = this.stateManager.devicePrefix(device);\n const planned = planCloudCapabilityWrites(caps, Boolean(device.lanIp), lanStateIds);\n // App-API and OpenAPI-MQTT deliver state IDs (battery, temperature,\n // humidity, lackWater, \u2026) that the Cloud-capability pipeline doesn't\n // declare for sensor/appliance SKUs \u2014 the state objects therefore\n // don't exist yet on first write. ensureSyntheticStateObject creates\n // them lazily with the right channel + role + unit.\n for (const mapped of planned) {\n await this.stateManager.ensureSyntheticStateObject(prefix, mapped.stateId);\n }\n const writes = planned.map(mapped => {\n const statePath = this.stateManager!.resolveStatePath(prefix, mapped.stateId);\n return this.setStateAsync(statePath, {\n val: mapped.value,\n ack: true,\n }).catch(() => undefined);\n });\n await Promise.all(writes);\n }\n\n /**\n * Find device for a state ID\n *\n * @param localId Local state ID without namespace prefix\n */\n private findDeviceForState(localId: string): GoveeDevice | undefined {\n if (!this.deviceManager || !this.stateManager) {\n return undefined;\n }\n\n for (const device of this.deviceManager.getDevices()) {\n const prefix = this.stateManager.devicePrefix(device);\n if (localId.startsWith(`${prefix}.`)) {\n return device;\n }\n }\n return undefined;\n }\n\n /**\n * Map state suffix to command name.\n *\n * Simple suffixes live in a lookup table, segment indices need regex\n * extraction because they're dynamic. The three music states all route\n * to the same \"music\" command \u2014 the handler reads sibling values.\n *\n * @param suffix State ID suffix (e.g. \"power\", \"brightness\")\n */\n private stateToCommand(suffix: string): string | null {\n const direct = STATE_TO_COMMAND[suffix];\n if (direct) {\n return direct;\n }\n const segColorMatch = /^segments\\.(\\d+)\\.color$/.exec(suffix);\n if (segColorMatch) {\n return `segmentColor:${segColorMatch[1]}`;\n }\n const segBrightMatch = /^segments\\.(\\d+)\\.brightness$/.exec(suffix);\n if (segBrightMatch) {\n return `segmentBrightness:${segBrightMatch[1]}`;\n }\n return null;\n }\n\n /**\n * Central entry point for manual-segment updates. Sets the device flags,\n * rebuilds the segment tree (which writes manual_mode + manual_list with\n * ack=true), and persists to cache. Both the user state-change handler\n * and the wizard route their final decisions here.\n *\n * @param device Target device\n * @param mode Whether manual mode should be active\n * @param indices Physical indices when mode=true, ignored otherwise\n */\n private async applyManualSegments(device: GoveeDevice, mode: boolean, indices?: number[]): Promise<void> {\n if (!this.stateManager) {\n return;\n }\n device.manualMode = mode;\n device.manualSegments = mode && Array.isArray(indices) && indices.length > 0 ? indices.slice() : undefined;\n await this.stateManager.createSegmentStates(device);\n this.deviceManager?.persistDeviceToCache(device);\n }\n\n /**\n * React to manual-segments state changes \u2014 parses list, forwards to\n * {@link applyManualSegments}. On parse error disables manual mode so the\n * rejected value doesn't survive in the state tree.\n *\n * @param device Target device\n * @param suffix State suffix (either \"segments.manual_mode\" or \"segments.manual_list\")\n * @param newValue Written value\n */\n private async handleManualSegmentsChange(device: GoveeDevice, suffix: string, newValue: unknown): Promise<void> {\n // Peer value that wasn't just written comes from the device object\n // (kept in sync via createSegmentStates), not a separate state read.\n const modeVal = suffix === \"segments.manual_mode\" ? Boolean(newValue) : device.manualMode === true;\n const listVal =\n suffix === \"segments.manual_list\"\n ? typeof newValue === \"string\"\n ? newValue\n : \"\"\n : Array.isArray(device.manualSegments)\n ? device.manualSegments.join(\",\")\n : \"\";\n\n if (!modeVal) {\n this.log.info(`${device.name}: manual segments disabled \u2014 strip treated as contiguous`);\n await this.applyManualSegments(device, false);\n return;\n }\n\n // Upper bound: cap at the real length if known, otherwise the protocol limit.\n // Real length can still grow via MQTT discovery, so SEGMENT_HARD_MAX is the\n // absolute safety net.\n const maxIndex =\n typeof device.segmentCount === \"number\" && device.segmentCount > 0 ? device.segmentCount - 1 : SEGMENT_HARD_MAX;\n const parsed = parseSegmentList(listVal, maxIndex);\n if (parsed.error) {\n this.log.warn(`${device.name}: manual_list invalid (${parsed.error}) \u2014 disabling manual mode`);\n await this.applyManualSegments(device, false);\n return;\n }\n\n this.log.debug(`${device.name}: manual segments active \u2014 ${parsed.indices.length} physical indices (${listVal})`);\n await this.applyManualSegments(device, true, parsed.indices);\n }\n\n // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Segment-Detection-Wizard \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Handle incoming sendTo messages (from jsonConfig).\n *\n * @param obj ioBroker message object\n */\n private onMessage(obj: ioBroker.Message): void {\n if (!obj?.command) {\n return;\n }\n // Never let a rejection bubble up from the event handler \u2014 the ioBroker\n // event emitter doesn't catch it, which would crash the adapter.\n this.handleMessage(obj).catch(e => {\n this.log.warn(`onMessage handler crashed for ${obj.command}: ${errMessage(e)}`);\n this.sendMessageResponse(obj, {\n error: e instanceof Error ? e.message : String(e),\n });\n });\n }\n\n private async handleMessage(obj: ioBroker.Message): Promise<void> {\n try {\n if (obj.command === \"getSegmentDevices\") {\n const devices = this.deviceManager?.getDevices() ?? [];\n const list = devices\n .filter(d => d.sku !== \"BaseGroup\" && d.state?.online === true && resolveSegmentCount(d) > 0)\n .map(d => {\n const count = resolveSegmentCount(d);\n return {\n value: this.deviceKeyFor(d),\n label: `${d.name} (${d.sku}, bisher ${count} Segmente)`,\n };\n });\n // selectSendTo expects the array directly, not wrapped in an object\n this.sendMessageResponse(obj, list);\n return;\n }\n if (obj.command === \"segmentWizard\") {\n const payload = (obj.message ?? {}) as {\n action?: string;\n device?: string;\n };\n const response = await this.runWizardStep(payload.action ?? \"\", payload.device ?? \"\");\n this.sendMessageResponse(obj, response);\n return;\n }\n if (obj.command === \"mqttAuth\") {\n const payload = (obj.message ?? {}) as { action?: string };\n const response = await this.runMqttAuthAction(payload.action ?? \"\");\n this.sendMessageResponse(obj, response);\n return;\n }\n } catch (e) {\n this.log.warn(`onMessage failed for ${obj.command}: ${errMessage(e)}`);\n this.sendMessageResponse(obj, {\n error: e instanceof Error ? e.message : String(e),\n });\n }\n }\n\n /**\n * Handle the `mqttAuth` onMessage commands triggered by the admin UI.\n *\n * Two actions:\n * - `test` \u2014 try a one-shot login with the current settings (incl. any code) and\n * report a single-line plaintext result the admin can show as a toast.\n * - `requestCode` \u2014 POST to /account/rest/account/v1/verification so Govee emails a fresh\n * code. 30-second in-memory throttle prevents double-click email spam.\n *\n * @param action Action name from the jsonConfig sendTo button\n */\n private async runMqttAuthAction(action: string): Promise<{ result: string }> {\n const config = this.config as unknown as AdapterConfig;\n if (!config.goveeEmail || !config.goveePassword) {\n return { result: \"Email + Passwort in den Adapter-Einstellungen n\u00F6tig.\" };\n }\n\n if (action === \"test\") {\n // One-shot client: don't reuse this.mqttClient \u2014 we don't want this\n // probe to take over its reconnect timer or auth-failure counter.\n const probe = new GoveeMqttClient(config.goveeEmail, config.goveePassword, this.log, this);\n probe.setVerificationCode(config.mqttVerificationCode ?? \"\");\n try {\n let connected = false;\n await probe.connect(\n () => {},\n isConnected => {\n connected = isConnected;\n },\n );\n probe.disconnect();\n return {\n result: connected\n ? \"Login erfolgreich \u2014 Echtzeit-Status-Updates aktiv.\"\n : \"Login angenommen, MQTT-Verbindung steht aber noch nicht \u2014 Adapter neu starten.\",\n };\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n if (/Verification required/i.test(msg)) {\n return {\n result:\n \"Govee verlangt 2-Faktor-Best\u00E4tigung. Bitte 'Verifizierungs-Code anfordern' klicken, Code aus der E-Mail eintragen und Speichern.\",\n };\n }\n if (/Verification code invalid/i.test(msg)) {\n return {\n result: \"2-Faktor-Code ung\u00FCltig oder abgelaufen \u2014 bitte einen neuen Code anfordern.\",\n };\n }\n if (/email not registered/i.test(msg)) {\n return { result: \"Diese E-Mail ist bei Govee nicht registriert.\" };\n }\n if (/Login failed/i.test(msg)) {\n return { result: \"Passwort wurde von Govee abgelehnt.\" };\n }\n if (/Rate limited/i.test(msg)) {\n return { result: \"Govee meldet Rate-Limit \u2014 bitte sp\u00E4ter erneut versuchen.\" };\n }\n if (/Account temporarily locked/i.test(msg)) {\n return { result: \"Govee-Account vor\u00FCbergehend gesperrt \u2014 Govee Home App \u00F6ffnen und Status pr\u00FCfen.\" };\n }\n return { result: `Login fehlgeschlagen: ${msg}` };\n }\n }\n\n if (action === \"requestCode\") {\n const now = Date.now();\n if (now - this.lastVerificationRequestMs < VERIFICATION_REQUEST_THROTTLE_MS) {\n const wait = Math.ceil((VERIFICATION_REQUEST_THROTTLE_MS - (now - this.lastVerificationRequestMs)) / 1000);\n return { result: `Bitte ${wait}s warten \u2014 gerade wurde schon ein Code angefordert.` };\n }\n this.lastVerificationRequestMs = now;\n const probe = new GoveeMqttClient(config.goveeEmail, config.goveePassword, this.log, this);\n try {\n await probe.requestVerificationCode();\n return { result: \"Code wurde an deine Govee-E-Mail-Adresse gesendet (Spam-Ordner pr\u00FCfen).\" };\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n return { result: `Govee hat den Code-Versand abgelehnt: ${msg}` };\n }\n }\n\n return { result: `Unbekannte Aktion '${action}'.` };\n }\n\n /**\n * Helper: clear `mqttVerificationCode` in adapter native after a successful\n * login or a 455-fail.\n *\n * Idempotent: liest erst den aktuellen Wert, schreibt nur wenn dirty.\n * Verhindert den Adapter-Restart der durch jeden\n * `extendForeignObjectAsync(system.adapter.X, native:...)`-Call ausgel\u00F6st\n * wird (Memory v2.1.3-Bug). Vorher gab es nach jedem 2FA-Login einen\n * unn\u00F6tigen Restart.\n */\n private async clearVerificationCodeSetting(): Promise<void> {\n try {\n const obj = await this.getForeignObjectAsync(`system.adapter.${this.namespace}`);\n const native = (obj?.native ?? {}) as Record<string, unknown>;\n // Skip wenn das Feld eh schon leer (oder undefined) \u2014 kein dirty write,\n // kein Restart-Trigger.\n if (typeof native.mqttVerificationCode !== \"string\" || native.mqttVerificationCode === \"\") {\n return;\n }\n await this.extendForeignObjectAsync(`system.adapter.${this.namespace}`, {\n native: { mqttVerificationCode: \"\" },\n });\n } catch (e) {\n this.log.warn(`Could not clear mqttVerificationCode: ${errMessage(e)}`);\n }\n }\n\n /**\n * Read persisted MQTT credentials from `info.mqttCredentials`. The\n * sensitive fields (bearer + cert + pass) are encrypted with the\n * system secret on save and decrypted here. Returns null if no\n * credentials are stored or the JSON is unparseable.\n *\n * State-based persistence (since v2.1.3) \u2014 writes to a state instead\n * of `system.adapter.X.native` so saving doesn't trigger an adapter\n * restart. The earlier native-based design caused an endless\n * login \u2192 save \u2192 restart \u2192 login loop.\n */\n private async loadPersistedCredsFromState(): Promise<PersistedMqttCredentials | null> {\n try {\n const s = await this.getStateAsync(\"info.mqttCredentials\");\n const raw = typeof s?.val === \"string\" ? s.val : \"\";\n if (!raw) {\n return null;\n }\n const obj = JSON.parse(raw) as {\n bearerToken?: unknown;\n iotEndpoint?: unknown;\n p12Cert?: unknown;\n p12Pass?: unknown;\n accountId?: unknown;\n accountTopic?: unknown;\n tokenExpiresAt?: unknown;\n };\n // typeof-Guards \u2014 JSON.parse liefert raw, this.decrypt() wirft auf\n // non-string-Input. Defensive: wenn das State-Blob durch ein Tool\n // editiert wurde und falsche Typen drin hat, returnen wir null.\n const safeStr = (v: unknown): string => (typeof v === \"string\" ? v : \"\");\n const bearerToken = this.decrypt(safeStr(obj.bearerToken));\n const p12Cert = this.decrypt(safeStr(obj.p12Cert));\n const p12Pass = this.decrypt(safeStr(obj.p12Pass));\n const iotEndpoint = safeStr(obj.iotEndpoint);\n const accountId = safeStr(obj.accountId);\n const accountTopic = safeStr(obj.accountTopic);\n const tokenExpiresAt = typeof obj.tokenExpiresAt === \"number\" ? obj.tokenExpiresAt : 0;\n if (!bearerToken || !iotEndpoint || !p12Cert || !accountId || !accountTopic || !tokenExpiresAt) {\n return null;\n }\n return { bearerToken, iotEndpoint, p12Cert, p12Pass, accountId, accountTopic, tokenExpiresAt };\n } catch {\n return null;\n }\n }\n\n /**\n * Persist freshly-issued MQTT credentials into `info.mqttCredentials`.\n * Sensitive fields go through `this.encrypt()` so the JSON blob is\n * useless without the system secret. State writes do NOT trigger an\n * adapter restart.\n *\n * @param creds The freshly-issued MQTT bundle from a successful login\n */\n private async persistCredsToState(creds: PersistedMqttCredentials): Promise<void> {\n const blob = JSON.stringify({\n bearerToken: this.encrypt(creds.bearerToken),\n iotEndpoint: creds.iotEndpoint,\n p12Cert: this.encrypt(creds.p12Cert),\n p12Pass: this.encrypt(creds.p12Pass),\n accountId: creds.accountId,\n accountTopic: creds.accountTopic,\n tokenExpiresAt: creds.tokenExpiresAt,\n });\n await this.setStateAsync(\"info.mqttCredentials\", { val: blob, ack: true });\n }\n\n /**\n * One-shot cleanup of legacy v2.1.0/v2.1.1/v2.1.2 plaintext credentials\n * sitting in `system.adapter.X.native`.\n *\n * Doppelte Idempotenz:\n * 1. State-Marker `info.legacyMqttCleaned=true` wird NACH erfolgreichem\n * Wipe gesetzt. Bei sp\u00E4teren Starts wird die Funktion \u00FCber den Marker\n * sofort verlassen \u2014 auch wenn das native irgendwie wieder dirty wird.\n * 2. Nur wenn KEINER der Legacy-Marker gesetzt ist UND das native dirty,\n * wird der einmalige extendForeignObjectAsync (Restart-Trigger) gemacht.\n *\n * Dieser Aufruf triggert genau einmal pro Adapter-Lifetime einen\n * Restart \u2014 das ist unvermeidlich, weil die Plaintext-Bytes weg m\u00FCssen.\n * Aber: nach erfolgreichem Cleanup bleibt der Marker, kein erneuter\n * Restart bei flaky writes.\n */\n private async cleanupLegacyMqttNativeOnce(): Promise<void> {\n try {\n // Marker-State zuerst pr\u00FCfen \u2014 wenn schon gecleant, sofort raus.\n const markerState = await this.getStateAsync(\"info.legacyMqttCleaned\");\n if (markerState?.val === true) {\n return;\n }\n const obj = await this.getForeignObjectAsync(`system.adapter.${this.namespace}`);\n const native = (obj?.native ?? {}) as Record<string, unknown>;\n const legacy = [\n \"mqttBearerToken\",\n \"mqttIotEndpoint\",\n \"mqttP12Cert\",\n \"mqttP12Pass\",\n \"mqttAccountId\",\n \"mqttAccountTopic\",\n \"mqttTokenExpiresAt\",\n ];\n const dirty = legacy.some(k => k in native && native[k] !== \"\" && native[k] !== 0);\n if (!dirty) {\n // Native ist clean (z.B. neue Installation oder schon migriert\n // ohne Marker). Marker setzen damit n\u00E4chster Start gar nicht erst\n // den Native-Read macht.\n await this.setStateAsync(\"info.legacyMqttCleaned\", { val: true, ack: true }).catch(() => undefined);\n return;\n }\n this.log.info(\"Removing legacy plaintext MQTT credentials from native (one-time migration)\");\n const wipe: Record<string, unknown> = {};\n for (const k of legacy) {\n wipe[k] = k === \"mqttTokenExpiresAt\" ? 0 : \"\";\n }\n await this.extendForeignObjectAsync(`system.adapter.${this.namespace}`, { native: wipe });\n // Marker setzen NACHDEM der Wipe erfolgreich war \u2014 bei einem etwaigen\n // Crash zwischen extend und setState l\u00E4uft der Cleanup beim n\u00E4chsten\n // Start nochmal (idempotent: native ist dann eh schon clean).\n await this.setStateAsync(\"info.legacyMqttCleaned\", { val: true, ack: true }).catch(() => undefined);\n } catch (e) {\n this.log.debug(`legacy MQTT cleanup skipped: ${errMessage(e)}`);\n }\n }\n\n private sendMessageResponse(obj: ioBroker.Message, data: unknown): void {\n if (obj.callback && obj.from) {\n this.sendTo(obj.from, obj.command, data as Record<string, unknown>, obj.callback);\n }\n }\n\n /**\n * Stable device key for wizard session tracking.\n *\n * @param device Target device\n */\n private deviceKeyFor(device: GoveeDevice): string {\n return `${device.sku}:${device.deviceId}`;\n }\n\n private findDeviceByKey(key: string): GoveeDevice | undefined {\n const devices = this.deviceManager?.getDevices() ?? [];\n return devices.find(d => this.deviceKeyFor(d) === key);\n }\n\n /** Construct the host object passed into SegmentWizard. */\n private buildWizardHost(): WizardHost {\n return {\n log: this.log,\n getState: id => this.getStateAsync(id),\n sendCommand: async (device, command, value) => {\n await this.deviceManager?.sendCommand(device, command, value);\n },\n flashSegmentAtomic: (device, idx) => {\n if (!device.lanIp || !this.lanClient) {\n return Promise.resolve(false);\n }\n this.lanClient.flashSingleSegment(device.lanIp, idx);\n return Promise.resolve(true);\n },\n restoreStripAtomic: (device, total, color, brightness) => {\n if (!device.lanIp || !this.lanClient) {\n return Promise.resolve(false);\n }\n const r = (color >> 16) & 0xff;\n const g = (color >> 8) & 0xff;\n const b = color & 0xff;\n this.lanClient.restoreAllSegments(device.lanIp, total, r, g, b, brightness);\n return Promise.resolve(true);\n },\n findDevice: key => this.findDeviceByKey(key),\n namespace: this.namespace,\n devicePrefix: device => this.stateManager?.devicePrefix(device) ?? \"\",\n setTimeout: (cb, ms) => this.setTimeout(cb, ms),\n clearTimeout: h => this.clearTimeout(h as ioBroker.Timeout),\n applyWizardResult: (device, result) => this.applyWizardResult(device, result),\n getLanguage: () => this.adminLanguage,\n };\n }\n\n /**\n * Apply a finished wizard's measurement: set the real segment count, then\n * route through {@link applyManualSegments} so the same state-tree rebuild\n * and cache-persist path runs for both wizard results and user edits.\n *\n * @param device Target device\n * @param result Wizard's measurement\n */\n private async applyWizardResult(device: GoveeDevice, result: WizardResult): Promise<void> {\n device.segmentCount = result.segmentCount;\n if (result.hasGaps) {\n const parsed = parseSegmentList(result.manualList, result.segmentCount - 1);\n await this.applyManualSegments(device, true, parsed.error ? undefined : parsed.indices);\n } else {\n await this.applyManualSegments(device, false);\n }\n this.log.debug(\n `applyWizardResult: ${device.sku} \u2192 segmentCount=${result.segmentCount}, ` +\n `manualMode=${device.manualMode}, list=\"${result.manualList}\"`,\n );\n }\n\n /**\n * Execute one wizard step (start/yes/no/abort). Delegates to\n * {@link SegmentWizard} \u2014 see `lib/segment-wizard.ts`.\n *\n * @param action \"start\" | \"yes\" | \"no\" | \"abort\"\n * @param deviceKey device identifier (only required for \"start\")\n */\n private async runWizardStep(action: string, deviceKey: string): Promise<Record<string, unknown>> {\n if (!this.segmentWizard) {\n this.segmentWizard = new SegmentWizard(this.buildWizardHost());\n }\n const response = await this.segmentWizard.runStep(action, deviceKey);\n // Mirror the current wizard status into a plain state so admin's\n // `type: \"state\"` component can show it live via state subscription.\n const statusText = this.segmentWizard.getStatusText();\n await this.setStateAsync(\"info.wizardStatus\", {\n val: statusText,\n ack: true,\n });\n return response;\n }\n\n /**\n * Save current device state as a local snapshot.\n *\n * @param device Target device\n * @param name Snapshot name\n */\n private async handleSnapshotSave(device: GoveeDevice, name: string): Promise<void> {\n if (!this.localSnapshots || !this.stateManager) {\n return;\n }\n\n const prefix = this.stateManager.devicePrefix(device);\n const ns = this.namespace;\n\n // Read device-level state in parallel\n const [powerState, brightState, colorState, ctState] = await Promise.all([\n this.getStateAsync(`${ns}.${prefix}.control.power`),\n this.getStateAsync(`${ns}.${prefix}.control.brightness`),\n this.getStateAsync(`${ns}.${prefix}.control.colorRgb`),\n this.getStateAsync(`${ns}.${prefix}.control.colorTemperature`),\n ]);\n\n // Read per-segment states in parallel \u2014 20 segments \u00D7 2 reads used to run\n // sequentially (~80ms), parallel completes in a single round-trip.\n let segments: SnapshotSegment[] | undefined;\n const segCount = device.segmentCount ?? 0;\n if (segCount > 0) {\n const segReads: Promise<[ioBroker.State | null | undefined, ioBroker.State | null | undefined]>[] = [];\n for (let i = 0; i < segCount; i++) {\n segReads.push(\n Promise.all([\n this.getStateAsync(`${ns}.${prefix}.segments.${i}.color`),\n this.getStateAsync(`${ns}.${prefix}.segments.${i}.brightness`),\n ]),\n );\n }\n const segResults = await Promise.all(segReads);\n segments = segResults.map(([segColor, segBright]) => ({\n color: typeof segColor?.val === \"string\" ? segColor.val : \"#000000\",\n brightness: typeof segBright?.val === \"number\" ? segBright.val : 100,\n }));\n }\n\n const snapshot: LocalSnapshot = {\n name,\n power: powerState?.val === true,\n brightness: typeof brightState?.val === \"number\" ? brightState.val : 0,\n colorRgb: typeof colorState?.val === \"string\" ? colorState.val : \"#000000\",\n colorTemperature: typeof ctState?.val === \"number\" ? ctState.val : 0,\n segments,\n savedAt: Date.now(),\n };\n\n this.localSnapshots.saveSnapshot(device.sku, device.deviceId, snapshot);\n this.log.info(`Local snapshot saved: \"${name}\" for ${device.name}`);\n\n // Targeted refresh \u2014 only this device's snapshot_local dropdown changed.\n this.refreshDeviceStates(device, this.deviceManager!.getDevices());\n }\n\n /**\n * Restore a local snapshot by index.\n *\n * @param device Target device\n * @param val Dropdown index value\n */\n private async handleSnapshotRestore(device: GoveeDevice, val: ioBroker.StateValue): Promise<void> {\n if (!this.localSnapshots || !this.deviceManager) {\n return;\n }\n\n const idx = parseInt(String(val), 10);\n if (idx < 1) {\n return;\n }\n\n const snaps = this.localSnapshots.getSnapshots(device.sku, device.deviceId);\n const snap = snaps[idx - 1];\n if (!snap) {\n this.log.warn(`Local snapshot index ${idx} not found for ${device.name}`);\n return;\n }\n\n this.log.info(`Restoring local snapshot \"${snap.name}\" for ${device.name}`);\n\n // Send each state via LAN \u2192 Cloud routing\n await this.deviceManager.sendCommand(device, \"power\", snap.power);\n if (snap.power) {\n await this.deviceManager.sendCommand(device, \"brightness\", snap.brightness);\n if (snap.colorTemperature > 0) {\n await this.deviceManager.sendCommand(device, \"colorTemperature\", snap.colorTemperature);\n } else {\n await this.deviceManager.sendCommand(device, \"colorRgb\", snap.colorRgb);\n }\n\n // Restore per-segment states via ptReal\n if (snap.segments && snap.segments.length > 0) {\n for (let i = 0; i < snap.segments.length; i++) {\n const seg = snap.segments[i];\n await this.deviceManager.sendCommand(device, `segmentColor:${i}`, seg.color);\n await this.deviceManager.sendCommand(device, `segmentBrightness:${i}`, seg.brightness);\n }\n }\n }\n }\n\n /**\n * Delete a local snapshot by name.\n *\n * @param device Target device\n * @param name Snapshot name to delete\n */\n private handleSnapshotDelete(device: GoveeDevice, name: string): void {\n if (!this.localSnapshots) {\n return;\n }\n\n if (this.localSnapshots.deleteSnapshot(device.sku, device.deviceId, name)) {\n this.log.info(`Local snapshot deleted: \"${name}\" for ${device.name}`);\n // Targeted refresh \u2014 only this device's snapshot_local dropdown changed.\n this.refreshDeviceStates(device, this.deviceManager!.getDevices());\n } else {\n this.log.warn(`Local snapshot \"${name}\" not found for ${device.name}`);\n }\n }\n\n /** Dropdowns whose value is a mode-selection \u2014 reset to \"---\" (0) when the mode stops. */\n private static readonly MODE_DROPDOWNS = [\n \"scenes.light_scene\",\n \"scenes.diy_scene\",\n \"snapshots.snapshot_cloud\",\n \"snapshots.snapshot_local\",\n \"music.music_mode\",\n ];\n\n /** Map command \u2192 its own dropdown path (excluded from reset when that mode is the one that was just activated). */\n private static readonly COMMAND_DROPDOWN: Record<string, string> = {\n lightScene: \"scenes.light_scene\",\n diyScene: \"scenes.diy_scene\",\n snapshot: \"snapshots.snapshot_cloud\",\n snapshotLocal: \"snapshots.snapshot_local\",\n music: \"music.music_mode\",\n colorRgb: \"\",\n colorTemperature: \"\",\n };\n\n /**\n * Diagnostics-Export-Button-Handler. Throttled auf 2s pro Device damit\n * wiederholte/Skript-Trigger keinen Burst von JSON-Serialisierungen\n * erzeugen.\n *\n * @param device Govee device\n * @param prefix Device state prefix\n * @param triggerStateId The state-id that triggered the export (so we can ack)\n */\n private async handleDiagnosticsExport(device: GoveeDevice, prefix: string, triggerStateId: string): Promise<void> {\n if (!this.deviceManager) {\n return;\n }\n const deviceKey = `${device.sku}:${device.deviceId}`;\n const now = Date.now();\n const last = this.diagnosticsLastRun.get(deviceKey) ?? 0;\n if (now - last < 2000) {\n this.log.debug(`Diagnostics export throttled for ${device.name} \u2014 last run ${now - last}ms ago`);\n await this.setStateAsync(triggerStateId, { val: false, ack: true });\n return;\n }\n this.diagnosticsLastRun.set(deviceKey, now);\n const diag = this.deviceManager.generateDiagnostics(device, this.version ?? \"unknown\");\n const resultId = `${this.namespace}.${prefix}.diag.result`;\n await this.setStateAsync(resultId, {\n val: JSON.stringify(diag, null, 2),\n ack: true,\n });\n await this.setStateAsync(triggerStateId, { val: false, ack: true });\n this.log.info(`Diagnostics exported for ${device.name} (${device.sku})`);\n }\n\n /**\n * Generischer Capability-Routing-Pfad f\u00FCr States die nicht im STATE_TO_COMMAND\n * Mapping sind. Liest `native.capabilityType`/`capabilityInstance` aus dem\n * State-Object und schickt das via Cloud-API.\n *\n * @param device Target device\n * @param id Full state-id (f\u00FCr ack)\n * @param stateSuffix State-Pfad innerhalb Device (f\u00FCr debug-log)\n * @param val Value zum Senden\n */\n private async handleGenericCapabilityCommand(\n device: GoveeDevice,\n id: string,\n stateSuffix: string,\n val: ioBroker.StateValue,\n ): Promise<void> {\n if (!this.deviceManager) {\n return;\n }\n const obj = await this.getObjectAsync(id);\n const capType = obj?.native?.capabilityType;\n const capInstance = obj?.native?.capabilityInstance;\n if (typeof capType === \"string\" && typeof capInstance === \"string\") {\n try {\n await this.deviceManager.sendCapabilityCommand(device, capType, capInstance, val);\n await this.setStateAsync(id, { val, ack: true });\n } catch (err) {\n this.log.warn(`Command failed for ${device.name}: ${errMessage(err)}`);\n }\n } else {\n this.log.debug(`Unknown writable state: ${stateSuffix}`);\n }\n }\n\n /**\n * Reset related dropdown states when switching between scenes/snapshots/colors.\n * Each mode-switch resets all OTHER mode dropdowns to \"---\" (0).\n *\n * @param prefix Device state prefix\n * @param activeCommand The command that was just executed\n */\n private async resetRelatedDropdowns(prefix: string, activeCommand: string): Promise<void> {\n if (!(activeCommand in GoveeAdapter.COMMAND_DROPDOWN)) {\n return;\n }\n const ownDropdown = GoveeAdapter.COMMAND_DROPDOWN[activeCommand];\n await this.resetModeDropdowns(prefix, ownDropdown);\n }\n\n /**\n * Reset every mode dropdown except `keep` (empty = reset all). Used both for\n * mode-switches (keep the new mode's own dropdown) and for power-off\n * (reset everything \u2014 a device that's off has no active mode).\n *\n * @param prefix Device state prefix\n * @param keep Dropdown path to leave untouched (e.g. \"music.music_mode\"), or \"\" to reset all\n */\n private async resetModeDropdowns(prefix: string, keep: string): Promise<void> {\n await Promise.all(\n GoveeAdapter.MODE_DROPDOWNS.filter(d => d !== keep).map(async dropdown => {\n const stateId = `${this.namespace}.${prefix}.${dropdown}`;\n const current = await this.getStateAsync(stateId);\n if (current?.val && current.val !== \"0\" && current.val !== 0) {\n await this.setStateAsync(stateId, { val: \"0\", ack: true });\n }\n }),\n );\n }\n}\n\nif (require.main !== module) {\n module.exports = (options: Partial<utils.AdapterOptions> | undefined) => new GoveeAdapter(options);\n} else {\n (() => new GoveeAdapter())();\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;AAAA,YAAuB;AACvB,+BAKO;AACP,6BAAkD;AAClD,4BAAqE;AACrE,8BAA+B;AAC/B,gCAAiC;AACjC,8BAA+B;AAC/B,+BAAgC;AAChC,uCAAuC;AACvC,6BAA6E;AAC7E,yBAAoD;AACpD,0BAA4B;AAC5B,4BAAkF;AAClF,uBAAyB;AACzB,2BAA6B;AAC7B,mBAaO;AACP,8BAAqF;AACrF,6BAAkC;AAClC,yBAA6B;AAO7B,MAAM,cAAc,EAAE,WAAW,GAAG,QAAQ,IAAK;AAQjD,MAAM,mCAAmC;AAOzC,MAAM,mBAAqD;AAAA,EACzD,iBAAiB;AAAA,EACjB,sBAAsB;AAAA,EACtB,oBAAoB;AAAA,EACpB,4BAA4B;AAAA,EAC5B,iBAAiB;AAAA,EACjB,2BAA2B;AAAA,EAC3B,sBAAsB;AAAA,EACtB,oBAAoB;AAAA,EACpB,sBAAsB;AAAA,EACtB,oBAAoB;AAAA,EACpB,2BAA2B;AAAA,EAC3B,0BAA0B;AAAA,EAC1B,4BAA4B;AAAA,EAC5B,oBAAoB;AACtB;AAEA,MAAM,qBAAqB,MAAM,QAAQ;AAAA,EAC/B,gBAAsC;AAAA,EACtC,eAAoC;AAAA,EACpC,YAAmC;AAAA,EACnC,aAAqC;AAAA,EACrC,oBAAmD;AAAA,EACnD,cAAuC;AAAA,EACvC,cAAkC;AAAA;AAAA,EAElC;AAAA;AAAA;AAAA;AAAA;AAAA,EAKA;AAAA;AAAA,EAEA;AAAA;AAAA;AAAA;AAAA;AAAA,EAKA,sBAAsC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMtC,cAAc;AAAA;AAAA,EAEd,cAAc;AAAA;AAAA,EAEd,gBAAgB;AAAA;AAAA,EAEhB,wBAAwB;AAAA;AAAA,EAExB,cAAc;AAAA;AAAA,EAEd,oBAAoB;AAAA;AAAA,EAEpB;AAAA;AAAA,EAEA,WAA4B;AAAA,EAC5B,iBAA4C;AAAA,EAC5C,qBAAsC,CAAC;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAoC;AAAA,EACpC,gBAAsC;AAAA,EACtC,4BAAgE;AAAA,EAChE,2BAA0D;AAAA;AAAA,EAE1D,qBAAqB,oBAAI,IAAoB;AAAA;AAAA,EAE7C,gBAAgB;AAAA;AAAA,EAEhB,4BAA4B;AAAA;AAAA,EAG7B,YAAY,UAAyC,CAAC,GAAG;AAC9D,UAAM,EAAE,GAAG,SAAS,MAAM,cAAc,CAAC;AAGzC,SAAK;AAAA,MAAG;AAAA,MAAS,MACf,KAAK,QAAQ,EAAE;AAAA,QAAM,OAAE;AA3I7B;AA4IQ,sBAAK,IAAI,MAAM,oBAAoB,aAAa,SAAS,OAAE,UAAF,YAAW,EAAE,UAAW,OAAO,CAAC,CAAC,EAAE;AAAA;AAAA,MAC9F;AAAA,IACF;AACA,SAAK;AAAA,MAAG;AAAA,MAAe,CAAC,IAAI,UAC1B,KAAK,cAAc,IAAI,KAAK,EAAE,MAAM,OAAK,KAAK,IAAI,KAAK,6BAA6B,EAAE,SAAK,yBAAW,CAAC,CAAC,EAAE,CAAC;AAAA,IAC7G;AACA,SAAK,GAAG,WAAW,SAAO,KAAK,UAAU,GAAG,CAAC;AAC7C,SAAK,GAAG,UAAU,cAAY,KAAK,SAAS,QAAQ,CAAC;AAKrD,SAAK,4BAA4B,CAAC,WAAoB;AAxJ1D;AAyJM,WAAK,IAAI;AAAA,QACP,wBAAwB,kBAAkB,SAAS,YAAO,UAAP,YAAgB,OAAO,UAAW,OAAO,MAAM,CAAC;AAAA,MACrG;AAAA,IACF;AACA,SAAK,2BAA2B,CAAC,QAAe;AA7JpD;AA8JM,WAAK,IAAI,MAAM,wBAAuB,SAAI,UAAJ,YAAa,IAAI,OAAO,EAAE;AAAA,IAClE;AACA,YAAQ,GAAG,sBAAsB,KAAK,yBAAyB;AAC/D,YAAQ,GAAG,qBAAqB,KAAK,wBAAwB;AAAA,EAC/D;AAAA;AAAA,EAGA,MAAc,UAAyB;AArKzC;AAsKI,UAAM,SAAS,KAAK;AAKpB,UAAM,KAAK,cAAc,mBAAmB,EAAE,KAAK,OAAO,KAAK,KAAK,CAAC;AACrE,UAAM,KAAK,cAAc,sBAAsB,EAAE,KAAK,OAAO,KAAK,KAAK,CAAC;AACxE,UAAM,KAAK,cAAc,uBAAuB,EAAE,KAAK,OAAO,KAAK,KAAK,CAAC;AACzE,UAAM,KAAK,cAAc,6BAA6B;AAAA,MACpD,KAAK;AAAA,MACL,KAAK;AAAA,IACP,CAAC;AACD,UAAM,KAAK,cAAc,2BAA2B;AAAA,MAClD,KAAK;AAAA,MACL,KAAK;AAAA,IACP,CAAC;AAGD,QAAI;AACF,YAAM,UAAU,MAAM,KAAK,sBAAsB,eAAe;AAChE,YAAM,QAAQ,wCAAS,WAAT,mBAAuD;AACrE,UAAI,OAAO,SAAS,YAAY,KAAK,SAAS,GAAG;AAC/C,aAAK,gBAAgB;AAAA,MACvB;AAAA,IACF,QAAQ;AAAA,IAER;AACA,UAAM,KAAK,cAAc,qBAAqB;AAAA,MAC5C,SAAK,sCAAe,KAAK,aAAa;AAAA,MACtC,KAAK;AAAA,IACP,CAAC;AAED,SAAK,eAAe,IAAI,kCAAa,IAAI;AAEzC,UAAM,KAAK,aAAa,wBAAwB,KAAK;AACrD,SAAK,gBAAgB,IAAI,oCAAc,KAAK,KAAK,IAAI;AACrD,UAAM,UAAU,MAAM,2BAA2B,IAAI;AAKrD,mDAAmB;AAAA,MACjB,cAAc,OAAO,uBAAuB;AAAA,MAC5C,KAAK,KAAK;AAAA,IACZ,CAAC;AACD,SAAK,WAAW,IAAI,0BAAS,SAAS,KAAK,GAAG;AAC9C,SAAK,iBAAiB,IAAI,0CAAmB,SAAS,KAAK,GAAG;AAC9D,SAAK,cAAc,YAAY,KAAK,QAAQ;AAG5C,UAAM,YAAY,IAAI,uCAAe;AACrC,cAAU,SAAS,OAAO,UAAU;AACpC,SAAK,cAAc,aAAa,SAAS;AAEzC,SAAK,cAAc;AAAA,MACjB,CAAC,QAAQ,UAAU,KAAK,oBAAoB,QAAQ,KAAK;AAAA,MACzD,aAAW,KAAK,oBAAoB,OAAO;AAAA,IAC7C;AAGA,SAAK,cAAc,iBAAiB,CAAC,QAAQ,OAAO;AAClD,YAAM,SAAS,KAAK,aAAc,aAAa,MAAM;AACrD,WAAK,cAAc,GAAG,MAAM,YAAY,EAAE,KAAK,IAAI,KAAK,KAAK,CAAC,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IAChF;AAGA,SAAK,cAAc,uBAAuB,CAAC,QAAQ,UAAU;AAC3D,YAAM,SAAS,KAAK,aAAc,aAAa,MAAM;AACrD,iBAAW,OAAO,MAAM,UAAU;AAChC,YAAI,MAAM,UAAU,QAAW;AAC7B,gBAAM,UAAM,0BAAY,MAAM,KAAK;AACnC,eAAK,cAAc,GAAG,MAAM,aAAa,GAAG,UAAU;AAAA,YACpD,KAAK;AAAA,YACL,KAAK;AAAA,UACP,CAAC,EAAE,MAAM,MAAM;AAAA,UAAC,CAAC;AAAA,QACnB;AACA,YAAI,MAAM,eAAe,QAAW;AAClC,eAAK,cAAc,GAAG,MAAM,aAAa,GAAG,eAAe;AAAA,YACzD,KAAK,MAAM;AAAA,YACX,KAAK;AAAA,UACP,CAAC,EAAE,MAAM,MAAM;AAAA,UAAC,CAAC;AAAA,QACnB;AAAA,MACF;AAAA,IACF;AAGA,SAAK,cAAc,sBAAsB,CAAC,QAAQ,aAAa;AAC7D,YAAM,SAAS,KAAK,aAAc,aAAa,MAAM;AACrD,iBAAW,OAAO,UAAU;AAC1B,aAAK,cAAc,GAAG,MAAM,aAAa,IAAI,KAAK,UAAU;AAAA,UAC1D,SAAK,uBAAS,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAAA,UACjC,KAAK;AAAA,QACP,CAAC,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AACjB,aAAK,cAAc,GAAG,MAAM,aAAa,IAAI,KAAK,eAAe;AAAA,UAC/D,KAAK,IAAI;AAAA,UACT,KAAK;AAAA,QACP,CAAC,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,MACnB;AAAA,IACF;AAIA,SAAK,cAAc,sBAAsB,YAAU;AACjD,UAAI,CAAC,KAAK,cAAc;AACtB;AAAA,MACF;AACA,WAAK,aAAa,oBAAoB,MAAM,EAAE,MAAM,OAAK;AACvD,aAAK,IAAI,KAAK,sCAAsC,OAAO,IAAI,4BAAwB,yBAAW,CAAC,CAAC,EAAE;AAAA,MACxG,CAAC;AAAA,IACH;AAGA,UAAM,gBAA0B,CAAC,KAAK;AACtC,QAAI,OAAO,QAAQ;AACjB,oBAAc,KAAK,OAAO;AAAA,IAC5B;AACA,QAAI,OAAO,cAAc,OAAO,eAAe;AAC7C,oBAAc,KAAK,MAAM;AAAA,IAC3B;AACA,SAAK,IAAI,KAAK,aAAa,cAAc,KAAK,IAAI,CAAC,GAAG;AAGtD,SAAK,YAAY,IAAI,uCAAe,KAAK,KAAK,IAAI;AAClD,SAAK,cAAc,aAAa,KAAK,SAAS;AAE9C,SAAK,UAAU;AAAA,MACb,eAAa;AApSnB,YAAAA;AAqSQ,aAAK,cAAe,mBAAmB,SAAS;AAIhD,YAAI,GAACA,MAAA,KAAK,eAAL,gBAAAA,IAAiB,YAAW;AAC/B,eAAK,UAAW,cAAc,UAAU,EAAE;AAAA,QAC5C;AAAA,MACF;AAAA,MACA,CAAC,UAAU,WAAW;AACpB,aAAK,cAAe,gBAAgB,UAAU,MAAM;AAAA,MACtD;AAAA,MACA;AAAA,MACA,OAAO,oBAAoB;AAAA,IAC7B;AAGA,SAAK,eAAe,KAAK,WAAW,MAAM;AACxC,WAAK,cAAc;AACnB,WAAK,cAAc;AAAA,IACrB,GAAG,GAAK;AAIR,QAAI,OAAO,cAAc,OAAO,eAAe;AAC7C,WAAK,aAAa,IAAI,yCAAgB,OAAO,YAAY,OAAO,eAAe,KAAK,KAAK,IAAI;AAI7F,WAAK,WAAW,cAAc,CAAC,UAAU,OAAO,QAAQ;AAjU9D,YAAAA;AAkUQ,SAAAA,MAAA,KAAK,kBAAL,gBAAAA,IAAoB,iBAAiB,cAAc,UAAU,OAAO;AAAA,MACtE,CAAC;AAID,WAAK,WAAW,qBAAoB,YAAO,yBAAP,YAA+B,EAAE;AACrE,WAAK,WAAW,0BAA0B,MAAM;AAC9C,aAAK,6BAA6B,EAAE,MAAM,OAAK;AAC7C,eAAK,IAAI,KAAK,6CAAyC,yBAAW,CAAC,CAAC,EAAE;AAAA,QACxE,CAAC;AAAA,MACH,CAAC;AACD,WAAK,WAAW,wBAAwB,YAAU;AAIhD,YAAI,WAAW,UAAU;AACvB,eAAK,6BAA6B,EAAE,MAAM,MAAM;AAAA,UAAC,CAAC;AAAA,QACpD;AAAA,MACF,CAAC;AAUD,YAAM,KAAK,4BAA4B;AACvC,YAAM,cAAc,MAAM,KAAK,4BAA4B;AAC3D,UAAI,aAAa;AACf,aAAK,WAAW,wBAAwB,WAAW;AAAA,MACrD;AACA,WAAK,WAAW,wBAAwB,WAAS;AAC/C,aAAK,oBAAoB,KAAK,EAAE,MAAM,OAAK;AACzC,eAAK,IAAI,KAAK,2CAAuC,yBAAW,CAAC,CAAC,EAAE;AAAA,QACtE,CAAC;AAAA,MACH,CAAC;AAED,YAAM,KAAK,WAAW;AAAA,QACpB,YAAU,KAAK,cAAe,iBAAiB,MAAM;AAAA,QACrD,eAAa;AACX,eAAK,cAAc,sBAAsB;AAAA,YACvC,KAAK;AAAA,YACL,KAAK;AAAA,UACP,CAAC,EAAE,MAAM,MAAM;AAAA,UAAC,CAAC;AACjB,cAAI,WAAW;AACb,iBAAK,cAAc;AAAA,UACrB;AACA,eAAK,sBAAsB;AAAA,QAC7B;AAAA;AAAA;AAAA,QAGA,WAAS,UAAU,eAAe,KAAK;AAAA,MACzC;AAAA,IACF;AAGA,UAAM,WAAW,KAAK,cAAc,cAAc;AAElD,QAAI,OAAO,QAAQ;AACjB,WAAK,cAAc,IAAI,2CAAiB,OAAO,QAAQ,KAAK,GAAG;AAG/D,WAAK,YAAY,gBAAgB,CAAC,UAAU,UAAU,SAAS;AAlYrE,YAAAA;AAmYQ,SAAAA,MAAA,KAAK,kBAAL,gBAAAA,IAAoB,iBAAiB,eAAe,UAAU,UAAU;AAAA,MAC1E,CAAC;AACD,WAAK,cAAc,eAAe,KAAK,WAAW;AAKlD,WAAK,cAAc,uBAAuB,CAAC,QAAQ,SAAS;AAC1D,aAAK,uBAAuB,QAAQ,IAAI,EAAE;AAAA,UAAM,OAC9C,KAAK,IAAI,KAAK,qCAAqC,OAAO,GAAG,SAAK,yBAAW,CAAC,CAAC,EAAE;AAAA,QACnF;AAAA,MACF,CAAC;AAED,WAAK,cAAc,IAAI,gCAAY,KAAK,KAAK,MAAM,YAAY,WAAW,YAAY,MAAM;AAC5F,WAAK,YAAY,MAAM;AACvB,WAAK,cAAc,eAAe,KAAK,WAAW;AAMlD,WAAK,oBAAoB,IAAI,wDAAuB,OAAO,QAAQ,KAAK,KAAK,IAAI;AACjF,WAAK,kBAAkB;AAAA,QACrB,WAAM;AA1Zd,cAAAA;AA0ZiB,kBAAAA,MAAA,KAAK,kBAAL,gBAAAA,IAAoB,mBAAmB;AAAA;AAAA,QAChD,eAAa;AACX,eAAK,cAAc,6BAA6B;AAAA,YAC9C,KAAK;AAAA,YACL,KAAK;AAAA,UACP,CAAC,EAAE,MAAM,MAAM;AAAA,UAAC,CAAC;AAAA,QACnB;AAAA,MACF;AAKA,YAAM,oBAAoB,MAAY;AAta5C,YAAAA;AAuaQ,SAAAA,MAAA,KAAK,kBAAL,gBAAAA,IACI,aACD,KAAK,MAAM;AAGV,cAAI,CAAC,KAAK,uBAAuB;AAC/B,iBAAK,wBAAwB;AAC7B,iBAAK,cAAc;AAAA,UACrB;AAAA,QACF,GACC,MAAM,OAAK,KAAK,IAAI,MAAM,0BAAsB,yBAAW,CAAC,CAAC,EAAE;AAAA,MACpE;AACA,WAAK,kBAAkB,KAAK,YAAY,mBAAmB,gDAAwB;AAKnF,WAAK,qBAAqB,KAAK,WAAW,mBAAmB,gDAAwB;AAErF,UAAI,CAAC,UAAU;AAGb,cAAM,SAAS,MAAM,KAAK,qBAAqB;AAC/C,aAAK,oBAAoB,OAAO;AAChC,aAAK,iBAAiB,EAAE,aAAa,OAAO,EAAE;AAC9C,aAAK,cAAc,uBAAuB;AAAA,UACxC,KAAK,OAAO;AAAA,UACZ,KAAK;AAAA,QACP,CAAC,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AACjB,mBAAK,iBAAL,mBAAmB,mBAAmB,OAAO,IAAI,MAAM,MAAM;AAAA,QAAC;AAE9D,YAAI,OAAO,IAAI;AACb,gBAAM,KAAK,gBAAgB;AAAA,QAC7B,OAAO;AACL,eAAK,mBAAmB,MAAM;AAAA,QAChC;AAAA,MACF,OAAO;AACL,aAAK,IAAI,KAAK,uDAAkD;AAChE,aAAK,oBAAoB;AACzB,aAAK,iBAAiB,EAAE,aAAa,IAAI;AACzC,aAAK,cAAc,uBAAuB;AAAA,UACxC,KAAK;AAAA,UACL,KAAK;AAAA,QACP,CAAC,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AACjB,mBAAK,iBAAL,mBAAmB,mBAAmB,MAAM,MAAM,MAAM;AAAA,QAAC;AAAA,MAC3D;AAEA,YAAM,KAAK,cAAc,iBAAiB;AAE1C,WAAK,gBAAgB;AAAA,IACvB;AAOA,WAAO,KAAK,mBAAmB,SAAS,GAAG;AACzC,YAAM,UAAU,KAAK;AACrB,WAAK,qBAAqB,CAAC;AAC3B,YAAM,QAAQ,IAAI,OAAO;AAAA,IAC3B;AACA,SAAK,cAAc;AAGnB,UAAM,KAAK,qBAAqB,WAAW;AAC3C,UAAM,KAAK,qBAAqB,UAAU;AAC1C,UAAM,KAAK,qBAAqB,yBAAyB;AAKzD,SAAK,eAAe,KAAK,WAAW,MAAM;AACxC,WAAK,iBAAiB,EAAE,MAAM,OAAK,KAAK,IAAI,MAAM,8BAA0B,yBAAW,CAAC,CAAC,EAAE,CAAC;AAAA,IAC9F,GAAG,GAAM;AAIT,SAAK,uBAAuB,KAAK;AAAA,MAC/B,MAAM;AACJ,aAAK,qBAAqB,EAAE,MAAM,OAAK,KAAK,IAAI,MAAM,gCAA4B,yBAAW,CAAC,CAAC,EAAE,CAAC;AAAA,MACpG;AAAA,MACA,KAAK,KAAK,KAAK;AAAA,IACjB;AACA,SAAK;AAAA,MACH,MAAM;AACJ,aAAK,qBAAqB,EAAE,MAAM,OAAK,KAAK,IAAI,MAAM,gCAA4B,yBAAW,CAAC,CAAC,EAAE,CAAC;AAAA,MACpG;AAAA,MACA,IAAI,KAAK;AAAA,IACX;AAEA,SAAK,sBAAsB;AAG3B,SAAK,cAAc;AAGnB,SAAK,aAAa,KAAK,WAAW,MAAM;AACtC,UAAI,CAAC,KAAK,aAAa;AAGrB,aAAK,cAAc;AACnB,aAAK,iBAAiB;AAAA,MACxB;AAAA,IACF,GAAG,GAAM;AAAA,EACX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,uBAAiD;AAC7D,QAAI,CAAC,KAAK,eAAe;AACvB,aAAO,EAAE,IAAI,OAAO,QAAQ,YAAY;AAAA,IAC1C;AACA,UAAM,cAAc,KAAK,cAAc,cAAc;AACrD,UAAM,iBAAiB,IAAI,QAAyB,aAAW;AAC7D,WAAK,iBAAiB,KAAK,WAAW,MAAM,QAAQ,EAAE,IAAI,OAAO,QAAQ,YAAY,CAAC,GAAG,wCAAgB;AAAA,IAC3G,CAAC;AACD,QAAI;AACF,aAAO,MAAM,QAAQ,KAAK,CAAC,aAAa,cAAc,CAAC;AAAA,IACzD,QAAQ;AACN,aAAO,EAAE,IAAI,OAAO,QAAQ,YAAY;AAAA,IAC1C;AAAA,EACF;AAAA;AAAA,EAGQ,sBAAsC;AAC5C,WAAO;AAAA,MACL,KAAK,KAAK;AAAA,MACV,YAAY,CAAC,IAAI,OAAO,KAAK,WAAW,IAAI,EAAE;AAAA,MAC9C,cAAc,OAAK,KAAK,aAAa,CAAqB;AAAA,MAC1D,eAAe,MAAM,KAAK,qBAAqB;AAAA,MAC/C,iBAAiB,YAAY;AA7iBnC;AA8iBQ,aAAK,oBAAoB;AACzB,aAAK,cAAc,uBAAuB;AAAA,UACxC,KAAK;AAAA,UACL,KAAK;AAAA,QACP,CAAC,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AACjB,mBAAK,iBAAL,mBAAmB,mBAAmB,MAAM,MAAM,MAAM;AAAA,QAAC;AACzD,cAAM,KAAK,gBAAgB;AAAA,MAC7B;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGQ,mBAAmC;AACzC,QAAI,CAAC,KAAK,YAAY;AACpB,WAAK,aAAa,IAAI,kCAAe,KAAK,oBAAoB,CAAC;AAC/D,WAAK,WAAW,aAAa,KAAK,iBAAiB;AAAA,IACrD;AACA,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,mBAAmB,QAA+B;AACxD,SAAK,iBAAiB,EAAE,aAAa,MAAM;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,2BAA0C;AACtD,QAAI,CAAC,KAAK,iBAAiB,CAAC,KAAK,aAAa;AAC5C,WAAK,IAAI,KAAK,uFAAkF;AAChG;AAAA,IACF;AACA,SAAK,IAAI,KAAK,sEAAsE;AACpF,QAAI;AACF,YAAM,UAAU,MAAM,KAAK,cAAc,iBAAiB;AAC1D,UAAI,SAAS;AACX,cAAM,KAAK,gBAAgB;AAAA,MAC7B;AAAA,IAIF,SAAS,GAAG;AACV,WAAK,IAAI,KAAK,kCAA8B,yBAAW,CAAC,CAAC,EAAE;AAAA,IAC7D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,SAAS,UAA4B;AAxmB/C;AAymBI,QAAI;AACF,UAAI,KAAK,cAAc;AACrB,aAAK,aAAa,KAAK,YAAY;AAAA,MACrC;AACA,UAAI,KAAK,cAAc;AACrB,aAAK,aAAa,KAAK,YAAY;AAAA,MACrC;AACA,UAAI,KAAK,YAAY;AACnB,aAAK,aAAa,KAAK,UAAU;AAAA,MACnC;AACA,UAAI,KAAK,iBAAiB;AACxB,aAAK,cAAc,KAAK,eAAe;AACvC,aAAK,kBAAkB;AAAA,MACzB;AACA,UAAI,KAAK,oBAAoB;AAC3B,aAAK,aAAa,KAAK,kBAAkB;AACzC,aAAK,qBAAqB;AAAA,MAC5B;AACA,UAAI,KAAK,gBAAgB;AACvB,aAAK,aAAa,KAAK,cAAc;AACrC,aAAK,iBAAiB;AAAA,MACxB;AACA,UAAI,KAAK,sBAAsB;AAC7B,aAAK,cAAc,KAAK,oBAAoB;AAC5C,aAAK,uBAAuB;AAAA,MAC9B;AACA,iBAAK,eAAL,mBAAiB;AACjB,iBAAK,kBAAL,mBAAoB;AACpB,iBAAK,cAAL,mBAAgB;AAChB,iBAAK,eAAL,mBAAiB;AACjB,iBAAK,sBAAL,mBAAwB;AACxB,iBAAK,gBAAL,mBAAkB;AAElB,UAAI,KAAK,2BAA2B;AAClC,gBAAQ,IAAI,sBAAsB,KAAK,yBAAyB;AAChE,aAAK,4BAA4B;AAAA,MACnC;AACA,UAAI,KAAK,0BAA0B;AACjC,gBAAQ,IAAI,qBAAqB,KAAK,wBAAwB;AAC9D,aAAK,2BAA2B;AAAA,MAClC;AAGA,WAAK,SAAS,mBAAmB,EAAE,KAAK,OAAO,KAAK,KAAK,CAAC,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAC1E,WAAK,SAAS,sBAAsB,EAAE,KAAK,OAAO,KAAK,KAAK,CAAC,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAC7E,WAAK,SAAS,6BAA6B;AAAA,QACzC,KAAK;AAAA,QACL,KAAK;AAAA,MACP,CAAC,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AACjB,WAAK,SAAS,uBAAuB,EAAE,KAAK,OAAO,KAAK,KAAK,CAAC,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IAChF,QAAQ;AAAA,IAER;AACA,aAAS;AAAA,EACX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,cAAc,IAAY,OAAyD;AAvqBnG;AAwqBI,QAAI,CAAC,SAAS,MAAM,OAAO,CAAC,KAAK,iBAAiB,CAAC,KAAK,cAAc;AACpE;AAAA,IACF;AAKA,QAAI,OAAO,GAAG,KAAK,SAAS,8BAA8B,MAAM,KAAK;AACnE,YAAM,KAAK,yBAAyB;AACpC,YAAM,KAAK,cAAc,IAAI,EAAE,KAAK,OAAO,KAAK,KAAK,CAAC;AACtD;AAAA,IACF;AAGA,UAAM,UAAU,GAAG,QAAQ,GAAG,KAAK,SAAS,KAAK,EAAE;AACnD,QAAI,CAAC,QAAQ,WAAW,UAAU,KAAK,CAAC,QAAQ,WAAW,SAAS,GAAG;AACrE;AAAA,IACF;AAEA,UAAM,SAAS,KAAK,mBAAmB,OAAO;AAC9C,QAAI,CAAC,QAAQ;AACX;AAAA,IACF;AAGA,UAAM,SAAS,KAAK,aAAa,aAAa,MAAM;AACpD,UAAM,cAAc,QAAQ,MAAM,OAAO,SAAS,CAAC;AAOnD,UAAM,WAAW,MAAM,KAAK,qBAAqB,IAAI,MAAM,GAAG;AAC9D,QAAI,CAAC,SAAS,IAAI;AAChB,WAAK,IAAI,KAAK,8BAA8B,EAAE,KAAK,OAAO,MAAM,GAAG,CAAC,kBAAa;AACjF;AAAA,IACF;AACA,UAAM,MAAM,SAAS;AAGrB,QAAI,OAAO,QAAQ,eAAe,OAAO,cAAc;AACrD,YAAM,KAAK,kBAAkB,QAAQ,aAAa,GAAG;AACrD,YAAM,KAAK,cAAc,IAAI,EAAE,KAAK,KAAK,KAAK,CAAC;AAC/C,UAAI,gBAAgB,wBAAwB,gBAAgB,oBAAoB;AAC9E,cAAM,KAAK,sBAAsB,QAAQ,gBAAgB,uBAAuB,eAAe,OAAO;AAAA,MACxG;AACA;AAAA,IACF;AAGA,QAAI,gBAAgB,6BAA6B,OAAO,QAAQ,YAAY,IAAI,KAAK,GAAG;AACtF,YAAM,KAAK,mBAAmB,QAAQ,IAAI,KAAK,CAAC;AAChD,YAAM,KAAK,cAAc,IAAI,EAAE,KAAK,IAAI,KAAK,KAAK,CAAC;AACnD;AAAA,IACF;AACA,QAAI,gBAAgB,4BAA4B;AAC9C,UAAI,QAAQ,OAAO,QAAQ,GAAG;AAC5B,cAAM,KAAK,sBAAsB,QAAQ,GAAG;AAC5C,cAAM,KAAK,sBAAsB,QAAQ,eAAe;AAAA,MAC1D;AACA,YAAM,KAAK,cAAc,IAAI,EAAE,KAAK,KAAK,KAAK,CAAC;AAC/C;AAAA,IACF;AACA,QAAI,gBAAgB,+BAA+B,OAAO,QAAQ,YAAY,IAAI,KAAK,GAAG;AACxF,WAAK,qBAAqB,QAAQ,IAAI,KAAK,CAAC;AAC5C,YAAM,KAAK,cAAc,IAAI,EAAE,KAAK,IAAI,KAAK,KAAK,CAAC;AACnD;AAAA,IACF;AAKA,QAAI,gBAAgB,0BAA0B,gBAAgB,wBAAwB;AACpF,YAAM,KAAK,2BAA2B,QAAQ,aAAa,GAAG;AAC9D;AAAA,IACF;AAEA,QAAI,gBAAgB,iBAAiB,KAAK;AACxC,YAAM,KAAK,wBAAwB,QAAQ,QAAQ,EAAE;AACrD;AAAA,IACF;AAEA,UAAM,UAAU,KAAK,eAAe,WAAW;AAE/C,QAAI,CAAC,SAAS;AACZ,YAAM,KAAK,+BAA+B,QAAQ,IAAI,aAAa,GAAG;AACtE;AAAA,IACF;AAGA,SAAK,YAAY,gBAAgB,YAAY,cAAc,YAAY,gBAAgB,QAAQ,OAAO,QAAQ,IAAI;AAChH,YAAM,KAAK,cAAc,IAAI,EAAE,KAAK,KAAK,KAAK,CAAC;AAC/C;AAAA,IACF;AAIA,QAAI,YAAY,cAAc;AAC5B,YAAM,QAAQ,OAAO,QAAQ,WAAW,MAAM,SAAS,OAAO,GAAG,GAAG,EAAE;AACtE,UAAI,CAAC,MAAM,KAAK,GAAG;AACjB,eAAO,aAAa;AACpB,mBAAK,kBAAL,mBAAoB,qBAAqB;AAAA,MAC3C;AACA,YAAM,KAAK,cAAc,IAAI,EAAE,KAAK,KAAK,KAAK,CAAC;AAC/C;AAAA,IACF;AAEA,QAAI;AAEF,UAAI,YAAY,SAAS;AAEvB,YAAI,gBAAgB,uBAAuB,QAAQ,OAAO,QAAQ,IAAI;AACpE,gBAAM,KAAK,cAAc,IAAI,EAAE,KAAK,KAAK,KAAK,CAAC;AAC/C;AAAA,QACF;AACA,cAAM,KAAK,iBAAiB,QAAQ,QAAQ,aAAa,GAAG;AAC5D,cAAM,KAAK,cAAc,IAAI,EAAE,KAAK,KAAK,KAAK,CAAC;AAE/C,YAAI,gBAAgB,oBAAoB;AACtC,gBAAM,KAAK,sBAAsB,QAAQ,OAAO;AAAA,QAClD;AACA;AAAA,MACF;AAEA,YAAM,KAAK,cAAc,YAAY,QAAQ,SAAS,GAAG;AAEzD,YAAM,KAAK,cAAc,IAAI,EAAE,KAAK,KAAK,KAAK,CAAC;AAK/C,UAAI,YAAY,WAAW,QAAQ,OAAO;AACxC,cAAM,KAAK,mBAAmB,QAAQ,EAAE;AAAA,MAC1C,OAAO;AACL,cAAM,KAAK,sBAAsB,QAAQ,OAAO;AAAA,MAClD;AAAA,IACF,SAAS,KAAK;AACZ,WAAK,IAAI,KAAK,sBAAsB,OAAO,IAAI,SAAK,yBAAW,GAAG,CAAC,EAAE;AAAA,IACvE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,MAAc,qBACZ,IACA,KACoD;AAx0BxD;AAy0BI,QAAI,QAAQ,QAAQ,QAAQ,QAAW;AACrC,aAAO,EAAE,KAAK,KAAK,IAAI,KAAK;AAAA,IAC9B;AAEA,QAAI,QAAQ,KAAK,QAAQ,OAAO,QAAQ,IAAI;AAC1C,aAAO,EAAE,KAAK,KAAK,IAAI,KAAK;AAAA,IAC9B;AAGA,QAAI,OAAO,QAAQ,YAAY,OAAO,QAAQ,UAAU;AACtD,aAAO,EAAE,KAAK,KAAK,IAAI,KAAK;AAAA,IAC9B;AACA,UAAM,MAAM,MAAM,KAAK,eAAe,EAAE;AACxC,UAAM,UAAS,gCAAK,WAAL,mBAAa;AAC5B,QAAI,CAAC,UAAU,OAAO,WAAW,UAAU;AACzC,aAAO,EAAE,KAAK,KAAK,IAAI,KAAK;AAAA,IAC9B;AACA,UAAM,eAAW,iCAAmB,KAAK,MAAgC;AACzE,QAAI,UAAU;AACZ,aAAO,EAAE,KAAK,SAAS,KAAK,IAAI,KAAK;AAAA,IACvC;AACA,WAAO,EAAE,KAAK,KAAK,IAAI,MAAM;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAc,iBACZ,QACA,QACA,eACA,UACe;AA/2BnB;AAg3BI,UAAM,YAAY,GAAG,KAAK,SAAS,IAAI,MAAM;AAG7C,UAAM,YAAY,MAAM,KAAK,cAAc,GAAG,SAAS,aAAa;AACpE,UAAM,YAAY,MAAM,KAAK,cAAc,GAAG,SAAS,oBAAoB;AAC3E,UAAM,YAAY,MAAM,KAAK,cAAc,GAAG,SAAS,mBAAmB;AAG1E,UAAM,YACJ,kBAAkB,qBAAqB,SAAS,OAAO,QAAQ,GAAG,EAAE,IAAI,SAAS,QAAO,4CAAW,QAAX,YAAkB,CAAC,GAAG,EAAE;AAClH,UAAM,cACJ,kBAAkB,4BAA6B,YAAwB,4CAAW,QAAX,YAA6B;AACtG,UAAM,YAAY,kBAAkB,2BAA4B,WAAW,IAAI,KAAK,uCAAW,OAAM,IAAI;AAEzG,QAAI,CAAC,aAAa,cAAc,GAAG;AACjC,WAAK,IAAI,MAAM,2CAA2C;AAC1D;AAAA,IACF;AAGA,QAAI,OAAO,SAAS,KAAK,WAAW;AAElC,UAAI,IAAI,GACN,IAAI,GACJ,IAAI;AACN,UAAI,cAAc,KAAK,cAAc,GAAG;AACtC,cAAM,aAAa,MAAM,KAAK,cAAc,GAAG,KAAK,SAAS,IAAI,MAAM,mBAAmB;AAC1F,aAAI,yCAAY,QAAO,OAAO,WAAW,QAAQ,UAAU;AACzD,WAAC,EAAE,GAAG,GAAG,EAAE,QAAI,uBAAS,WAAW,GAAG;AAAA,QACxC;AAAA,MACF;AACA,WAAK,UAAU,aAAa,OAAO,OAAO,WAAW,GAAG,GAAG,CAAC;AAC5D;AAAA,IACF;AAGA,UAAM,cAAuC;AAAA,MAC3C;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,UAAM,KAAK,cAAe;AAAA,MACxB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,oBAAoB,QAAqB,OAAmC;AAClF,QAAI,KAAK,cAAc;AACrB,WAAK,aAAa,kBAAkB,QAAQ,KAAK,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IACnE;AACA,SAAK,sBAAsB;AAG3B,QAAI,MAAM,WAAW,QAAW;AAC9B,WAAK,wBAAwB;AAAA,IAC/B;AAOA,UAAM,WAAW,MAAM,UAAU,SAAU,MAAM,UAAsB;AACvE,QAAI,YAAY,KAAK,cAAc;AACjC,YAAM,SAAS,KAAK,aAAa,aAAa,MAAM;AACpD,WAAK,mBAAmB,QAAQ,EAAE,EAAE,MAAM,MAAM,MAAS;AAAA,IAC3D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAc,kBAAkB,OAAoB,aAAqB,OAA2C;AAClH,QAAI,CAAC,KAAK,iBAAiB,CAAC,MAAM,cAAc;AAC9C;AAAA,IACF;AAEA,UAAM,UAAU,KAAK,cAAc,WAAW;AAC9C,UAAM,UAAU,KAAK,oBAAoB,OAAO,OAAO,EAAE,OAAO,OAAK,EAAE,MAAM,MAAM;AAEnF,QAAI,QAAQ,WAAW,GAAG;AACxB,WAAK,IAAI,MAAM,UAAU,MAAM,IAAI,qCAAqC;AACxE;AAAA,IACF;AAEA,UAAM,UAAU,KAAK,eAAe,WAAW;AAC/C,QAAI,CAAC,SAAS;AACZ;AAAA,IACF;AAGA,SAAK,YAAY,gBAAgB,YAAY,aAAa,UAAU,OAAO,UAAU,IAAI;AACvF;AAAA,IACF;AAEA,eAAW,UAAU,SAAS;AAC5B,UAAI;AACF,YAAI,YAAY,cAAc;AAC5B,gBAAM,KAAK,YAAY,OAAO,QAAQ,KAAK;AAAA,QAC7C,WAAW,YAAY,SAAS;AAC9B,gBAAM,KAAK,YAAY,OAAO,QAAQ,aAAa,KAAK;AAAA,QAC1D,OAAO;AACL,gBAAM,KAAK,cAAc,YAAY,QAAQ,SAAS,KAAK;AAAA,QAC7D;AAAA,MACF,SAAS,KAAK;AACZ,aAAK,IAAI,MAAM,oBAAoB,OAAO,IAAI,SAAK,yBAAW,GAAG,CAAC,EAAE;AAAA,MACtE;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAc,YAAY,OAAoB,QAAqB,OAA2C;AAr/BhH;AAs/BI,QAAI,CAAC,KAAK,iBAAiB,CAAC,KAAK,cAAc;AAC7C;AAAA,IACF;AAGA,UAAM,cAAc,KAAK,aAAa,aAAa,KAAK;AACxD,UAAM,MAAM,MAAM,KAAK,eAAe,GAAG,KAAK,SAAS,IAAI,WAAW,qBAAqB;AAC3F,UAAM,eAAc,gCAAK,WAAL,mBAAa;AACjC,UAAM,YAAY,2CAAc,OAAO,KAAK;AAC5C,QAAI,CAAC,WAAW;AACd;AAAA,IACF;AAGA,UAAM,YAAY,OAAO,OAAO,UAAU,OAAK,EAAE,SAAS,SAAS;AACnE,QAAI,aAAa,GAAG;AAClB,YAAM,KAAK,cAAc,YAAY,QAAQ,cAAc,YAAY,CAAC;AAAA,IAC1E;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAc,YACZ,OACA,QACA,aACA,OACe;AAvhCnB;AAwhCI,QAAI,CAAC,KAAK,iBAAiB,CAAC,KAAK,cAAc;AAC7C;AAAA,IACF;AAGA,QAAI,gBAAgB,oBAAoB;AACtC,YAAM,KAAK,iBAAiB,QAAQ,KAAK,aAAa,aAAa,MAAM,GAAG,aAAa,KAAK;AAC9F;AAAA,IACF;AAGA,UAAM,cAAc,KAAK,aAAa,aAAa,KAAK;AACxD,UAAM,MAAM,MAAM,KAAK,eAAe,GAAG,KAAK,SAAS,IAAI,WAAW,mBAAmB;AACzF,UAAM,eAAc,gCAAK,WAAL,mBAAa;AACjC,UAAM,YAAY,2CAAc,OAAO,KAAK;AAC5C,QAAI,CAAC,WAAW;AACd;AAAA,IACF;AAGA,UAAM,YAAY,OAAO,aAAa,UAAU,OAAK,EAAE,SAAS,SAAS;AACzE,QAAI,aAAa,GAAG;AAElB,YAAM,eAAe,KAAK,aAAa,aAAa,MAAM;AAE1D,YAAM,KAAK,iBAAiB,QAAQ,cAAc,oBAAoB,YAAY,CAAC;AAAA,IACrF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,oBAAoB,OAAoB,SAAuC;AACrF,QAAI,CAAC,MAAM,cAAc;AACvB,aAAO,CAAC;AAAA,IACV;AACA,WAAO,MAAM,aACV,IAAI,OAAK,QAAQ,KAAK,OAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,aAAa,EAAE,QAAQ,CAAC,EACxE,OAAO,CAAC,MAAwB,MAAM,MAAS;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,0BAAgC;AACtC,QAAI,CAAC,KAAK,iBAAiB,CAAC,KAAK,cAAc;AAC7C;AAAA,IACF;AACA,UAAM,UAAU,KAAK,cAAc,WAAW;AAC9C,eAAW,SAAS,SAAS;AAC3B,UAAI,MAAM,QAAQ,eAAe,CAAC,MAAM,cAAc;AACpD;AAAA,MACF;AACA,YAAM,gBAAgB,KAAK,oBAAoB,OAAO,OAAO;AAC7D,WAAK,aAAa,8BAA8B,OAAO,aAAa,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IACtF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWQ,oBAAoB,QAAqB,YAAiC;AA/lCpF;AAgmCI,QAAI,CAAC,KAAK,cAAc;AACtB;AAAA,IACF;AACA,UAAM,cAAa,UAAK,mBAAL,mBAAqB,aAAa,OAAO,KAAK,OAAO;AACxE,QAAI;AACJ,QAAI,OAAO,QAAQ,eAAe,OAAO,cAAc;AACrD,sBAAgB,KAAK,oBAAoB,QAAQ,UAAU;AAAA,IAC7D;AACA,UAAM,gBAAY,+CAAqB,QAAQ,YAAY,aAAa;AACxE,UAAM,IAAI,KAAK,aACZ,mBAAmB,QAAQ,SAAS,EACpC,KAAK,YAAY;AA3mCxB,UAAAA,KAAA;AA8mCQ,cAAMA,MAAA,KAAK,iBAAL,gBAAAA,IAAmB,yBAAyB;AAClD,cAAM,UAAK,iBAAL,mBAAmB,iBAAiB,YAAQ,sCAAc,OAAO,GAAG;AAAA,IAC5E,CAAC,EACA,MAAM,OAAK;AACV,WAAK,IAAI,MAAM,iCAAiC,OAAO,IAAI,SAAK,yBAAW,CAAC,CAAC,EAAE;AAAA,IACjF,CAAC;AAIH,QAAI,CAAC,KAAK,aAAa;AACrB,WAAK,mBAAmB,KAAK,CAAC;AAAA,IAChC,OAAO;AACL,WAAK;AAAA,IACP;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,oBAAoB,SAA8B;AACxD,QAAI,CAAC,KAAK,cAAc;AACtB;AAAA,IACF;AAEA,eAAW,UAAU,SAAS;AAC5B,WAAK,oBAAoB,QAAQ,OAAO;AAAA,IAC1C;AAEA,SAAK,sBAAsB;AAS3B,QAAI,KAAK,aAAa;AACpB,WAAK,iBAAiB,EAAE,MAAM,MAAM,MAAS;AAAA,IAC/C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBQ,wBAA8B;AAxqCxC;AAyqCI,UAAM,WAAU,gBAAK,kBAAL,mBAAoB,iBAApB,YAAoC,CAAC;AACrD,UAAM,aAAa,QAAQ,SAAS;AACpC,UAAM,YAAY,QAAQ,KAAK,OAAK,EAAE,MAAM,MAAM;AAClD,UAAM,aAAa,KAAK,cAAc;AACtC,UAAM,YAAY,aAAa,YAAY;AAC3C,QAAI,cAAc,KAAK,qBAAqB;AAC1C,WAAK,sBAAsB;AAC3B,WAAK,cAAc,mBAAmB,EAAE,KAAK,WAAW,KAAK,KAAK,CAAC,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IACrF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgCA,MAAc,uBAAsC;AAltCtD;AAmtCI,QAAI;AACF,YAAM,OAAO,UAAM,iCAA8E;AAAA,QAC/F,QAAQ;AAAA,QACR,KAAK;AAAA,QACL,SAAS,EAAE,cAAc,uBAAuB;AAAA,QAChD,SAAS;AAAA,MACX,CAAC;AACD,YAAM,eAAc,gBAAK,YAAL,mBAAe,OAAf,mBAAmB;AACvC,UAAI,OAAO,gBAAgB,YAAY,YAAY,WAAW,GAAG;AAC/D;AAAA,MACF;AACA,YAAM,aAAa,yCAAkB,MAAM,GAAG,EAAE,IAAI,MAAM;AAC1D,YAAM,YAAY,YAAY,MAAM,GAAG,EAAE,IAAI,MAAM;AAGnD,YAAM,cAAa,gBAAW,CAAC,MAAZ,YAAiB;AACpC,YAAM,cAAa,gBAAW,CAAC,MAAZ,YAAiB;AACpC,YAAM,aAAY,eAAU,CAAC,MAAX,YAAgB;AAClC,YAAM,aAAY,eAAU,CAAC,MAAX,YAAgB;AAClC,YAAM,YAAY,YAAY,MAAM;AACpC,YAAM,aAAa,aAAa,MAAM;AACtC,YAAM,aAAa,YAAY;AAC/B,YAAM,eACJ,eAAe,IACX,iBAAiB,WAAW,WAAW,wCAAiB,MACxD,cAAc,IACZ,qBAAqB,WAAW,WAAW,wCAAiB,MAC5D,eAAe,WAAW,WAAW,wCAAiB;AAC9D,YAAM,KAAK,cAAc,wBAAwB,EAAE,KAAK,cAAc,KAAK,KAAK,CAAC,EAAE,MAAM,MAAM,MAAS;AACxG,UAAI,aAAa,GAAG;AAClB,aAAK,IAAI;AAAA,UACP,iCAAiC,WAAW,aAAa,wCAAiB;AAAA,QAC5E;AAAA,MACF,OAAO;AACL,aAAK,IAAI,MAAM,gBAAgB,YAAY,EAAE;AAAA,MAC/C;AAAA,IACF,SAAS,GAAG;AACV,WAAK,IAAI,MAAM,iCAA6B,yBAAW,CAAC,CAAC,EAAE;AAAA,IAC7D;AAAA,EACF;AAAA,EAEA,MAAc,mBAAkC;AAC9C,QAAI,CAAC,KAAK,gBAAgB,CAAC,KAAK,eAAe;AAC7C;AAAA,IACF;AACA,UAAM,iBAAiB,KAAK,cAAc,WAAW;AACrD,UAAM,KAAK,aAAa,eAAe,cAAc;AAIrD,UAAM,gBAAgB,IAAI,IAAI,eAAe,IAAI,OAAK,EAAE,QAAQ,CAAC;AACjE,SAAK,cAAc,eAAe,EAAE,aAAa,aAAa;AAE9D,UAAM,WAAW,IAAI,IAAI,eAAe,IAAI,OAAK,GAAG,EAAE,GAAG,IAAI,EAAE,QAAQ,EAAE,CAAC;AAC1E,eAAW,OAAO,KAAK,mBAAmB,KAAK,GAAG;AAChD,UAAI,CAAC,SAAS,IAAI,GAAG,GAAG;AACtB,aAAK,mBAAmB,OAAO,GAAG;AAAA,MACpC;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,gBAAsB;AApxChC;AAqxCI,QAAI,KAAK,aAAa;AACpB;AAAA,IACF;AAEA,QAAI,CAAC,KAAK,aAAa;AACrB;AAAA,IACF;AAEA,QAAI,CAAC,KAAK,aAAa;AACrB;AAAA,IACF;AAEA,QAAI,KAAK,eAAe,CAAC,KAAK,eAAe;AAC3C;AAAA,IACF;AAEA,QAAI,KAAK,cAAc,CAAC,KAAK,WAAW,WAAW;AACjD;AAAA,IACF;AAIA,QAAI,KAAK,qBAAqB,CAAC,KAAK,kBAAkB,WAAW;AAC/D;AAAA,IACF;AAKA,UAAI,UAAK,kBAAL,mBAAoB,6BAA4B,CAAC,KAAK,uBAAuB;AAC/E;AAAA,IACF;AACA,SAAK,cAAc;AACnB,SAAK,iBAAiB;AAItB,eAAK,kBAAL,mBAAoB;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA,EAKQ,mBAAyB;AAh0CnC;AAi0CI,QAAI,CAAC,KAAK,eAAe;AACvB;AAAA,IACF;AACA,UAAM,MAAM,KAAK,cAAc,WAAW;AAC1C,UAAM,UAAU,IAAI,OAAO,OAAK,EAAE,QAAQ,WAAW;AACrD,UAAM,SAAS,IAAI,OAAO,OAAK,EAAE,QAAQ,WAAW;AAKpD,UAAM,WAAqB,CAAC,KAAK;AACjC,QAAI,KAAK,mBAAmB;AAC1B,eAAS,KAAK,OAAO;AAAA,IACvB;AACA,SAAI,UAAK,eAAL,mBAAiB,WAAW;AAC9B,eAAS,KAAK,MAAM;AAAA,IACtB;AACA,SAAI,UAAK,sBAAL,mBAAwB,WAAW;AACrC,eAAS,KAAK,cAAc;AAAA,IAC9B;AAGA,UAAM,eAAe,QAAQ,OAAO,OAAK,EAAE,SAAS,qBAAqB;AACzE,UAAM,gBAAgB,QAAQ,OAAO,OAAK,EAAE,MAAM,WAAW,IAAI;AACjE,UAAM,QAAkB,CAAC;AACzB,QAAI,QAAQ,SAAS,GAAG;AACtB,YAAM,eAAe,aAAa,OAAO,OAAK,EAAE,MAAM,WAAW,IAAI,EAAE;AACvE,YAAM,cAAc,aAAa;AACjC,UAAI,cAAc,GAAG;AACnB,cAAM;AAAA,UACJ,gBAAgB,eACZ,GAAG,WAAW,SAAS,cAAc,IAAI,MAAM,EAAE,YACjD,GAAG,WAAW,SAAS,cAAc,IAAI,MAAM,EAAE,KAAK,YAAY,YAAY,cAAc,YAAY;AAAA,QAC9G;AAAA,MACF;AACA,YAAM,UAAU,QAAQ,SAAS,aAAa;AAC9C,UAAI,UAAU,GAAG;AACf,cAAM,gBAAgB,cAAc,OAAO,OAAK,EAAE,SAAS,qBAAqB,EAAE;AAClF,cAAM,KAAK,GAAG,OAAO,UAAU,UAAU,IAAI,MAAM,EAAE,KAAK,aAAa,aAAa;AAAA,MACtF;AAAA,IACF;AACA,QAAI,OAAO,SAAS,GAAG;AACrB,YAAM,KAAK,GAAG,OAAO,MAAM,SAAS,OAAO,SAAS,IAAI,MAAM,EAAE,EAAE;AAAA,IACpE;AACA,UAAM,UAAU,MAAM,SAAS,IAAI,MAAM,KAAK,IAAI,IAAI;AACtD,SAAK,IAAI,KAAK,8BAAyB,OAAO,qBAAgB,SAAS,KAAK,GAAG,CAAC,EAAE;AAIlF,QAAI,KAAK,eAAe,CAAC,KAAK,mBAAmB;AAC/C,YAAM,SAAS,KAAK,YAAY,iBAAiB;AACjD,WAAK,IAAI,KAAK,SAAS,wBAAwB,MAAM,KAAK,+CAA0C;AAAA,IACtG;AACA,QAAI,KAAK,cAAc,CAAC,KAAK,WAAW,WAAW;AACjD,YAAM,SAAS,KAAK,WAAW,iBAAiB;AAChD,WAAK,IAAI,KAAK,SAAS,uBAAuB,MAAM,KAAK,8CAAyC;AAAA,IACpG;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,kBAAiC;AAC7C,QAAI,CAAC,KAAK,eAAe,CAAC,KAAK,iBAAiB,CAAC,KAAK,cAAc;AAClE;AAAA,IACF;AAEA,UAAM,UAAU,KAAK,cAAc,WAAW;AAE9C,UAAM,cAAc,IAAI,QAAI,8CAAoB,EAAE,IAAI,OAAK,EAAE,EAAE,CAAC;AAChE,QAAI,SAAS;AAEb,eAAW,UAAU,SAAS;AAC5B,UAAI,CAAC,OAAO,SAAS,SAAS,OAAO,aAAa,WAAW,GAAG;AAC9D;AAAA,MACF;AAEA,UAAI;AACF,cAAM,OAAO,MAAM,KAAK,YAAY,eAAe,OAAO,KAAK,OAAO,QAAQ;AAC9E,cAAM,SAAS,KAAK,aAAa,aAAa,MAAM;AAEpD,cAAM,SAA6B,CAAC;AACpC,mBAAW,OAAO,MAAM;AACtB,gBAAM,aAAS,6CAAmB,GAAG;AACrC,cAAI,CAAC,QAAQ;AACX;AAAA,UACF;AAEA,cAAI,OAAO,SAAS,YAAY,IAAI,OAAO,OAAO,GAAG;AACnD;AAAA,UACF;AACA,gBAAM,YAAY,KAAK,aAAa,iBAAiB,QAAQ,OAAO,OAAO;AAI3E,iBAAO;AAAA,YACL,KAAK,cAAc,WAAW;AAAA,cAC5B,KAAK,OAAO;AAAA,cACZ,KAAK;AAAA,YACP,CAAC,EAAE,MAAM,MAAM,MAAS;AAAA,UAC1B;AAAA,QACF;AACA,cAAM,QAAQ,IAAI,MAAM;AACxB;AAAA,MACF,QAAQ;AACN,aAAK,IAAI,MAAM,kCAAkC,OAAO,IAAI,KAAK,OAAO,GAAG,GAAG;AAAA,MAChF;AAAA,IACF;AAEA,QAAI,SAAS,GAAG;AACd,WAAK,IAAI,MAAM,2BAA2B,MAAM,UAAU;AAAA,IAC5D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAc,uBAAuB,QAAqB,MAA6C;AACrG,QAAI,CAAC,KAAK,cAAc;AACtB;AAAA,IACF;AACA,UAAM,cAAc,IAAI,QAAI,8CAAoB,EAAE,IAAI,OAAK,EAAE,EAAE,CAAC;AAChE,UAAM,SAAS,KAAK,aAAa,aAAa,MAAM;AACpD,UAAM,cAAU,oDAA0B,MAAM,QAAQ,OAAO,KAAK,GAAG,WAAW;AAMlF,eAAW,UAAU,SAAS;AAC5B,YAAM,KAAK,aAAa,2BAA2B,QAAQ,OAAO,OAAO;AAAA,IAC3E;AACA,UAAM,SAAS,QAAQ,IAAI,YAAU;AACnC,YAAM,YAAY,KAAK,aAAc,iBAAiB,QAAQ,OAAO,OAAO;AAC5E,aAAO,KAAK,cAAc,WAAW;AAAA,QACnC,KAAK,OAAO;AAAA,QACZ,KAAK;AAAA,MACP,CAAC,EAAE,MAAM,MAAM,MAAS;AAAA,IAC1B,CAAC;AACD,UAAM,QAAQ,IAAI,MAAM;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,mBAAmB,SAA0C;AACnE,QAAI,CAAC,KAAK,iBAAiB,CAAC,KAAK,cAAc;AAC7C,aAAO;AAAA,IACT;AAEA,eAAW,UAAU,KAAK,cAAc,WAAW,GAAG;AACpD,YAAM,SAAS,KAAK,aAAa,aAAa,MAAM;AACpD,UAAI,QAAQ,WAAW,GAAG,MAAM,GAAG,GAAG;AACpC,eAAO;AAAA,MACT;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWQ,eAAe,QAA+B;AACpD,UAAM,SAAS,iBAAiB,MAAM;AACtC,QAAI,QAAQ;AACV,aAAO;AAAA,IACT;AACA,UAAM,gBAAgB,2BAA2B,KAAK,MAAM;AAC5D,QAAI,eAAe;AACjB,aAAO,gBAAgB,cAAc,CAAC,CAAC;AAAA,IACzC;AACA,UAAM,iBAAiB,gCAAgC,KAAK,MAAM;AAClE,QAAI,gBAAgB;AAClB,aAAO,qBAAqB,eAAe,CAAC,CAAC;AAAA,IAC/C;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAc,oBAAoB,QAAqB,MAAe,SAAmC;AA5gD3G;AA6gDI,QAAI,CAAC,KAAK,cAAc;AACtB;AAAA,IACF;AACA,WAAO,aAAa;AACpB,WAAO,iBAAiB,QAAQ,MAAM,QAAQ,OAAO,KAAK,QAAQ,SAAS,IAAI,QAAQ,MAAM,IAAI;AACjG,UAAM,KAAK,aAAa,oBAAoB,MAAM;AAClD,eAAK,kBAAL,mBAAoB,qBAAqB;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAc,2BAA2B,QAAqB,QAAgB,UAAkC;AAG9G,UAAM,UAAU,WAAW,yBAAyB,QAAQ,QAAQ,IAAI,OAAO,eAAe;AAC9F,UAAM,UACJ,WAAW,yBACP,OAAO,aAAa,WAClB,WACA,KACF,MAAM,QAAQ,OAAO,cAAc,IACjC,OAAO,eAAe,KAAK,GAAG,IAC9B;AAER,QAAI,CAAC,SAAS;AACZ,WAAK,IAAI,KAAK,GAAG,OAAO,IAAI,+DAA0D;AACtF,YAAM,KAAK,oBAAoB,QAAQ,KAAK;AAC5C;AAAA,IACF;AAKA,UAAM,WACJ,OAAO,OAAO,iBAAiB,YAAY,OAAO,eAAe,IAAI,OAAO,eAAe,IAAI;AACjG,UAAM,aAAS,+BAAiB,SAAS,QAAQ;AACjD,QAAI,OAAO,OAAO;AAChB,WAAK,IAAI,KAAK,GAAG,OAAO,IAAI,0BAA0B,OAAO,KAAK,gCAA2B;AAC7F,YAAM,KAAK,oBAAoB,QAAQ,KAAK;AAC5C;AAAA,IACF;AAEA,SAAK,IAAI,MAAM,GAAG,OAAO,IAAI,mCAA8B,OAAO,QAAQ,MAAM,sBAAsB,OAAO,GAAG;AAChH,UAAM,KAAK,oBAAoB,QAAQ,MAAM,OAAO,OAAO;AAAA,EAC7D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,UAAU,KAA6B;AAC7C,QAAI,EAAC,2BAAK,UAAS;AACjB;AAAA,IACF;AAGA,SAAK,cAAc,GAAG,EAAE,MAAM,OAAK;AACjC,WAAK,IAAI,KAAK,iCAAiC,IAAI,OAAO,SAAK,yBAAW,CAAC,CAAC,EAAE;AAC9E,WAAK,oBAAoB,KAAK;AAAA,QAC5B,OAAO,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;AAAA,MAClD,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,cAAc,KAAsC;AAvlDpE;AAwlDI,QAAI;AACF,UAAI,IAAI,YAAY,qBAAqB;AACvC,cAAM,WAAU,gBAAK,kBAAL,mBAAoB,iBAApB,YAAoC,CAAC;AACrD,cAAM,OAAO,QACV,OAAO,OAAE;AA5lDpB,cAAAA;AA4lDuB,mBAAE,QAAQ,iBAAeA,MAAA,EAAE,UAAF,gBAAAA,IAAS,YAAW,YAAQ,2CAAoB,CAAC,IAAI;AAAA,SAAC,EAC3F,IAAI,OAAK;AACR,gBAAM,YAAQ,2CAAoB,CAAC;AACnC,iBAAO;AAAA,YACL,OAAO,KAAK,aAAa,CAAC;AAAA,YAC1B,OAAO,GAAG,EAAE,IAAI,KAAK,EAAE,GAAG,YAAY,KAAK;AAAA,UAC7C;AAAA,QACF,CAAC;AAEH,aAAK,oBAAoB,KAAK,IAAI;AAClC;AAAA,MACF;AACA,UAAI,IAAI,YAAY,iBAAiB;AACnC,cAAM,WAAW,SAAI,YAAJ,YAAe,CAAC;AAIjC,cAAM,WAAW,MAAM,KAAK,eAAc,aAAQ,WAAR,YAAkB,KAAI,aAAQ,WAAR,YAAkB,EAAE;AACpF,aAAK,oBAAoB,KAAK,QAAQ;AACtC;AAAA,MACF;AACA,UAAI,IAAI,YAAY,YAAY;AAC9B,cAAM,WAAW,SAAI,YAAJ,YAAe,CAAC;AACjC,cAAM,WAAW,MAAM,KAAK,mBAAkB,aAAQ,WAAR,YAAkB,EAAE;AAClE,aAAK,oBAAoB,KAAK,QAAQ;AACtC;AAAA,MACF;AAAA,IACF,SAAS,GAAG;AACV,WAAK,IAAI,KAAK,wBAAwB,IAAI,OAAO,SAAK,yBAAW,CAAC,CAAC,EAAE;AACrE,WAAK,oBAAoB,KAAK;AAAA,QAC5B,OAAO,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;AAAA,MAClD,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAc,kBAAkB,QAA6C;AA1oD/E;AA2oDI,UAAM,SAAS,KAAK;AACpB,QAAI,CAAC,OAAO,cAAc,CAAC,OAAO,eAAe;AAC/C,aAAO,EAAE,QAAQ,0DAAuD;AAAA,IAC1E;AAEA,QAAI,WAAW,QAAQ;AAGrB,YAAM,QAAQ,IAAI,yCAAgB,OAAO,YAAY,OAAO,eAAe,KAAK,KAAK,IAAI;AACzF,YAAM,qBAAoB,YAAO,yBAAP,YAA+B,EAAE;AAC3D,UAAI;AACF,YAAI,YAAY;AAChB,cAAM,MAAM;AAAA,UACV,MAAM;AAAA,UAAC;AAAA,UACP,iBAAe;AACb,wBAAY;AAAA,UACd;AAAA,QACF;AACA,cAAM,WAAW;AACjB,eAAO;AAAA,UACL,QAAQ,YACJ,4DACA;AAAA,QACN;AAAA,MACF,SAAS,GAAG;AACV,cAAM,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;AACrD,YAAI,yBAAyB,KAAK,GAAG,GAAG;AACtC,iBAAO;AAAA,YACL,QACE;AAAA,UACJ;AAAA,QACF;AACA,YAAI,6BAA6B,KAAK,GAAG,GAAG;AAC1C,iBAAO;AAAA,YACL,QAAQ;AAAA,UACV;AAAA,QACF;AACA,YAAI,wBAAwB,KAAK,GAAG,GAAG;AACrC,iBAAO,EAAE,QAAQ,gDAAgD;AAAA,QACnE;AACA,YAAI,gBAAgB,KAAK,GAAG,GAAG;AAC7B,iBAAO,EAAE,QAAQ,sCAAsC;AAAA,QACzD;AACA,YAAI,gBAAgB,KAAK,GAAG,GAAG;AAC7B,iBAAO,EAAE,QAAQ,mEAA2D;AAAA,QAC9E;AACA,YAAI,8BAA8B,KAAK,GAAG,GAAG;AAC3C,iBAAO,EAAE,QAAQ,gGAAkF;AAAA,QACrG;AACA,eAAO,EAAE,QAAQ,yBAAyB,GAAG,GAAG;AAAA,MAClD;AAAA,IACF;AAEA,QAAI,WAAW,eAAe;AAC5B,YAAM,MAAM,KAAK,IAAI;AACrB,UAAI,MAAM,KAAK,4BAA4B,kCAAkC;AAC3E,cAAM,OAAO,KAAK,MAAM,oCAAoC,MAAM,KAAK,8BAA8B,GAAI;AACzG,eAAO,EAAE,QAAQ,SAAS,IAAI,2DAAsD;AAAA,MACtF;AACA,WAAK,4BAA4B;AACjC,YAAM,QAAQ,IAAI,yCAAgB,OAAO,YAAY,OAAO,eAAe,KAAK,KAAK,IAAI;AACzF,UAAI;AACF,cAAM,MAAM,wBAAwB;AACpC,eAAO,EAAE,QAAQ,6EAA0E;AAAA,MAC7F,SAAS,GAAG;AACV,cAAM,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;AACrD,eAAO,EAAE,QAAQ,yCAAyC,GAAG,GAAG;AAAA,MAClE;AAAA,IACF;AAEA,WAAO,EAAE,QAAQ,sBAAsB,MAAM,KAAK;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAc,+BAA8C;AA9tD9D;AA+tDI,QAAI;AACF,YAAM,MAAM,MAAM,KAAK,sBAAsB,kBAAkB,KAAK,SAAS,EAAE;AAC/E,YAAM,UAAU,gCAAK,WAAL,YAAe,CAAC;AAGhC,UAAI,OAAO,OAAO,yBAAyB,YAAY,OAAO,yBAAyB,IAAI;AACzF;AAAA,MACF;AACA,YAAM,KAAK,yBAAyB,kBAAkB,KAAK,SAAS,IAAI;AAAA,QACtE,QAAQ,EAAE,sBAAsB,GAAG;AAAA,MACrC,CAAC;AAAA,IACH,SAAS,GAAG;AACV,WAAK,IAAI,KAAK,6CAAyC,yBAAW,CAAC,CAAC,EAAE;AAAA,IACxE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAc,8BAAwE;AACpF,QAAI;AACF,YAAM,IAAI,MAAM,KAAK,cAAc,sBAAsB;AACzD,YAAM,MAAM,QAAO,uBAAG,SAAQ,WAAW,EAAE,MAAM;AACjD,UAAI,CAAC,KAAK;AACR,eAAO;AAAA,MACT;AACA,YAAM,MAAM,KAAK,MAAM,GAAG;AAY1B,YAAM,UAAU,CAAC,MAAwB,OAAO,MAAM,WAAW,IAAI;AACrE,YAAM,cAAc,KAAK,QAAQ,QAAQ,IAAI,WAAW,CAAC;AACzD,YAAM,UAAU,KAAK,QAAQ,QAAQ,IAAI,OAAO,CAAC;AACjD,YAAM,UAAU,KAAK,QAAQ,QAAQ,IAAI,OAAO,CAAC;AACjD,YAAM,cAAc,QAAQ,IAAI,WAAW;AAC3C,YAAM,YAAY,QAAQ,IAAI,SAAS;AACvC,YAAM,eAAe,QAAQ,IAAI,YAAY;AAC7C,YAAM,iBAAiB,OAAO,IAAI,mBAAmB,WAAW,IAAI,iBAAiB;AACrF,UAAI,CAAC,eAAe,CAAC,eAAe,CAAC,WAAW,CAAC,aAAa,CAAC,gBAAgB,CAAC,gBAAgB;AAC9F,eAAO;AAAA,MACT;AACA,aAAO,EAAE,aAAa,aAAa,SAAS,SAAS,WAAW,cAAc,eAAe;AAAA,IAC/F,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAc,oBAAoB,OAAgD;AAChF,UAAM,OAAO,KAAK,UAAU;AAAA,MAC1B,aAAa,KAAK,QAAQ,MAAM,WAAW;AAAA,MAC3C,aAAa,MAAM;AAAA,MACnB,SAAS,KAAK,QAAQ,MAAM,OAAO;AAAA,MACnC,SAAS,KAAK,QAAQ,MAAM,OAAO;AAAA,MACnC,WAAW,MAAM;AAAA,MACjB,cAAc,MAAM;AAAA,MACpB,gBAAgB,MAAM;AAAA,IACxB,CAAC;AACD,UAAM,KAAK,cAAc,wBAAwB,EAAE,KAAK,MAAM,KAAK,KAAK,CAAC;AAAA,EAC3E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBA,MAAc,8BAA6C;AAn0D7D;AAo0DI,QAAI;AAEF,YAAM,cAAc,MAAM,KAAK,cAAc,wBAAwB;AACrE,WAAI,2CAAa,SAAQ,MAAM;AAC7B;AAAA,MACF;AACA,YAAM,MAAM,MAAM,KAAK,sBAAsB,kBAAkB,KAAK,SAAS,EAAE;AAC/E,YAAM,UAAU,gCAAK,WAAL,YAAe,CAAC;AAChC,YAAM,SAAS;AAAA,QACb;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AACA,YAAM,QAAQ,OAAO,KAAK,OAAK,KAAK,UAAU,OAAO,CAAC,MAAM,MAAM,OAAO,CAAC,MAAM,CAAC;AACjF,UAAI,CAAC,OAAO;AAIV,cAAM,KAAK,cAAc,0BAA0B,EAAE,KAAK,MAAM,KAAK,KAAK,CAAC,EAAE,MAAM,MAAM,MAAS;AAClG;AAAA,MACF;AACA,WAAK,IAAI,KAAK,6EAA6E;AAC3F,YAAM,OAAgC,CAAC;AACvC,iBAAW,KAAK,QAAQ;AACtB,aAAK,CAAC,IAAI,MAAM,uBAAuB,IAAI;AAAA,MAC7C;AACA,YAAM,KAAK,yBAAyB,kBAAkB,KAAK,SAAS,IAAI,EAAE,QAAQ,KAAK,CAAC;AAIxF,YAAM,KAAK,cAAc,0BAA0B,EAAE,KAAK,MAAM,KAAK,KAAK,CAAC,EAAE,MAAM,MAAM,MAAS;AAAA,IACpG,SAAS,GAAG;AACV,WAAK,IAAI,MAAM,oCAAgC,yBAAW,CAAC,CAAC,EAAE;AAAA,IAChE;AAAA,EACF;AAAA,EAEQ,oBAAoB,KAAuB,MAAqB;AACtE,QAAI,IAAI,YAAY,IAAI,MAAM;AAC5B,WAAK,OAAO,IAAI,MAAM,IAAI,SAAS,MAAiC,IAAI,QAAQ;AAAA,IAClF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,aAAa,QAA6B;AAChD,WAAO,GAAG,OAAO,GAAG,IAAI,OAAO,QAAQ;AAAA,EACzC;AAAA,EAEQ,gBAAgB,KAAsC;AA33DhE;AA43DI,UAAM,WAAU,gBAAK,kBAAL,mBAAoB,iBAApB,YAAoC,CAAC;AACrD,WAAO,QAAQ,KAAK,OAAK,KAAK,aAAa,CAAC,MAAM,GAAG;AAAA,EACvD;AAAA;AAAA,EAGQ,kBAA8B;AACpC,WAAO;AAAA,MACL,KAAK,KAAK;AAAA,MACV,UAAU,QAAM,KAAK,cAAc,EAAE;AAAA,MACrC,aAAa,OAAO,QAAQ,SAAS,UAAU;AAr4DrD;AAs4DQ,gBAAM,UAAK,kBAAL,mBAAoB,YAAY,QAAQ,SAAS;AAAA,MACzD;AAAA,MACA,oBAAoB,CAAC,QAAQ,QAAQ;AACnC,YAAI,CAAC,OAAO,SAAS,CAAC,KAAK,WAAW;AACpC,iBAAO,QAAQ,QAAQ,KAAK;AAAA,QAC9B;AACA,aAAK,UAAU,mBAAmB,OAAO,OAAO,GAAG;AACnD,eAAO,QAAQ,QAAQ,IAAI;AAAA,MAC7B;AAAA,MACA,oBAAoB,CAAC,QAAQ,OAAO,OAAO,eAAe;AACxD,YAAI,CAAC,OAAO,SAAS,CAAC,KAAK,WAAW;AACpC,iBAAO,QAAQ,QAAQ,KAAK;AAAA,QAC9B;AACA,cAAM,IAAK,SAAS,KAAM;AAC1B,cAAM,IAAK,SAAS,IAAK;AACzB,cAAM,IAAI,QAAQ;AAClB,aAAK,UAAU,mBAAmB,OAAO,OAAO,OAAO,GAAG,GAAG,GAAG,UAAU;AAC1E,eAAO,QAAQ,QAAQ,IAAI;AAAA,MAC7B;AAAA,MACA,YAAY,SAAO,KAAK,gBAAgB,GAAG;AAAA,MAC3C,WAAW,KAAK;AAAA,MAChB,cAAc,YAAO;AA35D3B;AA25D8B,gCAAK,iBAAL,mBAAmB,aAAa,YAAhC,YAA2C;AAAA;AAAA,MACnE,YAAY,CAAC,IAAI,OAAO,KAAK,WAAW,IAAI,EAAE;AAAA,MAC9C,cAAc,OAAK,KAAK,aAAa,CAAqB;AAAA,MAC1D,mBAAmB,CAAC,QAAQ,WAAW,KAAK,kBAAkB,QAAQ,MAAM;AAAA,MAC5E,aAAa,MAAM,KAAK;AAAA,IAC1B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAc,kBAAkB,QAAqB,QAAqC;AACxF,WAAO,eAAe,OAAO;AAC7B,QAAI,OAAO,SAAS;AAClB,YAAM,aAAS,+BAAiB,OAAO,YAAY,OAAO,eAAe,CAAC;AAC1E,YAAM,KAAK,oBAAoB,QAAQ,MAAM,OAAO,QAAQ,SAAY,OAAO,OAAO;AAAA,IACxF,OAAO;AACL,YAAM,KAAK,oBAAoB,QAAQ,KAAK;AAAA,IAC9C;AACA,SAAK,IAAI;AAAA,MACP,sBAAsB,OAAO,GAAG,wBAAmB,OAAO,YAAY,gBACtD,OAAO,UAAU,WAAW,OAAO,UAAU;AAAA,IAC/D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAc,cAAc,QAAgB,WAAqD;AAC/F,QAAI,CAAC,KAAK,eAAe;AACvB,WAAK,gBAAgB,IAAI,oCAAc,KAAK,gBAAgB,CAAC;AAAA,IAC/D;AACA,UAAM,WAAW,MAAM,KAAK,cAAc,QAAQ,QAAQ,SAAS;AAGnE,UAAM,aAAa,KAAK,cAAc,cAAc;AACpD,UAAM,KAAK,cAAc,qBAAqB;AAAA,MAC5C,KAAK;AAAA,MACL,KAAK;AAAA,IACP,CAAC;AACD,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,mBAAmB,QAAqB,MAA6B;AAr9DrF;AAs9DI,QAAI,CAAC,KAAK,kBAAkB,CAAC,KAAK,cAAc;AAC9C;AAAA,IACF;AAEA,UAAM,SAAS,KAAK,aAAa,aAAa,MAAM;AACpD,UAAM,KAAK,KAAK;AAGhB,UAAM,CAAC,YAAY,aAAa,YAAY,OAAO,IAAI,MAAM,QAAQ,IAAI;AAAA,MACvE,KAAK,cAAc,GAAG,EAAE,IAAI,MAAM,gBAAgB;AAAA,MAClD,KAAK,cAAc,GAAG,EAAE,IAAI,MAAM,qBAAqB;AAAA,MACvD,KAAK,cAAc,GAAG,EAAE,IAAI,MAAM,mBAAmB;AAAA,MACrD,KAAK,cAAc,GAAG,EAAE,IAAI,MAAM,2BAA2B;AAAA,IAC/D,CAAC;AAID,QAAI;AACJ,UAAM,YAAW,YAAO,iBAAP,YAAuB;AACxC,QAAI,WAAW,GAAG;AAChB,YAAM,WAA8F,CAAC;AACrG,eAAS,IAAI,GAAG,IAAI,UAAU,KAAK;AACjC,iBAAS;AAAA,UACP,QAAQ,IAAI;AAAA,YACV,KAAK,cAAc,GAAG,EAAE,IAAI,MAAM,aAAa,CAAC,QAAQ;AAAA,YACxD,KAAK,cAAc,GAAG,EAAE,IAAI,MAAM,aAAa,CAAC,aAAa;AAAA,UAC/D,CAAC;AAAA,QACH;AAAA,MACF;AACA,YAAM,aAAa,MAAM,QAAQ,IAAI,QAAQ;AAC7C,iBAAW,WAAW,IAAI,CAAC,CAAC,UAAU,SAAS,OAAO;AAAA,QACpD,OAAO,QAAO,qCAAU,SAAQ,WAAW,SAAS,MAAM;AAAA,QAC1D,YAAY,QAAO,uCAAW,SAAQ,WAAW,UAAU,MAAM;AAAA,MACnE,EAAE;AAAA,IACJ;AAEA,UAAM,WAA0B;AAAA,MAC9B;AAAA,MACA,QAAO,yCAAY,SAAQ;AAAA,MAC3B,YAAY,QAAO,2CAAa,SAAQ,WAAW,YAAY,MAAM;AAAA,MACrE,UAAU,QAAO,yCAAY,SAAQ,WAAW,WAAW,MAAM;AAAA,MACjE,kBAAkB,QAAO,mCAAS,SAAQ,WAAW,QAAQ,MAAM;AAAA,MACnE;AAAA,MACA,SAAS,KAAK,IAAI;AAAA,IACpB;AAEA,SAAK,eAAe,aAAa,OAAO,KAAK,OAAO,UAAU,QAAQ;AACtE,SAAK,IAAI,KAAK,0BAA0B,IAAI,SAAS,OAAO,IAAI,EAAE;AAGlE,SAAK,oBAAoB,QAAQ,KAAK,cAAe,WAAW,CAAC;AAAA,EACnE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,sBAAsB,QAAqB,KAAyC;AAChG,QAAI,CAAC,KAAK,kBAAkB,CAAC,KAAK,eAAe;AAC/C;AAAA,IACF;AAEA,UAAM,MAAM,SAAS,OAAO,GAAG,GAAG,EAAE;AACpC,QAAI,MAAM,GAAG;AACX;AAAA,IACF;AAEA,UAAM,QAAQ,KAAK,eAAe,aAAa,OAAO,KAAK,OAAO,QAAQ;AAC1E,UAAM,OAAO,MAAM,MAAM,CAAC;AAC1B,QAAI,CAAC,MAAM;AACT,WAAK,IAAI,KAAK,wBAAwB,GAAG,kBAAkB,OAAO,IAAI,EAAE;AACxE;AAAA,IACF;AAEA,SAAK,IAAI,KAAK,6BAA6B,KAAK,IAAI,SAAS,OAAO,IAAI,EAAE;AAG1E,UAAM,KAAK,cAAc,YAAY,QAAQ,SAAS,KAAK,KAAK;AAChE,QAAI,KAAK,OAAO;AACd,YAAM,KAAK,cAAc,YAAY,QAAQ,cAAc,KAAK,UAAU;AAC1E,UAAI,KAAK,mBAAmB,GAAG;AAC7B,cAAM,KAAK,cAAc,YAAY,QAAQ,oBAAoB,KAAK,gBAAgB;AAAA,MACxF,OAAO;AACL,cAAM,KAAK,cAAc,YAAY,QAAQ,YAAY,KAAK,QAAQ;AAAA,MACxE;AAGA,UAAI,KAAK,YAAY,KAAK,SAAS,SAAS,GAAG;AAC7C,iBAAS,IAAI,GAAG,IAAI,KAAK,SAAS,QAAQ,KAAK;AAC7C,gBAAM,MAAM,KAAK,SAAS,CAAC;AAC3B,gBAAM,KAAK,cAAc,YAAY,QAAQ,gBAAgB,CAAC,IAAI,IAAI,KAAK;AAC3E,gBAAM,KAAK,cAAc,YAAY,QAAQ,qBAAqB,CAAC,IAAI,IAAI,UAAU;AAAA,QACvF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,qBAAqB,QAAqB,MAAoB;AACpE,QAAI,CAAC,KAAK,gBAAgB;AACxB;AAAA,IACF;AAEA,QAAI,KAAK,eAAe,eAAe,OAAO,KAAK,OAAO,UAAU,IAAI,GAAG;AACzE,WAAK,IAAI,KAAK,4BAA4B,IAAI,SAAS,OAAO,IAAI,EAAE;AAEpE,WAAK,oBAAoB,QAAQ,KAAK,cAAe,WAAW,CAAC;AAAA,IACnE,OAAO;AACL,WAAK,IAAI,KAAK,mBAAmB,IAAI,mBAAmB,OAAO,IAAI,EAAE;AAAA,IACvE;AAAA,EACF;AAAA;AAAA,EAGA,OAAwB,iBAAiB;AAAA,IACvC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA;AAAA,EAGA,OAAwB,mBAA2C;AAAA,IACjE,YAAY;AAAA,IACZ,UAAU;AAAA,IACV,UAAU;AAAA,IACV,eAAe;AAAA,IACf,OAAO;AAAA,IACP,UAAU;AAAA,IACV,kBAAkB;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAc,wBAAwB,QAAqB,QAAgB,gBAAuC;AA1mEpH;AA2mEI,QAAI,CAAC,KAAK,eAAe;AACvB;AAAA,IACF;AACA,UAAM,YAAY,GAAG,OAAO,GAAG,IAAI,OAAO,QAAQ;AAClD,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,QAAO,UAAK,mBAAmB,IAAI,SAAS,MAArC,YAA0C;AACvD,QAAI,MAAM,OAAO,KAAM;AACrB,WAAK,IAAI,MAAM,oCAAoC,OAAO,IAAI,oBAAe,MAAM,IAAI,QAAQ;AAC/F,YAAM,KAAK,cAAc,gBAAgB,EAAE,KAAK,OAAO,KAAK,KAAK,CAAC;AAClE;AAAA,IACF;AACA,SAAK,mBAAmB,IAAI,WAAW,GAAG;AAC1C,UAAM,OAAO,KAAK,cAAc,oBAAoB,SAAQ,UAAK,YAAL,YAAgB,SAAS;AACrF,UAAM,WAAW,GAAG,KAAK,SAAS,IAAI,MAAM;AAC5C,UAAM,KAAK,cAAc,UAAU;AAAA,MACjC,KAAK,KAAK,UAAU,MAAM,MAAM,CAAC;AAAA,MACjC,KAAK;AAAA,IACP,CAAC;AACD,UAAM,KAAK,cAAc,gBAAgB,EAAE,KAAK,OAAO,KAAK,KAAK,CAAC;AAClE,SAAK,IAAI,KAAK,4BAA4B,OAAO,IAAI,KAAK,OAAO,GAAG,GAAG;AAAA,EACzE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAc,+BACZ,QACA,IACA,aACA,KACe;AAhpEnB;AAipEI,QAAI,CAAC,KAAK,eAAe;AACvB;AAAA,IACF;AACA,UAAM,MAAM,MAAM,KAAK,eAAe,EAAE;AACxC,UAAM,WAAU,gCAAK,WAAL,mBAAa;AAC7B,UAAM,eAAc,gCAAK,WAAL,mBAAa;AACjC,QAAI,OAAO,YAAY,YAAY,OAAO,gBAAgB,UAAU;AAClE,UAAI;AACF,cAAM,KAAK,cAAc,sBAAsB,QAAQ,SAAS,aAAa,GAAG;AAChF,cAAM,KAAK,cAAc,IAAI,EAAE,KAAK,KAAK,KAAK,CAAC;AAAA,MACjD,SAAS,KAAK;AACZ,aAAK,IAAI,KAAK,sBAAsB,OAAO,IAAI,SAAK,yBAAW,GAAG,CAAC,EAAE;AAAA,MACvE;AAAA,IACF,OAAO;AACL,WAAK,IAAI,MAAM,2BAA2B,WAAW,EAAE;AAAA,IACzD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAc,sBAAsB,QAAgB,eAAsC;AACxF,QAAI,EAAE,iBAAiB,aAAa,mBAAmB;AACrD;AAAA,IACF;AACA,UAAM,cAAc,aAAa,iBAAiB,aAAa;AAC/D,UAAM,KAAK,mBAAmB,QAAQ,WAAW;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAc,mBAAmB,QAAgB,MAA6B;AAC5E,UAAM,QAAQ;AAAA,MACZ,aAAa,eAAe,OAAO,OAAK,MAAM,IAAI,EAAE,IAAI,OAAM,aAAY;AACxE,cAAM,UAAU,GAAG,KAAK,SAAS,IAAI,MAAM,IAAI,QAAQ;AACvD,cAAM,UAAU,MAAM,KAAK,cAAc,OAAO;AAChD,aAAI,mCAAS,QAAO,QAAQ,QAAQ,OAAO,QAAQ,QAAQ,GAAG;AAC5D,gBAAM,KAAK,cAAc,SAAS,EAAE,KAAK,KAAK,KAAK,KAAK,CAAC;AAAA,QAC3D;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAEA,IAAI,QAAQ,SAAS,QAAQ;AAC3B,SAAO,UAAU,CAAC,YAAuD,IAAI,aAAa,OAAO;AACnG,OAAO;AACL,GAAC,MAAM,IAAI,aAAa,GAAG;AAC7B;",
6
6
  "names": ["_a"]
7
7
  }
package/io-package.json CHANGED
@@ -1,8 +1,34 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "govee-smart",
4
- "version": "2.2.0",
4
+ "version": "2.3.1",
5
5
  "news": {
6
+ "2.3.1": {
7
+ "en": "Test coverage: smoke tests for GoveeCloudClient + GoveeMqttClient. Initial-state and setter checks now in place; full mock-based test paths come in a later release.",
8
+ "de": "Test-Coverage: Smoke-Tests für GoveeCloudClient + GoveeMqttClient. Initial-State + Setter-Checks drin; volle Mock-basierte Test-Pfade kommen separat.",
9
+ "ru": "Покрытие тестами: smoke-тесты для GoveeCloudClient + GoveeMqttClient. Initial-state и setter-проверки добавлены; полные mock-пути позже.",
10
+ "pt": "Cobertura de testes: smoke tests para GoveeCloudClient + GoveeMqttClient. Initial-state e setter-checks adicionados; caminhos completos com mock serao em release separado.",
11
+ "nl": "Test-coverage: smoke tests voor GoveeCloudClient + GoveeMqttClient. Initial-state en setter-checks toegevoegd; volle mock-paths komen apart.",
12
+ "fr": "Couverture de tests : smoke tests pour GoveeCloudClient + GoveeMqttClient. Initial-state et setter-checks ajoutes; chemins complets avec mocks plus tard.",
13
+ "it": "Test coverage: smoke tests per GoveeCloudClient + GoveeMqttClient. Controlli initial-state e setter aggiunti; percorsi completi con mock separati.",
14
+ "es": "Cobertura de tests: smoke tests para GoveeCloudClient + GoveeMqttClient. Initial-state y setter-checks agregados; rutas completas con mocks por separado.",
15
+ "pl": "Pokrycie testami: smoke testy dla GoveeCloudClient + GoveeMqttClient. Initial-state i setter-checks dodane; pelne sciezki mock w osobnym release.",
16
+ "uk": "Покриття тестами: smoke-тести для GoveeCloudClient + GoveeMqttClient. Initial-state та setter-перевірки додані; повні mock-шляхи окремо.",
17
+ "zh-cn": "测试覆盖:GoveeCloudClient + GoveeMqttClient 的 smoke 测试。初始状态和 setter 检查已加入;完整 mock 路径将在后续发布。"
18
+ },
19
+ "2.3.0": {
20
+ "en": "App-version drift monitor: daily iTunes lookup warns when local Govee-App-Version constant gets too stale. Plus code-hygiene: onStateChange handlers split out, timing-constants module.",
21
+ "de": "App-Version-Drift-Monitor: täglicher iTunes-Lookup warnt wenn lokale Govee-App-Version zu alt ist. Plus Code-Hygiene: onStateChange-Handler aufgeteilt, timing-constants-Modul.",
22
+ "ru": "Монитор дрифта App-версии: ежедневная проверка через iTunes warnt при устаревании локальной Govee-App-Version. Плюс гигиена кода: onStateChange-handlers выделены.",
23
+ "pt": "Monitor de drift da App-version: lookup diario no iTunes alerta quando a Govee-App-Version local fica obsoleta. Plus higiene de codigo: onStateChange handlers separados.",
24
+ "nl": "App-version drift monitor: dagelijkse iTunes-lookup waarschuwt als lokale Govee-App-Version verouderd is. Plus code-hygiene: onStateChange handlers uitgesplitst.",
25
+ "fr": "Moniteur de drift App-version : lookup iTunes quotidien alerte quand la Govee-App-Version locale devient obsolete. Plus hygiene de code : onStateChange handlers separes.",
26
+ "it": "Monitor drift App-version: lookup iTunes giornaliero avvisa quando la Govee-App-Version locale diventa obsoleta. Plus igiene del codice: onStateChange handlers separati.",
27
+ "es": "Monitor drift App-version: lookup diario en iTunes avisa cuando la Govee-App-Version local se queda obsoleta. Plus higiene de codigo: onStateChange handlers separados.",
28
+ "pl": "Monitor driftu App-version: codzienny lookup iTunes ostrzega gdy lokalna Govee-App-Version jest przestarzala. Plus higiena kodu: onStateChange handlers wydzielone.",
29
+ "uk": "Монітор дрейфу App-версії: щоденний iTunes-лукап попереджає коли локальна Govee-App-Version застаріла. Плюс гігієна коду: onStateChange handlers виділені.",
30
+ "zh-cn": "App 版本漂移监控:每日 iTunes 查询,本地 Govee 应用版本过旧时发出警告。代码卫生:onStateChange handlers 拆分。"
31
+ },
6
32
  "2.2.0": {
7
33
  "en": "Quality release: 2FA no longer restarts, MQTT pushes type-safe, sensors in right channel. Ready-log shows channel status, persistent UDP sockets, abortable HTTP, no more group-WARN spam.",
8
34
  "de": "Quality-Release: 2FA ohne Restart, MQTT-Pushes typsicher, Sensoren im richtigen Kanal. Ready-Log mit Channel-Status, persistente UDP-Sockets, abbrechbares HTTP, kein Group-WARN-Spam.",
@@ -67,32 +93,6 @@
67
93
  "pl": "Fix bezpieczenstwa: w v2.1.0 logowanie MQTT bylo bez szyfrowania przez pomylke — teraz szyfrowane. Diagnostyka przemianowana na diag.*. Ostrzezenie 2FA nie powtarza sie juz.",
68
94
  "uk": "Фікс безпеки: у v2.1.0 MQTT-логін помилково зберігався незашифрованим — тепер шифрується. Діагностика перейменована на diag.export/result/tier. 2FA-warn не повторюється при кожному реконекті.",
69
95
  "zh-cn": "安全修复:v2.1.0 中 MQTT 登录信息因错误而未加密保存 — 现已加密。诊断数据点改名为 diag.export/result/tier。2FA 警告不再在每次重连时重复。"
70
- },
71
- "2.1.0": {
72
- "en": "Govee accounts that need email verification can now be used. Sensors no longer stuck offline (e.g. H5179 thermometer). Scene dropdowns visible from the first start. New diag.tier per device.",
73
- "de": "Govee-Konten mit Mail-Verifizierung lassen sich jetzt nutzen. Sensoren bleiben nicht mehr offline (z.B. H5179). Szenen-Dropdown ab erstem Start sichtbar. Neuer diag.tier pro Geraet.",
74
- "ru": "Аккаунты Govee с email-верификацией теперь работают. Сенсоры не зависают как offline (напр. H5179). Дропдауны сцен видны с первого старта. Новый diag.tier на устройство.",
75
- "pt": "Contas Govee com verificacao por email agora funcionam. Sensores nao ficam offline por engano (ex. H5179). Dropdowns de cenas visiveis no primeiro start. Novo diag.tier por dispositivo.",
76
- "nl": "Govee-accounts met e-mailverificatie werken nu. Sensoren blijven niet meer ten onrechte offline (bv. H5179). Scene-dropdowns zichtbaar vanaf de eerste start. Nieuwe diag.tier per apparaat.",
77
- "fr": "Les comptes Govee avec verification email fonctionnent maintenant. Capteurs plus bloques offline (ex. H5179). Listes de scenes visibles au premier demarrage. Nouveau diag.tier par appareil.",
78
- "it": "Gli account Govee con verifica email ora funzionano. Sensori non restano piu offline per errore (es. H5179). Dropdown scene visibili al primo avvio. Nuovo diag.tier per dispositivo.",
79
- "es": "Cuentas Govee con verificacion email ya funcionan. Sensores ya no se quedan offline por error (p.ej. H5179). Desplegables de escenas visibles al primer arranque. Nuevo diag.tier por dispositivo.",
80
- "pl": "Konta Govee z weryfikacja email mozna teraz uzywac. Czujniki nie zostaja juz blednie offline (np. termometr H5179). Dropdowny scen widoczne od pierwszego startu. Nowy diag.tier na urzadzenie.",
81
- "uk": "Облікові записи Govee з email-верифікацією тепер працюють. Сенсори не залишаються офлайн помилково (напр. H5179). Випадаючі списки сцен видно з першого старту. Новий diag.tier на пристрій.",
82
- "zh-cn": "需要邮件验证的 Govee 账户现在可以使用。传感器不再错误地卡在离线状态(如 H5179 温度计)。场景下拉菜单从首次启动起就可见。每设备新增 diag.tier。"
83
- },
84
- "2.0.3": {
85
- "en": "Min js-controller >=6.0.11, admin >=7.6.20 (correcting an accidental bump in 2.0.2).",
86
- "de": "Min js-controller >=6.0.11, admin >=7.6.20 (korrigiert einen versehentlichen Bump in 2.0.2).",
87
- "ru": "Мин. js-controller >=6.0.11, admin >=7.6.20 (исправлен случайный бамп в 2.0.2).",
88
- "pt": "Min js-controller >=6.0.11, admin >=7.6.20 (corrige um bump acidental em 2.0.2).",
89
- "nl": "Min js-controller >=6.0.11, admin >=7.6.20 (corrigeert een onbedoelde verhoging in 2.0.2).",
90
- "fr": "Min js-controller >=6.0.11, admin >=7.6.20 (corrige une montee accidentelle en 2.0.2).",
91
- "it": "Min js-controller >=6.0.11, admin >=7.6.20 (corregge un bump accidentale in 2.0.2).",
92
- "es": "Min js-controller >=6.0.11, admin >=7.6.20 (corrige un bump accidental en 2.0.2).",
93
- "pl": "Min js-controller >=6.0.11, admin >=7.6.20 (poprawia przypadkowy bump w 2.0.2).",
94
- "uk": "Мін. js-controller >=6.0.11, admin >=7.6.20 (виправлено випадковий bump у 2.0.2).",
95
- "zh-cn": "最低 js-controller >=6.0.11,admin >=7.6.20(修正 2.0.2 中的意外提升)。"
96
96
  }
97
97
  },
98
98
  "titleLang": {
@@ -304,6 +304,20 @@
304
304
  },
305
305
  "native": {}
306
306
  },
307
+ {
308
+ "_id": "info.appVersionDrift",
309
+ "type": "state",
310
+ "common": {
311
+ "name": "Govee App Version drift",
312
+ "type": "string",
313
+ "role": "text",
314
+ "read": true,
315
+ "write": false,
316
+ "def": "",
317
+ "desc": "Comparison of the local GOVEE_APP_VERSION constant vs the latest iOS App Store version. 'current' / 'minor drift' / 'STALE'."
318
+ },
319
+ "native": {}
320
+ },
307
321
  {
308
322
  "_id": "devices",
309
323
  "type": "folder",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.govee-smart",
3
- "version": "2.2.0",
3
+ "version": "2.3.1",
4
4
  "description": "Control Govee WiFi devices via LAN, MQTT and Cloud.",
5
5
  "author": {
6
6
  "name": "krobi",