iobroker.govee-smart 2.1.3 → 2.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -124,6 +124,10 @@ This adapter's MQTT authentication and BLE-over-LAN (ptReal) protocol implementa
124
124
  ---
125
125
 
126
126
  ## Changelog
127
+ ### 2.1.4 (2026-05-03)
128
+
129
+ - Online status correct again after adapter restart — lights flip to online with the first LAN scan, sensors with the first cloud poll (5 s after start instead of 2 minutes).
130
+
127
131
  ### 2.1.3 (2026-05-03)
128
132
 
129
133
  - Critical fix: no more restart-loop after entering the verification code. The cached login is now stored in a state, not in the adapter config — saving the config doesn't trigger a restart anymore.
@@ -157,10 +161,6 @@ This adapter's MQTT authentication and BLE-over-LAN (ptReal) protocol implementa
157
161
  - The Refresh Cloud Data button reloads the scene / music / DIY libraries again (had been skipped since v1.10.1).
158
162
  - Min js-controller `>=7.0.7`, min admin `>=7.7.22`.
159
163
 
160
- ### 2.0.3 (2026-04-26)
161
-
162
- - Min js-controller `>=6.0.11`, admin `>=7.6.20` (correcting an accidental bump in 2.0.2).
163
-
164
164
  Older entries are in [CHANGELOG_OLD.md](CHANGELOG_OLD.md).
165
165
 
166
166
  ## Support
@@ -701,7 +701,7 @@ class DeviceManager {
701
701
  * @param lanDevice Discovered LAN device
702
702
  */
703
703
  handleLanDiscovery(lanDevice) {
704
- var _a, _b;
704
+ var _a, _b, _c;
705
705
  let matched;
706
706
  for (const dev of this.devices.values()) {
707
707
  if ((0, import_types.normalizeDeviceId)(dev.deviceId) === (0, import_types.normalizeDeviceId)(lanDevice.device)) {
@@ -715,6 +715,7 @@ class DeviceManager {
715
715
  }
716
716
  if (matched) {
717
717
  const ipChanged = matched.lanIp !== lanDevice.ip;
718
+ const wasOffline = matched.state.online !== true;
718
719
  matched.lanIp = lanDevice.ip;
719
720
  matched.channels.lan = true;
720
721
  matched.lastSeenOnNetwork = Date.now();
@@ -722,6 +723,10 @@ class DeviceManager {
722
723
  this.log.debug(`LAN: ${matched.name} (${matched.sku}) at ${lanDevice.ip}`);
723
724
  (_a = this.onLanIpChanged) == null ? void 0 : _a.call(this, matched, lanDevice.ip);
724
725
  }
726
+ if (wasOffline) {
727
+ matched.state.online = true;
728
+ (_b = this.onDeviceUpdate) == null ? void 0 : _b.call(this, matched, { online: true });
729
+ }
725
730
  } else {
726
731
  const shortId = (0, import_types.normalizeDeviceId)(lanDevice.device).slice(-4);
727
732
  const device = {
@@ -746,7 +751,7 @@ class DeviceManager {
746
751
  this.diagnostics.addLog(lanDevice.device, "info", `LAN-discovered at ${lanDevice.ip}`);
747
752
  this.log.debug(`LAN: New device ${lanDevice.sku} at ${lanDevice.ip}`);
748
753
  this.maybeNudgeSeedSku(lanDevice.sku, device.name);
749
- (_b = this.onDeviceListChanged) == null ? void 0 : _b.call(this, this.getDevices());
754
+ (_c = this.onDeviceListChanged) == null ? void 0 : _c.call(this, this.getDevices());
750
755
  }
751
756
  }
752
757
  /**
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/device-manager.ts"],
4
- "sourcesContent": ["import { hasDynamicSceneCapability } from \"./capability-mapper\";\nimport { CommandRouter } from \"./command-router\";\nimport { getDeviceQuirks, getDeviceTier, isSeedAndDormant } from \"./device-registry\";\nimport { DiagnosticsCollector } from \"./diagnostics\";\nimport type { AppDeviceEntry, GoveeApiClient } from \"./govee-api-client\";\nimport type { GoveeCloudClient } from \"./govee-cloud-client\";\nimport type { GoveeLanClient } from \"./govee-lan-client\";\nimport type { RateLimiter } from \"./rate-limiter\";\nimport type { CachedDeviceData, SkuCache } from \"./sku-cache\";\nimport {\n classifyError,\n normalizeDeviceId,\n rgbToHex,\n type CloudDevice,\n type CloudLoadResult,\n type CloudStateCapability,\n type DeviceState,\n type ErrorCategory,\n type GoveeDevice,\n type LanDevice,\n type MqttStatusUpdate,\n type TimerAdapter,\n} from \"./types\";\nimport { HttpError } from \"./http-client\";\n\n/** Parsed per-segment data from MQTT BLE packets */\nexport interface MqttSegmentData {\n /** Segment index (0-based) */\n index: number;\n /** Per-segment brightness 0-100 */\n brightness: number;\n /** Red channel 0-255 */\n r: number;\n /** Green channel 0-255 */\n g: number;\n /** Blue channel 0-255 */\n b: number;\n}\n\n/**\n * Parse AA A5 BLE notification packets from MQTT op.command.\n * 5 packets \u00D7 4 segment slots = max 20 segments per push. The device sends\n * exactly as many packets as it has physical segments \u2014 so parsing out all\n * slots (and filtering empty-slot padding) gives us a reliable count of\n * what actually exists on the strip.\n *\n * Format per slot: [Brightness 0-100] [R] [G] [B].\n *\n * An \"empty\" slot (brightness = 0 AND r = g = b = 0) is treated as padding\n * in a partially-filled final packet, not as a real unlit segment \u2014 this\n * matters for devices that don't pad their last packet to 4 slots.\n *\n * @param commands Base64-encoded BLE packets from MQTT op.command\n */\nexport function parseMqttSegmentData(commands: string[]): MqttSegmentData[] {\n if (!Array.isArray(commands)) {\n return [];\n }\n\n const segments: MqttSegmentData[] = [];\n // Track the highest packetNum seen so we know where real data ends.\n let highestPacket = 0;\n\n for (const cmd of commands) {\n if (typeof cmd !== \"string\") {\n continue;\n }\n const bytes = Buffer.from(cmd, \"base64\");\n if (bytes.length < 20 || bytes[0] !== 0xaa || bytes[1] !== 0xa5) {\n continue;\n }\n\n const packetNum = bytes[2];\n if (packetNum < 1 || packetNum > 5) {\n continue;\n }\n if (packetNum > highestPacket) {\n highestPacket = packetNum;\n }\n\n const baseIndex = (packetNum - 1) * 4;\n for (let slot = 0; slot < 4; slot++) {\n const segIdx = baseIndex + slot;\n const offset = 3 + slot * 4;\n segments.push({\n index: segIdx,\n brightness: bytes[offset],\n r: bytes[offset + 1],\n g: bytes[offset + 2],\n b: bytes[offset + 3],\n });\n }\n }\n\n // Trim trailing padding slots from the highest packet: Govee pads short\n // packets with 0x00 bytes, so a run of all-zero slots at the end is not\n // real segment data but filler. Keep any zero-slots that are followed by\n // a real one \u2014 they're legitimately-unlit middle segments.\n while (segments.length > 0) {\n const tail = segments[segments.length - 1];\n if (tail.brightness === 0 && tail.r === 0 && tail.g === 0 && tail.b === 0) {\n segments.pop();\n } else {\n break;\n }\n }\n\n return segments;\n}\n\n/**\n * Effective physical segment indices for a device.\n * Uses `device.manualSegments` when `device.manualMode=true` (cut strip override),\n * falls back to `0..segmentCount-1` otherwise. Empty if device has no segments.\n *\n * @param device Target device\n */\nexport function getEffectiveSegmentIndices(device: GoveeDevice): number[] {\n if (device.manualMode && Array.isArray(device.manualSegments) && device.manualSegments.length > 0) {\n return device.manualSegments.slice();\n }\n const count = device.segmentCount ?? 0;\n if (count <= 0) {\n return [];\n }\n return Array.from({ length: count }, (_, i) => i);\n}\n\n/**\n * Resolve the authoritative segment count for a device.\n *\n * Priority:\n * 1. `device.segmentCount` if already set (from cache, MQTT discovery, or wizard)\n * 2. Minimum of positive `segment_color_setting` capability counts\n * 3. 0 if no capability advertises segments\n *\n * Why `min` over the capability caps: Govee reports `segmentedBrightness` and\n * `segmentedColorRgb` separately, and on at least one SKU (H70D1) those two\n * disagree \u2014 brightness says 10, colorRgb says 15, real device has 10.\n * Picking the smaller value is the safer starting point; MQTT discovery can\n * then grow it if the real device pushes more slots.\n *\n * @param device Target device\n */\nexport function resolveSegmentCount(device: GoveeDevice): number {\n if (typeof device.segmentCount === \"number\" && device.segmentCount > 0) {\n return device.segmentCount;\n }\n const caps = Array.isArray(device.capabilities) ? device.capabilities : [];\n let min = Number.POSITIVE_INFINITY;\n for (const c of caps) {\n if (!c || typeof c.type !== \"string\" || !c.type.includes(\"segment_color_setting\")) {\n continue;\n }\n const params = (c as { parameters?: { fields?: unknown[] } }).parameters;\n const fields = Array.isArray(params?.fields) ? params.fields : [];\n for (const f of fields) {\n if (!f || typeof f !== \"object\") {\n continue;\n }\n const fn = (f as { fieldName?: unknown }).fieldName;\n const er = (f as { elementRange?: { max?: unknown } }).elementRange;\n const rawMax = er && typeof er.max === \"number\" ? er.max : -1;\n if (fn === \"segment\" && rawMax >= 0) {\n const n = rawMax + 1;\n if (n > 0 && n < min) {\n min = n;\n }\n }\n }\n }\n return Number.isFinite(min) ? min : 0;\n}\n\n/** Protocol limit: Govee's segment bitmask is 7 bytes \u00D7 8 bits = 56 slots (0..55). */\nexport const SEGMENT_HARD_MAX = 55;\n\n/**\n * Device manager \u2014 maintains unified device list and routes commands\n * through the fastest available channel: LAN \u2192 Cloud.\n * MQTT is status-push only and never used for commands.\n */\nexport class DeviceManager {\n private readonly log: ioBroker.Logger;\n private readonly devices = new Map<string, GoveeDevice>();\n private readonly commandRouter: CommandRouter;\n private readonly diagnostics: DiagnosticsCollector;\n /** SKUs we already nudged about \u2014 log only once per adapter lifetime, per SKU. */\n private readonly nudgedSeedSkus = new Set<string>();\n private cloudClient: GoveeCloudClient | null = null;\n private apiClient: GoveeApiClient | null = null;\n private skuCache: SkuCache | null = null;\n private onDeviceUpdate: ((device: GoveeDevice, state: Partial<DeviceState>) => void) | null = null;\n private onDeviceListChanged: ((devices: GoveeDevice[]) => void) | null = null;\n private onCloudCapabilities: ((device: GoveeDevice, caps: CloudStateCapability[]) => void) | null = null;\n /** Per-source dedup so a Cloud NETWORK error doesn't shadow an App-API one. */\n private lastErrorCategory: ErrorCategory | null = null;\n private lastAppApiErrorCategory: ErrorCategory | null = null;\n\n /**\n * @param log ioBroker logger\n * @param timers Adapter timer wrapper (forwarded to CommandRouter for\n * onUnload-safe delays).\n */\n constructor(log: ioBroker.Logger, timers: TimerAdapter) {\n this.log = log;\n this.commandRouter = new CommandRouter(log, timers);\n this.diagnostics = new DiagnosticsCollector();\n }\n\n /**\n * Expose the diagnostics collector so adapter-side hooks (MQTT,\n * Cloud, log wrapper) can write into the per-device ring buffers.\n */\n getDiagnostics(): DiagnosticsCollector {\n return this.diagnostics;\n }\n\n /**\n * Pull the HTTP status code out of any error shape we know about\n * (HttpError, Govee API responses with `.statusCode` / `.status`).\n * Returns undefined for network errors / generic failures so the\n * diagnostics entry shows \"no status \u2014 likely network/timeout\".\n *\n * @param e Caught error value\n */\n private extractStatus(e: unknown): number | undefined {\n if (e instanceof HttpError) {\n return e.statusCode;\n }\n if (typeof e === \"object\" && e !== null) {\n const x = e as { statusCode?: unknown; status?: unknown };\n if (typeof x.statusCode === \"number\") {\n return x.statusCode;\n }\n if (typeof x.status === \"number\") {\n return x.status;\n }\n }\n return undefined;\n }\n\n /**\n * Register the LAN client\n *\n * @param client LAN UDP client instance\n */\n setLanClient(client: GoveeLanClient): void {\n this.commandRouter.setLanClient(client);\n }\n\n /**\n * Register the undocumented API client for scene/music/DIY libraries\n *\n * @param client API client instance\n */\n setApiClient(client: GoveeApiClient): void {\n this.apiClient = client;\n }\n\n /**\n * Register the Cloud client\n *\n * @param client Cloud API client instance\n */\n setCloudClient(client: GoveeCloudClient): void {\n this.cloudClient = client;\n this.commandRouter.setCloudClient(client);\n }\n\n /**\n * Register the rate limiter for cloud calls\n *\n * @param limiter Rate limiter instance\n */\n setRateLimiter(limiter: RateLimiter): void {\n this.commandRouter.setRateLimiter(limiter);\n }\n\n /**\n * Register the SKU cache for persistent device data\n *\n * @param cache SKU cache instance\n */\n setSkuCache(cache: SkuCache): void {\n this.skuCache = cache;\n }\n\n /**\n * Set callbacks for device state changes and list changes.\n *\n * @param onUpdate Called when a device state changes (from any channel)\n * @param onListChanged Called when the device list changes (new/removed devices)\n */\n setCallbacks(\n onUpdate: (device: GoveeDevice, state: Partial<DeviceState>) => void,\n onListChanged: (devices: GoveeDevice[]) => void,\n ): void {\n this.onDeviceUpdate = onUpdate;\n this.onDeviceListChanged = onListChanged;\n }\n\n /** Get all known devices */\n getDevices(): GoveeDevice[] {\n return Array.from(this.devices.values());\n }\n\n /**\n * Load devices from local SKU cache.\n * Returns true if any devices were loaded (= Cloud not needed).\n */\n loadFromCache(): boolean {\n if (!this.skuCache) {\n return false;\n }\n\n const cached = this.skuCache.loadAll();\n if (cached.length === 0) {\n return false;\n }\n\n let changed = false;\n for (const entry of cached) {\n const key = this.deviceKey(entry.sku, entry.deviceId);\n const existing = this.devices.get(key);\n if (existing) {\n // Merge cached data into LAN-discovered device. Segment-specific\n // fields (segmentCount, manualMode, manualSegments) MUST be merged\n // too \u2014 LAN discovery runs before the cache load on every start, so\n // the existing-branch is the normal path. Missing these three meant\n // every restart threw away the wizard/MQTT-learned segment state and\n // fell back to Cloud's min-advertised count.\n existing.name = entry.name || existing.name;\n existing.type = entry.type || existing.type;\n existing.capabilities = entry.capabilities;\n existing.scenes = entry.scenes;\n existing.diyScenes = entry.diyScenes;\n existing.snapshots = entry.snapshots;\n existing.sceneLibrary = entry.sceneLibrary;\n existing.musicLibrary = entry.musicLibrary;\n existing.diyLibrary = entry.diyLibrary;\n existing.skuFeatures = entry.skuFeatures;\n existing.snapshotBleCmds = entry.snapshotBleCmds;\n existing.scenesChecked = entry.scenesChecked;\n existing.lastSeenOnNetwork = entry.lastSeenOnNetwork;\n existing.segmentCount = entry.segmentCount;\n existing.manualMode = entry.manualMode;\n existing.manualSegments = entry.manualSegments;\n existing.channels.cloud = entry.capabilities.length > 0;\n changed = true;\n } else {\n this.devices.set(key, this.cachedToGoveeDevice(entry));\n changed = true;\n }\n }\n\n if (changed) {\n this.log.info(`Loaded ${cached.length} device(s) from cache`);\n }\n\n // Always refetch cloud data on startup \u2014 scenesChecked is purely diagnostic\n // now, not a gate. Snapshots are user-content (created dynamically in the\n // Govee Home app) and would miss new entries if we relied solely on the\n // cache. The refetch costs one call per light device per startup, well\n // within rate limits. Users can also trigger a fresh fetch without\n // restart via `info.refresh_cloud_data`.\n const hasLight = Array.from(this.devices.values()).some(d => d.type === \"devices.types.light\");\n if (hasLight) {\n this.log.debug(\"Cache loaded \u2014 will refresh scenes/snapshots via Cloud\");\n return false;\n }\n\n // Fill scenes from sceneLibrary for devices where Cloud scenes are missing\n for (const device of this.devices.values()) {\n this.populateScenesFromLibrary(device);\n }\n\n if (changed) {\n this.onDeviceListChanged?.(this.getDevices());\n }\n return cached.length > 0;\n }\n\n /**\n * Load devices from Cloud API and save to cache.\n * Only called when cache is empty (first start) or manual refresh.\n */\n async loadFromCloud(): Promise<CloudLoadResult> {\n if (!this.cloudClient) {\n return { ok: false, reason: \"transient\" };\n }\n\n try {\n const rawCloudDevices = await this.cloudClient.getDevices();\n\n // Hard-filter: Govee's Device-List API returns historical/stale entries\n // (deleted devices that are no longer in the app). Filter out entries\n // without capabilities \u2014 those are almost certainly stale registrations.\n const cloudDevices = Array.isArray(rawCloudDevices)\n ? rawCloudDevices.filter(\n cd =>\n cd &&\n typeof cd.sku === \"string\" &&\n typeof cd.device === \"string\" &&\n Array.isArray(cd.capabilities) &&\n cd.capabilities.length > 0,\n )\n : [];\n\n if (Array.isArray(rawCloudDevices) && rawCloudDevices.length !== cloudDevices.length) {\n this.log.info(\n `Cloud: received ${rawCloudDevices.length} devices raw, ${cloudDevices.length} after filter (skipped stale entries without capabilities)`,\n );\n }\n\n // Step 1: Merge Cloud devices into local device map\n let changed = this.mergeCloudDevices(cloudDevices);\n\n // Step 2: Load scenes, snapshots, and libraries for any device that\n // exposes a `dynamic_scene` capability \u2014 independent of `cd.type`.\n // Govee occasionally returns devices with `type` missing or a value\n // we don't recognise; keying off the capability is what the rest of\n // the codebase already uses to decide whether scene/snapshot states\n // exist, so the loader has to follow the same rule.\n for (const cd of cloudDevices) {\n const caps = Array.isArray(cd.capabilities) ? cd.capabilities : [];\n const hasSceneCap =\n hasDynamicSceneCapability(caps, \"lightScene\") ||\n hasDynamicSceneCapability(caps, \"diyScene\") ||\n hasDynamicSceneCapability(caps, \"snapshot\");\n const isLight = cd.type === \"devices.types.light\" || hasSceneCap;\n if (isLight) {\n const device = this.devices.get(this.deviceKey(cd.sku, cd.device));\n if (device) {\n if (await this.loadDeviceScenes(device, cd)) {\n changed = true;\n }\n if (await this.loadDeviceLibraries(device, cd.sku)) {\n changed = true;\n }\n // Mark scenes as checked regardless of result \u2014 empty is legitimate,\n // and we've now confirmed that via Cloud. Prevents refetch loop.\n device.scenesChecked = true;\n }\n }\n }\n\n // Step 3: Prune stale cache entries (only after successful Cloud-load\n // with a plausible response \u2014 never prune on Cloud failure or empty list)\n if (this.skuCache && cloudDevices.length > 0) {\n this.skuCache.pruneStale(14);\n }\n\n // Step 4: Save to cache and finalize\n this.saveDevicesToCache();\n\n for (const device of this.devices.values()) {\n this.populateScenesFromLibrary(device);\n }\n\n if (changed) {\n this.onDeviceListChanged?.(this.getDevices());\n }\n this.lastErrorCategory = null;\n return { ok: true };\n } catch (err) {\n this.logDedup(\"Cloud device list failed\", err);\n\n // Govee 429: respect Retry-After header (default 60s if missing)\n if (err instanceof HttpError && err.statusCode === 429) {\n const retryAfterRaw = err.headers[\"retry-after\"];\n const retryAfterSec =\n typeof retryAfterRaw === \"string\" && /^\\d+$/.test(retryAfterRaw) ? parseInt(retryAfterRaw, 10) : 60;\n return {\n ok: false,\n reason: \"rate-limited\",\n retryAfterMs: retryAfterSec * 1000,\n };\n }\n\n // Auth failure: API-Key falsch oder widerrufen \u2014 KEIN Retry\n const category = classifyError(err);\n if (category === \"AUTH\") {\n return {\n ok: false,\n reason: \"auth-failed\",\n message: err instanceof Error ? err.message : String(err),\n };\n }\n\n // Netzwerk/Timeout/Unknown: transient, einfach sp\u00E4ter\n return { ok: false, reason: \"transient\" };\n }\n }\n\n /**\n * Re-fetch scenes, snapshots AND libraries for all known light devices\n * without re-running the full Cloud bootstrap. Used by the\n * `info.refresh_cloud_data` button: \"new snapshot/scene was saved in\n * the Govee Home app, show it here\".\n *\n * v1.10.1 had skipped libraries to keep the per-click call count low \u2014\n * but for users on accounts where a library actually grew (new\n * scene-set rolled out by Govee, new sceneCode minted) the only way\n * back to fresh data was a full adapter restart. v2.1.0 reinstates the\n * library refresh on the same button and forces past the\n * `length === 0` guards inside `loadDeviceLibraries`.\n *\n * @returns true when any device's scene/snapshot/library data changed\n */\n async refreshSceneData(): Promise<boolean> {\n if (!this.cloudClient) {\n return false;\n }\n let anyChanged = false;\n const lights = Array.from(this.devices.values()).filter(d => d.type === \"devices.types.light\");\n for (const dev of lights) {\n this.diagnostics.addLog(dev.deviceId, \"info\", `User-triggered refresh-cloud-data for ${dev.sku}`);\n }\n for (const device of lights) {\n const cd: CloudDevice = {\n sku: device.sku,\n device: device.deviceId,\n deviceName: device.name,\n type: device.type,\n capabilities: Array.isArray(device.capabilities) ? device.capabilities : [],\n };\n if (await this.loadDeviceScenes(device, cd)) {\n anyChanged = true;\n }\n if (await this.loadDeviceLibraries(device, cd.sku, /* force */ true)) {\n anyChanged = true;\n }\n }\n if (anyChanged) {\n this.saveDevicesToCache();\n for (const device of this.devices.values()) {\n this.populateScenesFromLibrary(device);\n }\n this.onDeviceListChanged?.(this.getDevices());\n }\n return anyChanged;\n }\n\n /**\n * Merge Cloud device list into local device map.\n * Updates existing devices, adds new ones.\n *\n * @param cloudDevices Devices from Cloud API\n * @returns true if any new devices were added\n */\n private mergeCloudDevices(cloudDevices: CloudDevice[]): boolean {\n let changed = false;\n if (!Array.isArray(cloudDevices)) {\n return false;\n }\n for (const cd of cloudDevices) {\n // Defensive guard against malformed cloud entries\n if (!cd || typeof cd.sku !== \"string\" || typeof cd.device !== \"string\") {\n continue;\n }\n const existing = this.devices.get(this.deviceKey(cd.sku, cd.device));\n if (existing) {\n existing.name = cd.deviceName || existing.name;\n existing.capabilities = Array.isArray(cd.capabilities) ? cd.capabilities : [];\n existing.type = cd.type;\n existing.channels.cloud = true;\n } else {\n const device = this.cloudDeviceToGoveeDevice(cd);\n this.devices.set(this.deviceKey(cd.sku, cd.device), device);\n changed = true;\n this.log.debug(`Cloud: New device ${cd.deviceName} (${cd.sku})`);\n this.maybeNudgeSeedSku(cd.sku, cd.deviceName);\n }\n\n const quirks = getDeviceQuirks(cd.sku);\n if (quirks?.brokenPlatformApi) {\n this.log.debug(`${cd.sku} has known broken platform API metadata \u2014 capabilities may be incomplete`);\n }\n }\n return changed;\n }\n\n /**\n * Load scenes, DIY scenes, and snapshots for a device from Cloud API.\n *\n * @param device Target device to populate\n * @param cd Cloud device data with capabilities\n * @returns true if any scene data changed\n */\n private async loadDeviceScenes(device: GoveeDevice, cd: CloudDevice): Promise<boolean> {\n this.diagnostics.addLog(cd.device, \"debug\", `loadDeviceScenes called for ${cd.sku}`);\n // Scenes from dedicated scenes endpoint (rate-limited).\n // Guards are per-list, not combined: Govee's /device/scenes sometimes\n // returns 149 lightScenes + 0 snapshots (or vice versa) on back-to-back\n // calls even though the snapshot exists. A combined guard (if any list\n // non-empty, overwrite all) would wipe the other lists on that call and\n // break the dropdown until the next lucky round-trip. One guard per\n // list keeps the last-known-good data in place.\n const loadScenes = async (): Promise<void> => {\n try {\n const { lightScenes, diyScenes, snapshots } = await this.cloudClient!.getScenes(cd.sku, cd.device);\n // Cloud-client already captured the raw response on success \u2014 but\n // record the catch path here so the diag JSON shows \"/device/scenes\n // attempted, returned 403\" instead of an empty slot.\n if (lightScenes.length > 0) {\n device.scenes = lightScenes;\n }\n if (diyScenes.length > 0) {\n device.diyScenes = diyScenes;\n }\n if (snapshots.length > 0) {\n device.snapshots = snapshots;\n }\n } catch (e) {\n this.diagnostics.recordApiFailure(cd.device, \"/router/api/v1/device/scenes\", e, this.extractStatus(e));\n this.log.debug(`Could not load scenes for ${cd.sku}: ${e instanceof Error ? e.message : String(e)}`);\n }\n };\n await this.commandRouter.executeRateLimited(loadScenes, 2);\n\n // DIY scenes from dedicated endpoint\n if (device.diyScenes.length === 0) {\n const loadDiy = async (): Promise<void> => {\n try {\n const diy = await this.cloudClient!.getDiyScenes(cd.sku, cd.device);\n if (diy.length > 0) {\n device.diyScenes = diy;\n }\n } catch (e) {\n this.diagnostics.recordApiFailure(cd.device, \"/router/api/v1/device/diy-scenes\", e, this.extractStatus(e));\n this.log.debug(`Could not load DIY scenes for ${cd.sku}: ${e instanceof Error ? e.message : String(e)}`);\n }\n };\n await this.commandRouter.executeRateLimited(loadDiy, 2);\n }\n\n // Snapshots from device capabilities (fallback)\n if (device.snapshots.length === 0) {\n const caps = Array.isArray(cd.capabilities) ? cd.capabilities : [];\n const snapCap = caps.find(\n c =>\n c &&\n c.type === \"devices.capabilities.dynamic_scene\" &&\n c.instance === \"snapshot\" &&\n Array.isArray(c.parameters?.options),\n );\n if (snapCap?.parameters?.options) {\n device.snapshots = snapCap.parameters.options\n .filter(o => o && typeof o.name === \"string\" && o.value !== undefined && o.value !== null)\n .map(o => ({\n name: o.name,\n value: typeof o.value === \"number\" ? o.value : (o.value as Record<string, unknown>),\n }));\n this.log.debug(`Snapshots from capabilities for ${cd.sku}: ${device.snapshots.length}`);\n }\n }\n\n // \"Changed\" = we ended up with any scene/snapshot data. Inner tracking\n // was redundant with this single-source check.\n return device.scenes.length > 0 || device.diyScenes.length > 0 || device.snapshots.length > 0;\n }\n\n /**\n * Load scene/music/DIY libraries and SKU features from undocumented API.\n *\n * Each fetch runs through the rate-limiter so a fresh install with 10\n * devices doesn't slam app2.govee.com with 40 back-to-back requests \u2014\n * those endpoints are undocumented and aggressive callers can get the\n * account temporarily locked.\n *\n * @param device Target device to populate\n * @param sku Product model\n * @param force When true, refetch every endpoint regardless of cache \u2014\n * used by the user-triggered refresh button so a stale library\n * actually gets replaced\n * @returns true if any library data changed\n */\n private async loadDeviceLibraries(device: GoveeDevice, sku: string, force = false): Promise<boolean> {\n if (!this.apiClient) {\n return false;\n }\n\n this.diagnostics.addLog(device.deviceId, \"debug\", `loadDeviceLibraries called for ${sku} (force=${force})`);\n let changed = false;\n\n // Run each fetch inside a rate-limited slot. Priority 2 = below\n // control commands and scene/snapshot loads; library data is cache-only\n // and can wait for a quieter moment.\n const runLimited = async (fn: () => Promise<void>): Promise<void> => {\n await this.commandRouter.executeRateLimited(fn, 2);\n };\n\n if (force || device.sceneLibrary.length === 0) {\n await runLimited(async () => {\n const ep = `/light-effect-libraries?sku=${sku}`;\n try {\n const lib = await this.apiClient!.fetchSceneLibrary(sku);\n this.diagnostics.recordApiSuccess(device.deviceId, ep, { count: lib.length, names: lib.map(s => s.name) });\n if (lib.length > 0) {\n device.sceneLibrary = lib;\n changed = true;\n this.log.debug(`Scene library for ${sku}: ${lib.length} scenes`);\n }\n } catch (e) {\n this.diagnostics.recordApiFailure(device.deviceId, ep, e, this.extractStatus(e));\n this.log.debug(`Could not load scene library for ${sku}: ${e instanceof Error ? e.message : String(e)}`);\n }\n });\n }\n\n if (force || device.musicLibrary.length === 0) {\n await runLimited(async () => {\n const ep = `/light-effect-libraries-music?sku=${sku}`;\n try {\n const lib = await this.apiClient!.fetchMusicLibrary(sku);\n this.diagnostics.recordApiSuccess(device.deviceId, ep, { count: lib.length, names: lib.map(m => m.name) });\n if (lib.length > 0) {\n device.musicLibrary = lib;\n changed = true;\n this.log.debug(`Music library for ${sku}: ${lib.length} modes`);\n }\n } catch (e) {\n this.diagnostics.recordApiFailure(device.deviceId, ep, e, this.extractStatus(e));\n this.log.debug(`Could not load music library for ${sku}: ${e instanceof Error ? e.message : String(e)}`);\n }\n });\n }\n\n if (force || device.diyLibrary.length === 0) {\n await runLimited(async () => {\n const ep = `/diy-effect-libraries?sku=${sku}`;\n try {\n const lib = await this.apiClient!.fetchDiyLibrary(sku);\n this.diagnostics.recordApiSuccess(device.deviceId, ep, { count: lib.length, names: lib.map(d => d.name) });\n if (lib.length > 0) {\n device.diyLibrary = lib;\n changed = true;\n this.log.debug(`DIY library for ${sku}: ${lib.length} effects`);\n }\n } catch (e) {\n this.diagnostics.recordApiFailure(device.deviceId, ep, e, this.extractStatus(e));\n this.log.debug(`Could not load DIY library for ${sku}: ${e instanceof Error ? e.message : String(e)}`);\n }\n });\n }\n\n if (force || !device.skuFeatures) {\n await runLimited(async () => {\n const ep = `/sku-features?sku=${sku}`;\n try {\n const features = await this.apiClient!.fetchSkuFeatures(sku);\n this.diagnostics.recordApiSuccess(device.deviceId, ep, features);\n if (features) {\n device.skuFeatures = features;\n changed = true;\n this.log.debug(`SKU features for ${sku}: ${JSON.stringify(features).slice(0, 200)}`);\n }\n } catch (e) {\n this.diagnostics.recordApiFailure(device.deviceId, ep, e, this.extractStatus(e));\n this.log.debug(`Could not load SKU features for ${sku}: ${e instanceof Error ? e.message : String(e)}`);\n }\n });\n }\n\n // Load snapshot BLE commands for local activation\n if (!device.snapshotBleCmds && device.snapshots.length > 0) {\n await runLimited(async () => {\n try {\n const snaps = await this.apiClient!.fetchSnapshots(sku, device.deviceId);\n if (snaps.length > 0) {\n device.snapshotBleCmds = device.snapshots.map(ds => {\n const match = snaps.find(s => s.name === ds.name);\n return match?.bleCmds ?? [];\n });\n changed = true;\n this.log.debug(`Snapshot BLE for ${sku}: ${snaps.length} snapshots with local data`);\n }\n } catch (e) {\n this.log.debug(`Could not load snapshot BLE for ${sku}: ${e instanceof Error ? e.message : String(e)}`);\n }\n });\n }\n\n return changed;\n }\n\n /**\n * Load group membership from undocumented API and attach to BaseGroup devices.\n * Resolves member device references against the current device map.\n *\n * @returns true if any group memberships were resolved\n */\n async loadGroupMembers(): Promise<boolean> {\n if (!this.apiClient) {\n return false;\n }\n if (!this.apiClient.hasBearerToken()) {\n this.log.debug(\"Group membership requires Email+Password \u2014 skipping member resolution\");\n return false;\n }\n\n try {\n const apiGroups = await this.apiClient.fetchGroupMembers();\n if (apiGroups.length === 0) {\n this.log.debug(\"No group membership data from API\");\n return false;\n }\n\n let changed = false;\n for (const group of this.devices.values()) {\n if (group.sku !== \"BaseGroup\") {\n continue;\n }\n // Match by groupId: BaseGroup deviceId is the numeric group ID as string\n const apiGroup = apiGroups.find(g => String(g.groupId) === group.deviceId);\n if (!apiGroup) {\n continue;\n }\n\n // Resolve member devices against our device map\n const members: { sku: string; deviceId: string }[] = [];\n for (const m of apiGroup.devices) {\n const resolved = this.findDeviceBySkuAndId(m.sku, m.deviceId);\n if (resolved) {\n members.push({ sku: resolved.sku, deviceId: resolved.deviceId });\n } else {\n this.log.debug(`Group \"${group.name}\": member ${m.sku}/${m.deviceId} not in device map`);\n }\n }\n\n group.groupMembers = members;\n if (members.length > 0) {\n changed = true;\n }\n this.log.debug(`Group \"${group.name}\": ${members.length}/${apiGroup.devices.length} members resolved`);\n }\n\n if (changed) {\n this.onDeviceListChanged?.(this.getDevices());\n }\n return changed;\n } catch (e) {\n this.log.debug(`Could not load group members: ${e instanceof Error ? e.message : String(e)}`);\n return false;\n }\n }\n\n /** Save all devices to SKU cache, skipping only those never confirmed via Cloud yet. */\n public saveDevicesToCache(): void {\n if (!this.skuCache) {\n return;\n }\n\n let cachedCount = 0;\n let skippedCount = 0;\n for (const device of this.devices.values()) {\n const isLight = device.type === \"devices.types.light\";\n // Skip only if we never asked Cloud yet \u2014 empty scenes are legitimate\n // once confirmed via scenesChecked=true.\n if (isLight && !device.scenesChecked) {\n skippedCount++;\n this.log.debug(`Not caching ${device.name} (${device.sku}) \u2014 scenes not yet checked`);\n } else {\n this.skuCache.save(this.goveeDeviceToCached(device));\n cachedCount++;\n }\n }\n // Routine persistence \u2014 debug level only. Users don't need a play-by-play\n // for every cache write. Significant events (scenes fetched, MQTT bumps)\n // log themselves elsewhere.\n if (skippedCount > 0) {\n this.log.debug(`Cached ${cachedCount} device(s), skipped ${skippedCount} not yet checked`);\n } else {\n this.log.debug(`Cached ${cachedCount} device(s) \u2014 next start uses cache`);\n }\n }\n\n /**\n * Handle LAN device discovery \u2014 match against known devices or create new.\n *\n * @param lanDevice Discovered LAN device\n */\n handleLanDiscovery(lanDevice: LanDevice): void {\n // Try to find by device ID (colon-separated in Cloud, varies in LAN)\n let matched: GoveeDevice | undefined;\n for (const dev of this.devices.values()) {\n if (normalizeDeviceId(dev.deviceId) === normalizeDeviceId(lanDevice.device)) {\n matched = dev;\n break;\n }\n // Also match by SKU if device IDs don't match format\n if (dev.sku === lanDevice.sku && !dev.lanIp) {\n matched = dev;\n break;\n }\n }\n\n if (matched) {\n const ipChanged = matched.lanIp !== lanDevice.ip;\n matched.lanIp = lanDevice.ip;\n matched.channels.lan = true;\n matched.lastSeenOnNetwork = Date.now();\n if (ipChanged) {\n this.log.debug(`LAN: ${matched.name} (${matched.sku}) at ${lanDevice.ip}`);\n this.onLanIpChanged?.(matched, lanDevice.ip);\n }\n } else {\n // LAN-only device (no Cloud data yet)\n // Include short device ID suffix for uniqueness (multiple devices can share same SKU)\n const shortId = normalizeDeviceId(lanDevice.device).slice(-4);\n const device: GoveeDevice = {\n sku: lanDevice.sku,\n deviceId: lanDevice.device,\n name: `${lanDevice.sku}_${shortId}`,\n type: \"devices.types.light\",\n lanIp: lanDevice.ip,\n capabilities: [],\n scenes: [],\n diyScenes: [],\n snapshots: [],\n sceneLibrary: [],\n musicLibrary: [],\n diyLibrary: [],\n skuFeatures: null,\n lastSeenOnNetwork: Date.now(),\n state: { online: true },\n channels: { lan: true, mqtt: false, cloud: false },\n };\n this.devices.set(this.deviceKey(lanDevice.sku, lanDevice.device), device);\n this.diagnostics.addLog(lanDevice.device, \"info\", `LAN-discovered at ${lanDevice.ip}`);\n this.log.debug(`LAN: New device ${lanDevice.sku} at ${lanDevice.ip}`);\n this.maybeNudgeSeedSku(lanDevice.sku, device.name);\n this.onDeviceListChanged?.(this.getDevices());\n }\n }\n\n /**\n * Log the device's trust tier \u2014 once per SKU per adapter lifetime, so\n * device reconnects don't spam the log. Behaviour by tier:\n * - verified / reported: silent (the catalog backs the device, no\n * action needed). The tier is still surfaced via the\n * `diag.tier` state for any user who wants to check.\n * - seed (toggle off): warn \u2014 points the user at the experimental\n * toggle that gates the per-SKU corrections we'd otherwise apply.\n * - seed (toggle on): info \u2014 confirms quirks are active.\n * - unknown: warn \u2014 asks for a diagnostics export so we can add the\n * SKU to the catalogue.\n *\n * @param sku Govee SKU\n * @param displayName Device name as shown in Govee Home\n */\n private maybeNudgeSeedSku(sku: string, displayName: string | undefined): void {\n const upper = (typeof sku === \"string\" ? sku : \"\").toUpperCase();\n if (!upper || this.nudgedSeedSkus.has(upper)) {\n return;\n }\n this.nudgedSeedSkus.add(upper);\n const tier = getDeviceTier(upper);\n const label = displayName ? `${displayName} (${upper})` : upper;\n switch (tier) {\n case \"verified\":\n case \"reported\":\n return;\n case \"seed\":\n if (isSeedAndDormant(upper)) {\n this.log.warn(\n `Device ${label} is in beta and needs the \"Experimentelle Ger\u00E4te-Unterst\u00FCtzung aktivieren\" toggle in adapter settings to apply known per-SKU corrections.`,\n );\n } else {\n this.log.info(`Device ${label} is in beta \u2014 experimental quirks are active.`);\n }\n return;\n case \"unknown\":\n this.log.warn(\n `Device ${label} is not in the supported device list. Please trigger diag.export and post the resulting JSON in a GitHub issue so the SKU can be added.`,\n );\n return;\n }\n }\n\n /**\n * Handle MQTT status update \u2014 update device state.\n *\n * @param update MQTT status message\n */\n handleMqttStatus(update: MqttStatusUpdate): void {\n const device = this.findDeviceBySkuAndId(update.sku, update.device);\n if (!device) {\n this.log.debug(`MQTT: Unknown device ${update.sku} ${update.device}`);\n return;\n }\n\n device.channels.mqtt = true;\n device.lastSeenOnNetwork = Date.now();\n const state: Partial<DeviceState> = { online: true };\n\n if (update.state) {\n if (update.state.onOff !== undefined) {\n state.power = update.state.onOff === 1;\n }\n if (update.state.brightness !== undefined) {\n state.brightness = update.state.brightness;\n }\n if (update.state.color) {\n const { r, g, b } = update.state.color;\n state.colorRgb = rgbToHex(r, g, b);\n }\n if (update.state.colorTemInKelvin) {\n state.colorTemperature = update.state.colorTemInKelvin;\n }\n }\n\n // Merge into device state\n Object.assign(device.state, state);\n this.onDeviceUpdate?.(device, state);\n\n // Parse per-segment data from BLE notification packets (AA A5).\n // MQTT is authoritative for segment count \u2014 the device tells us what it\n // actually has. Cloud only gives an initial best-guess from capabilities.\n if (update.op?.command) {\n const segData = parseMqttSegmentData(update.op.command);\n\n if (segData.length > 0) {\n const maxSeen = Math.max(...segData.map(s => s.index)) + 1;\n const current = device.segmentCount ?? 0;\n if (maxSeen > current) {\n this.log.info(\n `${device.name}: detected ${maxSeen} segments via MQTT (was ${current}) \u2014 rebuilding state tree`,\n );\n device.segmentCount = maxSeen;\n // Persist now so a restart starts from the real value instead of\n // falling back to Cloud capabilities and deleting the extra slots.\n if (this.skuCache) {\n this.skuCache.save(this.goveeDeviceToCached(device));\n }\n // Skip per-segment sync for this push \u2014 the new datapoints don't\n // exist yet. The next AA A5 push hits the fully-built tree.\n this.onSegmentCountGrown?.(device);\n return;\n }\n }\n\n // Filter by manual-segments override if active \u2014 ignore indices the\n // user has declared as \"not physically present\" (cut strip).\n const filtered =\n device.manualMode && Array.isArray(device.manualSegments) && device.manualSegments.length > 0\n ? segData.filter(s => device.manualSegments!.includes(s.index))\n : segData;\n if (filtered.length > 0) {\n this.onMqttSegmentUpdate?.(device, filtered);\n }\n }\n }\n\n /**\n * Handle LAN status response.\n *\n * @param ip Source IP address\n * @param status LAN status data\n * @param status.onOff Power state (1=on, 0=off)\n * @param status.brightness Brightness 0-100\n * @param status.color RGB color values\n * @param status.color.r Red channel 0-255\n * @param status.color.g Green channel 0-255\n * @param status.color.b Blue channel 0-255\n * @param status.colorTemInKelvin Color temperature in Kelvin\n */\n handleLanStatus(\n ip: string,\n status: {\n onOff: number;\n brightness: number;\n color: { r: number; g: number; b: number };\n colorTemInKelvin: number;\n },\n ): void {\n // Find device by LAN IP\n let device: GoveeDevice | undefined;\n for (const dev of this.devices.values()) {\n if (dev.lanIp === ip) {\n device = dev;\n break;\n }\n }\n if (!device) {\n return;\n }\n\n device.lastSeenOnNetwork = Date.now();\n const { r, g, b } = status.color;\n const state: Partial<DeviceState> = {\n online: true,\n power: status.onOff === 1,\n brightness: status.brightness,\n colorRgb: rgbToHex(r, g, b),\n colorTemperature: status.colorTemInKelvin || undefined,\n };\n\n Object.assign(device.state, state);\n this.onDeviceUpdate?.(device, state);\n }\n\n /**\n * Set the callback for batch segment state sync.\n * Forwards to the internal CommandRouter.\n *\n * @param callback Called when a segment batch command updates segment states\n */\n set onSegmentBatchUpdate(\n callback:\n | ((device: GoveeDevice, batch: { segments: number[]; color?: number; brightness?: number }) => void)\n | undefined,\n ) {\n this.commandRouter.onSegmentBatchUpdate = callback;\n }\n\n /**\n * Send a command to a device \u2014 routes through LAN \u2192 Cloud.\n *\n * @param device Target device\n * @param command Command type\n * @param value Command value\n */\n async sendCommand(device: GoveeDevice, command: string, value: unknown): Promise<void> {\n return this.commandRouter.sendCommand(device, command, value);\n }\n\n /**\n * Send a generic capability command via Cloud API.\n * Used for capability types not explicitly handled (toggle, dynamic_scene, etc.)\n *\n * @param device Target device\n * @param capabilityType Full capability type (e.g. \"devices.capabilities.toggle\")\n * @param capabilityInstance Capability instance name (e.g. \"gradientToggle\")\n * @param value Command value\n */\n async sendCapabilityCommand(\n device: GoveeDevice,\n capabilityType: string,\n capabilityInstance: string,\n value: unknown,\n ): Promise<void> {\n return this.commandRouter.sendCapabilityCommand(device, capabilityType, capabilityInstance, value);\n }\n\n /** Callback when device LAN IP changes */\n onLanIpChanged?: (device: GoveeDevice, ip: string) => void;\n\n /** Callback when MQTT delivers per-segment state data (AA A5 BLE packets) */\n onMqttSegmentUpdate?: (device: GoveeDevice, segments: MqttSegmentData[]) => void;\n\n /**\n * Callback when the device's physical segment count turns out to be\n * larger than the Cloud-reported value (observed via MQTT AA A5 stream).\n * The adapter rebuilds the state tree in response so the extra indices\n * appear as datapoints.\n */\n onSegmentCountGrown?: (device: GoveeDevice) => void;\n\n /**\n * Convert Cloud device to internal device model\n *\n * @param cd Cloud API device data\n */\n private cloudDeviceToGoveeDevice(cd: CloudDevice): GoveeDevice {\n return {\n sku: cd.sku,\n deviceId: cd.device,\n name: cd.deviceName || cd.sku,\n type: cd.type || \"unknown\",\n capabilities: Array.isArray(cd.capabilities) ? cd.capabilities : [],\n scenes: [],\n diyScenes: [],\n snapshots: [],\n sceneLibrary: [],\n musicLibrary: [],\n diyLibrary: [],\n skuFeatures: null,\n state: { online: true },\n channels: { lan: false, mqtt: false, cloud: true },\n };\n }\n\n /**\n * Find device by SKU and device ID (handles format differences)\n *\n * @param sku Product model\n * @param deviceId Device identifier\n */\n private findDeviceBySkuAndId(sku: string, deviceId: string): GoveeDevice | undefined {\n // Direct key lookup\n const direct = this.devices.get(this.deviceKey(sku, deviceId));\n if (direct) {\n return direct;\n }\n\n // Normalized search\n const normalizedId = normalizeDeviceId(deviceId);\n for (const dev of this.devices.values()) {\n if (dev.sku === sku && normalizeDeviceId(dev.deviceId) === normalizedId) {\n return dev;\n }\n }\n return undefined;\n }\n\n /**\n * Generate unique key for a device\n *\n * @param sku Product model\n * @param deviceId Device identifier\n */\n private deviceKey(sku: string, deviceId: string): string {\n return `${sku}_${normalizeDeviceId(deviceId)}`;\n }\n\n /**\n * Log error with dedup \u2014 only warn on category change, debug on repeat.\n *\n * @param context Error context description\n * @param err Error to log\n */\n private logDedup(context: string, err: unknown): void {\n const category = classifyError(err);\n const msg = `${context}: ${err instanceof Error ? err.message : String(err)}`;\n if (category !== this.lastErrorCategory) {\n this.lastErrorCategory = category;\n this.log.warn(msg);\n } else {\n this.log.debug(`${msg} (repeated)`);\n }\n }\n\n /**\n * Fill device.scenes from sceneLibrary when Cloud scenes are missing.\n * ptReal activation matches by name, so sceneLibrary names are sufficient.\n *\n * @param device Device to populate scenes for\n */\n private populateScenesFromLibrary(device: GoveeDevice): void {\n if (device.scenes.length === 0 && device.sceneLibrary.length > 0) {\n device.scenes = device.sceneLibrary.map(entry => ({\n name: entry.name,\n value: {}, // ptReal uses sceneLibrary directly, Cloud payload not needed\n }));\n this.log.debug(`${device.sku}: ${device.scenes.length} scenes from library (Cloud scenes missing)`);\n }\n }\n\n /**\n * Convert cached data to a GoveeDevice (runtime fields set to defaults)\n *\n * @param cached Cached device data\n */\n private cachedToGoveeDevice(cached: CachedDeviceData): GoveeDevice {\n return {\n sku: cached.sku,\n deviceId: cached.deviceId,\n name: cached.name,\n type: cached.type,\n capabilities: cached.capabilities,\n scenes: cached.scenes,\n diyScenes: cached.diyScenes,\n snapshots: cached.snapshots,\n sceneLibrary: cached.sceneLibrary,\n musicLibrary: cached.musicLibrary,\n diyLibrary: cached.diyLibrary,\n skuFeatures: cached.skuFeatures,\n snapshotBleCmds: cached.snapshotBleCmds,\n scenesChecked: cached.scenesChecked,\n lastSeenOnNetwork: cached.lastSeenOnNetwork,\n // Restore learned count so it wins over Cloud capability on next start.\n segmentCount: cached.segmentCount,\n manualMode: cached.manualMode,\n manualSegments: cached.manualSegments,\n sceneSpeed: cached.sceneSpeed,\n state: { online: false },\n channels: { lan: false, mqtt: false, cloud: false },\n };\n }\n\n /**\n * Persist a device's current runtime state to the SKU cache.\n * Safe no-op when no cache is configured.\n *\n * @param device Target device\n */\n public persistDeviceToCache(device: GoveeDevice): void {\n if (!this.skuCache) {\n return;\n }\n this.skuCache.save(this.goveeDeviceToCached(device));\n }\n\n /**\n * Extract cacheable data from a GoveeDevice.\n *\n * @param device Runtime device\n */\n private goveeDeviceToCached(device: GoveeDevice): CachedDeviceData {\n return {\n sku: device.sku,\n deviceId: device.deviceId,\n name: device.name,\n type: device.type,\n capabilities: device.capabilities,\n scenes: device.scenes,\n diyScenes: device.diyScenes,\n snapshots: device.snapshots,\n sceneLibrary: device.sceneLibrary,\n musicLibrary: device.musicLibrary,\n diyLibrary: device.diyLibrary,\n skuFeatures: device.skuFeatures,\n snapshotBleCmds: device.snapshotBleCmds,\n scenesChecked: device.scenesChecked,\n lastSeenOnNetwork: device.lastSeenOnNetwork,\n segmentCount:\n typeof device.segmentCount === \"number\" && device.segmentCount > 0 ? device.segmentCount : undefined,\n manualMode: device.manualMode ? true : undefined,\n manualSegments:\n device.manualMode && Array.isArray(device.manualSegments) && device.manualSegments.length > 0\n ? device.manualSegments.slice()\n : undefined,\n sceneSpeed: typeof device.sceneSpeed === \"number\" && device.sceneSpeed > 0 ? device.sceneSpeed : undefined,\n cachedAt: Date.now(),\n };\n }\n\n /**\n * Generate diagnostics data for a device \u2014 structured JSON for GitHub\n * issue submission. Delegates to the DiagnosticsCollector so the JSON\n * also includes ring-buffer context (recent logs, MQTT packets, last\n * API responses).\n *\n * @param device Target device\n * @param adapterVersion Adapter version string\n */\n generateDiagnostics(device: GoveeDevice, adapterVersion: string): Record<string, unknown> {\n return this.diagnostics.generate(device, adapterVersion);\n }\n\n /**\n * Poll the undocumented app-API for sensor-like devices (H5179 et al.)\n * where OpenAPI v2 `/device/state` returns empty. Each entry is converted\n * to synthetic capabilities and routed back through the same callback as\n * regular Cloud state, so the existing setState pipeline picks it up\n * without a special-case branch.\n *\n * Bearer token comes from the MQTT login flow \u2014 without MQTT credentials\n * (Email + Password) this is a no-op.\n *\n * @returns Number of devices that received an update\n */\n async pollAppApi(): Promise<number> {\n if (!this.apiClient || !this.apiClient.hasBearerToken()) {\n return 0;\n }\n // Skip the entire round-trip when no device in the registry would\n // actually consume App-API readings. The App API is only used for\n // sensor and appliance state (thermometers, heaters, kettles, \u2026);\n // a Lights-only setup would otherwise burn one Govee call every 2\n // minutes for nothing.\n if (!this.hasDeviceNeedingAppApi()) {\n return 0;\n }\n let entries: AppDeviceEntry[];\n try {\n entries = await this.apiClient.fetchDeviceList();\n } catch (err) {\n const category = classifyError(err);\n const msg = `App API fetch failed: ${err instanceof Error ? err.message : String(err)}`;\n if (category !== this.lastAppApiErrorCategory) {\n this.lastAppApiErrorCategory = category;\n this.log.warn(msg);\n } else {\n this.log.debug(msg);\n }\n return 0;\n }\n // Reset on success so the next failure warns again.\n this.lastAppApiErrorCategory = null;\n let updated = 0;\n for (const entry of entries) {\n const device = this.devices.get(this.deviceKey(entry.sku, entry.device));\n if (!device) {\n continue;\n }\n const caps = buildCapabilitiesFromAppEntry(entry);\n if (caps.length === 0) {\n continue;\n }\n // Route synthetic capabilities through the existing\n // onCloudCapabilities callback so main.ts's normal setState\n // pipeline (mapCloudStateValue + setStateAsync) handles them.\n this.onCloudCapabilities?.(device, caps);\n // mapSingleCapability returns null for the synthetic `online`\n // cap (online is a device-level property, not a regular state),\n // so onCloudCapabilities never reaches info.online via the\n // capability pipeline. Pluck it out and apply it directly \u2014\n // otherwise sensor SKUs like H5179 stay at info.online=false\n // forever even while their readings keep updating.\n this.applyOnlineCap(device, caps);\n this.diagnostics.setApiResponse(device.deviceId, \"/device/rest/devices/v1/list\", entry);\n updated++;\n }\n return updated;\n }\n\n /**\n * Pull the `devices.capabilities.online` entry (if any) out of a\n * synthetic capability list and apply it directly to\n * `device.state.online` plus `lastSeenOnNetwork`. Surfaces via\n * onDeviceUpdate so the adapter's `info.online` state matches the\n * App-API / OpenAPI-MQTT signal. If no online cap is in the list but\n * the list is non-empty (i.e. fresh data arrived), the device is\n * considered online \u2014 same convention as the LAN/MQTT paths.\n *\n * @param device Target device\n * @param caps Capability list from the source pipeline\n */\n private applyOnlineCap(device: GoveeDevice, caps: CloudStateCapability[]): void {\n let online: boolean | undefined;\n for (const c of caps) {\n if (\n c &&\n typeof c.type === \"string\" &&\n (c.type === \"devices.capabilities.online\" || c.type === \"online\") &&\n c.state &&\n typeof c.state.value === \"boolean\"\n ) {\n online = c.state.value;\n break;\n }\n }\n // Fresh data with no online flag \u2192 assume online (LAN/MQTT use the\n // same \"we just heard from the device\" convention).\n if (online === undefined && caps.length > 0) {\n online = true;\n }\n if (online === undefined) {\n return;\n }\n if (device.state.online === online && online === true) {\n // Already online + still online \u2014 only refresh the lastSeen ts\n // and skip the onDeviceUpdate noise.\n device.lastSeenOnNetwork = Date.now();\n return;\n }\n device.state.online = online;\n if (online) {\n device.lastSeenOnNetwork = Date.now();\n }\n this.onDeviceUpdate?.(device, { online });\n }\n\n /**\n * Hook callback for sources that emit `CloudStateCapability[]` updates\n * outside the normal Cloud-poll path (App-API, OpenAPI-MQTT). Caller is\n * responsible for wiring it to the adapter-side state-write path.\n *\n * @param cb Callback receiving (device, caps)\n */\n setOnCloudCapabilities(cb: ((device: GoveeDevice, caps: CloudStateCapability[]) => void) | null): void {\n this.onCloudCapabilities = cb;\n }\n\n /**\n * Whether at least one device in the registry would consume App-API\n * readings (sensors, appliances). Used to skip the App-API poll on\n * Lights-only installations.\n */\n private hasDeviceNeedingAppApi(): boolean {\n for (const dev of this.devices.values()) {\n if (dev.type !== \"devices.types.light\" && dev.sku !== \"BaseGroup\") {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Process a parsed OpenAPI-MQTT event by forwarding its capabilities\n * through the same hook used by App-API polls. Called from the\n * adapter-side OpenAPI-MQTT message handler.\n *\n * @param event Parsed event from the OpenAPI-MQTT broker\n * @param event.sku Govee SKU (e.g. \"H5179\")\n * @param event.device MAC-style device identifier\n * @param event.capabilities Capability list synthesised from the broker payload\n */\n handleOpenApiEvent(event: { sku: string; device: string; capabilities: CloudStateCapability[] }): void {\n if (!event || typeof event.sku !== \"string\" || typeof event.device !== \"string\") {\n return;\n }\n if (!Array.isArray(event.capabilities) || event.capabilities.length === 0) {\n return;\n }\n const device = this.devices.get(this.deviceKey(event.sku, event.device));\n if (!device) {\n return;\n }\n this.onCloudCapabilities?.(device, event.capabilities);\n // Same online-cap unwrap as the App-API path. OpenAPI-MQTT events\n // are the only signal we get for appliance state (heater on/off,\n // ice-bucket-full, \u2026) \u2014 without this, info.online for those SKUs\n // never flips to true even while events stream in.\n this.applyOnlineCap(device, event.capabilities);\n }\n}\n\n/**\n * Convert an app-API device entry into a list of synthetic Cloud-state\n * capabilities the existing `mapCloudStateValue` pipeline can consume.\n *\n * Govee stores temperature and humidity as integer hundredths of a unit\n * (`tem: 2370` \u2192 23.70 \u00B0C, `hum: 4290` \u2192 42.90 % RH). Battery may live\n * either at the lastData level or in deviceSettings \u2014 lastData wins\n * because it's the more recent reading.\n *\n * @param entry One entry from `GoveeApiClient.fetchDeviceList()`\n */\nexport function buildCapabilitiesFromAppEntry(entry: AppDeviceEntry): CloudStateCapability[] {\n const caps: CloudStateCapability[] = [];\n const last = entry.lastData;\n if (!last) {\n return caps;\n }\n if (typeof last.online === \"boolean\") {\n caps.push({\n type: \"devices.capabilities.online\",\n instance: \"online\",\n state: { value: last.online },\n });\n }\n if (typeof last.tem === \"number\" && Number.isFinite(last.tem)) {\n caps.push({\n type: \"devices.capabilities.property\",\n instance: \"sensorTemperature\",\n state: { value: last.tem / 100 },\n });\n }\n if (typeof last.hum === \"number\" && Number.isFinite(last.hum)) {\n caps.push({\n type: \"devices.capabilities.property\",\n instance: \"sensorHumidity\",\n state: { value: last.hum / 100 },\n });\n }\n if (typeof last.battery === \"number\" && Number.isFinite(last.battery)) {\n caps.push({\n type: \"devices.capabilities.property\",\n instance: \"battery\",\n state: { value: last.battery },\n });\n } else if (entry.settings && typeof entry.settings.battery === \"number\" && Number.isFinite(entry.settings.battery)) {\n caps.push({\n type: \"devices.capabilities.property\",\n instance: \"battery\",\n state: { value: entry.settings.battery },\n });\n }\n return caps;\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { hasDynamicSceneCapability } from \"./capability-mapper\";\nimport { CommandRouter } from \"./command-router\";\nimport { getDeviceQuirks, getDeviceTier, isSeedAndDormant } from \"./device-registry\";\nimport { DiagnosticsCollector } from \"./diagnostics\";\nimport type { AppDeviceEntry, GoveeApiClient } from \"./govee-api-client\";\nimport type { GoveeCloudClient } from \"./govee-cloud-client\";\nimport type { GoveeLanClient } from \"./govee-lan-client\";\nimport type { RateLimiter } from \"./rate-limiter\";\nimport type { CachedDeviceData, SkuCache } from \"./sku-cache\";\nimport {\n classifyError,\n normalizeDeviceId,\n rgbToHex,\n type CloudDevice,\n type CloudLoadResult,\n type CloudStateCapability,\n type DeviceState,\n type ErrorCategory,\n type GoveeDevice,\n type LanDevice,\n type MqttStatusUpdate,\n type TimerAdapter,\n} from \"./types\";\nimport { HttpError } from \"./http-client\";\n\n/** Parsed per-segment data from MQTT BLE packets */\nexport interface MqttSegmentData {\n /** Segment index (0-based) */\n index: number;\n /** Per-segment brightness 0-100 */\n brightness: number;\n /** Red channel 0-255 */\n r: number;\n /** Green channel 0-255 */\n g: number;\n /** Blue channel 0-255 */\n b: number;\n}\n\n/**\n * Parse AA A5 BLE notification packets from MQTT op.command.\n * 5 packets \u00D7 4 segment slots = max 20 segments per push. The device sends\n * exactly as many packets as it has physical segments \u2014 so parsing out all\n * slots (and filtering empty-slot padding) gives us a reliable count of\n * what actually exists on the strip.\n *\n * Format per slot: [Brightness 0-100] [R] [G] [B].\n *\n * An \"empty\" slot (brightness = 0 AND r = g = b = 0) is treated as padding\n * in a partially-filled final packet, not as a real unlit segment \u2014 this\n * matters for devices that don't pad their last packet to 4 slots.\n *\n * @param commands Base64-encoded BLE packets from MQTT op.command\n */\nexport function parseMqttSegmentData(commands: string[]): MqttSegmentData[] {\n if (!Array.isArray(commands)) {\n return [];\n }\n\n const segments: MqttSegmentData[] = [];\n // Track the highest packetNum seen so we know where real data ends.\n let highestPacket = 0;\n\n for (const cmd of commands) {\n if (typeof cmd !== \"string\") {\n continue;\n }\n const bytes = Buffer.from(cmd, \"base64\");\n if (bytes.length < 20 || bytes[0] !== 0xaa || bytes[1] !== 0xa5) {\n continue;\n }\n\n const packetNum = bytes[2];\n if (packetNum < 1 || packetNum > 5) {\n continue;\n }\n if (packetNum > highestPacket) {\n highestPacket = packetNum;\n }\n\n const baseIndex = (packetNum - 1) * 4;\n for (let slot = 0; slot < 4; slot++) {\n const segIdx = baseIndex + slot;\n const offset = 3 + slot * 4;\n segments.push({\n index: segIdx,\n brightness: bytes[offset],\n r: bytes[offset + 1],\n g: bytes[offset + 2],\n b: bytes[offset + 3],\n });\n }\n }\n\n // Trim trailing padding slots from the highest packet: Govee pads short\n // packets with 0x00 bytes, so a run of all-zero slots at the end is not\n // real segment data but filler. Keep any zero-slots that are followed by\n // a real one \u2014 they're legitimately-unlit middle segments.\n while (segments.length > 0) {\n const tail = segments[segments.length - 1];\n if (tail.brightness === 0 && tail.r === 0 && tail.g === 0 && tail.b === 0) {\n segments.pop();\n } else {\n break;\n }\n }\n\n return segments;\n}\n\n/**\n * Effective physical segment indices for a device.\n * Uses `device.manualSegments` when `device.manualMode=true` (cut strip override),\n * falls back to `0..segmentCount-1` otherwise. Empty if device has no segments.\n *\n * @param device Target device\n */\nexport function getEffectiveSegmentIndices(device: GoveeDevice): number[] {\n if (device.manualMode && Array.isArray(device.manualSegments) && device.manualSegments.length > 0) {\n return device.manualSegments.slice();\n }\n const count = device.segmentCount ?? 0;\n if (count <= 0) {\n return [];\n }\n return Array.from({ length: count }, (_, i) => i);\n}\n\n/**\n * Resolve the authoritative segment count for a device.\n *\n * Priority:\n * 1. `device.segmentCount` if already set (from cache, MQTT discovery, or wizard)\n * 2. Minimum of positive `segment_color_setting` capability counts\n * 3. 0 if no capability advertises segments\n *\n * Why `min` over the capability caps: Govee reports `segmentedBrightness` and\n * `segmentedColorRgb` separately, and on at least one SKU (H70D1) those two\n * disagree \u2014 brightness says 10, colorRgb says 15, real device has 10.\n * Picking the smaller value is the safer starting point; MQTT discovery can\n * then grow it if the real device pushes more slots.\n *\n * @param device Target device\n */\nexport function resolveSegmentCount(device: GoveeDevice): number {\n if (typeof device.segmentCount === \"number\" && device.segmentCount > 0) {\n return device.segmentCount;\n }\n const caps = Array.isArray(device.capabilities) ? device.capabilities : [];\n let min = Number.POSITIVE_INFINITY;\n for (const c of caps) {\n if (!c || typeof c.type !== \"string\" || !c.type.includes(\"segment_color_setting\")) {\n continue;\n }\n const params = (c as { parameters?: { fields?: unknown[] } }).parameters;\n const fields = Array.isArray(params?.fields) ? params.fields : [];\n for (const f of fields) {\n if (!f || typeof f !== \"object\") {\n continue;\n }\n const fn = (f as { fieldName?: unknown }).fieldName;\n const er = (f as { elementRange?: { max?: unknown } }).elementRange;\n const rawMax = er && typeof er.max === \"number\" ? er.max : -1;\n if (fn === \"segment\" && rawMax >= 0) {\n const n = rawMax + 1;\n if (n > 0 && n < min) {\n min = n;\n }\n }\n }\n }\n return Number.isFinite(min) ? min : 0;\n}\n\n/** Protocol limit: Govee's segment bitmask is 7 bytes \u00D7 8 bits = 56 slots (0..55). */\nexport const SEGMENT_HARD_MAX = 55;\n\n/**\n * Device manager \u2014 maintains unified device list and routes commands\n * through the fastest available channel: LAN \u2192 Cloud.\n * MQTT is status-push only and never used for commands.\n */\nexport class DeviceManager {\n private readonly log: ioBroker.Logger;\n private readonly devices = new Map<string, GoveeDevice>();\n private readonly commandRouter: CommandRouter;\n private readonly diagnostics: DiagnosticsCollector;\n /** SKUs we already nudged about \u2014 log only once per adapter lifetime, per SKU. */\n private readonly nudgedSeedSkus = new Set<string>();\n private cloudClient: GoveeCloudClient | null = null;\n private apiClient: GoveeApiClient | null = null;\n private skuCache: SkuCache | null = null;\n private onDeviceUpdate: ((device: GoveeDevice, state: Partial<DeviceState>) => void) | null = null;\n private onDeviceListChanged: ((devices: GoveeDevice[]) => void) | null = null;\n private onCloudCapabilities: ((device: GoveeDevice, caps: CloudStateCapability[]) => void) | null = null;\n /** Per-source dedup so a Cloud NETWORK error doesn't shadow an App-API one. */\n private lastErrorCategory: ErrorCategory | null = null;\n private lastAppApiErrorCategory: ErrorCategory | null = null;\n\n /**\n * @param log ioBroker logger\n * @param timers Adapter timer wrapper (forwarded to CommandRouter for\n * onUnload-safe delays).\n */\n constructor(log: ioBroker.Logger, timers: TimerAdapter) {\n this.log = log;\n this.commandRouter = new CommandRouter(log, timers);\n this.diagnostics = new DiagnosticsCollector();\n }\n\n /**\n * Expose the diagnostics collector so adapter-side hooks (MQTT,\n * Cloud, log wrapper) can write into the per-device ring buffers.\n */\n getDiagnostics(): DiagnosticsCollector {\n return this.diagnostics;\n }\n\n /**\n * Pull the HTTP status code out of any error shape we know about\n * (HttpError, Govee API responses with `.statusCode` / `.status`).\n * Returns undefined for network errors / generic failures so the\n * diagnostics entry shows \"no status \u2014 likely network/timeout\".\n *\n * @param e Caught error value\n */\n private extractStatus(e: unknown): number | undefined {\n if (e instanceof HttpError) {\n return e.statusCode;\n }\n if (typeof e === \"object\" && e !== null) {\n const x = e as { statusCode?: unknown; status?: unknown };\n if (typeof x.statusCode === \"number\") {\n return x.statusCode;\n }\n if (typeof x.status === \"number\") {\n return x.status;\n }\n }\n return undefined;\n }\n\n /**\n * Register the LAN client\n *\n * @param client LAN UDP client instance\n */\n setLanClient(client: GoveeLanClient): void {\n this.commandRouter.setLanClient(client);\n }\n\n /**\n * Register the undocumented API client for scene/music/DIY libraries\n *\n * @param client API client instance\n */\n setApiClient(client: GoveeApiClient): void {\n this.apiClient = client;\n }\n\n /**\n * Register the Cloud client\n *\n * @param client Cloud API client instance\n */\n setCloudClient(client: GoveeCloudClient): void {\n this.cloudClient = client;\n this.commandRouter.setCloudClient(client);\n }\n\n /**\n * Register the rate limiter for cloud calls\n *\n * @param limiter Rate limiter instance\n */\n setRateLimiter(limiter: RateLimiter): void {\n this.commandRouter.setRateLimiter(limiter);\n }\n\n /**\n * Register the SKU cache for persistent device data\n *\n * @param cache SKU cache instance\n */\n setSkuCache(cache: SkuCache): void {\n this.skuCache = cache;\n }\n\n /**\n * Set callbacks for device state changes and list changes.\n *\n * @param onUpdate Called when a device state changes (from any channel)\n * @param onListChanged Called when the device list changes (new/removed devices)\n */\n setCallbacks(\n onUpdate: (device: GoveeDevice, state: Partial<DeviceState>) => void,\n onListChanged: (devices: GoveeDevice[]) => void,\n ): void {\n this.onDeviceUpdate = onUpdate;\n this.onDeviceListChanged = onListChanged;\n }\n\n /** Get all known devices */\n getDevices(): GoveeDevice[] {\n return Array.from(this.devices.values());\n }\n\n /**\n * Load devices from local SKU cache.\n * Returns true if any devices were loaded (= Cloud not needed).\n */\n loadFromCache(): boolean {\n if (!this.skuCache) {\n return false;\n }\n\n const cached = this.skuCache.loadAll();\n if (cached.length === 0) {\n return false;\n }\n\n let changed = false;\n for (const entry of cached) {\n const key = this.deviceKey(entry.sku, entry.deviceId);\n const existing = this.devices.get(key);\n if (existing) {\n // Merge cached data into LAN-discovered device. Segment-specific\n // fields (segmentCount, manualMode, manualSegments) MUST be merged\n // too \u2014 LAN discovery runs before the cache load on every start, so\n // the existing-branch is the normal path. Missing these three meant\n // every restart threw away the wizard/MQTT-learned segment state and\n // fell back to Cloud's min-advertised count.\n existing.name = entry.name || existing.name;\n existing.type = entry.type || existing.type;\n existing.capabilities = entry.capabilities;\n existing.scenes = entry.scenes;\n existing.diyScenes = entry.diyScenes;\n existing.snapshots = entry.snapshots;\n existing.sceneLibrary = entry.sceneLibrary;\n existing.musicLibrary = entry.musicLibrary;\n existing.diyLibrary = entry.diyLibrary;\n existing.skuFeatures = entry.skuFeatures;\n existing.snapshotBleCmds = entry.snapshotBleCmds;\n existing.scenesChecked = entry.scenesChecked;\n existing.lastSeenOnNetwork = entry.lastSeenOnNetwork;\n existing.segmentCount = entry.segmentCount;\n existing.manualMode = entry.manualMode;\n existing.manualSegments = entry.manualSegments;\n existing.channels.cloud = entry.capabilities.length > 0;\n changed = true;\n } else {\n this.devices.set(key, this.cachedToGoveeDevice(entry));\n changed = true;\n }\n }\n\n if (changed) {\n this.log.info(`Loaded ${cached.length} device(s) from cache`);\n }\n\n // Always refetch cloud data on startup \u2014 scenesChecked is purely diagnostic\n // now, not a gate. Snapshots are user-content (created dynamically in the\n // Govee Home app) and would miss new entries if we relied solely on the\n // cache. The refetch costs one call per light device per startup, well\n // within rate limits. Users can also trigger a fresh fetch without\n // restart via `info.refresh_cloud_data`.\n const hasLight = Array.from(this.devices.values()).some(d => d.type === \"devices.types.light\");\n if (hasLight) {\n this.log.debug(\"Cache loaded \u2014 will refresh scenes/snapshots via Cloud\");\n return false;\n }\n\n // Fill scenes from sceneLibrary for devices where Cloud scenes are missing\n for (const device of this.devices.values()) {\n this.populateScenesFromLibrary(device);\n }\n\n if (changed) {\n this.onDeviceListChanged?.(this.getDevices());\n }\n return cached.length > 0;\n }\n\n /**\n * Load devices from Cloud API and save to cache.\n * Only called when cache is empty (first start) or manual refresh.\n */\n async loadFromCloud(): Promise<CloudLoadResult> {\n if (!this.cloudClient) {\n return { ok: false, reason: \"transient\" };\n }\n\n try {\n const rawCloudDevices = await this.cloudClient.getDevices();\n\n // Hard-filter: Govee's Device-List API returns historical/stale entries\n // (deleted devices that are no longer in the app). Filter out entries\n // without capabilities \u2014 those are almost certainly stale registrations.\n const cloudDevices = Array.isArray(rawCloudDevices)\n ? rawCloudDevices.filter(\n cd =>\n cd &&\n typeof cd.sku === \"string\" &&\n typeof cd.device === \"string\" &&\n Array.isArray(cd.capabilities) &&\n cd.capabilities.length > 0,\n )\n : [];\n\n if (Array.isArray(rawCloudDevices) && rawCloudDevices.length !== cloudDevices.length) {\n this.log.info(\n `Cloud: received ${rawCloudDevices.length} devices raw, ${cloudDevices.length} after filter (skipped stale entries without capabilities)`,\n );\n }\n\n // Step 1: Merge Cloud devices into local device map\n let changed = this.mergeCloudDevices(cloudDevices);\n\n // Step 2: Load scenes, snapshots, and libraries for any device that\n // exposes a `dynamic_scene` capability \u2014 independent of `cd.type`.\n // Govee occasionally returns devices with `type` missing or a value\n // we don't recognise; keying off the capability is what the rest of\n // the codebase already uses to decide whether scene/snapshot states\n // exist, so the loader has to follow the same rule.\n for (const cd of cloudDevices) {\n const caps = Array.isArray(cd.capabilities) ? cd.capabilities : [];\n const hasSceneCap =\n hasDynamicSceneCapability(caps, \"lightScene\") ||\n hasDynamicSceneCapability(caps, \"diyScene\") ||\n hasDynamicSceneCapability(caps, \"snapshot\");\n const isLight = cd.type === \"devices.types.light\" || hasSceneCap;\n if (isLight) {\n const device = this.devices.get(this.deviceKey(cd.sku, cd.device));\n if (device) {\n if (await this.loadDeviceScenes(device, cd)) {\n changed = true;\n }\n if (await this.loadDeviceLibraries(device, cd.sku)) {\n changed = true;\n }\n // Mark scenes as checked regardless of result \u2014 empty is legitimate,\n // and we've now confirmed that via Cloud. Prevents refetch loop.\n device.scenesChecked = true;\n }\n }\n }\n\n // Step 3: Prune stale cache entries (only after successful Cloud-load\n // with a plausible response \u2014 never prune on Cloud failure or empty list)\n if (this.skuCache && cloudDevices.length > 0) {\n this.skuCache.pruneStale(14);\n }\n\n // Step 4: Save to cache and finalize\n this.saveDevicesToCache();\n\n for (const device of this.devices.values()) {\n this.populateScenesFromLibrary(device);\n }\n\n if (changed) {\n this.onDeviceListChanged?.(this.getDevices());\n }\n this.lastErrorCategory = null;\n return { ok: true };\n } catch (err) {\n this.logDedup(\"Cloud device list failed\", err);\n\n // Govee 429: respect Retry-After header (default 60s if missing)\n if (err instanceof HttpError && err.statusCode === 429) {\n const retryAfterRaw = err.headers[\"retry-after\"];\n const retryAfterSec =\n typeof retryAfterRaw === \"string\" && /^\\d+$/.test(retryAfterRaw) ? parseInt(retryAfterRaw, 10) : 60;\n return {\n ok: false,\n reason: \"rate-limited\",\n retryAfterMs: retryAfterSec * 1000,\n };\n }\n\n // Auth failure: API-Key falsch oder widerrufen \u2014 KEIN Retry\n const category = classifyError(err);\n if (category === \"AUTH\") {\n return {\n ok: false,\n reason: \"auth-failed\",\n message: err instanceof Error ? err.message : String(err),\n };\n }\n\n // Netzwerk/Timeout/Unknown: transient, einfach sp\u00E4ter\n return { ok: false, reason: \"transient\" };\n }\n }\n\n /**\n * Re-fetch scenes, snapshots AND libraries for all known light devices\n * without re-running the full Cloud bootstrap. Used by the\n * `info.refresh_cloud_data` button: \"new snapshot/scene was saved in\n * the Govee Home app, show it here\".\n *\n * v1.10.1 had skipped libraries to keep the per-click call count low \u2014\n * but for users on accounts where a library actually grew (new\n * scene-set rolled out by Govee, new sceneCode minted) the only way\n * back to fresh data was a full adapter restart. v2.1.0 reinstates the\n * library refresh on the same button and forces past the\n * `length === 0` guards inside `loadDeviceLibraries`.\n *\n * @returns true when any device's scene/snapshot/library data changed\n */\n async refreshSceneData(): Promise<boolean> {\n if (!this.cloudClient) {\n return false;\n }\n let anyChanged = false;\n const lights = Array.from(this.devices.values()).filter(d => d.type === \"devices.types.light\");\n for (const dev of lights) {\n this.diagnostics.addLog(dev.deviceId, \"info\", `User-triggered refresh-cloud-data for ${dev.sku}`);\n }\n for (const device of lights) {\n const cd: CloudDevice = {\n sku: device.sku,\n device: device.deviceId,\n deviceName: device.name,\n type: device.type,\n capabilities: Array.isArray(device.capabilities) ? device.capabilities : [],\n };\n if (await this.loadDeviceScenes(device, cd)) {\n anyChanged = true;\n }\n if (await this.loadDeviceLibraries(device, cd.sku, /* force */ true)) {\n anyChanged = true;\n }\n }\n if (anyChanged) {\n this.saveDevicesToCache();\n for (const device of this.devices.values()) {\n this.populateScenesFromLibrary(device);\n }\n this.onDeviceListChanged?.(this.getDevices());\n }\n return anyChanged;\n }\n\n /**\n * Merge Cloud device list into local device map.\n * Updates existing devices, adds new ones.\n *\n * @param cloudDevices Devices from Cloud API\n * @returns true if any new devices were added\n */\n private mergeCloudDevices(cloudDevices: CloudDevice[]): boolean {\n let changed = false;\n if (!Array.isArray(cloudDevices)) {\n return false;\n }\n for (const cd of cloudDevices) {\n // Defensive guard against malformed cloud entries\n if (!cd || typeof cd.sku !== \"string\" || typeof cd.device !== \"string\") {\n continue;\n }\n const existing = this.devices.get(this.deviceKey(cd.sku, cd.device));\n if (existing) {\n existing.name = cd.deviceName || existing.name;\n existing.capabilities = Array.isArray(cd.capabilities) ? cd.capabilities : [];\n existing.type = cd.type;\n existing.channels.cloud = true;\n } else {\n const device = this.cloudDeviceToGoveeDevice(cd);\n this.devices.set(this.deviceKey(cd.sku, cd.device), device);\n changed = true;\n this.log.debug(`Cloud: New device ${cd.deviceName} (${cd.sku})`);\n this.maybeNudgeSeedSku(cd.sku, cd.deviceName);\n }\n\n const quirks = getDeviceQuirks(cd.sku);\n if (quirks?.brokenPlatformApi) {\n this.log.debug(`${cd.sku} has known broken platform API metadata \u2014 capabilities may be incomplete`);\n }\n }\n return changed;\n }\n\n /**\n * Load scenes, DIY scenes, and snapshots for a device from Cloud API.\n *\n * @param device Target device to populate\n * @param cd Cloud device data with capabilities\n * @returns true if any scene data changed\n */\n private async loadDeviceScenes(device: GoveeDevice, cd: CloudDevice): Promise<boolean> {\n this.diagnostics.addLog(cd.device, \"debug\", `loadDeviceScenes called for ${cd.sku}`);\n // Scenes from dedicated scenes endpoint (rate-limited).\n // Guards are per-list, not combined: Govee's /device/scenes sometimes\n // returns 149 lightScenes + 0 snapshots (or vice versa) on back-to-back\n // calls even though the snapshot exists. A combined guard (if any list\n // non-empty, overwrite all) would wipe the other lists on that call and\n // break the dropdown until the next lucky round-trip. One guard per\n // list keeps the last-known-good data in place.\n const loadScenes = async (): Promise<void> => {\n try {\n const { lightScenes, diyScenes, snapshots } = await this.cloudClient!.getScenes(cd.sku, cd.device);\n // Cloud-client already captured the raw response on success \u2014 but\n // record the catch path here so the diag JSON shows \"/device/scenes\n // attempted, returned 403\" instead of an empty slot.\n if (lightScenes.length > 0) {\n device.scenes = lightScenes;\n }\n if (diyScenes.length > 0) {\n device.diyScenes = diyScenes;\n }\n if (snapshots.length > 0) {\n device.snapshots = snapshots;\n }\n } catch (e) {\n this.diagnostics.recordApiFailure(cd.device, \"/router/api/v1/device/scenes\", e, this.extractStatus(e));\n this.log.debug(`Could not load scenes for ${cd.sku}: ${e instanceof Error ? e.message : String(e)}`);\n }\n };\n await this.commandRouter.executeRateLimited(loadScenes, 2);\n\n // DIY scenes from dedicated endpoint\n if (device.diyScenes.length === 0) {\n const loadDiy = async (): Promise<void> => {\n try {\n const diy = await this.cloudClient!.getDiyScenes(cd.sku, cd.device);\n if (diy.length > 0) {\n device.diyScenes = diy;\n }\n } catch (e) {\n this.diagnostics.recordApiFailure(cd.device, \"/router/api/v1/device/diy-scenes\", e, this.extractStatus(e));\n this.log.debug(`Could not load DIY scenes for ${cd.sku}: ${e instanceof Error ? e.message : String(e)}`);\n }\n };\n await this.commandRouter.executeRateLimited(loadDiy, 2);\n }\n\n // Snapshots from device capabilities (fallback)\n if (device.snapshots.length === 0) {\n const caps = Array.isArray(cd.capabilities) ? cd.capabilities : [];\n const snapCap = caps.find(\n c =>\n c &&\n c.type === \"devices.capabilities.dynamic_scene\" &&\n c.instance === \"snapshot\" &&\n Array.isArray(c.parameters?.options),\n );\n if (snapCap?.parameters?.options) {\n device.snapshots = snapCap.parameters.options\n .filter(o => o && typeof o.name === \"string\" && o.value !== undefined && o.value !== null)\n .map(o => ({\n name: o.name,\n value: typeof o.value === \"number\" ? o.value : (o.value as Record<string, unknown>),\n }));\n this.log.debug(`Snapshots from capabilities for ${cd.sku}: ${device.snapshots.length}`);\n }\n }\n\n // \"Changed\" = we ended up with any scene/snapshot data. Inner tracking\n // was redundant with this single-source check.\n return device.scenes.length > 0 || device.diyScenes.length > 0 || device.snapshots.length > 0;\n }\n\n /**\n * Load scene/music/DIY libraries and SKU features from undocumented API.\n *\n * Each fetch runs through the rate-limiter so a fresh install with 10\n * devices doesn't slam app2.govee.com with 40 back-to-back requests \u2014\n * those endpoints are undocumented and aggressive callers can get the\n * account temporarily locked.\n *\n * @param device Target device to populate\n * @param sku Product model\n * @param force When true, refetch every endpoint regardless of cache \u2014\n * used by the user-triggered refresh button so a stale library\n * actually gets replaced\n * @returns true if any library data changed\n */\n private async loadDeviceLibraries(device: GoveeDevice, sku: string, force = false): Promise<boolean> {\n if (!this.apiClient) {\n return false;\n }\n\n this.diagnostics.addLog(device.deviceId, \"debug\", `loadDeviceLibraries called for ${sku} (force=${force})`);\n let changed = false;\n\n // Run each fetch inside a rate-limited slot. Priority 2 = below\n // control commands and scene/snapshot loads; library data is cache-only\n // and can wait for a quieter moment.\n const runLimited = async (fn: () => Promise<void>): Promise<void> => {\n await this.commandRouter.executeRateLimited(fn, 2);\n };\n\n if (force || device.sceneLibrary.length === 0) {\n await runLimited(async () => {\n const ep = `/light-effect-libraries?sku=${sku}`;\n try {\n const lib = await this.apiClient!.fetchSceneLibrary(sku);\n this.diagnostics.recordApiSuccess(device.deviceId, ep, { count: lib.length, names: lib.map(s => s.name) });\n if (lib.length > 0) {\n device.sceneLibrary = lib;\n changed = true;\n this.log.debug(`Scene library for ${sku}: ${lib.length} scenes`);\n }\n } catch (e) {\n this.diagnostics.recordApiFailure(device.deviceId, ep, e, this.extractStatus(e));\n this.log.debug(`Could not load scene library for ${sku}: ${e instanceof Error ? e.message : String(e)}`);\n }\n });\n }\n\n if (force || device.musicLibrary.length === 0) {\n await runLimited(async () => {\n const ep = `/light-effect-libraries-music?sku=${sku}`;\n try {\n const lib = await this.apiClient!.fetchMusicLibrary(sku);\n this.diagnostics.recordApiSuccess(device.deviceId, ep, { count: lib.length, names: lib.map(m => m.name) });\n if (lib.length > 0) {\n device.musicLibrary = lib;\n changed = true;\n this.log.debug(`Music library for ${sku}: ${lib.length} modes`);\n }\n } catch (e) {\n this.diagnostics.recordApiFailure(device.deviceId, ep, e, this.extractStatus(e));\n this.log.debug(`Could not load music library for ${sku}: ${e instanceof Error ? e.message : String(e)}`);\n }\n });\n }\n\n if (force || device.diyLibrary.length === 0) {\n await runLimited(async () => {\n const ep = `/diy-effect-libraries?sku=${sku}`;\n try {\n const lib = await this.apiClient!.fetchDiyLibrary(sku);\n this.diagnostics.recordApiSuccess(device.deviceId, ep, { count: lib.length, names: lib.map(d => d.name) });\n if (lib.length > 0) {\n device.diyLibrary = lib;\n changed = true;\n this.log.debug(`DIY library for ${sku}: ${lib.length} effects`);\n }\n } catch (e) {\n this.diagnostics.recordApiFailure(device.deviceId, ep, e, this.extractStatus(e));\n this.log.debug(`Could not load DIY library for ${sku}: ${e instanceof Error ? e.message : String(e)}`);\n }\n });\n }\n\n if (force || !device.skuFeatures) {\n await runLimited(async () => {\n const ep = `/sku-features?sku=${sku}`;\n try {\n const features = await this.apiClient!.fetchSkuFeatures(sku);\n this.diagnostics.recordApiSuccess(device.deviceId, ep, features);\n if (features) {\n device.skuFeatures = features;\n changed = true;\n this.log.debug(`SKU features for ${sku}: ${JSON.stringify(features).slice(0, 200)}`);\n }\n } catch (e) {\n this.diagnostics.recordApiFailure(device.deviceId, ep, e, this.extractStatus(e));\n this.log.debug(`Could not load SKU features for ${sku}: ${e instanceof Error ? e.message : String(e)}`);\n }\n });\n }\n\n // Load snapshot BLE commands for local activation\n if (!device.snapshotBleCmds && device.snapshots.length > 0) {\n await runLimited(async () => {\n try {\n const snaps = await this.apiClient!.fetchSnapshots(sku, device.deviceId);\n if (snaps.length > 0) {\n device.snapshotBleCmds = device.snapshots.map(ds => {\n const match = snaps.find(s => s.name === ds.name);\n return match?.bleCmds ?? [];\n });\n changed = true;\n this.log.debug(`Snapshot BLE for ${sku}: ${snaps.length} snapshots with local data`);\n }\n } catch (e) {\n this.log.debug(`Could not load snapshot BLE for ${sku}: ${e instanceof Error ? e.message : String(e)}`);\n }\n });\n }\n\n return changed;\n }\n\n /**\n * Load group membership from undocumented API and attach to BaseGroup devices.\n * Resolves member device references against the current device map.\n *\n * @returns true if any group memberships were resolved\n */\n async loadGroupMembers(): Promise<boolean> {\n if (!this.apiClient) {\n return false;\n }\n if (!this.apiClient.hasBearerToken()) {\n this.log.debug(\"Group membership requires Email+Password \u2014 skipping member resolution\");\n return false;\n }\n\n try {\n const apiGroups = await this.apiClient.fetchGroupMembers();\n if (apiGroups.length === 0) {\n this.log.debug(\"No group membership data from API\");\n return false;\n }\n\n let changed = false;\n for (const group of this.devices.values()) {\n if (group.sku !== \"BaseGroup\") {\n continue;\n }\n // Match by groupId: BaseGroup deviceId is the numeric group ID as string\n const apiGroup = apiGroups.find(g => String(g.groupId) === group.deviceId);\n if (!apiGroup) {\n continue;\n }\n\n // Resolve member devices against our device map\n const members: { sku: string; deviceId: string }[] = [];\n for (const m of apiGroup.devices) {\n const resolved = this.findDeviceBySkuAndId(m.sku, m.deviceId);\n if (resolved) {\n members.push({ sku: resolved.sku, deviceId: resolved.deviceId });\n } else {\n this.log.debug(`Group \"${group.name}\": member ${m.sku}/${m.deviceId} not in device map`);\n }\n }\n\n group.groupMembers = members;\n if (members.length > 0) {\n changed = true;\n }\n this.log.debug(`Group \"${group.name}\": ${members.length}/${apiGroup.devices.length} members resolved`);\n }\n\n if (changed) {\n this.onDeviceListChanged?.(this.getDevices());\n }\n return changed;\n } catch (e) {\n this.log.debug(`Could not load group members: ${e instanceof Error ? e.message : String(e)}`);\n return false;\n }\n }\n\n /** Save all devices to SKU cache, skipping only those never confirmed via Cloud yet. */\n public saveDevicesToCache(): void {\n if (!this.skuCache) {\n return;\n }\n\n let cachedCount = 0;\n let skippedCount = 0;\n for (const device of this.devices.values()) {\n const isLight = device.type === \"devices.types.light\";\n // Skip only if we never asked Cloud yet \u2014 empty scenes are legitimate\n // once confirmed via scenesChecked=true.\n if (isLight && !device.scenesChecked) {\n skippedCount++;\n this.log.debug(`Not caching ${device.name} (${device.sku}) \u2014 scenes not yet checked`);\n } else {\n this.skuCache.save(this.goveeDeviceToCached(device));\n cachedCount++;\n }\n }\n // Routine persistence \u2014 debug level only. Users don't need a play-by-play\n // for every cache write. Significant events (scenes fetched, MQTT bumps)\n // log themselves elsewhere.\n if (skippedCount > 0) {\n this.log.debug(`Cached ${cachedCount} device(s), skipped ${skippedCount} not yet checked`);\n } else {\n this.log.debug(`Cached ${cachedCount} device(s) \u2014 next start uses cache`);\n }\n }\n\n /**\n * Handle LAN device discovery \u2014 match against known devices or create new.\n *\n * @param lanDevice Discovered LAN device\n */\n handleLanDiscovery(lanDevice: LanDevice): void {\n // Try to find by device ID (colon-separated in Cloud, varies in LAN)\n let matched: GoveeDevice | undefined;\n for (const dev of this.devices.values()) {\n if (normalizeDeviceId(dev.deviceId) === normalizeDeviceId(lanDevice.device)) {\n matched = dev;\n break;\n }\n // Also match by SKU if device IDs don't match format\n if (dev.sku === lanDevice.sku && !dev.lanIp) {\n matched = dev;\n break;\n }\n }\n\n if (matched) {\n const ipChanged = matched.lanIp !== lanDevice.ip;\n const wasOffline = matched.state.online !== true;\n matched.lanIp = lanDevice.ip;\n matched.channels.lan = true;\n matched.lastSeenOnNetwork = Date.now();\n if (ipChanged) {\n this.log.debug(`LAN: ${matched.name} (${matched.sku}) at ${lanDevice.ip}`);\n this.onLanIpChanged?.(matched, lanDevice.ip);\n }\n // Discovery-Antwort beweist dass das Ger\u00E4t am Netz ist. main.ts skipped\n // den expliziten devStatus-Poll wenn MQTT connected ist, und MQTT pushed\n // nur bei tats\u00E4chlichen Zustandswechseln \u2014 d.h. ohne diesen Pfad bleibt\n // info.online f\u00FCr gecachte Lichter forever false.\n if (wasOffline) {\n matched.state.online = true;\n this.onDeviceUpdate?.(matched, { online: true });\n }\n } else {\n // LAN-only device (no Cloud data yet)\n // Include short device ID suffix for uniqueness (multiple devices can share same SKU)\n const shortId = normalizeDeviceId(lanDevice.device).slice(-4);\n const device: GoveeDevice = {\n sku: lanDevice.sku,\n deviceId: lanDevice.device,\n name: `${lanDevice.sku}_${shortId}`,\n type: \"devices.types.light\",\n lanIp: lanDevice.ip,\n capabilities: [],\n scenes: [],\n diyScenes: [],\n snapshots: [],\n sceneLibrary: [],\n musicLibrary: [],\n diyLibrary: [],\n skuFeatures: null,\n lastSeenOnNetwork: Date.now(),\n state: { online: true },\n channels: { lan: true, mqtt: false, cloud: false },\n };\n this.devices.set(this.deviceKey(lanDevice.sku, lanDevice.device), device);\n this.diagnostics.addLog(lanDevice.device, \"info\", `LAN-discovered at ${lanDevice.ip}`);\n this.log.debug(`LAN: New device ${lanDevice.sku} at ${lanDevice.ip}`);\n this.maybeNudgeSeedSku(lanDevice.sku, device.name);\n this.onDeviceListChanged?.(this.getDevices());\n }\n }\n\n /**\n * Log the device's trust tier \u2014 once per SKU per adapter lifetime, so\n * device reconnects don't spam the log. Behaviour by tier:\n * - verified / reported: silent (the catalog backs the device, no\n * action needed). The tier is still surfaced via the\n * `diag.tier` state for any user who wants to check.\n * - seed (toggle off): warn \u2014 points the user at the experimental\n * toggle that gates the per-SKU corrections we'd otherwise apply.\n * - seed (toggle on): info \u2014 confirms quirks are active.\n * - unknown: warn \u2014 asks for a diagnostics export so we can add the\n * SKU to the catalogue.\n *\n * @param sku Govee SKU\n * @param displayName Device name as shown in Govee Home\n */\n private maybeNudgeSeedSku(sku: string, displayName: string | undefined): void {\n const upper = (typeof sku === \"string\" ? sku : \"\").toUpperCase();\n if (!upper || this.nudgedSeedSkus.has(upper)) {\n return;\n }\n this.nudgedSeedSkus.add(upper);\n const tier = getDeviceTier(upper);\n const label = displayName ? `${displayName} (${upper})` : upper;\n switch (tier) {\n case \"verified\":\n case \"reported\":\n return;\n case \"seed\":\n if (isSeedAndDormant(upper)) {\n this.log.warn(\n `Device ${label} is in beta and needs the \"Experimentelle Ger\u00E4te-Unterst\u00FCtzung aktivieren\" toggle in adapter settings to apply known per-SKU corrections.`,\n );\n } else {\n this.log.info(`Device ${label} is in beta \u2014 experimental quirks are active.`);\n }\n return;\n case \"unknown\":\n this.log.warn(\n `Device ${label} is not in the supported device list. Please trigger diag.export and post the resulting JSON in a GitHub issue so the SKU can be added.`,\n );\n return;\n }\n }\n\n /**\n * Handle MQTT status update \u2014 update device state.\n *\n * @param update MQTT status message\n */\n handleMqttStatus(update: MqttStatusUpdate): void {\n const device = this.findDeviceBySkuAndId(update.sku, update.device);\n if (!device) {\n this.log.debug(`MQTT: Unknown device ${update.sku} ${update.device}`);\n return;\n }\n\n device.channels.mqtt = true;\n device.lastSeenOnNetwork = Date.now();\n const state: Partial<DeviceState> = { online: true };\n\n if (update.state) {\n if (update.state.onOff !== undefined) {\n state.power = update.state.onOff === 1;\n }\n if (update.state.brightness !== undefined) {\n state.brightness = update.state.brightness;\n }\n if (update.state.color) {\n const { r, g, b } = update.state.color;\n state.colorRgb = rgbToHex(r, g, b);\n }\n if (update.state.colorTemInKelvin) {\n state.colorTemperature = update.state.colorTemInKelvin;\n }\n }\n\n // Merge into device state\n Object.assign(device.state, state);\n this.onDeviceUpdate?.(device, state);\n\n // Parse per-segment data from BLE notification packets (AA A5).\n // MQTT is authoritative for segment count \u2014 the device tells us what it\n // actually has. Cloud only gives an initial best-guess from capabilities.\n if (update.op?.command) {\n const segData = parseMqttSegmentData(update.op.command);\n\n if (segData.length > 0) {\n const maxSeen = Math.max(...segData.map(s => s.index)) + 1;\n const current = device.segmentCount ?? 0;\n if (maxSeen > current) {\n this.log.info(\n `${device.name}: detected ${maxSeen} segments via MQTT (was ${current}) \u2014 rebuilding state tree`,\n );\n device.segmentCount = maxSeen;\n // Persist now so a restart starts from the real value instead of\n // falling back to Cloud capabilities and deleting the extra slots.\n if (this.skuCache) {\n this.skuCache.save(this.goveeDeviceToCached(device));\n }\n // Skip per-segment sync for this push \u2014 the new datapoints don't\n // exist yet. The next AA A5 push hits the fully-built tree.\n this.onSegmentCountGrown?.(device);\n return;\n }\n }\n\n // Filter by manual-segments override if active \u2014 ignore indices the\n // user has declared as \"not physically present\" (cut strip).\n const filtered =\n device.manualMode && Array.isArray(device.manualSegments) && device.manualSegments.length > 0\n ? segData.filter(s => device.manualSegments!.includes(s.index))\n : segData;\n if (filtered.length > 0) {\n this.onMqttSegmentUpdate?.(device, filtered);\n }\n }\n }\n\n /**\n * Handle LAN status response.\n *\n * @param ip Source IP address\n * @param status LAN status data\n * @param status.onOff Power state (1=on, 0=off)\n * @param status.brightness Brightness 0-100\n * @param status.color RGB color values\n * @param status.color.r Red channel 0-255\n * @param status.color.g Green channel 0-255\n * @param status.color.b Blue channel 0-255\n * @param status.colorTemInKelvin Color temperature in Kelvin\n */\n handleLanStatus(\n ip: string,\n status: {\n onOff: number;\n brightness: number;\n color: { r: number; g: number; b: number };\n colorTemInKelvin: number;\n },\n ): void {\n // Find device by LAN IP\n let device: GoveeDevice | undefined;\n for (const dev of this.devices.values()) {\n if (dev.lanIp === ip) {\n device = dev;\n break;\n }\n }\n if (!device) {\n return;\n }\n\n device.lastSeenOnNetwork = Date.now();\n const { r, g, b } = status.color;\n const state: Partial<DeviceState> = {\n online: true,\n power: status.onOff === 1,\n brightness: status.brightness,\n colorRgb: rgbToHex(r, g, b),\n colorTemperature: status.colorTemInKelvin || undefined,\n };\n\n Object.assign(device.state, state);\n this.onDeviceUpdate?.(device, state);\n }\n\n /**\n * Set the callback for batch segment state sync.\n * Forwards to the internal CommandRouter.\n *\n * @param callback Called when a segment batch command updates segment states\n */\n set onSegmentBatchUpdate(\n callback:\n | ((device: GoveeDevice, batch: { segments: number[]; color?: number; brightness?: number }) => void)\n | undefined,\n ) {\n this.commandRouter.onSegmentBatchUpdate = callback;\n }\n\n /**\n * Send a command to a device \u2014 routes through LAN \u2192 Cloud.\n *\n * @param device Target device\n * @param command Command type\n * @param value Command value\n */\n async sendCommand(device: GoveeDevice, command: string, value: unknown): Promise<void> {\n return this.commandRouter.sendCommand(device, command, value);\n }\n\n /**\n * Send a generic capability command via Cloud API.\n * Used for capability types not explicitly handled (toggle, dynamic_scene, etc.)\n *\n * @param device Target device\n * @param capabilityType Full capability type (e.g. \"devices.capabilities.toggle\")\n * @param capabilityInstance Capability instance name (e.g. \"gradientToggle\")\n * @param value Command value\n */\n async sendCapabilityCommand(\n device: GoveeDevice,\n capabilityType: string,\n capabilityInstance: string,\n value: unknown,\n ): Promise<void> {\n return this.commandRouter.sendCapabilityCommand(device, capabilityType, capabilityInstance, value);\n }\n\n /** Callback when device LAN IP changes */\n onLanIpChanged?: (device: GoveeDevice, ip: string) => void;\n\n /** Callback when MQTT delivers per-segment state data (AA A5 BLE packets) */\n onMqttSegmentUpdate?: (device: GoveeDevice, segments: MqttSegmentData[]) => void;\n\n /**\n * Callback when the device's physical segment count turns out to be\n * larger than the Cloud-reported value (observed via MQTT AA A5 stream).\n * The adapter rebuilds the state tree in response so the extra indices\n * appear as datapoints.\n */\n onSegmentCountGrown?: (device: GoveeDevice) => void;\n\n /**\n * Convert Cloud device to internal device model\n *\n * @param cd Cloud API device data\n */\n private cloudDeviceToGoveeDevice(cd: CloudDevice): GoveeDevice {\n return {\n sku: cd.sku,\n deviceId: cd.device,\n name: cd.deviceName || cd.sku,\n type: cd.type || \"unknown\",\n capabilities: Array.isArray(cd.capabilities) ? cd.capabilities : [],\n scenes: [],\n diyScenes: [],\n snapshots: [],\n sceneLibrary: [],\n musicLibrary: [],\n diyLibrary: [],\n skuFeatures: null,\n state: { online: true },\n channels: { lan: false, mqtt: false, cloud: true },\n };\n }\n\n /**\n * Find device by SKU and device ID (handles format differences)\n *\n * @param sku Product model\n * @param deviceId Device identifier\n */\n private findDeviceBySkuAndId(sku: string, deviceId: string): GoveeDevice | undefined {\n // Direct key lookup\n const direct = this.devices.get(this.deviceKey(sku, deviceId));\n if (direct) {\n return direct;\n }\n\n // Normalized search\n const normalizedId = normalizeDeviceId(deviceId);\n for (const dev of this.devices.values()) {\n if (dev.sku === sku && normalizeDeviceId(dev.deviceId) === normalizedId) {\n return dev;\n }\n }\n return undefined;\n }\n\n /**\n * Generate unique key for a device\n *\n * @param sku Product model\n * @param deviceId Device identifier\n */\n private deviceKey(sku: string, deviceId: string): string {\n return `${sku}_${normalizeDeviceId(deviceId)}`;\n }\n\n /**\n * Log error with dedup \u2014 only warn on category change, debug on repeat.\n *\n * @param context Error context description\n * @param err Error to log\n */\n private logDedup(context: string, err: unknown): void {\n const category = classifyError(err);\n const msg = `${context}: ${err instanceof Error ? err.message : String(err)}`;\n if (category !== this.lastErrorCategory) {\n this.lastErrorCategory = category;\n this.log.warn(msg);\n } else {\n this.log.debug(`${msg} (repeated)`);\n }\n }\n\n /**\n * Fill device.scenes from sceneLibrary when Cloud scenes are missing.\n * ptReal activation matches by name, so sceneLibrary names are sufficient.\n *\n * @param device Device to populate scenes for\n */\n private populateScenesFromLibrary(device: GoveeDevice): void {\n if (device.scenes.length === 0 && device.sceneLibrary.length > 0) {\n device.scenes = device.sceneLibrary.map(entry => ({\n name: entry.name,\n value: {}, // ptReal uses sceneLibrary directly, Cloud payload not needed\n }));\n this.log.debug(`${device.sku}: ${device.scenes.length} scenes from library (Cloud scenes missing)`);\n }\n }\n\n /**\n * Convert cached data to a GoveeDevice (runtime fields set to defaults)\n *\n * @param cached Cached device data\n */\n private cachedToGoveeDevice(cached: CachedDeviceData): GoveeDevice {\n return {\n sku: cached.sku,\n deviceId: cached.deviceId,\n name: cached.name,\n type: cached.type,\n capabilities: cached.capabilities,\n scenes: cached.scenes,\n diyScenes: cached.diyScenes,\n snapshots: cached.snapshots,\n sceneLibrary: cached.sceneLibrary,\n musicLibrary: cached.musicLibrary,\n diyLibrary: cached.diyLibrary,\n skuFeatures: cached.skuFeatures,\n snapshotBleCmds: cached.snapshotBleCmds,\n scenesChecked: cached.scenesChecked,\n lastSeenOnNetwork: cached.lastSeenOnNetwork,\n // Restore learned count so it wins over Cloud capability on next start.\n segmentCount: cached.segmentCount,\n manualMode: cached.manualMode,\n manualSegments: cached.manualSegments,\n sceneSpeed: cached.sceneSpeed,\n state: { online: false },\n channels: { lan: false, mqtt: false, cloud: false },\n };\n }\n\n /**\n * Persist a device's current runtime state to the SKU cache.\n * Safe no-op when no cache is configured.\n *\n * @param device Target device\n */\n public persistDeviceToCache(device: GoveeDevice): void {\n if (!this.skuCache) {\n return;\n }\n this.skuCache.save(this.goveeDeviceToCached(device));\n }\n\n /**\n * Extract cacheable data from a GoveeDevice.\n *\n * @param device Runtime device\n */\n private goveeDeviceToCached(device: GoveeDevice): CachedDeviceData {\n return {\n sku: device.sku,\n deviceId: device.deviceId,\n name: device.name,\n type: device.type,\n capabilities: device.capabilities,\n scenes: device.scenes,\n diyScenes: device.diyScenes,\n snapshots: device.snapshots,\n sceneLibrary: device.sceneLibrary,\n musicLibrary: device.musicLibrary,\n diyLibrary: device.diyLibrary,\n skuFeatures: device.skuFeatures,\n snapshotBleCmds: device.snapshotBleCmds,\n scenesChecked: device.scenesChecked,\n lastSeenOnNetwork: device.lastSeenOnNetwork,\n segmentCount:\n typeof device.segmentCount === \"number\" && device.segmentCount > 0 ? device.segmentCount : undefined,\n manualMode: device.manualMode ? true : undefined,\n manualSegments:\n device.manualMode && Array.isArray(device.manualSegments) && device.manualSegments.length > 0\n ? device.manualSegments.slice()\n : undefined,\n sceneSpeed: typeof device.sceneSpeed === \"number\" && device.sceneSpeed > 0 ? device.sceneSpeed : undefined,\n cachedAt: Date.now(),\n };\n }\n\n /**\n * Generate diagnostics data for a device \u2014 structured JSON for GitHub\n * issue submission. Delegates to the DiagnosticsCollector so the JSON\n * also includes ring-buffer context (recent logs, MQTT packets, last\n * API responses).\n *\n * @param device Target device\n * @param adapterVersion Adapter version string\n */\n generateDiagnostics(device: GoveeDevice, adapterVersion: string): Record<string, unknown> {\n return this.diagnostics.generate(device, adapterVersion);\n }\n\n /**\n * Poll the undocumented app-API for sensor-like devices (H5179 et al.)\n * where OpenAPI v2 `/device/state` returns empty. Each entry is converted\n * to synthetic capabilities and routed back through the same callback as\n * regular Cloud state, so the existing setState pipeline picks it up\n * without a special-case branch.\n *\n * Bearer token comes from the MQTT login flow \u2014 without MQTT credentials\n * (Email + Password) this is a no-op.\n *\n * @returns Number of devices that received an update\n */\n async pollAppApi(): Promise<number> {\n if (!this.apiClient || !this.apiClient.hasBearerToken()) {\n return 0;\n }\n // Skip the entire round-trip when no device in the registry would\n // actually consume App-API readings. The App API is only used for\n // sensor and appliance state (thermometers, heaters, kettles, \u2026);\n // a Lights-only setup would otherwise burn one Govee call every 2\n // minutes for nothing.\n if (!this.hasDeviceNeedingAppApi()) {\n return 0;\n }\n let entries: AppDeviceEntry[];\n try {\n entries = await this.apiClient.fetchDeviceList();\n } catch (err) {\n const category = classifyError(err);\n const msg = `App API fetch failed: ${err instanceof Error ? err.message : String(err)}`;\n if (category !== this.lastAppApiErrorCategory) {\n this.lastAppApiErrorCategory = category;\n this.log.warn(msg);\n } else {\n this.log.debug(msg);\n }\n return 0;\n }\n // Reset on success so the next failure warns again.\n this.lastAppApiErrorCategory = null;\n let updated = 0;\n for (const entry of entries) {\n const device = this.devices.get(this.deviceKey(entry.sku, entry.device));\n if (!device) {\n continue;\n }\n const caps = buildCapabilitiesFromAppEntry(entry);\n if (caps.length === 0) {\n continue;\n }\n // Route synthetic capabilities through the existing\n // onCloudCapabilities callback so main.ts's normal setState\n // pipeline (mapCloudStateValue + setStateAsync) handles them.\n this.onCloudCapabilities?.(device, caps);\n // mapSingleCapability returns null for the synthetic `online`\n // cap (online is a device-level property, not a regular state),\n // so onCloudCapabilities never reaches info.online via the\n // capability pipeline. Pluck it out and apply it directly \u2014\n // otherwise sensor SKUs like H5179 stay at info.online=false\n // forever even while their readings keep updating.\n this.applyOnlineCap(device, caps);\n this.diagnostics.setApiResponse(device.deviceId, \"/device/rest/devices/v1/list\", entry);\n updated++;\n }\n return updated;\n }\n\n /**\n * Pull the `devices.capabilities.online` entry (if any) out of a\n * synthetic capability list and apply it directly to\n * `device.state.online` plus `lastSeenOnNetwork`. Surfaces via\n * onDeviceUpdate so the adapter's `info.online` state matches the\n * App-API / OpenAPI-MQTT signal. If no online cap is in the list but\n * the list is non-empty (i.e. fresh data arrived), the device is\n * considered online \u2014 same convention as the LAN/MQTT paths.\n *\n * @param device Target device\n * @param caps Capability list from the source pipeline\n */\n private applyOnlineCap(device: GoveeDevice, caps: CloudStateCapability[]): void {\n let online: boolean | undefined;\n for (const c of caps) {\n if (\n c &&\n typeof c.type === \"string\" &&\n (c.type === \"devices.capabilities.online\" || c.type === \"online\") &&\n c.state &&\n typeof c.state.value === \"boolean\"\n ) {\n online = c.state.value;\n break;\n }\n }\n // Fresh data with no online flag \u2192 assume online (LAN/MQTT use the\n // same \"we just heard from the device\" convention).\n if (online === undefined && caps.length > 0) {\n online = true;\n }\n if (online === undefined) {\n return;\n }\n if (device.state.online === online && online === true) {\n // Already online + still online \u2014 only refresh the lastSeen ts\n // and skip the onDeviceUpdate noise.\n device.lastSeenOnNetwork = Date.now();\n return;\n }\n device.state.online = online;\n if (online) {\n device.lastSeenOnNetwork = Date.now();\n }\n this.onDeviceUpdate?.(device, { online });\n }\n\n /**\n * Hook callback for sources that emit `CloudStateCapability[]` updates\n * outside the normal Cloud-poll path (App-API, OpenAPI-MQTT). Caller is\n * responsible for wiring it to the adapter-side state-write path.\n *\n * @param cb Callback receiving (device, caps)\n */\n setOnCloudCapabilities(cb: ((device: GoveeDevice, caps: CloudStateCapability[]) => void) | null): void {\n this.onCloudCapabilities = cb;\n }\n\n /**\n * Whether at least one device in the registry would consume App-API\n * readings (sensors, appliances). Used to skip the App-API poll on\n * Lights-only installations.\n */\n private hasDeviceNeedingAppApi(): boolean {\n for (const dev of this.devices.values()) {\n if (dev.type !== \"devices.types.light\" && dev.sku !== \"BaseGroup\") {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Process a parsed OpenAPI-MQTT event by forwarding its capabilities\n * through the same hook used by App-API polls. Called from the\n * adapter-side OpenAPI-MQTT message handler.\n *\n * @param event Parsed event from the OpenAPI-MQTT broker\n * @param event.sku Govee SKU (e.g. \"H5179\")\n * @param event.device MAC-style device identifier\n * @param event.capabilities Capability list synthesised from the broker payload\n */\n handleOpenApiEvent(event: { sku: string; device: string; capabilities: CloudStateCapability[] }): void {\n if (!event || typeof event.sku !== \"string\" || typeof event.device !== \"string\") {\n return;\n }\n if (!Array.isArray(event.capabilities) || event.capabilities.length === 0) {\n return;\n }\n const device = this.devices.get(this.deviceKey(event.sku, event.device));\n if (!device) {\n return;\n }\n this.onCloudCapabilities?.(device, event.capabilities);\n // Same online-cap unwrap as the App-API path. OpenAPI-MQTT events\n // are the only signal we get for appliance state (heater on/off,\n // ice-bucket-full, \u2026) \u2014 without this, info.online for those SKUs\n // never flips to true even while events stream in.\n this.applyOnlineCap(device, event.capabilities);\n }\n}\n\n/**\n * Convert an app-API device entry into a list of synthetic Cloud-state\n * capabilities the existing `mapCloudStateValue` pipeline can consume.\n *\n * Govee stores temperature and humidity as integer hundredths of a unit\n * (`tem: 2370` \u2192 23.70 \u00B0C, `hum: 4290` \u2192 42.90 % RH). Battery may live\n * either at the lastData level or in deviceSettings \u2014 lastData wins\n * because it's the more recent reading.\n *\n * @param entry One entry from `GoveeApiClient.fetchDeviceList()`\n */\nexport function buildCapabilitiesFromAppEntry(entry: AppDeviceEntry): CloudStateCapability[] {\n const caps: CloudStateCapability[] = [];\n const last = entry.lastData;\n if (!last) {\n return caps;\n }\n if (typeof last.online === \"boolean\") {\n caps.push({\n type: \"devices.capabilities.online\",\n instance: \"online\",\n state: { value: last.online },\n });\n }\n if (typeof last.tem === \"number\" && Number.isFinite(last.tem)) {\n caps.push({\n type: \"devices.capabilities.property\",\n instance: \"sensorTemperature\",\n state: { value: last.tem / 100 },\n });\n }\n if (typeof last.hum === \"number\" && Number.isFinite(last.hum)) {\n caps.push({\n type: \"devices.capabilities.property\",\n instance: \"sensorHumidity\",\n state: { value: last.hum / 100 },\n });\n }\n if (typeof last.battery === \"number\" && Number.isFinite(last.battery)) {\n caps.push({\n type: \"devices.capabilities.property\",\n instance: \"battery\",\n state: { value: last.battery },\n });\n } else if (entry.settings && typeof entry.settings.battery === \"number\" && Number.isFinite(entry.settings.battery)) {\n caps.push({\n type: \"devices.capabilities.property\",\n instance: \"battery\",\n state: { value: entry.settings.battery },\n });\n }\n return caps;\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,+BAA0C;AAC1C,4BAA8B;AAC9B,6BAAiE;AACjE,yBAAqC;AAMrC,mBAaO;AACP,yBAA0B;AA+BnB,SAAS,qBAAqB,UAAuC;AAC1E,MAAI,CAAC,MAAM,QAAQ,QAAQ,GAAG;AAC5B,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,WAA8B,CAAC;AAErC,MAAI,gBAAgB;AAEpB,aAAW,OAAO,UAAU;AAC1B,QAAI,OAAO,QAAQ,UAAU;AAC3B;AAAA,IACF;AACA,UAAM,QAAQ,OAAO,KAAK,KAAK,QAAQ;AACvC,QAAI,MAAM,SAAS,MAAM,MAAM,CAAC,MAAM,OAAQ,MAAM,CAAC,MAAM,KAAM;AAC/D;AAAA,IACF;AAEA,UAAM,YAAY,MAAM,CAAC;AACzB,QAAI,YAAY,KAAK,YAAY,GAAG;AAClC;AAAA,IACF;AACA,QAAI,YAAY,eAAe;AAC7B,sBAAgB;AAAA,IAClB;AAEA,UAAM,aAAa,YAAY,KAAK;AACpC,aAAS,OAAO,GAAG,OAAO,GAAG,QAAQ;AACnC,YAAM,SAAS,YAAY;AAC3B,YAAM,SAAS,IAAI,OAAO;AAC1B,eAAS,KAAK;AAAA,QACZ,OAAO;AAAA,QACP,YAAY,MAAM,MAAM;AAAA,QACxB,GAAG,MAAM,SAAS,CAAC;AAAA,QACnB,GAAG,MAAM,SAAS,CAAC;AAAA,QACnB,GAAG,MAAM,SAAS,CAAC;AAAA,MACrB,CAAC;AAAA,IACH;AAAA,EACF;AAMA,SAAO,SAAS,SAAS,GAAG;AAC1B,UAAM,OAAO,SAAS,SAAS,SAAS,CAAC;AACzC,QAAI,KAAK,eAAe,KAAK,KAAK,MAAM,KAAK,KAAK,MAAM,KAAK,KAAK,MAAM,GAAG;AACzE,eAAS,IAAI;AAAA,IACf,OAAO;AACL;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AASO,SAAS,2BAA2B,QAA+B;AArH1E;AAsHE,MAAI,OAAO,cAAc,MAAM,QAAQ,OAAO,cAAc,KAAK,OAAO,eAAe,SAAS,GAAG;AACjG,WAAO,OAAO,eAAe,MAAM;AAAA,EACrC;AACA,QAAM,SAAQ,YAAO,iBAAP,YAAuB;AACrC,MAAI,SAAS,GAAG;AACd,WAAO,CAAC;AAAA,EACV;AACA,SAAO,MAAM,KAAK,EAAE,QAAQ,MAAM,GAAG,CAAC,GAAG,MAAM,CAAC;AAClD;AAkBO,SAAS,oBAAoB,QAA6B;AAC/D,MAAI,OAAO,OAAO,iBAAiB,YAAY,OAAO,eAAe,GAAG;AACtE,WAAO,OAAO;AAAA,EAChB;AACA,QAAM,OAAO,MAAM,QAAQ,OAAO,YAAY,IAAI,OAAO,eAAe,CAAC;AACzE,MAAI,MAAM,OAAO;AACjB,aAAW,KAAK,MAAM;AACpB,QAAI,CAAC,KAAK,OAAO,EAAE,SAAS,YAAY,CAAC,EAAE,KAAK,SAAS,uBAAuB,GAAG;AACjF;AAAA,IACF;AACA,UAAM,SAAU,EAA8C;AAC9D,UAAM,SAAS,MAAM,QAAQ,iCAAQ,MAAM,IAAI,OAAO,SAAS,CAAC;AAChE,eAAW,KAAK,QAAQ;AACtB,UAAI,CAAC,KAAK,OAAO,MAAM,UAAU;AAC/B;AAAA,MACF;AACA,YAAM,KAAM,EAA8B;AAC1C,YAAM,KAAM,EAA2C;AACvD,YAAM,SAAS,MAAM,OAAO,GAAG,QAAQ,WAAW,GAAG,MAAM;AAC3D,UAAI,OAAO,aAAa,UAAU,GAAG;AACnC,cAAM,IAAI,SAAS;AACnB,YAAI,IAAI,KAAK,IAAI,KAAK;AACpB,gBAAM;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO,OAAO,SAAS,GAAG,IAAI,MAAM;AACtC;AAGO,MAAM,mBAAmB;AAOzB,MAAM,cAAc;AAAA,EACR;AAAA,EACA,UAAU,oBAAI,IAAyB;AAAA,EACvC;AAAA,EACA;AAAA;AAAA,EAEA,iBAAiB,oBAAI,IAAY;AAAA,EAC1C,cAAuC;AAAA,EACvC,YAAmC;AAAA,EACnC,WAA4B;AAAA,EAC5B,iBAAsF;AAAA,EACtF,sBAAiE;AAAA,EACjE,sBAA4F;AAAA;AAAA,EAE5F,oBAA0C;AAAA,EAC1C,0BAAgD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOxD,YAAY,KAAsB,QAAsB;AACtD,SAAK,MAAM;AACX,SAAK,gBAAgB,IAAI,oCAAc,KAAK,MAAM;AAClD,SAAK,cAAc,IAAI,wCAAqB;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,iBAAuC;AACrC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUQ,cAAc,GAAgC;AACpD,QAAI,aAAa,8BAAW;AAC1B,aAAO,EAAE;AAAA,IACX;AACA,QAAI,OAAO,MAAM,YAAY,MAAM,MAAM;AACvC,YAAM,IAAI;AACV,UAAI,OAAO,EAAE,eAAe,UAAU;AACpC,eAAO,EAAE;AAAA,MACX;AACA,UAAI,OAAO,EAAE,WAAW,UAAU;AAChC,eAAO,EAAE;AAAA,MACX;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,aAAa,QAA8B;AACzC,SAAK,cAAc,aAAa,MAAM;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,aAAa,QAA8B;AACzC,SAAK,YAAY;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,eAAe,QAAgC;AAC7C,SAAK,cAAc;AACnB,SAAK,cAAc,eAAe,MAAM;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,eAAe,SAA4B;AACzC,SAAK,cAAc,eAAe,OAAO;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,YAAY,OAAuB;AACjC,SAAK,WAAW;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,aACE,UACA,eACM;AACN,SAAK,iBAAiB;AACtB,SAAK,sBAAsB;AAAA,EAC7B;AAAA;AAAA,EAGA,aAA4B;AAC1B,WAAO,MAAM,KAAK,KAAK,QAAQ,OAAO,CAAC;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,gBAAyB;AAvT3B;AAwTI,QAAI,CAAC,KAAK,UAAU;AAClB,aAAO;AAAA,IACT;AAEA,UAAM,SAAS,KAAK,SAAS,QAAQ;AACrC,QAAI,OAAO,WAAW,GAAG;AACvB,aAAO;AAAA,IACT;AAEA,QAAI,UAAU;AACd,eAAW,SAAS,QAAQ;AAC1B,YAAM,MAAM,KAAK,UAAU,MAAM,KAAK,MAAM,QAAQ;AACpD,YAAM,WAAW,KAAK,QAAQ,IAAI,GAAG;AACrC,UAAI,UAAU;AAOZ,iBAAS,OAAO,MAAM,QAAQ,SAAS;AACvC,iBAAS,OAAO,MAAM,QAAQ,SAAS;AACvC,iBAAS,eAAe,MAAM;AAC9B,iBAAS,SAAS,MAAM;AACxB,iBAAS,YAAY,MAAM;AAC3B,iBAAS,YAAY,MAAM;AAC3B,iBAAS,eAAe,MAAM;AAC9B,iBAAS,eAAe,MAAM;AAC9B,iBAAS,aAAa,MAAM;AAC5B,iBAAS,cAAc,MAAM;AAC7B,iBAAS,kBAAkB,MAAM;AACjC,iBAAS,gBAAgB,MAAM;AAC/B,iBAAS,oBAAoB,MAAM;AACnC,iBAAS,eAAe,MAAM;AAC9B,iBAAS,aAAa,MAAM;AAC5B,iBAAS,iBAAiB,MAAM;AAChC,iBAAS,SAAS,QAAQ,MAAM,aAAa,SAAS;AACtD,kBAAU;AAAA,MACZ,OAAO;AACL,aAAK,QAAQ,IAAI,KAAK,KAAK,oBAAoB,KAAK,CAAC;AACrD,kBAAU;AAAA,MACZ;AAAA,IACF;AAEA,QAAI,SAAS;AACX,WAAK,IAAI,KAAK,UAAU,OAAO,MAAM,uBAAuB;AAAA,IAC9D;AAQA,UAAM,WAAW,MAAM,KAAK,KAAK,QAAQ,OAAO,CAAC,EAAE,KAAK,OAAK,EAAE,SAAS,qBAAqB;AAC7F,QAAI,UAAU;AACZ,WAAK,IAAI,MAAM,6DAAwD;AACvE,aAAO;AAAA,IACT;AAGA,eAAW,UAAU,KAAK,QAAQ,OAAO,GAAG;AAC1C,WAAK,0BAA0B,MAAM;AAAA,IACvC;AAEA,QAAI,SAAS;AACX,iBAAK,wBAAL,8BAA2B,KAAK,WAAW;AAAA,IAC7C;AACA,WAAO,OAAO,SAAS;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,gBAA0C;AAnYlD;AAoYI,QAAI,CAAC,KAAK,aAAa;AACrB,aAAO,EAAE,IAAI,OAAO,QAAQ,YAAY;AAAA,IAC1C;AAEA,QAAI;AACF,YAAM,kBAAkB,MAAM,KAAK,YAAY,WAAW;AAK1D,YAAM,eAAe,MAAM,QAAQ,eAAe,IAC9C,gBAAgB;AAAA,QACd,QACE,MACA,OAAO,GAAG,QAAQ,YAClB,OAAO,GAAG,WAAW,YACrB,MAAM,QAAQ,GAAG,YAAY,KAC7B,GAAG,aAAa,SAAS;AAAA,MAC7B,IACA,CAAC;AAEL,UAAI,MAAM,QAAQ,eAAe,KAAK,gBAAgB,WAAW,aAAa,QAAQ;AACpF,aAAK,IAAI;AAAA,UACP,mBAAmB,gBAAgB,MAAM,iBAAiB,aAAa,MAAM;AAAA,QAC/E;AAAA,MACF;AAGA,UAAI,UAAU,KAAK,kBAAkB,YAAY;AAQjD,iBAAW,MAAM,cAAc;AAC7B,cAAM,OAAO,MAAM,QAAQ,GAAG,YAAY,IAAI,GAAG,eAAe,CAAC;AACjE,cAAM,kBACJ,oDAA0B,MAAM,YAAY,SAC5C,oDAA0B,MAAM,UAAU,SAC1C,oDAA0B,MAAM,UAAU;AAC5C,cAAM,UAAU,GAAG,SAAS,yBAAyB;AACrD,YAAI,SAAS;AACX,gBAAM,SAAS,KAAK,QAAQ,IAAI,KAAK,UAAU,GAAG,KAAK,GAAG,MAAM,CAAC;AACjE,cAAI,QAAQ;AACV,gBAAI,MAAM,KAAK,iBAAiB,QAAQ,EAAE,GAAG;AAC3C,wBAAU;AAAA,YACZ;AACA,gBAAI,MAAM,KAAK,oBAAoB,QAAQ,GAAG,GAAG,GAAG;AAClD,wBAAU;AAAA,YACZ;AAGA,mBAAO,gBAAgB;AAAA,UACzB;AAAA,QACF;AAAA,MACF;AAIA,UAAI,KAAK,YAAY,aAAa,SAAS,GAAG;AAC5C,aAAK,SAAS,WAAW,EAAE;AAAA,MAC7B;AAGA,WAAK,mBAAmB;AAExB,iBAAW,UAAU,KAAK,QAAQ,OAAO,GAAG;AAC1C,aAAK,0BAA0B,MAAM;AAAA,MACvC;AAEA,UAAI,SAAS;AACX,mBAAK,wBAAL,8BAA2B,KAAK,WAAW;AAAA,MAC7C;AACA,WAAK,oBAAoB;AACzB,aAAO,EAAE,IAAI,KAAK;AAAA,IACpB,SAAS,KAAK;AACZ,WAAK,SAAS,4BAA4B,GAAG;AAG7C,UAAI,eAAe,gCAAa,IAAI,eAAe,KAAK;AACtD,cAAM,gBAAgB,IAAI,QAAQ,aAAa;AAC/C,cAAM,gBACJ,OAAO,kBAAkB,YAAY,QAAQ,KAAK,aAAa,IAAI,SAAS,eAAe,EAAE,IAAI;AACnG,eAAO;AAAA,UACL,IAAI;AAAA,UACJ,QAAQ;AAAA,UACR,cAAc,gBAAgB;AAAA,QAChC;AAAA,MACF;AAGA,YAAM,eAAW,4BAAc,GAAG;AAClC,UAAI,aAAa,QAAQ;AACvB,eAAO;AAAA,UACL,IAAI;AAAA,UACJ,QAAQ;AAAA,UACR,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,QAC1D;AAAA,MACF;AAGA,aAAO,EAAE,IAAI,OAAO,QAAQ,YAAY;AAAA,IAC1C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,MAAM,mBAAqC;AA9f7C;AA+fI,QAAI,CAAC,KAAK,aAAa;AACrB,aAAO;AAAA,IACT;AACA,QAAI,aAAa;AACjB,UAAM,SAAS,MAAM,KAAK,KAAK,QAAQ,OAAO,CAAC,EAAE,OAAO,OAAK,EAAE,SAAS,qBAAqB;AAC7F,eAAW,OAAO,QAAQ;AACxB,WAAK,YAAY,OAAO,IAAI,UAAU,QAAQ,yCAAyC,IAAI,GAAG,EAAE;AAAA,IAClG;AACA,eAAW,UAAU,QAAQ;AAC3B,YAAM,KAAkB;AAAA,QACtB,KAAK,OAAO;AAAA,QACZ,QAAQ,OAAO;AAAA,QACf,YAAY,OAAO;AAAA,QACnB,MAAM,OAAO;AAAA,QACb,cAAc,MAAM,QAAQ,OAAO,YAAY,IAAI,OAAO,eAAe,CAAC;AAAA,MAC5E;AACA,UAAI,MAAM,KAAK,iBAAiB,QAAQ,EAAE,GAAG;AAC3C,qBAAa;AAAA,MACf;AACA,UAAI,MAAM,KAAK;AAAA,QAAoB;AAAA,QAAQ,GAAG;AAAA;AAAA,QAAiB;AAAA,MAAI,GAAG;AACpE,qBAAa;AAAA,MACf;AAAA,IACF;AACA,QAAI,YAAY;AACd,WAAK,mBAAmB;AACxB,iBAAW,UAAU,KAAK,QAAQ,OAAO,GAAG;AAC1C,aAAK,0BAA0B,MAAM;AAAA,MACvC;AACA,iBAAK,wBAAL,8BAA2B,KAAK,WAAW;AAAA,IAC7C;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,kBAAkB,cAAsC;AAC9D,QAAI,UAAU;AACd,QAAI,CAAC,MAAM,QAAQ,YAAY,GAAG;AAChC,aAAO;AAAA,IACT;AACA,eAAW,MAAM,cAAc;AAE7B,UAAI,CAAC,MAAM,OAAO,GAAG,QAAQ,YAAY,OAAO,GAAG,WAAW,UAAU;AACtE;AAAA,MACF;AACA,YAAM,WAAW,KAAK,QAAQ,IAAI,KAAK,UAAU,GAAG,KAAK,GAAG,MAAM,CAAC;AACnE,UAAI,UAAU;AACZ,iBAAS,OAAO,GAAG,cAAc,SAAS;AAC1C,iBAAS,eAAe,MAAM,QAAQ,GAAG,YAAY,IAAI,GAAG,eAAe,CAAC;AAC5E,iBAAS,OAAO,GAAG;AACnB,iBAAS,SAAS,QAAQ;AAAA,MAC5B,OAAO;AACL,cAAM,SAAS,KAAK,yBAAyB,EAAE;AAC/C,aAAK,QAAQ,IAAI,KAAK,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM;AAC1D,kBAAU;AACV,aAAK,IAAI,MAAM,qBAAqB,GAAG,UAAU,KAAK,GAAG,GAAG,GAAG;AAC/D,aAAK,kBAAkB,GAAG,KAAK,GAAG,UAAU;AAAA,MAC9C;AAEA,YAAM,aAAS,wCAAgB,GAAG,GAAG;AACrC,UAAI,iCAAQ,mBAAmB;AAC7B,aAAK,IAAI,MAAM,GAAG,GAAG,GAAG,+EAA0E;AAAA,MACpG;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAc,iBAAiB,QAAqB,IAAmC;AA9kBzF;AA+kBI,SAAK,YAAY,OAAO,GAAG,QAAQ,SAAS,+BAA+B,GAAG,GAAG,EAAE;AAQnF,UAAM,aAAa,YAA2B;AAC5C,UAAI;AACF,cAAM,EAAE,aAAa,WAAW,UAAU,IAAI,MAAM,KAAK,YAAa,UAAU,GAAG,KAAK,GAAG,MAAM;AAIjG,YAAI,YAAY,SAAS,GAAG;AAC1B,iBAAO,SAAS;AAAA,QAClB;AACA,YAAI,UAAU,SAAS,GAAG;AACxB,iBAAO,YAAY;AAAA,QACrB;AACA,YAAI,UAAU,SAAS,GAAG;AACxB,iBAAO,YAAY;AAAA,QACrB;AAAA,MACF,SAAS,GAAG;AACV,aAAK,YAAY,iBAAiB,GAAG,QAAQ,gCAAgC,GAAG,KAAK,cAAc,CAAC,CAAC;AACrG,aAAK,IAAI,MAAM,6BAA6B,GAAG,GAAG,KAAK,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC,EAAE;AAAA,MACrG;AAAA,IACF;AACA,UAAM,KAAK,cAAc,mBAAmB,YAAY,CAAC;AAGzD,QAAI,OAAO,UAAU,WAAW,GAAG;AACjC,YAAM,UAAU,YAA2B;AACzC,YAAI;AACF,gBAAM,MAAM,MAAM,KAAK,YAAa,aAAa,GAAG,KAAK,GAAG,MAAM;AAClE,cAAI,IAAI,SAAS,GAAG;AAClB,mBAAO,YAAY;AAAA,UACrB;AAAA,QACF,SAAS,GAAG;AACV,eAAK,YAAY,iBAAiB,GAAG,QAAQ,oCAAoC,GAAG,KAAK,cAAc,CAAC,CAAC;AACzG,eAAK,IAAI,MAAM,iCAAiC,GAAG,GAAG,KAAK,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC,EAAE;AAAA,QACzG;AAAA,MACF;AACA,YAAM,KAAK,cAAc,mBAAmB,SAAS,CAAC;AAAA,IACxD;AAGA,QAAI,OAAO,UAAU,WAAW,GAAG;AACjC,YAAM,OAAO,MAAM,QAAQ,GAAG,YAAY,IAAI,GAAG,eAAe,CAAC;AACjE,YAAM,UAAU,KAAK;AAAA,QACnB,OAAE;AAjoBV,cAAAA;AAkoBU,sBACA,EAAE,SAAS,wCACX,EAAE,aAAa,cACf,MAAM,SAAQA,MAAA,EAAE,eAAF,gBAAAA,IAAc,OAAO;AAAA;AAAA,MACvC;AACA,WAAI,wCAAS,eAAT,mBAAqB,SAAS;AAChC,eAAO,YAAY,QAAQ,WAAW,QACnC,OAAO,OAAK,KAAK,OAAO,EAAE,SAAS,YAAY,EAAE,UAAU,UAAa,EAAE,UAAU,IAAI,EACxF,IAAI,QAAM;AAAA,UACT,MAAM,EAAE;AAAA,UACR,OAAO,OAAO,EAAE,UAAU,WAAW,EAAE,QAAS,EAAE;AAAA,QACpD,EAAE;AACJ,aAAK,IAAI,MAAM,mCAAmC,GAAG,GAAG,KAAK,OAAO,UAAU,MAAM,EAAE;AAAA,MACxF;AAAA,IACF;AAIA,WAAO,OAAO,OAAO,SAAS,KAAK,OAAO,UAAU,SAAS,KAAK,OAAO,UAAU,SAAS;AAAA,EAC9F;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,MAAc,oBAAoB,QAAqB,KAAa,QAAQ,OAAyB;AACnG,QAAI,CAAC,KAAK,WAAW;AACnB,aAAO;AAAA,IACT;AAEA,SAAK,YAAY,OAAO,OAAO,UAAU,SAAS,kCAAkC,GAAG,WAAW,KAAK,GAAG;AAC1G,QAAI,UAAU;AAKd,UAAM,aAAa,OAAO,OAA2C;AACnE,YAAM,KAAK,cAAc,mBAAmB,IAAI,CAAC;AAAA,IACnD;AAEA,QAAI,SAAS,OAAO,aAAa,WAAW,GAAG;AAC7C,YAAM,WAAW,YAAY;AAC3B,cAAM,KAAK,+BAA+B,GAAG;AAC7C,YAAI;AACF,gBAAM,MAAM,MAAM,KAAK,UAAW,kBAAkB,GAAG;AACvD,eAAK,YAAY,iBAAiB,OAAO,UAAU,IAAI,EAAE,OAAO,IAAI,QAAQ,OAAO,IAAI,IAAI,OAAK,EAAE,IAAI,EAAE,CAAC;AACzG,cAAI,IAAI,SAAS,GAAG;AAClB,mBAAO,eAAe;AACtB,sBAAU;AACV,iBAAK,IAAI,MAAM,qBAAqB,GAAG,KAAK,IAAI,MAAM,SAAS;AAAA,UACjE;AAAA,QACF,SAAS,GAAG;AACV,eAAK,YAAY,iBAAiB,OAAO,UAAU,IAAI,GAAG,KAAK,cAAc,CAAC,CAAC;AAC/E,eAAK,IAAI,MAAM,oCAAoC,GAAG,KAAK,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC,EAAE;AAAA,QACzG;AAAA,MACF,CAAC;AAAA,IACH;AAEA,QAAI,SAAS,OAAO,aAAa,WAAW,GAAG;AAC7C,YAAM,WAAW,YAAY;AAC3B,cAAM,KAAK,qCAAqC,GAAG;AACnD,YAAI;AACF,gBAAM,MAAM,MAAM,KAAK,UAAW,kBAAkB,GAAG;AACvD,eAAK,YAAY,iBAAiB,OAAO,UAAU,IAAI,EAAE,OAAO,IAAI,QAAQ,OAAO,IAAI,IAAI,OAAK,EAAE,IAAI,EAAE,CAAC;AACzG,cAAI,IAAI,SAAS,GAAG;AAClB,mBAAO,eAAe;AACtB,sBAAU;AACV,iBAAK,IAAI,MAAM,qBAAqB,GAAG,KAAK,IAAI,MAAM,QAAQ;AAAA,UAChE;AAAA,QACF,SAAS,GAAG;AACV,eAAK,YAAY,iBAAiB,OAAO,UAAU,IAAI,GAAG,KAAK,cAAc,CAAC,CAAC;AAC/E,eAAK,IAAI,MAAM,oCAAoC,GAAG,KAAK,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC,EAAE;AAAA,QACzG;AAAA,MACF,CAAC;AAAA,IACH;AAEA,QAAI,SAAS,OAAO,WAAW,WAAW,GAAG;AAC3C,YAAM,WAAW,YAAY;AAC3B,cAAM,KAAK,6BAA6B,GAAG;AAC3C,YAAI;AACF,gBAAM,MAAM,MAAM,KAAK,UAAW,gBAAgB,GAAG;AACrD,eAAK,YAAY,iBAAiB,OAAO,UAAU,IAAI,EAAE,OAAO,IAAI,QAAQ,OAAO,IAAI,IAAI,OAAK,EAAE,IAAI,EAAE,CAAC;AACzG,cAAI,IAAI,SAAS,GAAG;AAClB,mBAAO,aAAa;AACpB,sBAAU;AACV,iBAAK,IAAI,MAAM,mBAAmB,GAAG,KAAK,IAAI,MAAM,UAAU;AAAA,UAChE;AAAA,QACF,SAAS,GAAG;AACV,eAAK,YAAY,iBAAiB,OAAO,UAAU,IAAI,GAAG,KAAK,cAAc,CAAC,CAAC;AAC/E,eAAK,IAAI,MAAM,kCAAkC,GAAG,KAAK,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC,EAAE;AAAA,QACvG;AAAA,MACF,CAAC;AAAA,IACH;AAEA,QAAI,SAAS,CAAC,OAAO,aAAa;AAChC,YAAM,WAAW,YAAY;AAC3B,cAAM,KAAK,qBAAqB,GAAG;AACnC,YAAI;AACF,gBAAM,WAAW,MAAM,KAAK,UAAW,iBAAiB,GAAG;AAC3D,eAAK,YAAY,iBAAiB,OAAO,UAAU,IAAI,QAAQ;AAC/D,cAAI,UAAU;AACZ,mBAAO,cAAc;AACrB,sBAAU;AACV,iBAAK,IAAI,MAAM,oBAAoB,GAAG,KAAK,KAAK,UAAU,QAAQ,EAAE,MAAM,GAAG,GAAG,CAAC,EAAE;AAAA,UACrF;AAAA,QACF,SAAS,GAAG;AACV,eAAK,YAAY,iBAAiB,OAAO,UAAU,IAAI,GAAG,KAAK,cAAc,CAAC,CAAC;AAC/E,eAAK,IAAI,MAAM,mCAAmC,GAAG,KAAK,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC,EAAE;AAAA,QACxG;AAAA,MACF,CAAC;AAAA,IACH;AAGA,QAAI,CAAC,OAAO,mBAAmB,OAAO,UAAU,SAAS,GAAG;AAC1D,YAAM,WAAW,YAAY;AAC3B,YAAI;AACF,gBAAM,QAAQ,MAAM,KAAK,UAAW,eAAe,KAAK,OAAO,QAAQ;AACvE,cAAI,MAAM,SAAS,GAAG;AACpB,mBAAO,kBAAkB,OAAO,UAAU,IAAI,QAAM;AAnwBhE;AAowBc,oBAAM,QAAQ,MAAM,KAAK,OAAK,EAAE,SAAS,GAAG,IAAI;AAChD,sBAAO,oCAAO,YAAP,YAAkB,CAAC;AAAA,YAC5B,CAAC;AACD,sBAAU;AACV,iBAAK,IAAI,MAAM,oBAAoB,GAAG,KAAK,MAAM,MAAM,4BAA4B;AAAA,UACrF;AAAA,QACF,SAAS,GAAG;AACV,eAAK,IAAI,MAAM,mCAAmC,GAAG,KAAK,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC,EAAE;AAAA,QACxG;AAAA,MACF,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,mBAAqC;AAzxB7C;AA0xBI,QAAI,CAAC,KAAK,WAAW;AACnB,aAAO;AAAA,IACT;AACA,QAAI,CAAC,KAAK,UAAU,eAAe,GAAG;AACpC,WAAK,IAAI,MAAM,4EAAuE;AACtF,aAAO;AAAA,IACT;AAEA,QAAI;AACF,YAAM,YAAY,MAAM,KAAK,UAAU,kBAAkB;AACzD,UAAI,UAAU,WAAW,GAAG;AAC1B,aAAK,IAAI,MAAM,mCAAmC;AAClD,eAAO;AAAA,MACT;AAEA,UAAI,UAAU;AACd,iBAAW,SAAS,KAAK,QAAQ,OAAO,GAAG;AACzC,YAAI,MAAM,QAAQ,aAAa;AAC7B;AAAA,QACF;AAEA,cAAM,WAAW,UAAU,KAAK,OAAK,OAAO,EAAE,OAAO,MAAM,MAAM,QAAQ;AACzE,YAAI,CAAC,UAAU;AACb;AAAA,QACF;AAGA,cAAM,UAA+C,CAAC;AACtD,mBAAW,KAAK,SAAS,SAAS;AAChC,gBAAM,WAAW,KAAK,qBAAqB,EAAE,KAAK,EAAE,QAAQ;AAC5D,cAAI,UAAU;AACZ,oBAAQ,KAAK,EAAE,KAAK,SAAS,KAAK,UAAU,SAAS,SAAS,CAAC;AAAA,UACjE,OAAO;AACL,iBAAK,IAAI,MAAM,UAAU,MAAM,IAAI,aAAa,EAAE,GAAG,IAAI,EAAE,QAAQ,oBAAoB;AAAA,UACzF;AAAA,QACF;AAEA,cAAM,eAAe;AACrB,YAAI,QAAQ,SAAS,GAAG;AACtB,oBAAU;AAAA,QACZ;AACA,aAAK,IAAI,MAAM,UAAU,MAAM,IAAI,MAAM,QAAQ,MAAM,IAAI,SAAS,QAAQ,MAAM,mBAAmB;AAAA,MACvG;AAEA,UAAI,SAAS;AACX,mBAAK,wBAAL,8BAA2B,KAAK,WAAW;AAAA,MAC7C;AACA,aAAO;AAAA,IACT,SAAS,GAAG;AACV,WAAK,IAAI,MAAM,iCAAiC,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC,EAAE;AAC5F,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA,EAGO,qBAA2B;AAChC,QAAI,CAAC,KAAK,UAAU;AAClB;AAAA,IACF;AAEA,QAAI,cAAc;AAClB,QAAI,eAAe;AACnB,eAAW,UAAU,KAAK,QAAQ,OAAO,GAAG;AAC1C,YAAM,UAAU,OAAO,SAAS;AAGhC,UAAI,WAAW,CAAC,OAAO,eAAe;AACpC;AACA,aAAK,IAAI,MAAM,eAAe,OAAO,IAAI,KAAK,OAAO,GAAG,iCAA4B;AAAA,MACtF,OAAO;AACL,aAAK,SAAS,KAAK,KAAK,oBAAoB,MAAM,CAAC;AACnD;AAAA,MACF;AAAA,IACF;AAIA,QAAI,eAAe,GAAG;AACpB,WAAK,IAAI,MAAM,UAAU,WAAW,uBAAuB,YAAY,kBAAkB;AAAA,IAC3F,OAAO;AACL,WAAK,IAAI,MAAM,UAAU,WAAW,yCAAoC;AAAA,IAC1E;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,mBAAmB,WAA4B;AAn3BjD;AAq3BI,QAAI;AACJ,eAAW,OAAO,KAAK,QAAQ,OAAO,GAAG;AACvC,cAAI,gCAAkB,IAAI,QAAQ,UAAM,gCAAkB,UAAU,MAAM,GAAG;AAC3E,kBAAU;AACV;AAAA,MACF;AAEA,UAAI,IAAI,QAAQ,UAAU,OAAO,CAAC,IAAI,OAAO;AAC3C,kBAAU;AACV;AAAA,MACF;AAAA,IACF;AAEA,QAAI,SAAS;AACX,YAAM,YAAY,QAAQ,UAAU,UAAU;AAC9C,YAAM,aAAa,QAAQ,MAAM,WAAW;AAC5C,cAAQ,QAAQ,UAAU;AAC1B,cAAQ,SAAS,MAAM;AACvB,cAAQ,oBAAoB,KAAK,IAAI;AACrC,UAAI,WAAW;AACb,aAAK,IAAI,MAAM,QAAQ,QAAQ,IAAI,KAAK,QAAQ,GAAG,QAAQ,UAAU,EAAE,EAAE;AACzE,mBAAK,mBAAL,8BAAsB,SAAS,UAAU;AAAA,MAC3C;AAKA,UAAI,YAAY;AACd,gBAAQ,MAAM,SAAS;AACvB,mBAAK,mBAAL,8BAAsB,SAAS,EAAE,QAAQ,KAAK;AAAA,MAChD;AAAA,IACF,OAAO;AAGL,YAAM,cAAU,gCAAkB,UAAU,MAAM,EAAE,MAAM,EAAE;AAC5D,YAAM,SAAsB;AAAA,QAC1B,KAAK,UAAU;AAAA,QACf,UAAU,UAAU;AAAA,QACpB,MAAM,GAAG,UAAU,GAAG,IAAI,OAAO;AAAA,QACjC,MAAM;AAAA,QACN,OAAO,UAAU;AAAA,QACjB,cAAc,CAAC;AAAA,QACf,QAAQ,CAAC;AAAA,QACT,WAAW,CAAC;AAAA,QACZ,WAAW,CAAC;AAAA,QACZ,cAAc,CAAC;AAAA,QACf,cAAc,CAAC;AAAA,QACf,YAAY,CAAC;AAAA,QACb,aAAa;AAAA,QACb,mBAAmB,KAAK,IAAI;AAAA,QAC5B,OAAO,EAAE,QAAQ,KAAK;AAAA,QACtB,UAAU,EAAE,KAAK,MAAM,MAAM,OAAO,OAAO,MAAM;AAAA,MACnD;AACA,WAAK,QAAQ,IAAI,KAAK,UAAU,UAAU,KAAK,UAAU,MAAM,GAAG,MAAM;AACxE,WAAK,YAAY,OAAO,UAAU,QAAQ,QAAQ,qBAAqB,UAAU,EAAE,EAAE;AACrF,WAAK,IAAI,MAAM,mBAAmB,UAAU,GAAG,OAAO,UAAU,EAAE,EAAE;AACpE,WAAK,kBAAkB,UAAU,KAAK,OAAO,IAAI;AACjD,iBAAK,wBAAL,8BAA2B,KAAK,WAAW;AAAA,IAC7C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBQ,kBAAkB,KAAa,aAAuC;AAC5E,UAAM,SAAS,OAAO,QAAQ,WAAW,MAAM,IAAI,YAAY;AAC/D,QAAI,CAAC,SAAS,KAAK,eAAe,IAAI,KAAK,GAAG;AAC5C;AAAA,IACF;AACA,SAAK,eAAe,IAAI,KAAK;AAC7B,UAAM,WAAO,sCAAc,KAAK;AAChC,UAAM,QAAQ,cAAc,GAAG,WAAW,KAAK,KAAK,MAAM;AAC1D,YAAQ,MAAM;AAAA,MACZ,KAAK;AAAA,MACL,KAAK;AACH;AAAA,MACF,KAAK;AACH,gBAAI,yCAAiB,KAAK,GAAG;AAC3B,eAAK,IAAI;AAAA,YACP,UAAU,KAAK;AAAA,UACjB;AAAA,QACF,OAAO;AACL,eAAK,IAAI,KAAK,UAAU,KAAK,oDAA+C;AAAA,QAC9E;AACA;AAAA,MACF,KAAK;AACH,aAAK,IAAI;AAAA,UACP,UAAU,KAAK;AAAA,QACjB;AACA;AAAA,IACJ;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,iBAAiB,QAAgC;AAn+BnD;AAo+BI,UAAM,SAAS,KAAK,qBAAqB,OAAO,KAAK,OAAO,MAAM;AAClE,QAAI,CAAC,QAAQ;AACX,WAAK,IAAI,MAAM,wBAAwB,OAAO,GAAG,IAAI,OAAO,MAAM,EAAE;AACpE;AAAA,IACF;AAEA,WAAO,SAAS,OAAO;AACvB,WAAO,oBAAoB,KAAK,IAAI;AACpC,UAAM,QAA8B,EAAE,QAAQ,KAAK;AAEnD,QAAI,OAAO,OAAO;AAChB,UAAI,OAAO,MAAM,UAAU,QAAW;AACpC,cAAM,QAAQ,OAAO,MAAM,UAAU;AAAA,MACvC;AACA,UAAI,OAAO,MAAM,eAAe,QAAW;AACzC,cAAM,aAAa,OAAO,MAAM;AAAA,MAClC;AACA,UAAI,OAAO,MAAM,OAAO;AACtB,cAAM,EAAE,GAAG,GAAG,EAAE,IAAI,OAAO,MAAM;AACjC,cAAM,eAAW,uBAAS,GAAG,GAAG,CAAC;AAAA,MACnC;AACA,UAAI,OAAO,MAAM,kBAAkB;AACjC,cAAM,mBAAmB,OAAO,MAAM;AAAA,MACxC;AAAA,IACF;AAGA,WAAO,OAAO,OAAO,OAAO,KAAK;AACjC,eAAK,mBAAL,8BAAsB,QAAQ;AAK9B,SAAI,YAAO,OAAP,mBAAW,SAAS;AACtB,YAAM,UAAU,qBAAqB,OAAO,GAAG,OAAO;AAEtD,UAAI,QAAQ,SAAS,GAAG;AACtB,cAAM,UAAU,KAAK,IAAI,GAAG,QAAQ,IAAI,OAAK,EAAE,KAAK,CAAC,IAAI;AACzD,cAAM,WAAU,YAAO,iBAAP,YAAuB;AACvC,YAAI,UAAU,SAAS;AACrB,eAAK,IAAI;AAAA,YACP,GAAG,OAAO,IAAI,cAAc,OAAO,2BAA2B,OAAO;AAAA,UACvE;AACA,iBAAO,eAAe;AAGtB,cAAI,KAAK,UAAU;AACjB,iBAAK,SAAS,KAAK,KAAK,oBAAoB,MAAM,CAAC;AAAA,UACrD;AAGA,qBAAK,wBAAL,8BAA2B;AAC3B;AAAA,QACF;AAAA,MACF;AAIA,YAAM,WACJ,OAAO,cAAc,MAAM,QAAQ,OAAO,cAAc,KAAK,OAAO,eAAe,SAAS,IACxF,QAAQ,OAAO,OAAK,OAAO,eAAgB,SAAS,EAAE,KAAK,CAAC,IAC5D;AACN,UAAI,SAAS,SAAS,GAAG;AACvB,mBAAK,wBAAL,8BAA2B,QAAQ;AAAA,MACrC;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,gBACE,IACA,QAMM;AA7jCV;AA+jCI,QAAI;AACJ,eAAW,OAAO,KAAK,QAAQ,OAAO,GAAG;AACvC,UAAI,IAAI,UAAU,IAAI;AACpB,iBAAS;AACT;AAAA,MACF;AAAA,IACF;AACA,QAAI,CAAC,QAAQ;AACX;AAAA,IACF;AAEA,WAAO,oBAAoB,KAAK,IAAI;AACpC,UAAM,EAAE,GAAG,GAAG,EAAE,IAAI,OAAO;AAC3B,UAAM,QAA8B;AAAA,MAClC,QAAQ;AAAA,MACR,OAAO,OAAO,UAAU;AAAA,MACxB,YAAY,OAAO;AAAA,MACnB,cAAU,uBAAS,GAAG,GAAG,CAAC;AAAA,MAC1B,kBAAkB,OAAO,oBAAoB;AAAA,IAC/C;AAEA,WAAO,OAAO,OAAO,OAAO,KAAK;AACjC,eAAK,mBAAL,8BAAsB,QAAQ;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,IAAI,qBACF,UAGA;AACA,SAAK,cAAc,uBAAuB;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,YAAY,QAAqB,SAAiB,OAA+B;AACrF,WAAO,KAAK,cAAc,YAAY,QAAQ,SAAS,KAAK;AAAA,EAC9D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,sBACJ,QACA,gBACA,oBACA,OACe;AACf,WAAO,KAAK,cAAc,sBAAsB,QAAQ,gBAAgB,oBAAoB,KAAK;AAAA,EACnG;AAAA;AAAA,EAGA;AAAA;AAAA,EAGA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,yBAAyB,IAA8B;AAC7D,WAAO;AAAA,MACL,KAAK,GAAG;AAAA,MACR,UAAU,GAAG;AAAA,MACb,MAAM,GAAG,cAAc,GAAG;AAAA,MAC1B,MAAM,GAAG,QAAQ;AAAA,MACjB,cAAc,MAAM,QAAQ,GAAG,YAAY,IAAI,GAAG,eAAe,CAAC;AAAA,MAClE,QAAQ,CAAC;AAAA,MACT,WAAW,CAAC;AAAA,MACZ,WAAW,CAAC;AAAA,MACZ,cAAc,CAAC;AAAA,MACf,cAAc,CAAC;AAAA,MACf,YAAY,CAAC;AAAA,MACb,aAAa;AAAA,MACb,OAAO,EAAE,QAAQ,KAAK;AAAA,MACtB,UAAU,EAAE,KAAK,OAAO,MAAM,OAAO,OAAO,KAAK;AAAA,IACnD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,qBAAqB,KAAa,UAA2C;AAEnF,UAAM,SAAS,KAAK,QAAQ,IAAI,KAAK,UAAU,KAAK,QAAQ,CAAC;AAC7D,QAAI,QAAQ;AACV,aAAO;AAAA,IACT;AAGA,UAAM,mBAAe,gCAAkB,QAAQ;AAC/C,eAAW,OAAO,KAAK,QAAQ,OAAO,GAAG;AACvC,UAAI,IAAI,QAAQ,WAAO,gCAAkB,IAAI,QAAQ,MAAM,cAAc;AACvE,eAAO;AAAA,MACT;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,UAAU,KAAa,UAA0B;AACvD,WAAO,GAAG,GAAG,QAAI,gCAAkB,QAAQ,CAAC;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,SAAS,SAAiB,KAAoB;AACpD,UAAM,eAAW,4BAAc,GAAG;AAClC,UAAM,MAAM,GAAG,OAAO,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAC3E,QAAI,aAAa,KAAK,mBAAmB;AACvC,WAAK,oBAAoB;AACzB,WAAK,IAAI,KAAK,GAAG;AAAA,IACnB,OAAO;AACL,WAAK,IAAI,MAAM,GAAG,GAAG,aAAa;AAAA,IACpC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,0BAA0B,QAA2B;AAC3D,QAAI,OAAO,OAAO,WAAW,KAAK,OAAO,aAAa,SAAS,GAAG;AAChE,aAAO,SAAS,OAAO,aAAa,IAAI,YAAU;AAAA,QAChD,MAAM,MAAM;AAAA,QACZ,OAAO,CAAC;AAAA;AAAA,MACV,EAAE;AACF,WAAK,IAAI,MAAM,GAAG,OAAO,GAAG,KAAK,OAAO,OAAO,MAAM,6CAA6C;AAAA,IACpG;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,oBAAoB,QAAuC;AACjE,WAAO;AAAA,MACL,KAAK,OAAO;AAAA,MACZ,UAAU,OAAO;AAAA,MACjB,MAAM,OAAO;AAAA,MACb,MAAM,OAAO;AAAA,MACb,cAAc,OAAO;AAAA,MACrB,QAAQ,OAAO;AAAA,MACf,WAAW,OAAO;AAAA,MAClB,WAAW,OAAO;AAAA,MAClB,cAAc,OAAO;AAAA,MACrB,cAAc,OAAO;AAAA,MACrB,YAAY,OAAO;AAAA,MACnB,aAAa,OAAO;AAAA,MACpB,iBAAiB,OAAO;AAAA,MACxB,eAAe,OAAO;AAAA,MACtB,mBAAmB,OAAO;AAAA;AAAA,MAE1B,cAAc,OAAO;AAAA,MACrB,YAAY,OAAO;AAAA,MACnB,gBAAgB,OAAO;AAAA,MACvB,YAAY,OAAO;AAAA,MACnB,OAAO,EAAE,QAAQ,MAAM;AAAA,MACvB,UAAU,EAAE,KAAK,OAAO,MAAM,OAAO,OAAO,MAAM;AAAA,IACpD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQO,qBAAqB,QAA2B;AACrD,QAAI,CAAC,KAAK,UAAU;AAClB;AAAA,IACF;AACA,SAAK,SAAS,KAAK,KAAK,oBAAoB,MAAM,CAAC;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,oBAAoB,QAAuC;AACjE,WAAO;AAAA,MACL,KAAK,OAAO;AAAA,MACZ,UAAU,OAAO;AAAA,MACjB,MAAM,OAAO;AAAA,MACb,MAAM,OAAO;AAAA,MACb,cAAc,OAAO;AAAA,MACrB,QAAQ,OAAO;AAAA,MACf,WAAW,OAAO;AAAA,MAClB,WAAW,OAAO;AAAA,MAClB,cAAc,OAAO;AAAA,MACrB,cAAc,OAAO;AAAA,MACrB,YAAY,OAAO;AAAA,MACnB,aAAa,OAAO;AAAA,MACpB,iBAAiB,OAAO;AAAA,MACxB,eAAe,OAAO;AAAA,MACtB,mBAAmB,OAAO;AAAA,MAC1B,cACE,OAAO,OAAO,iBAAiB,YAAY,OAAO,eAAe,IAAI,OAAO,eAAe;AAAA,MAC7F,YAAY,OAAO,aAAa,OAAO;AAAA,MACvC,gBACE,OAAO,cAAc,MAAM,QAAQ,OAAO,cAAc,KAAK,OAAO,eAAe,SAAS,IACxF,OAAO,eAAe,MAAM,IAC5B;AAAA,MACN,YAAY,OAAO,OAAO,eAAe,YAAY,OAAO,aAAa,IAAI,OAAO,aAAa;AAAA,MACjG,UAAU,KAAK,IAAI;AAAA,IACrB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,oBAAoB,QAAqB,gBAAiD;AACxF,WAAO,KAAK,YAAY,SAAS,QAAQ,cAAc;AAAA,EACzD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,MAAM,aAA8B;AAn1CtC;AAo1CI,QAAI,CAAC,KAAK,aAAa,CAAC,KAAK,UAAU,eAAe,GAAG;AACvD,aAAO;AAAA,IACT;AAMA,QAAI,CAAC,KAAK,uBAAuB,GAAG;AAClC,aAAO;AAAA,IACT;AACA,QAAI;AACJ,QAAI;AACF,gBAAU,MAAM,KAAK,UAAU,gBAAgB;AAAA,IACjD,SAAS,KAAK;AACZ,YAAM,eAAW,4BAAc,GAAG;AAClC,YAAM,MAAM,yBAAyB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AACrF,UAAI,aAAa,KAAK,yBAAyB;AAC7C,aAAK,0BAA0B;AAC/B,aAAK,IAAI,KAAK,GAAG;AAAA,MACnB,OAAO;AACL,aAAK,IAAI,MAAM,GAAG;AAAA,MACpB;AACA,aAAO;AAAA,IACT;AAEA,SAAK,0BAA0B;AAC/B,QAAI,UAAU;AACd,eAAW,SAAS,SAAS;AAC3B,YAAM,SAAS,KAAK,QAAQ,IAAI,KAAK,UAAU,MAAM,KAAK,MAAM,MAAM,CAAC;AACvE,UAAI,CAAC,QAAQ;AACX;AAAA,MACF;AACA,YAAM,OAAO,8BAA8B,KAAK;AAChD,UAAI,KAAK,WAAW,GAAG;AACrB;AAAA,MACF;AAIA,iBAAK,wBAAL,8BAA2B,QAAQ;AAOnC,WAAK,eAAe,QAAQ,IAAI;AAChC,WAAK,YAAY,eAAe,OAAO,UAAU,gCAAgC,KAAK;AACtF;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcQ,eAAe,QAAqB,MAAoC;AAt5ClF;AAu5CI,QAAI;AACJ,eAAW,KAAK,MAAM;AACpB,UACE,KACA,OAAO,EAAE,SAAS,aACjB,EAAE,SAAS,iCAAiC,EAAE,SAAS,aACxD,EAAE,SACF,OAAO,EAAE,MAAM,UAAU,WACzB;AACA,iBAAS,EAAE,MAAM;AACjB;AAAA,MACF;AAAA,IACF;AAGA,QAAI,WAAW,UAAa,KAAK,SAAS,GAAG;AAC3C,eAAS;AAAA,IACX;AACA,QAAI,WAAW,QAAW;AACxB;AAAA,IACF;AACA,QAAI,OAAO,MAAM,WAAW,UAAU,WAAW,MAAM;AAGrD,aAAO,oBAAoB,KAAK,IAAI;AACpC;AAAA,IACF;AACA,WAAO,MAAM,SAAS;AACtB,QAAI,QAAQ;AACV,aAAO,oBAAoB,KAAK,IAAI;AAAA,IACtC;AACA,eAAK,mBAAL,8BAAsB,QAAQ,EAAE,OAAO;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,uBAAuB,IAAgF;AACrG,SAAK,sBAAsB;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,yBAAkC;AACxC,eAAW,OAAO,KAAK,QAAQ,OAAO,GAAG;AACvC,UAAI,IAAI,SAAS,yBAAyB,IAAI,QAAQ,aAAa;AACjE,eAAO;AAAA,MACT;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,mBAAmB,OAAoF;AA59CzG;AA69CI,QAAI,CAAC,SAAS,OAAO,MAAM,QAAQ,YAAY,OAAO,MAAM,WAAW,UAAU;AAC/E;AAAA,IACF;AACA,QAAI,CAAC,MAAM,QAAQ,MAAM,YAAY,KAAK,MAAM,aAAa,WAAW,GAAG;AACzE;AAAA,IACF;AACA,UAAM,SAAS,KAAK,QAAQ,IAAI,KAAK,UAAU,MAAM,KAAK,MAAM,MAAM,CAAC;AACvE,QAAI,CAAC,QAAQ;AACX;AAAA,IACF;AACA,eAAK,wBAAL,8BAA2B,QAAQ,MAAM;AAKzC,SAAK,eAAe,QAAQ,MAAM,YAAY;AAAA,EAChD;AACF;AAaO,SAAS,8BAA8B,OAA+C;AAC3F,QAAM,OAA+B,CAAC;AACtC,QAAM,OAAO,MAAM;AACnB,MAAI,CAAC,MAAM;AACT,WAAO;AAAA,EACT;AACA,MAAI,OAAO,KAAK,WAAW,WAAW;AACpC,SAAK,KAAK;AAAA,MACR,MAAM;AAAA,MACN,UAAU;AAAA,MACV,OAAO,EAAE,OAAO,KAAK,OAAO;AAAA,IAC9B,CAAC;AAAA,EACH;AACA,MAAI,OAAO,KAAK,QAAQ,YAAY,OAAO,SAAS,KAAK,GAAG,GAAG;AAC7D,SAAK,KAAK;AAAA,MACR,MAAM;AAAA,MACN,UAAU;AAAA,MACV,OAAO,EAAE,OAAO,KAAK,MAAM,IAAI;AAAA,IACjC,CAAC;AAAA,EACH;AACA,MAAI,OAAO,KAAK,QAAQ,YAAY,OAAO,SAAS,KAAK,GAAG,GAAG;AAC7D,SAAK,KAAK;AAAA,MACR,MAAM;AAAA,MACN,UAAU;AAAA,MACV,OAAO,EAAE,OAAO,KAAK,MAAM,IAAI;AAAA,IACjC,CAAC;AAAA,EACH;AACA,MAAI,OAAO,KAAK,YAAY,YAAY,OAAO,SAAS,KAAK,OAAO,GAAG;AACrE,SAAK,KAAK;AAAA,MACR,MAAM;AAAA,MACN,UAAU;AAAA,MACV,OAAO,EAAE,OAAO,KAAK,QAAQ;AAAA,IAC/B,CAAC;AAAA,EACH,WAAW,MAAM,YAAY,OAAO,MAAM,SAAS,YAAY,YAAY,OAAO,SAAS,MAAM,SAAS,OAAO,GAAG;AAClH,SAAK,KAAK;AAAA,MACR,MAAM;AAAA,MACN,UAAU;AAAA,MACV,OAAO,EAAE,OAAO,MAAM,SAAS,QAAQ;AAAA,IACzC,CAAC;AAAA,EACH;AACA,SAAO;AACT;",
6
6
  "names": ["_a"]
7
7
  }
package/build/main.js CHANGED
@@ -320,13 +320,12 @@ class GoveeAdapter extends utils.Adapter {
320
320
  });
321
321
  }
322
322
  );
323
- this.appApiPollTimer = this.setInterval(
324
- () => {
325
- var _a2;
326
- (_a2 = this.deviceManager) == null ? void 0 : _a2.pollAppApi().catch((e) => this.log.debug(`pollAppApi failed: ${e instanceof Error ? e.message : String(e)}`));
327
- },
328
- 2 * 60 * 1e3
329
- );
323
+ const triggerAppApiPoll = () => {
324
+ var _a2;
325
+ (_a2 = this.deviceManager) == null ? void 0 : _a2.pollAppApi().catch((e) => this.log.debug(`pollAppApi failed: ${e instanceof Error ? e.message : String(e)}`));
326
+ };
327
+ this.appApiPollTimer = this.setInterval(triggerAppApiPoll, 2 * 60 * 1e3);
328
+ this.setTimeout(triggerAppApiPoll, 5e3);
330
329
  if (!cachedOk) {
331
330
  const result = await this.cloudInitWithTimeout();
332
331
  this.cloudWasConnected = result.ok;
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 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 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 =>\n this.log.warn(`onStateChange crashed for ${id}: ${e instanceof Error ? e.message : String(e)}`),\n ),\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(\n `Failed to rebuild segment tree for ${device.name} after count growth: ${e instanceof Error ? e.message : String(e)}`,\n );\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: ${e instanceof Error ? e.message : String(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: ${e instanceof Error ? e.message : String(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(\n `applyCloudCapabilities failed for ${device.sku}: ${e instanceof Error ? e.message : String(e)}`,\n ),\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 this.appApiPollTimer = this.setInterval(\n () => {\n this.deviceManager\n ?.pollAppApi()\n .catch(e => this.log.debug(`pollAppApi failed: ${e instanceof Error ? e.message : String(e)}`));\n },\n 2 * 60 * 1000,\n );\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 =>\n this.log.debug(`Device cleanup failed: ${e instanceof Error ? e.message : String(e)}`),\n );\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.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 this.log.info(\"Refresh cloud data: done\");\n } catch (e) {\n this.log.warn(`Refresh cloud data failed: ${e instanceof Error ? e.message : String(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 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}: ${err instanceof Error ? err.message : String(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}: ${err instanceof Error ? err.message : String(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 if (state.power === false && 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}: ${err instanceof Error ? err.message : String(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}: ${e instanceof Error ? e.message : String(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 /** Update global info.connection */\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 this.setStateAsync(\"info.connection\", { val: connected, ack: true }).catch(() => {});\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 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 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 const channels: string[] = [\"LAN\"];\n if (this.cloudWasConnected) {\n channels.push(\"Cloud\");\n }\n if (this.mqttClient?.connected) {\n channels.push(\"MQTT\");\n }\n\n // Build the device summary\n const parts: string[] = [];\n if (devices.length > 0) {\n parts.push(`${devices.length} device${devices.length > 1 ? \"s\" : \"\"}`);\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} (${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 this.log.warn(\"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}: ${e instanceof Error ? e.message : String(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}: ${e instanceof Error ? e.message : String(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. Triggers a settings-reload by the admin UI but the\n * adapter restart is gentle (only the field changed, not the connection).\n */\n private async clearVerificationCodeSetting(): Promise<void> {\n await this.extendForeignObjectAsync(`system.adapter.${this.namespace}`, {\n native: { mqttVerificationCode: \"\" },\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?: string;\n iotEndpoint?: string;\n p12Cert?: string;\n p12Pass?: string;\n accountId?: string;\n accountTopic?: string;\n tokenExpiresAt?: number;\n };\n const bearerToken = this.decrypt(obj.bearerToken ?? \"\");\n const p12Cert = this.decrypt(obj.p12Cert ?? \"\");\n const p12Pass = this.decrypt(obj.p12Pass ?? \"\");\n const iotEndpoint = obj.iotEndpoint ?? \"\";\n const accountId = obj.accountId ?? \"\";\n const accountTopic = obj.accountTopic ?? \"\";\n const 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`. Reads the current native, and\n * if any of the seven legacy mqtt* fields exist with content, blanks\n * them. This triggers ONE js-controller restart (config-change), but\n * it's idempotent \u2014 second start finds them empty, skips the cleanup.\n */\n private async cleanupLegacyMqttNativeOnce(): Promise<void> {\n try {\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 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 } catch {\n // best-effort \u2014 if cleanup fails, leftover plaintext stays but new flow ignores it\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 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 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 =>\n this.log.warn(`onStateChange crashed for ${id}: ${e instanceof Error ? e.message : String(e)}`),\n ),\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(\n `Failed to rebuild segment tree for ${device.name} after count growth: ${e instanceof Error ? e.message : String(e)}`,\n );\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: ${e instanceof Error ? e.message : String(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: ${e instanceof Error ? e.message : String(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(\n `applyCloudCapabilities failed for ${device.sku}: ${e instanceof Error ? e.message : String(e)}`,\n ),\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 .catch(e => this.log.debug(`pollAppApi failed: ${e instanceof Error ? e.message : String(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 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 =>\n this.log.debug(`Device cleanup failed: ${e instanceof Error ? e.message : String(e)}`),\n );\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.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 this.log.info(\"Refresh cloud data: done\");\n } catch (e) {\n this.log.warn(`Refresh cloud data failed: ${e instanceof Error ? e.message : String(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 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}: ${err instanceof Error ? err.message : String(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}: ${err instanceof Error ? err.message : String(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 if (state.power === false && 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}: ${err instanceof Error ? err.message : String(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}: ${e instanceof Error ? e.message : String(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 /** Update global info.connection */\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 this.setStateAsync(\"info.connection\", { val: connected, ack: true }).catch(() => {});\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 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 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 const channels: string[] = [\"LAN\"];\n if (this.cloudWasConnected) {\n channels.push(\"Cloud\");\n }\n if (this.mqttClient?.connected) {\n channels.push(\"MQTT\");\n }\n\n // Build the device summary\n const parts: string[] = [];\n if (devices.length > 0) {\n parts.push(`${devices.length} device${devices.length > 1 ? \"s\" : \"\"}`);\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} (${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 this.log.warn(\"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}: ${e instanceof Error ? e.message : String(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}: ${e instanceof Error ? e.message : String(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. Triggers a settings-reload by the admin UI but the\n * adapter restart is gentle (only the field changed, not the connection).\n */\n private async clearVerificationCodeSetting(): Promise<void> {\n await this.extendForeignObjectAsync(`system.adapter.${this.namespace}`, {\n native: { mqttVerificationCode: \"\" },\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?: string;\n iotEndpoint?: string;\n p12Cert?: string;\n p12Pass?: string;\n accountId?: string;\n accountTopic?: string;\n tokenExpiresAt?: number;\n };\n const bearerToken = this.decrypt(obj.bearerToken ?? \"\");\n const p12Cert = this.decrypt(obj.p12Cert ?? \"\");\n const p12Pass = this.decrypt(obj.p12Pass ?? \"\");\n const iotEndpoint = obj.iotEndpoint ?? \"\";\n const accountId = obj.accountId ?? \"\";\n const accountTopic = obj.accountTopic ?? \"\";\n const 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`. Reads the current native, and\n * if any of the seven legacy mqtt* fields exist with content, blanks\n * them. This triggers ONE js-controller restart (config-change), but\n * it's idempotent \u2014 second start finds them empty, skips the cleanup.\n */\n private async cleanupLegacyMqttNativeOnce(): Promise<void> {\n try {\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 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 } catch {\n // best-effort \u2014 if cleanup fails, leftover plaintext stays but new flow ignores it\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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",
6
6
  "names": ["_a"]
7
7
  }
package/io-package.json CHANGED
@@ -1,8 +1,21 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "govee-smart",
4
- "version": "2.1.3",
4
+ "version": "2.1.4",
5
5
  "news": {
6
+ "2.1.4": {
7
+ "en": "Online status correct again after adapter restart — lights flip to online with the first LAN scan, sensors with the first cloud poll (5 s after start instead of 2 minutes).",
8
+ "de": "Online-Status nach Adapter-Restart wieder korrekt — Lichter werden direkt mit dem ersten LAN-Scan online, Sensoren mit dem ersten Cloud-Poll (5 s nach Start statt 2 Minuten).",
9
+ "ru": "Состояние «онлайн» снова корректно после рестарта адаптера — лампы переключаются на онлайн с первым LAN-сканом, сенсоры — с первым облачным опросом (5 с после старта вместо 2 минут).",
10
+ "pt": "Estado online correto novamente apos reiniciar o adapter — luzes ficam online com o primeiro scan LAN, sensores com o primeiro polling de nuvem (5 s apos o inicio em vez de 2 minutos).",
11
+ "nl": "Online-status weer correct na herstart van de adapter — lampen springen op online bij de eerste LAN-scan, sensoren bij de eerste cloud-poll (5 s na start in plaats van 2 minuten).",
12
+ "fr": "Statut en ligne correct apres redemarrage de l'adapter — les lumieres passent online au premier scan LAN, les capteurs au premier poll cloud (5 s apres le demarrage au lieu de 2 minutes).",
13
+ "it": "Stato online di nuovo corretto dopo il riavvio dell'adapter — le luci passano online al primo scan LAN, i sensori al primo poll cloud (5 s dopo l'avvio invece di 2 minuti).",
14
+ "es": "Estado online correcto otra vez tras reiniciar el adaptador — las luces pasan a online con el primer escaneo LAN, los sensores con el primer poll de nube (5 s tras el inicio en vez de 2 minutos).",
15
+ "pl": "Status online znow poprawny po restarcie adaptera — lampy przechodza w online przy pierwszym skanie LAN, sensory przy pierwszym pollu chmury (5 s po starcie zamiast 2 minut).",
16
+ "uk": "Стан «онлайн» знову коректний після рестарту адаптера — лампи перемикаються на онлайн з першим LAN-сканом, сенсори — з першим хмарним опитуванням (5 с після старту замість 2 хвилин).",
17
+ "zh-cn": "适配器重启后在线状态恢复准确——灯具会在第一次 LAN 扫描时切换为在线,传感器在第一次云轮询时(启动后 5 秒,而不是 2 分钟)。"
18
+ },
6
19
  "2.1.3": {
7
20
  "en": "Critical fix: no more restart-loop after entering the verification code. Saving credentials in the adapter config works again. Honest startup messages — when MQTT can't connect, the log says why.",
8
21
  "de": "Kritischer Fix: kein Restart-Loop mehr nach dem Verifizierungs-Code. Speichern in der Config funktioniert wieder. Ehrliche Startup-Meldungen — wenn MQTT nicht verbindet, steht der Grund im Log.",
@@ -80,19 +93,6 @@
80
93
  "pl": "Eventy czujnikow i urzadzen przychodza teraz niezawodnie takze po reconnectach. Min js-controller >=7.0.23.",
81
94
  "uk": "Події від сенсорів і пристроїв тепер надходять надійно й після реконектів. Мін. js-controller >=7.0.23.",
82
95
  "zh-cn": "传感器和设备事件现在在重连后也能可靠到达。最低 js-controller >=7.0.23。"
83
- },
84
- "2.0.1": {
85
- "en": "Hotfixes for v2.0.0: sensor values land under sensor/, events under events/. Snapshots no longer added to thermometers/heaters/kettles. Less log noise on startup.",
86
- "de": "Hotfixes fuer v2.0.0: Sensorwerte landen unter sensor/, Events unter events/. Snapshots werden nicht mehr an Thermometer/Heizgeraete/Wasserkocher angehaengt. Weniger Log-Rauschen beim Start.",
87
- "ru": "Хотфиксы для v2.0.0: значения сенсоров теперь под sensor/, события под events/. Снапшоты больше не добавляются термометрам/нагревателям/чайникам. Меньше шума в логе при старте.",
88
- "pt": "Hotfixes para v2.0.0: valores de sensores ficam sob sensor/, eventos sob events/. Snapshots nao mais adicionados a termometros/aquecedores/chaleiras. Menos ruido no log no arranque.",
89
- "nl": "Hotfixes voor v2.0.0: sensorwaarden onder sensor/, events onder events/. Snapshots niet meer toegevoegd aan thermometers/verwarmers/waterkokers. Minder log-ruis bij start.",
90
- "fr": "Hotfixes pour v2.0.0: valeurs des capteurs sous sensor/, evenements sous events/. Snapshots ne sont plus ajoutes aux thermometres/chauffages/bouilloires. Moins de bruit dans le log au demarrage.",
91
- "it": "Hotfix per v2.0.0: i valori dei sensori sotto sensor/, eventi sotto events/. Gli snapshot non vengono piu aggiunti a termometri/riscaldatori/bollitori. Meno rumore nel log all'avvio.",
92
- "es": "Hotfixes para v2.0.0: los valores de sensores bajo sensor/, eventos bajo events/. Los snapshots ya no se agregan a termometros/calefactores/hervidores. Menos ruido en el log al iniciar.",
93
- "pl": "Hotfixy dla v2.0.0: wartosci czujnikow pod sensor/, eventy pod events/. Snapshoty nie sa juz dodawane do termometrow/grzejnikow/czajnikow. Mniej szumu w logu przy starcie.",
94
- "uk": "Хотфікси для v2.0.0: значення сенсорів під sensor/, події під events/. Снапшоти більше не додаються термометрам/обігрівачам/чайникам. Менше шуму в лозі при старті.",
95
- "zh-cn": "v2.0.0 热修复:传感器值放在 sensor/ 下,事件放在 events/ 下。快照不再添加到温度计/加热器/水壶。启动时日志更干净。"
96
96
  }
97
97
  },
98
98
  "titleLang": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.govee-smart",
3
- "version": "2.1.3",
3
+ "version": "2.1.4",
4
4
  "description": "Control Govee WiFi devices via LAN, MQTT and Cloud.",
5
5
  "author": {
6
6
  "name": "krobi",