iobroker.govee-smart 2.6.2 → 2.6.3

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.
Files changed (35) hide show
  1. package/README.md +10 -5
  2. package/build/lib/device-manager.js +29 -18
  3. package/build/lib/device-manager.js.map +2 -2
  4. package/build/lib/govee-api-client.js +3 -0
  5. package/build/lib/govee-api-client.js.map +2 -2
  6. package/build/lib/govee-cloud-client.js +33 -6
  7. package/build/lib/govee-cloud-client.js.map +2 -2
  8. package/build/lib/govee-lan-client.js +68 -14
  9. package/build/lib/govee-lan-client.js.map +2 -2
  10. package/build/lib/govee-mqtt-client.js +37 -9
  11. package/build/lib/govee-mqtt-client.js.map +2 -2
  12. package/build/lib/govee-openapi-mqtt-client.js +39 -9
  13. package/build/lib/govee-openapi-mqtt-client.js.map +2 -2
  14. package/build/lib/http-client.js +21 -4
  15. package/build/lib/http-client.js.map +2 -2
  16. package/build/lib/local-snapshots.js +11 -1
  17. package/build/lib/local-snapshots.js.map +2 -2
  18. package/build/lib/rate-limiter.js +16 -0
  19. package/build/lib/rate-limiter.js.map +2 -2
  20. package/build/lib/segment-wizard.js +31 -10
  21. package/build/lib/segment-wizard.js.map +2 -2
  22. package/build/lib/sku-cache.js +6 -0
  23. package/build/lib/sku-cache.js.map +2 -2
  24. package/build/lib/snapshot-handler.js +30 -5
  25. package/build/lib/snapshot-handler.js.map +2 -2
  26. package/build/lib/state-manager.js +54 -11
  27. package/build/lib/state-manager.js.map +2 -2
  28. package/build/lib/timing-constants.js +14 -2
  29. package/build/lib/timing-constants.js.map +2 -2
  30. package/build/lib/types.js +6 -2
  31. package/build/lib/types.js.map +2 -2
  32. package/build/main.js +42 -6
  33. package/build/main.js.map +2 -2
  34. package/io-package.json +14 -14
  35. package/package.json +1 -1
package/README.md CHANGED
@@ -128,6 +128,16 @@ This adapter's MQTT authentication and BLE-over-LAN (ptReal) protocol implementa
128
128
  Placeholder for the next version (at the beginning of the line):
129
129
  ### **WORK IN PROGRESS**
130
130
  -->
131
+ ### 2.6.3 (2026-05-10)
132
+
133
+ - Real-time status push (`info.mqttConnected`, `info.openapiMqttConnected`) now recovers automatically after a brief Govee outage. Previously, a rare subscribe rejection could leave the indicator stuck on `false` until the next adapter restart.
134
+ - Restoring a local snapshot on a multi-segment LED strip is fast again — a 30-segment snapshot replays in well under a second instead of about nine.
135
+ - A wrong manual pairing IP, a network blip mid-request, or a Govee Cloud command that the cloud rejects with a payload-level error are now reported instead of failing silently.
136
+ - LED segment number 0 displaying blue when a colour was set with a short hex value (e.g. `#FF`) is fixed — invalid colour input falls back to black instead of an unintended colour.
137
+ - The segment-detection wizard now restores the strip's previous look on adapter-stop, so the test pattern doesn't stay on the wall when you click "Save adapter settings".
138
+ - A configured but rejected Govee API key now surfaces as `Cloud: device list returned empty — check the API key matches the account that owns the devices` so you have a starting point.
139
+ - Various behind-the-scenes hardening of all four communication channels (LAN, real-time MQTT, Cloud-events, Cloud REST) — invisible if everything was already running fine, robustness if something is unstable.
140
+
131
141
  ### 2.6.2 (2026-05-09)
132
142
 
133
143
  - Adapter log messages are now English only, in line with the ioBroker community standard. Localized state names, descriptions and dropdown labels (11 languages) are unchanged. The user-visible segment-detection wizard text in the admin UI also remains localized.
@@ -144,11 +154,6 @@ This adapter's MQTT authentication and BLE-over-LAN (ptReal) protocol implementa
144
154
 
145
155
  - Test-coverage expansion: `mqtt.connect` is now an injectable constructor parameter (like `httpsRequest` in v2.5.1), and 7 new mock tests cover the getIotKey path and persisted-credentials reuse.
146
156
 
147
- ### 2.5.3 (2026-05-04)
148
-
149
- - Segment-detection wizard no longer spams "has no existing object" WARN for indices above the real strip length — echo packets are filtered against `segmentCount` (Issue #8).
150
- - Command before cloud-init (e.g. cloud-only device right after restart) is silent now instead of warning "No channel available".
151
-
152
157
  Older entries are in [CHANGELOG_OLD.md](CHANGELOG_OLD.md).
153
158
 
154
159
  ## Support
@@ -140,6 +140,8 @@ class DeviceManager {
140
140
  /** Per-source dedup so a Cloud NETWORK error doesn't shadow an App-API one. */
141
141
  lastErrorCategory = null;
142
142
  lastAppApiErrorCategory = null;
143
+ /** Dedup tracker for `loadGroupMembers` errors — first warn per category, rest debug. */
144
+ lastGroupMembersErrorCategory = null;
143
145
  /**
144
146
  * @param log ioBroker logger
145
147
  * @param timers Adapter timer wrapper (forwarded to CommandRouter for
@@ -693,9 +695,15 @@ class DeviceManager {
693
695
  if (changed) {
694
696
  (_a = this.onDeviceListChanged) == null ? void 0 : _a.call(this, this.getDevices());
695
697
  }
698
+ this.lastGroupMembersErrorCategory = null;
696
699
  return changed;
697
700
  } catch (e) {
698
- this.log.debug(`Could not load group members: ${(0, import_types.errMessage)(e)}`);
701
+ this.lastGroupMembersErrorCategory = (0, import_types.logDedup)(
702
+ this.log,
703
+ this.lastGroupMembersErrorCategory,
704
+ "Group membership",
705
+ e
706
+ );
699
707
  return false;
700
708
  }
701
709
  }
@@ -1151,7 +1159,6 @@ class DeviceManager {
1151
1159
  * @returns Number of devices that received an update
1152
1160
  */
1153
1161
  async pollAppApi() {
1154
- var _a;
1155
1162
  if (!this.apiClient || !this.apiClient.hasBearerToken()) {
1156
1163
  return 0;
1157
1164
  }
@@ -1173,22 +1180,26 @@ class DeviceManager {
1173
1180
  return 0;
1174
1181
  }
1175
1182
  this.lastAppApiErrorCategory = null;
1176
- let updated = 0;
1177
- for (const entry of entries) {
1178
- const device = this.devices.get(this.deviceKey(entry.sku, entry.device));
1179
- if (!device) {
1180
- continue;
1181
- }
1182
- const caps = buildCapabilitiesFromAppEntry(entry);
1183
- if (caps.length === 0) {
1184
- continue;
1185
- }
1186
- (_a = this.onCloudCapabilities) == null ? void 0 : _a.call(this, device, caps);
1187
- this.applyOnlineCap(device, caps);
1188
- this.diagnostics.setApiResponse(device.deviceId, "/device/rest/devices/v1/list", entry);
1189
- updated++;
1190
- }
1191
- return updated;
1183
+ const results = await Promise.all(
1184
+ entries.map(
1185
+ (entry) => Promise.resolve().then(() => {
1186
+ var _a;
1187
+ const device = this.devices.get(this.deviceKey(entry.sku, entry.device));
1188
+ if (!device) {
1189
+ return false;
1190
+ }
1191
+ const caps = buildCapabilitiesFromAppEntry(entry);
1192
+ if (caps.length === 0) {
1193
+ return false;
1194
+ }
1195
+ (_a = this.onCloudCapabilities) == null ? void 0 : _a.call(this, device, caps);
1196
+ this.applyOnlineCap(device, caps);
1197
+ this.diagnostics.setApiResponse(device.deviceId, "/device/rest/devices/v1/list", entry);
1198
+ return true;
1199
+ })
1200
+ )
1201
+ );
1202
+ return results.filter(Boolean).length;
1192
1203
  }
1193
1204
  /**
1194
1205
  * Pull the `devices.capabilities.online` entry (if any) out of a
@@ -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 coerceFiniteNumber,\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 errMessage,\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 // M2 \u2014 XOR-Checksum-Validation. Govee BLE-Pakete haben am letzten Byte\n // (Index 19) einen XOR \u00FCber Bytes 0-18. Spoofed/malformed Pakete\n // schl\u00FCpfen sonst durch und persistieren falsche segmentCount.\n let xor = 0;\n for (let i = 0; i < 19; i++) {\n xor ^= bytes[i];\n }\n if (xor !== bytes[19]) {\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 * Entfernt ein Ger\u00E4t aus dem internen Tracking. Aufgerufen wenn ein Ger\u00E4t\n * aus dem Govee-Account entfernt wurde \u2014 die jsonl-Objects r\u00E4umt\n * `cleanupDevices` (state-manager) ab; hier nur die in-memory-Maps.\n *\n * Returnt die deviceId des gedroppten Ger\u00E4ts (zur Diagnostics-Cleanup),\n * oder null wenn nichts zu entfernen war.\n *\n * @param sku Govee-SKU\n * @param deviceId Device-ID (mit/ohne Doppelpunkte)\n */\n removeDevice(sku: string, deviceId: string): string | null {\n const key = this.deviceKey(sku, deviceId);\n const dev = this.devices.get(key);\n if (!dev) {\n return null;\n }\n this.devices.delete(key);\n // nudgedSeedSkus bleibt \u2014 wir wollen den seed-Hinweis nicht erneut\n // pushen wenn ein gleicher SKU sp\u00E4ter wieder reinpoppt.\n return dev.deviceId;\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}: ${errMessage(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}: ${errMessage(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}: ${errMessage(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}: ${errMessage(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}: ${errMessage(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}: ${errMessage(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}: ${errMessage(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: ${errMessage(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 // Prim\u00E4rer Match \u2014 exakte Ger\u00E4te-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 }\n // SKU-Fallback nur wenn EXACTLY ONE matchender Eintrag existiert.\n // Bei mehreren same-SKU-devices ohne lanIp k\u00F6nnte sonst das falsche\n // Ger\u00E4t gebunden werden (Memory `feedback_doppel_audit_pattern`).\n if (!matched) {\n const skuMatches = Array.from(this.devices.values()).filter(dev => dev.sku === lanDevice.sku && !dev.lanIp);\n if (skuMatches.length === 1) {\n matched = skuMatches[0];\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 \"Enable experimental device support\" 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 // API-Boundary: Govee schickt gelegentlich brightness/onOff/color als\n // String oder mit unerwarteten Typen. coerceFiniteNumber/coerceBool\n // returnt null bei Drift \u2192 Feld bleibt unver\u00E4ndert (vorhandener Wert\n // bleibt stehen, kein State-Schreibung mit kaputtem Wert).\n const onOff = coerceFiniteNumber(update.state.onOff);\n if (onOff !== null) {\n state.power = onOff === 1;\n }\n const brightness = coerceFiniteNumber(update.state.brightness);\n if (brightness !== null) {\n state.brightness = brightness;\n }\n if (update.state.color && typeof update.state.color === \"object\") {\n const r = coerceFiniteNumber((update.state.color as { r?: unknown }).r);\n const g = coerceFiniteNumber((update.state.color as { g?: unknown }).g);\n const b = coerceFiniteNumber((update.state.color as { b?: unknown }).b);\n if (r !== null && g !== null && b !== null) {\n state.colorRgb = rgbToHex(r, g, b);\n }\n }\n // 0 = \"not in colortemp mode\" \u2014 drop intentionally (Govee-Konvention).\n const ctk = coerceFiniteNumber(update.state.colorTemInKelvin);\n if (ctk !== null && ctk > 0) {\n state.colorTemperature = ctk;\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 // L6 \u2014 Plausibilit\u00E4ts-Cap: SEGMENT_HARD_MAX (55) ist die Govee-\n // Protokoll-Obergrenze. Werte dar\u00FCber kommen nur aus broken oder\n // spoofed Paketen \u2014 niemals persistieren.\n if (maxSeen > SEGMENT_HARD_MAX) {\n this.log.debug(`${device.name}: ignoring segmentCount=${maxSeen} (above protocol limit ${SEGMENT_HARD_MAX})`);\n return;\n }\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}: ${errMessage(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: ${errMessage(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 /**\n * True wenn mindestens ein Device App-API-Werte konsumiert (Sensoren,\n * Appliances). Adapter-checkAllReady wartet darauf damit \u201Eready\" erst\n * geloggt wird wenn Sensor-Werte tats\u00E4chlich da sind.\n */\n public 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 coerceFiniteNumber,\n logDedup,\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 errMessage,\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 // M2 \u2014 XOR-Checksum-Validation. Govee BLE-Pakete haben am letzten Byte\n // (Index 19) einen XOR \u00FCber Bytes 0-18. Spoofed/malformed Pakete\n // schl\u00FCpfen sonst durch und persistieren falsche segmentCount.\n let xor = 0;\n for (let i = 0; i < 19; i++) {\n xor ^= bytes[i];\n }\n if (xor !== bytes[19]) {\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 /** Dedup tracker for `loadGroupMembers` errors \u2014 first warn per category, rest debug. */\n private lastGroupMembersErrorCategory: 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 * Entfernt ein Ger\u00E4t aus dem internen Tracking. Aufgerufen wenn ein Ger\u00E4t\n * aus dem Govee-Account entfernt wurde \u2014 die jsonl-Objects r\u00E4umt\n * `cleanupDevices` (state-manager) ab; hier nur die in-memory-Maps.\n *\n * Returnt die deviceId des gedroppten Ger\u00E4ts (zur Diagnostics-Cleanup),\n * oder null wenn nichts zu entfernen war.\n *\n * @param sku Govee-SKU\n * @param deviceId Device-ID (mit/ohne Doppelpunkte)\n */\n removeDevice(sku: string, deviceId: string): string | null {\n const key = this.deviceKey(sku, deviceId);\n const dev = this.devices.get(key);\n if (!dev) {\n return null;\n }\n this.devices.delete(key);\n // nudgedSeedSkus bleibt \u2014 wir wollen den seed-Hinweis nicht erneut\n // pushen wenn ein gleicher SKU sp\u00E4ter wieder reinpoppt.\n return dev.deviceId;\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}: ${errMessage(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}: ${errMessage(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}: ${errMessage(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}: ${errMessage(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}: ${errMessage(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}: ${errMessage(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}: ${errMessage(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 // Reset dedup on success so a future failure warns again.\n this.lastGroupMembersErrorCategory = null;\n return changed;\n } catch (e) {\n // Group-membership is best-effort \u2014 but a persistent failure (e.g. API\n // permission revoked) should still surface once so the user knows\n // groups won't fan-out. logDedup demotes repeats to debug.\n this.lastGroupMembersErrorCategory = logDedup(\n this.log,\n this.lastGroupMembersErrorCategory,\n \"Group membership\",\n e,\n );\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 // Prim\u00E4rer Match \u2014 exakte Ger\u00E4te-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 }\n // SKU-Fallback nur wenn EXACTLY ONE matchender Eintrag existiert.\n // Bei mehreren same-SKU-devices ohne lanIp k\u00F6nnte sonst das falsche\n // Ger\u00E4t gebunden werden (Memory `feedback_doppel_audit_pattern`).\n if (!matched) {\n const skuMatches = Array.from(this.devices.values()).filter(dev => dev.sku === lanDevice.sku && !dev.lanIp);\n if (skuMatches.length === 1) {\n matched = skuMatches[0];\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 \"Enable experimental device support\" 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 // API-Boundary: Govee schickt gelegentlich brightness/onOff/color als\n // String oder mit unerwarteten Typen. coerceFiniteNumber/coerceBool\n // returnt null bei Drift \u2192 Feld bleibt unver\u00E4ndert (vorhandener Wert\n // bleibt stehen, kein State-Schreibung mit kaputtem Wert).\n const onOff = coerceFiniteNumber(update.state.onOff);\n if (onOff !== null) {\n state.power = onOff === 1;\n }\n const brightness = coerceFiniteNumber(update.state.brightness);\n if (brightness !== null) {\n state.brightness = brightness;\n }\n if (update.state.color && typeof update.state.color === \"object\") {\n const r = coerceFiniteNumber((update.state.color as { r?: unknown }).r);\n const g = coerceFiniteNumber((update.state.color as { g?: unknown }).g);\n const b = coerceFiniteNumber((update.state.color as { b?: unknown }).b);\n if (r !== null && g !== null && b !== null) {\n state.colorRgb = rgbToHex(r, g, b);\n }\n }\n // 0 = \"not in colortemp mode\" \u2014 drop intentionally (Govee-Konvention).\n const ctk = coerceFiniteNumber(update.state.colorTemInKelvin);\n if (ctk !== null && ctk > 0) {\n state.colorTemperature = ctk;\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 // L6 \u2014 Plausibilit\u00E4ts-Cap: SEGMENT_HARD_MAX (55) ist die Govee-\n // Protokoll-Obergrenze. Werte dar\u00FCber kommen nur aus broken oder\n // spoofed Paketen \u2014 niemals persistieren.\n if (maxSeen > SEGMENT_HARD_MAX) {\n this.log.debug(`${device.name}: ignoring segmentCount=${maxSeen} (above protocol limit ${SEGMENT_HARD_MAX})`);\n return;\n }\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}: ${errMessage(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: ${errMessage(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 // Process all entries in parallel \u2014 each entry only touches its own\n // device (no shared mutation), and the downstream callbacks (onCloud-\n // Capabilities \u2192 main.applyCloudCapabilities \u2192 setStateAsync queue)\n // are async-safe. Sequential `for` blocked the App-API tick on a slow\n // setStateAsync round-trip per device.\n // Wrap each per-entry block in `Promise.resolve` so the iterable is a\n // true Thenable \u2014 synchronous returns confuse `await Promise.all`'s\n // type-checker (await-thenable lint rule) even though the runtime would\n // accept them. No-op at runtime, makes the intent explicit and lints\n // without `require-await`.\n const results = await Promise.all(\n entries.map(entry =>\n Promise.resolve().then(() => {\n const device = this.devices.get(this.deviceKey(entry.sku, entry.device));\n if (!device) {\n return false;\n }\n const caps = buildCapabilitiesFromAppEntry(entry);\n if (caps.length === 0) {\n return false;\n }\n this.onCloudCapabilities?.(device, caps);\n // mapSingleCapability returns null for the synthetic `online` cap\n // (online is a device-level property, not a regular state), so\n // onCloudCapabilities never reaches info.online via the capability\n // pipeline. Pluck it out and apply it directly \u2014 otherwise sensor\n // SKUs like H5179 stay at info.online=false forever even while\n // their readings keep updating.\n this.applyOnlineCap(device, caps);\n this.diagnostics.setApiResponse(device.deviceId, \"/device/rest/devices/v1/list\", entry);\n return true;\n }),\n ),\n );\n return results.filter(Boolean).length;\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 /**\n * True wenn mindestens ein Device App-API-Werte konsumiert (Sensoren,\n * Appliances). Adapter-checkAllReady wartet darauf damit \u201Eready\" erst\n * geloggt wird wenn Sensor-Werte tats\u00E4chlich da sind.\n */\n public 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,mBAgBO;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;AAKA,QAAI,MAAM;AACV,aAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,aAAO,MAAM,CAAC;AAAA,IAChB;AACA,QAAI,QAAQ,MAAM,EAAE,GAAG;AACrB;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;AAnI1E;AAoIE,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,EAEhD,gCAAsD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAO9D,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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,aAAa,KAAa,UAAiC;AACzD,UAAM,MAAM,KAAK,UAAU,KAAK,QAAQ;AACxC,UAAM,MAAM,KAAK,QAAQ,IAAI,GAAG;AAChC,QAAI,CAAC,KAAK;AACR,aAAO;AAAA,IACT;AACA,SAAK,QAAQ,OAAO,GAAG;AAGvB,WAAO,IAAI;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,gBAAyB;AA9V3B;AA+VI,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;AA1alD;AA2aI,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;AAriB7C;AAsiBI,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;AArnBzF;AAsnBI,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,SAAK,yBAAW,CAAC,CAAC,EAAE;AAAA,MACxE;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,SAAK,yBAAW,CAAC,CAAC,EAAE;AAAA,QAC5E;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;AAxqBV,cAAAA;AAyqBU,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,SAAK,yBAAW,CAAC,CAAC,EAAE;AAAA,QAC5E;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,SAAK,yBAAW,CAAC,CAAC,EAAE;AAAA,QAC5E;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,SAAK,yBAAW,CAAC,CAAC,EAAE;AAAA,QAC1E;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,SAAK,yBAAW,CAAC,CAAC,EAAE;AAAA,QAC3E;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;AA1yBhE;AA2yBc,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,SAAK,yBAAW,CAAC,CAAC,EAAE;AAAA,QAC3E;AAAA,MACF,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,mBAAqC;AAh0B7C;AAi0BI,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;AAEA,WAAK,gCAAgC;AACrC,aAAO;AAAA,IACT,SAAS,GAAG;AAIV,WAAK,oCAAgC;AAAA,QACnC,KAAK;AAAA,QACL,KAAK;AAAA,QACL;AAAA,QACA;AAAA,MACF;AACA,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;AAp6BjD;AAs6BI,QAAI;AACJ,eAAW,OAAO,KAAK,QAAQ,OAAO,GAAG;AACvC,cAAI,gCAAkB,IAAI,QAAQ,UAAM,gCAAkB,UAAU,MAAM,GAAG;AAC3E,kBAAU;AACV;AAAA,MACF;AAAA,IACF;AAIA,QAAI,CAAC,SAAS;AACZ,YAAM,aAAa,MAAM,KAAK,KAAK,QAAQ,OAAO,CAAC,EAAE,OAAO,SAAO,IAAI,QAAQ,UAAU,OAAO,CAAC,IAAI,KAAK;AAC1G,UAAI,WAAW,WAAW,GAAG;AAC3B,kBAAU,WAAW,CAAC;AAAA,MACxB;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;AAxhCnD;AAyhCI,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;AAKhB,YAAM,YAAQ,iCAAmB,OAAO,MAAM,KAAK;AACnD,UAAI,UAAU,MAAM;AAClB,cAAM,QAAQ,UAAU;AAAA,MAC1B;AACA,YAAM,iBAAa,iCAAmB,OAAO,MAAM,UAAU;AAC7D,UAAI,eAAe,MAAM;AACvB,cAAM,aAAa;AAAA,MACrB;AACA,UAAI,OAAO,MAAM,SAAS,OAAO,OAAO,MAAM,UAAU,UAAU;AAChE,cAAM,QAAI,iCAAoB,OAAO,MAAM,MAA0B,CAAC;AACtE,cAAM,QAAI,iCAAoB,OAAO,MAAM,MAA0B,CAAC;AACtE,cAAM,QAAI,iCAAoB,OAAO,MAAM,MAA0B,CAAC;AACtE,YAAI,MAAM,QAAQ,MAAM,QAAQ,MAAM,MAAM;AAC1C,gBAAM,eAAW,uBAAS,GAAG,GAAG,CAAC;AAAA,QACnC;AAAA,MACF;AAEA,YAAM,UAAM,iCAAmB,OAAO,MAAM,gBAAgB;AAC5D,UAAI,QAAQ,QAAQ,MAAM,GAAG;AAC3B,cAAM,mBAAmB;AAAA,MAC3B;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;AAIvC,YAAI,UAAU,kBAAkB;AAC9B,eAAK,IAAI,MAAM,GAAG,OAAO,IAAI,2BAA2B,OAAO,0BAA0B,gBAAgB,GAAG;AAC5G;AAAA,QACF;AACA,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;AAroCV;AAuoCI,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,SAAK,yBAAW,GAAG,CAAC;AAC1C,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;AAClC,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,6BAAyB,yBAAW,GAAG,CAAC;AACpD,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;AAW/B,UAAM,UAAU,MAAM,QAAQ;AAAA,MAC5B,QAAQ;AAAA,QAAI,WACV,QAAQ,QAAQ,EAAE,KAAK,MAAM;AAn8CrC;AAo8CU,gBAAM,SAAS,KAAK,QAAQ,IAAI,KAAK,UAAU,MAAM,KAAK,MAAM,MAAM,CAAC;AACvE,cAAI,CAAC,QAAQ;AACX,mBAAO;AAAA,UACT;AACA,gBAAM,OAAO,8BAA8B,KAAK;AAChD,cAAI,KAAK,WAAW,GAAG;AACrB,mBAAO;AAAA,UACT;AACA,qBAAK,wBAAL,8BAA2B,QAAQ;AAOnC,eAAK,eAAe,QAAQ,IAAI;AAChC,eAAK,YAAY,eAAe,OAAO,UAAU,gCAAgC,KAAK;AACtF,iBAAO;AAAA,QACT,CAAC;AAAA,MACH;AAAA,IACF;AACA,WAAO,QAAQ,OAAO,OAAO,EAAE;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcQ,eAAe,QAAqB,MAAoC;AAx+ClF;AAy+CI,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;AAAA;AAAA;AAAA;AAAA;AAAA,EAYO,yBAAkC;AACvC,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;AAnjDzG;AAojDI,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
  }
@@ -258,6 +258,9 @@ class GoveeApiClient {
258
258
  }
259
259
  const url = `https://app2.govee.com/appsku/v1/sku-supported-feature?sku=${encodeURIComponent(sku)}`;
260
260
  const resp = await (0, import_http_client.httpsRequest)({ method: "GET", url, headers: this.authHeaders() });
261
+ if (!resp || typeof resp !== "object") {
262
+ return null;
263
+ }
261
264
  return (_a = resp.data) != null ? _a : null;
262
265
  }
263
266
  /**