iobroker.govee-smart 2.5.2 → 2.5.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.
- package/README.md +5 -4
- package/build/lib/command-router.js +4 -0
- package/build/lib/command-router.js.map +2 -2
- package/build/main.js +8 -0
- package/build/main.js.map +2 -2
- package/io-package.json +14 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -124,6 +124,11 @@ This adapter's MQTT authentication and BLE-over-LAN (ptReal) protocol implementa
|
|
|
124
124
|
---
|
|
125
125
|
|
|
126
126
|
## Changelog
|
|
127
|
+
### 2.5.3 (2026-05-04)
|
|
128
|
+
|
|
129
|
+
- Segment-Erkennungs-Wizard: kein „has no existing object"-Spam mehr für Indizes oberhalb der echten Strip-Länge — Echo-Pakete werden defensiv gegen `segmentCount` gefiltert (Issue #8).
|
|
130
|
+
- Adapter-Init nach Restart: Befehle gegen ein noch-nicht-geladenes Cloud-Backend werden still verworfen statt mit „No channel available" zu warnen — der Befehl ist eh hin, der WARN war irreführend.
|
|
131
|
+
|
|
127
132
|
### 2.5.2 (2026-05-04)
|
|
128
133
|
|
|
129
134
|
- WARN-Spam alle 2 Min behoben: `groups.*.info.membersUnreachable` bleibt bei vollständig erreichbaren Gruppen mit leerem Wert vorhanden statt gelöscht zu werden.
|
|
@@ -141,10 +146,6 @@ This adapter's MQTT authentication and BLE-over-LAN (ptReal) protocol implementa
|
|
|
141
146
|
|
|
142
147
|
- Group-Fan-Out-Pfad (Mitglieder-Steuerung beim Schalten der Gruppe) ist jetzt eine eigene Klasse mit Host-Interface — `main.ts` nochmal kleiner. Verhalten identisch.
|
|
143
148
|
|
|
144
|
-
### 2.4.0 (2026-05-04)
|
|
145
|
-
|
|
146
|
-
- Lokaler Snapshot-Manager (Save/Restore/Delete) ist jetzt eine eigene Klasse mit Host-Interface — `main.ts` ist kleiner und der Snapshot-Pfad ist isoliert testbar. Verhalten identisch.
|
|
147
|
-
|
|
148
149
|
Older entries are in [CHANGELOG_OLD.md](CHANGELOG_OLD.md).
|
|
149
150
|
|
|
150
151
|
## Support
|
|
@@ -183,6 +183,10 @@ class CommandRouter {
|
|
|
183
183
|
await this.sendCloudCommand(device, command, value);
|
|
184
184
|
return;
|
|
185
185
|
}
|
|
186
|
+
if (device.channels.cloud && !this.cloudClient) {
|
|
187
|
+
this.log.debug(`Command for ${device.name} dropped: Cloud client not ready yet`);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
186
190
|
this.log.warn(`No channel available for ${device.name} (${device.sku})`);
|
|
187
191
|
}
|
|
188
192
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/command-router.ts"],
|
|
4
|
-
"sourcesContent": ["import { hexToRgb, logDedup, type ErrorCategory, type GoveeDevice, type TimerAdapter } from \"./types\";\nimport type { GoveeCloudClient } from \"./govee-cloud-client\";\nimport type { GoveeLanClient } from \"./govee-lan-client\";\nimport { applySceneSpeed } from \"./govee-lan-client\";\nimport type { RateLimiter } from \"./rate-limiter\";\n\n/**\n * Delay between switching the device into static-color mode and sending the\n * follow-up segment commands. Empirically the firmware needs ~150 ms for the\n * mode flip; shorter delays leave the device still in scene/music mode and the\n * subsequent segment writes are silently dropped.\n */\nconst FORCE_COLOR_MODE_SETTLE_MS = 150;\n\n/**\n * Command router \u2014 routes device commands through the fastest available\n * channel: LAN \u2192 Cloud.\n */\nexport class CommandRouter {\n private readonly log: ioBroker.Logger;\n private readonly timers: TimerAdapter;\n private lanClient: GoveeLanClient | null = null;\n private cloudClient: GoveeCloudClient | null = null;\n private rateLimiter: RateLimiter | null = null;\n /**\n * Letzte Fehler-Kategorie pro Cloud-Fallback-Fail \u2014 verhindert\n * log-Spam wenn das gleiche Symptom mehrfach kommt.\n */\n private lastCloudFallbackError: ErrorCategory | null = null;\n /** Dedup-Tracker f\u00FCr \u201Ekein Cloud-channel\"-Warns (M20). */\n private lastNoChannelCategory: ErrorCategory | null = null;\n\n /** Callback for batch segment state sync */\n onSegmentBatchUpdate?: (\n device: GoveeDevice,\n batch: { segments: number[]; color?: number; brightness?: number },\n ) => void;\n\n /**\n * @param log ioBroker logger\n * @param timers Adapter timer wrapper \u2014 routed through `this.setTimeout` so\n * pending color-mode delays get cleared on onUnload.\n */\n constructor(log: ioBroker.Logger, timers: TimerAdapter) {\n this.log = log;\n this.timers = timers;\n }\n\n /**\n * Register the LAN client\n *\n * @param client LAN UDP client instance\n */\n setLanClient(client: GoveeLanClient): void {\n this.lanClient = 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 }\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.rateLimiter = limiter;\n }\n\n /**\n * Execute a function through the rate limiter if available, or directly.\n *\n * @param fn Async function to execute\n * @param priority Queue priority (0 = highest)\n */\n async executeRateLimited(fn: () => Promise<void>, priority = 0): Promise<void> {\n if (this.rateLimiter) {\n await this.rateLimiter.tryExecute(fn, priority);\n } else {\n await fn();\n }\n }\n\n /**\n * Force the device into static-color mode before sending segment_color_setting\n * ptReal packets. Without this, the device silently ignores segment-level\n * overrides while it's in Scene/Gradient/Music mode \u2014 the classic \"I set\n * segment 5 red and nothing happened\" symptom. Sends a `colorwc` command with\n * the device's last-known colorRgb (so the strip doesn't visibly change if it\n * was already in color mode), then waits 150 ms so the firmware can switch.\n *\n * As a bonus: once the device is in color mode, subsequent segment commands\n * trigger AA A5 MQTT pushes \u2014 so the adapter learns the real segmentCount\n * automatically the first time the user touches segment controls.\n *\n * @param device Target device\n */\n private async forceColorMode(device: GoveeDevice): Promise<void> {\n if (!device.lanIp || !this.lanClient) {\n return;\n }\n const current = typeof device.state.colorRgb === \"string\" ? device.state.colorRgb : null;\n const { r, g, b } = current ? hexToRgb(current) : { r: 255, g: 255, b: 255 };\n this.lanClient.setColor(device.lanIp, r, g, b);\n // Delay routed through the adapter's timer wrapper so it gets cancelled\n // if the adapter unloads mid-delay. Native setTimeout would leave a\n // pending handle that fires into a half-torn-down adapter.\n await new Promise<void>(resolve => this.timers.setTimeout(() => resolve(), FORCE_COLOR_MODE_SETTLE_MS));\n }\n\n /**\n * Send a command to a device \u2014 routes through LAN \u2192 Cloud.\n * MQTT is status-push only and never used for commands.\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 // Segment color: LAN ptReal first, Cloud fallback\n if (command.startsWith(\"segmentColor:\")) {\n const segIdx = parseInt(command.split(\":\")[1], 10);\n if (isNaN(segIdx) || segIdx < 0) {\n return;\n }\n if (device.lanIp && this.lanClient) {\n await this.forceColorMode(device);\n const { r, g, b } = hexToRgb(value as string);\n this.lanClient.setSegmentColor(device.lanIp, r, g, b, [segIdx]);\n return;\n }\n if (device.channels.cloud && this.cloudClient) {\n await this.sendCloudCommand(device, command, value);\n return;\n }\n return;\n }\n\n // Segment batch: LAN ptReal first (multi-segment bitmask), Cloud fallback.\n // Accepts either a string in the user-batch syntax (\"0-5:#ff0000:20\") or a\n // pre-parsed object { segments, color?, brightness? } from internal callers\n // like the detection wizard (avoids a string\u2192parse round-trip).\n if (command === \"segmentBatch\") {\n const parsed = typeof value === \"string\" ? this.parseSegmentBatch(device, value) : this.coerceParsedBatch(value);\n if (parsed) {\n this.onSegmentBatchUpdate?.(device, parsed);\n }\n if (device.lanIp && this.lanClient && parsed) {\n await this.forceColorMode(device);\n if (parsed.color !== undefined) {\n const r = (parsed.color >> 16) & 0xff;\n const g = (parsed.color >> 8) & 0xff;\n const b = parsed.color & 0xff;\n this.lanClient.setSegmentColor(device.lanIp, r, g, b, parsed.segments);\n }\n if (parsed.brightness !== undefined) {\n this.lanClient.setSegmentBrightness(device.lanIp, parsed.brightness, parsed.segments);\n }\n return;\n }\n if (device.channels.cloud && this.cloudClient && parsed) {\n await this.sendSegmentBatchParsed(device, typeof value === \"string\" ? value : \"\", parsed);\n return;\n }\n return;\n }\n\n // Segment brightness: LAN ptReal first, Cloud fallback\n if (command.startsWith(\"segmentBrightness:\")) {\n const segIdx = parseInt(command.split(\":\")[1], 10);\n if (isNaN(segIdx) || segIdx < 0) {\n return;\n }\n if (device.lanIp && this.lanClient) {\n await this.forceColorMode(device);\n this.lanClient.setSegmentBrightness(device.lanIp, value as number, [segIdx]);\n return;\n }\n if (device.channels.cloud && this.cloudClient) {\n await this.sendCloudCommand(device, command, value);\n return;\n }\n return;\n }\n\n // Priority 1: LAN\n if (device.lanIp && this.lanClient) {\n this.sendLanCommand(device, command, value);\n return;\n }\n\n // Priority 2: Cloud (rate-limited)\n if (device.channels.cloud && this.cloudClient) {\n await this.sendCloudCommand(device, command, value);\n return;\n }\n\n this.log.warn(`No channel available for ${device.name} (${device.sku})`);\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 if (!this.cloudClient || !device.channels.cloud) {\n this.log.debug(`Cloud not available for generic command on ${device.name}`);\n return;\n }\n\n const shortType = capabilityType.replace(\"devices.capabilities.\", \"\");\n let cloudValue: unknown = value;\n\n if (shortType === \"toggle\") {\n cloudValue = value ? 1 : 0;\n }\n\n const execute = async (): Promise<void> => {\n await this.cloudClient!.controlDevice(\n device.sku,\n device.deviceId,\n capabilityType,\n capabilityInstance,\n cloudValue,\n );\n };\n\n await this.executeRateLimited(execute);\n }\n\n /**\n * Send a batch segment command with pre-parsed data.\n *\n * @param device Target device\n * @param commandStr Original command string (for error messages)\n * @param parsed Pre-parsed batch data (null = invalid command)\n */\n private async sendSegmentBatchParsed(\n device: GoveeDevice,\n commandStr: string,\n parsed: { segments: number[]; color?: number; brightness?: number } | null,\n ): Promise<void> {\n if (!this.cloudClient) {\n return;\n }\n\n if (!parsed) {\n this.log.warn(`Invalid segment command \"${commandStr}\" for ${device.name}`);\n return;\n }\n\n const cap = this.findCapabilityForCommand(device, \"segmentColor:0\");\n if (!cap) {\n this.log.debug(`No segment capability for ${device.name}`);\n return;\n }\n\n if (parsed.color !== undefined) {\n const execute = async (): Promise<void> => {\n await this.cloudClient!.controlDevice(device.sku, device.deviceId, cap.type, cap.instance, {\n segment: parsed.segments,\n rgb: parsed.color,\n });\n };\n await this.executeRateLimited(execute);\n }\n\n if (parsed.brightness !== undefined) {\n const caps = Array.isArray(device.capabilities) ? device.capabilities : [];\n const brightCap = caps.find(\n c =>\n c &&\n typeof c.type === \"string\" &&\n typeof c.instance === \"string\" &&\n c.type.includes(\"segment_color_setting\") &&\n c.instance.toLowerCase().includes(\"brightness\"),\n );\n const execute = async (): Promise<void> => {\n await this.cloudClient!.controlDevice(\n device.sku,\n device.deviceId,\n (brightCap ?? cap).type,\n (brightCap ?? cap).instance,\n { segment: parsed.segments, brightness: parsed.brightness },\n );\n };\n await this.executeRateLimited(execute);\n }\n\n // Update individual segment states to stay in sync\n this.onSegmentBatchUpdate?.(device, parsed);\n }\n\n /**\n * Parse batch segment command string.\n *\n * @param device Target device (for segment count)\n * @param cmd Command string (e.g. \"1-5:#ff0000:20\")\n */\n parseSegmentBatch(\n device: GoveeDevice,\n cmd: string,\n ): {\n segments: number[];\n color?: number;\n brightness?: number;\n } | null {\n // Defensive guard \u2014 non-string input (e.g. from internal caller passing\n // an already-parsed object) would crash cmd.split(). Treat as no-op.\n if (typeof cmd !== \"string\") {\n return null;\n }\n const parts = cmd.split(\":\");\n if (parts.length < 1 || !parts[0]) {\n return null;\n }\n\n // Effective physical segments: honor manual override for cut strips\n const validIndices =\n device.manualMode && Array.isArray(device.manualSegments) && device.manualSegments.length > 0\n ? new Set(device.manualSegments)\n : null;\n const segCount = device.segmentCount ?? 0;\n const isValid = (i: number): boolean => (validIndices ? validIndices.has(i) : i >= 0 && i < segCount);\n\n // Parse segment indices\n const segStr = parts[0].trim();\n let segments: number[];\n\n if (segStr === \"all\") {\n // \"all\" expands to valid physical segments only (skip cut ones)\n segments = validIndices\n ? Array.from(validIndices).sort((a, b) => a - b)\n : Array.from({ length: segCount }, (_, i) => i);\n } else {\n segments = [];\n for (const part of segStr.split(\",\")) {\n const rangeMatch = /^(\\d+)-(\\d+)$/.exec(part.trim());\n if (rangeMatch) {\n const start = parseInt(rangeMatch[1], 10);\n const end = parseInt(rangeMatch[2], 10);\n for (let i = start; i <= end; i++) {\n if (isValid(i)) {\n segments.push(i);\n }\n }\n } else {\n const idx = parseInt(part.trim(), 10);\n if (!isNaN(idx) && isValid(idx)) {\n segments.push(idx);\n }\n }\n }\n }\n\n if (segments.length === 0) {\n return null;\n }\n\n // Parse color (#RRGGBB \u2192 packed int)\n let color: number | undefined;\n if (parts.length >= 2 && parts[1]) {\n const colorStr = parts[1].trim();\n if (/^#?[0-9a-fA-F]{6}$/.test(colorStr)) {\n color = parseInt(colorStr.replace(\"#\", \"\"), 16);\n }\n }\n\n // Parse brightness (0-100)\n let brightness: number | undefined;\n if (parts.length >= 3 && parts[2]) {\n const bri = parseInt(parts[2].trim(), 10);\n if (!isNaN(bri) && bri >= 0 && bri <= 100) {\n brightness = bri;\n }\n }\n\n if (color === undefined && brightness === undefined) {\n return null;\n }\n\n return { segments, color, brightness };\n }\n\n /**\n * Coerce a pre-parsed batch object (from internal callers) to the canonical\n * shape. Returns null if the input is not a valid {segments, ...} object.\n *\n * @param value Candidate object\n */\n private coerceParsedBatch(value: unknown): {\n segments: number[];\n color?: number;\n brightness?: number;\n } | null {\n if (!value || typeof value !== \"object\") {\n return null;\n }\n const v = value as Record<string, unknown>;\n if (!Array.isArray(v.segments) || v.segments.length === 0) {\n return null;\n }\n const segments = v.segments.filter(n => typeof n === \"number\" && Number.isFinite(n) && n >= 0) as number[];\n if (segments.length === 0) {\n return null;\n }\n const color = typeof v.color === \"number\" && Number.isFinite(v.color) ? v.color & 0xffffff : undefined;\n const brightness =\n typeof v.brightness === \"number\" && Number.isFinite(v.brightness)\n ? Math.max(0, Math.min(100, Math.round(v.brightness)))\n : undefined;\n if (color === undefined && brightness === undefined) {\n return null;\n }\n return { segments, color, brightness };\n }\n\n /**\n * Convert adapter value to Cloud API value\n *\n * @param device Target device (for scene/snapshot lookup)\n * @param command Command type\n * @param value Adapter-side value to convert\n */\n toCloudValue(device: GoveeDevice, command: string, value: unknown): unknown {\n switch (command) {\n case \"power\":\n return value ? 1 : 0;\n case \"brightness\":\n return value;\n case \"colorRgb\": {\n const { r, g, b } = hexToRgb(value as string);\n return (r << 16) | (g << 8) | b;\n }\n case \"colorTemperature\":\n return value;\n case \"scene\":\n return value;\n case \"lightScene\": {\n // Value is the dropdown index (string) \u2014 resolve to scene activation payload\n const idx = parseInt(String(value), 10);\n if (isNaN(idx) || idx < 1 || idx > device.scenes.length) {\n this.log.warn(`${device.sku}: invalid light scene index ${String(value)} for cloud`);\n return value;\n }\n return device.scenes[idx - 1].value;\n }\n case \"diyScene\": {\n const idx = parseInt(String(value), 10);\n if (isNaN(idx) || idx < 1 || idx > device.diyScenes.length) {\n this.log.warn(`${device.sku}: invalid DIY scene index ${String(value)} for cloud`);\n return value;\n }\n return device.diyScenes[idx - 1].value;\n }\n case \"snapshot\": {\n const idx = parseInt(String(value), 10);\n if (isNaN(idx) || idx < 1 || idx > device.snapshots.length) {\n this.log.warn(`${device.sku}: invalid snapshot index ${String(value)} for cloud`);\n return value;\n }\n return device.snapshots[idx - 1].value;\n }\n default:\n if (command.startsWith(\"segmentColor:\")) {\n const segIdx = parseInt(command.split(\":\")[1], 10);\n if (isNaN(segIdx) || segIdx < 0) {\n this.log.warn(`${device.sku}: invalid segment index in ${command}`);\n return value;\n }\n const { r, g, b } = hexToRgb(value as string);\n return { segment: [segIdx], rgb: (r << 16) | (g << 8) | b };\n }\n if (command.startsWith(\"segmentBrightness:\")) {\n const segIdx = parseInt(command.split(\":\")[1], 10);\n if (isNaN(segIdx) || segIdx < 0) {\n this.log.warn(`${device.sku}: invalid segment index in ${command}`);\n return value;\n }\n return { segment: [segIdx], brightness: value };\n }\n return value;\n }\n }\n\n /**\n * Find capability matching a command name\n *\n * @param device Target device\n * @param command Command type to find capability for\n */\n findCapabilityForCommand(device: GoveeDevice, command: string): { type: string; instance: string } | undefined {\n const caps = Array.isArray(device.capabilities) ? device.capabilities : [];\n for (const cap of caps) {\n if (!cap || typeof cap.type !== \"string\" || typeof cap.instance !== \"string\") {\n continue;\n }\n const shortType = cap.type.replace(\"devices.capabilities.\", \"\");\n if (command === \"power\" && shortType === \"on_off\") {\n return cap;\n }\n if (command === \"brightness\" && shortType === \"range\" && cap.instance.toLowerCase().includes(\"brightness\")) {\n return cap;\n }\n if (command === \"colorRgb\" && shortType === \"color_setting\" && cap.instance === \"colorRgb\") {\n return cap;\n }\n if (command === \"colorTemperature\" && shortType === \"color_setting\" && cap.instance.includes(\"colorTem\")) {\n return cap;\n }\n if (command === \"scene\" && shortType === \"mode\" && cap.instance === \"presetScene\") {\n return cap;\n }\n if (command === \"lightScene\" && shortType === \"dynamic_scene\" && cap.instance === \"lightScene\") {\n return cap;\n }\n if (command === \"diyScene\" && shortType === \"dynamic_scene\" && cap.instance === \"diyScene\") {\n return cap;\n }\n if (command === \"snapshot\" && shortType === \"dynamic_scene\" && cap.instance === \"snapshot\") {\n return cap;\n }\n if (\n command.startsWith(\"segmentColor:\") &&\n shortType === \"segment_color_setting\" &&\n !cap.instance.toLowerCase().includes(\"brightness\")\n ) {\n return cap;\n }\n if (\n command.startsWith(\"segmentBrightness:\") &&\n shortType === \"segment_color_setting\" &&\n cap.instance.toLowerCase().includes(\"brightness\")\n ) {\n return cap;\n }\n }\n return undefined;\n }\n\n /**\n * Send command via LAN UDP\n *\n * @param device Target device\n * @param command Command type\n * @param value Command value\n */\n private sendLanCommand(device: GoveeDevice, command: string, value: unknown): void {\n if (!device.lanIp || !this.lanClient) {\n return;\n }\n\n switch (command) {\n case \"power\":\n this.lanClient.setPower(device.lanIp, value as boolean);\n break;\n case \"brightness\":\n this.lanClient.setBrightness(device.lanIp, value as number);\n break;\n case \"colorRgb\": {\n const { r, g, b } = hexToRgb(value as string);\n this.lanClient.setColor(device.lanIp, r, g, b);\n break;\n }\n case \"colorTemperature\":\n this.lanClient.setColorTemperature(device.lanIp, value as number);\n break;\n case \"gradientToggle\":\n this.lanClient.setGradient(device.lanIp, value as boolean);\n break;\n case \"diyScene\": {\n // Try ptReal BLE-over-LAN if DIY scene is in library\n const diyIdx = parseInt(String(value), 10);\n if (isNaN(diyIdx) || diyIdx < 1 || diyIdx > device.diyScenes.length) {\n this.log.warn(`${device.sku}: invalid DIY scene index ${String(value)}`);\n return;\n }\n const diyScene = device.diyScenes[diyIdx - 1];\n if (diyScene) {\n const diyLib = device.diyLibrary.find(d => d.name === diyScene.name);\n if (diyLib) {\n this.log.debug(`ptReal DIY: ${diyScene.name} \u2192 code=${diyLib.diyCode}`);\n this.lanClient.setDiyScene(device.lanIp, diyLib.scenceParam ?? \"\");\n return;\n }\n }\n // No library match \u2014 fall through to Cloud\n this.sendCloudCommand(device, command, value).catch(e => {\n this.lastCloudFallbackError = logDedup(\n this.log,\n this.lastCloudFallbackError,\n `Cloud fallback for ${device.name}/${command}`,\n e,\n );\n });\n break;\n }\n case \"lightScene\": {\n // Try ptReal BLE-over-LAN if scene is in scene library\n const idx = parseInt(String(value), 10);\n if (isNaN(idx) || idx < 1 || idx > device.scenes.length) {\n this.log.warn(`${device.sku}: invalid light scene index ${String(value)}`);\n return;\n }\n const scene = device.scenes[idx - 1];\n if (scene) {\n // Match by exact name first, then by base name (strip -A/-B suffix)\n const baseName = scene.name.replace(/-[A-Z]$/, \"\");\n const libEntry =\n device.sceneLibrary.find(s => s.name === scene.name) ?? device.sceneLibrary.find(s => s.name === baseName);\n if (libEntry) {\n const baseParam = libEntry.scenceParam ?? \"\";\n // Devices without segment hardware (e.g. H70B3 Curtain Lights)\n // can't parse the A3-framed multi-packet ptReal protocol that\n // `scenceParam` carries \u2014 those packets describe per-segment\n // animation data. On such devices the packets are silently\n // dropped: the scene activation never happens locally. Fall\n // straight to Cloud activation for them so the scene still\n // works. Simple scenes without scenceParam (older presets like\n // \"Valentine's Day\") still take the ptReal path \u2014 the single\n // activation packet is understood by every Govee light.\n const hasSegments = typeof device.segmentCount === \"number\" && device.segmentCount > 0;\n if (!hasSegments && baseParam.length > 0) {\n this.log.debug(\n `ptReal scene ${scene.name} skipped \u2014 ${device.sku} has no segments, falling through to Cloud`,\n );\n this.sendCloudCommand(device, command, value).catch(e => {\n this.lastCloudFallbackError = logDedup(\n this.log,\n this.lastCloudFallbackError,\n `Cloud fallback for ${device.name}/${command}`,\n e,\n );\n });\n return;\n }\n let param = baseParam;\n if (\n device.sceneSpeed !== undefined &&\n device.sceneSpeed > 0 &&\n libEntry.speedInfo?.supSpeed &&\n libEntry.speedInfo.config\n ) {\n param = applySceneSpeed(param, device.sceneSpeed, libEntry.speedInfo.config);\n }\n this.log.debug(`ptReal: ${scene.name} \u2192 code=${libEntry.sceneCode}`);\n this.lanClient.setScene(device.lanIp, libEntry.sceneCode, param);\n return;\n }\n }\n // Scene not in library \u2014 fall through to Cloud\n this.sendCloudCommand(device, command, value).catch(e => {\n this.lastCloudFallbackError = logDedup(\n this.log,\n this.lastCloudFallbackError,\n `Cloud fallback for ${device.name}/${command}`,\n e,\n );\n });\n break;\n }\n case \"snapshot\": {\n const idx = parseInt(String(value), 10);\n if (isNaN(idx) || idx < 1 || idx > device.snapshots.length) {\n this.log.warn(`${device.sku}: invalid snapshot index ${String(value)}`);\n return;\n }\n const cmdGroups = device.snapshotBleCmds?.[idx - 1];\n if (cmdGroups && cmdGroups.length > 0) {\n const allPackets = cmdGroups.flat();\n if (allPackets.length > 0) {\n this.log.debug(`ptReal Snapshot: ${device.snapshots[idx - 1].name} \u2192 ${allPackets.length} packets`);\n this.lanClient.sendPtReal(device.lanIp, allPackets);\n return;\n }\n }\n // No BLE data \u2014 fall through to Cloud\n this.sendCloudCommand(device, command, value).catch(e => {\n this.lastCloudFallbackError = logDedup(\n this.log,\n this.lastCloudFallbackError,\n `Cloud fallback for ${device.name}/${command}`,\n e,\n );\n });\n break;\n }\n default:\n // LAN doesn't support this command \u2014 fall through to Cloud\n this.sendCloudCommand(device, command, value).catch(e => {\n this.lastCloudFallbackError = logDedup(\n this.log,\n this.lastCloudFallbackError,\n `Cloud fallback for ${device.name}/${command}`,\n e,\n );\n });\n }\n }\n\n /**\n * Send command via Cloud API (rate-limited)\n *\n * @param device Target device\n * @param command Command type\n * @param value Command value\n */\n private async sendCloudCommand(device: GoveeDevice, command: string, value: unknown): Promise<void> {\n // M19 \u2014 Closure capture: lokale Variable nach Guard. Verhindert Race\n // wenn `setCloudClient(null)` zwischen Guard-Check und executeRateLimited\n // l\u00E4uft (z.B. Adapter-Stop mid-await).\n const cloudClient = this.cloudClient;\n if (!cloudClient) {\n return;\n }\n\n // Find the matching capability\n const cap = this.findCapabilityForCommand(device, command);\n if (!cap) {\n // M20 \u2014 dedup-warn statt nur debug. User klickt einen State, kein\n // Channel-Match \u2192 Fehlersuche braucht das Erstauftreten als warn.\n this.lastNoChannelCategory = logDedup(\n this.log,\n this.lastNoChannelCategory,\n `No channel for ${device.name}/${command}`,\n new Error(\"no matching capability\"),\n );\n return;\n }\n\n const cloudValue = this.toCloudValue(device, command, value);\n\n const execute = async (): Promise<void> => {\n await cloudClient.controlDevice(device.sku, device.deviceId, cap.type, cap.instance, cloudValue);\n };\n\n await this.executeRateLimited(execute);\n }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { hexToRgb, logDedup, type ErrorCategory, type GoveeDevice, type TimerAdapter } from \"./types\";\nimport type { GoveeCloudClient } from \"./govee-cloud-client\";\nimport type { GoveeLanClient } from \"./govee-lan-client\";\nimport { applySceneSpeed } from \"./govee-lan-client\";\nimport type { RateLimiter } from \"./rate-limiter\";\n\n/**\n * Delay between switching the device into static-color mode and sending the\n * follow-up segment commands. Empirically the firmware needs ~150 ms for the\n * mode flip; shorter delays leave the device still in scene/music mode and the\n * subsequent segment writes are silently dropped.\n */\nconst FORCE_COLOR_MODE_SETTLE_MS = 150;\n\n/**\n * Command router \u2014 routes device commands through the fastest available\n * channel: LAN \u2192 Cloud.\n */\nexport class CommandRouter {\n private readonly log: ioBroker.Logger;\n private readonly timers: TimerAdapter;\n private lanClient: GoveeLanClient | null = null;\n private cloudClient: GoveeCloudClient | null = null;\n private rateLimiter: RateLimiter | null = null;\n /**\n * Letzte Fehler-Kategorie pro Cloud-Fallback-Fail \u2014 verhindert\n * log-Spam wenn das gleiche Symptom mehrfach kommt.\n */\n private lastCloudFallbackError: ErrorCategory | null = null;\n /** Dedup-Tracker f\u00FCr \u201Ekein Cloud-channel\"-Warns (M20). */\n private lastNoChannelCategory: ErrorCategory | null = null;\n\n /** Callback for batch segment state sync */\n onSegmentBatchUpdate?: (\n device: GoveeDevice,\n batch: { segments: number[]; color?: number; brightness?: number },\n ) => void;\n\n /**\n * @param log ioBroker logger\n * @param timers Adapter timer wrapper \u2014 routed through `this.setTimeout` so\n * pending color-mode delays get cleared on onUnload.\n */\n constructor(log: ioBroker.Logger, timers: TimerAdapter) {\n this.log = log;\n this.timers = timers;\n }\n\n /**\n * Register the LAN client\n *\n * @param client LAN UDP client instance\n */\n setLanClient(client: GoveeLanClient): void {\n this.lanClient = 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 }\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.rateLimiter = limiter;\n }\n\n /**\n * Execute a function through the rate limiter if available, or directly.\n *\n * @param fn Async function to execute\n * @param priority Queue priority (0 = highest)\n */\n async executeRateLimited(fn: () => Promise<void>, priority = 0): Promise<void> {\n if (this.rateLimiter) {\n await this.rateLimiter.tryExecute(fn, priority);\n } else {\n await fn();\n }\n }\n\n /**\n * Force the device into static-color mode before sending segment_color_setting\n * ptReal packets. Without this, the device silently ignores segment-level\n * overrides while it's in Scene/Gradient/Music mode \u2014 the classic \"I set\n * segment 5 red and nothing happened\" symptom. Sends a `colorwc` command with\n * the device's last-known colorRgb (so the strip doesn't visibly change if it\n * was already in color mode), then waits 150 ms so the firmware can switch.\n *\n * As a bonus: once the device is in color mode, subsequent segment commands\n * trigger AA A5 MQTT pushes \u2014 so the adapter learns the real segmentCount\n * automatically the first time the user touches segment controls.\n *\n * @param device Target device\n */\n private async forceColorMode(device: GoveeDevice): Promise<void> {\n if (!device.lanIp || !this.lanClient) {\n return;\n }\n const current = typeof device.state.colorRgb === \"string\" ? device.state.colorRgb : null;\n const { r, g, b } = current ? hexToRgb(current) : { r: 255, g: 255, b: 255 };\n this.lanClient.setColor(device.lanIp, r, g, b);\n // Delay routed through the adapter's timer wrapper so it gets cancelled\n // if the adapter unloads mid-delay. Native setTimeout would leave a\n // pending handle that fires into a half-torn-down adapter.\n await new Promise<void>(resolve => this.timers.setTimeout(() => resolve(), FORCE_COLOR_MODE_SETTLE_MS));\n }\n\n /**\n * Send a command to a device \u2014 routes through LAN \u2192 Cloud.\n * MQTT is status-push only and never used for commands.\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 // Segment color: LAN ptReal first, Cloud fallback\n if (command.startsWith(\"segmentColor:\")) {\n const segIdx = parseInt(command.split(\":\")[1], 10);\n if (isNaN(segIdx) || segIdx < 0) {\n return;\n }\n if (device.lanIp && this.lanClient) {\n await this.forceColorMode(device);\n const { r, g, b } = hexToRgb(value as string);\n this.lanClient.setSegmentColor(device.lanIp, r, g, b, [segIdx]);\n return;\n }\n if (device.channels.cloud && this.cloudClient) {\n await this.sendCloudCommand(device, command, value);\n return;\n }\n return;\n }\n\n // Segment batch: LAN ptReal first (multi-segment bitmask), Cloud fallback.\n // Accepts either a string in the user-batch syntax (\"0-5:#ff0000:20\") or a\n // pre-parsed object { segments, color?, brightness? } from internal callers\n // like the detection wizard (avoids a string\u2192parse round-trip).\n if (command === \"segmentBatch\") {\n const parsed = typeof value === \"string\" ? this.parseSegmentBatch(device, value) : this.coerceParsedBatch(value);\n if (parsed) {\n this.onSegmentBatchUpdate?.(device, parsed);\n }\n if (device.lanIp && this.lanClient && parsed) {\n await this.forceColorMode(device);\n if (parsed.color !== undefined) {\n const r = (parsed.color >> 16) & 0xff;\n const g = (parsed.color >> 8) & 0xff;\n const b = parsed.color & 0xff;\n this.lanClient.setSegmentColor(device.lanIp, r, g, b, parsed.segments);\n }\n if (parsed.brightness !== undefined) {\n this.lanClient.setSegmentBrightness(device.lanIp, parsed.brightness, parsed.segments);\n }\n return;\n }\n if (device.channels.cloud && this.cloudClient && parsed) {\n await this.sendSegmentBatchParsed(device, typeof value === \"string\" ? value : \"\", parsed);\n return;\n }\n return;\n }\n\n // Segment brightness: LAN ptReal first, Cloud fallback\n if (command.startsWith(\"segmentBrightness:\")) {\n const segIdx = parseInt(command.split(\":\")[1], 10);\n if (isNaN(segIdx) || segIdx < 0) {\n return;\n }\n if (device.lanIp && this.lanClient) {\n await this.forceColorMode(device);\n this.lanClient.setSegmentBrightness(device.lanIp, value as number, [segIdx]);\n return;\n }\n if (device.channels.cloud && this.cloudClient) {\n await this.sendCloudCommand(device, command, value);\n return;\n }\n return;\n }\n\n // Priority 1: LAN\n if (device.lanIp && this.lanClient) {\n this.sendLanCommand(device, command, value);\n return;\n }\n\n // Priority 2: Cloud (rate-limited)\n if (device.channels.cloud && this.cloudClient) {\n await this.sendCloudCommand(device, command, value);\n return;\n }\n\n // Cloud-f\u00E4hig aber Client noch nicht ready (Init-Phase nach Restart):\n // typischer Race-Fall, wenn der User sofort einen Befehl schickt w\u00E4hrend\n // der Adapter Cloud-Login + IoT-Key noch erledigt. Kein WARN, nur debug \u2014\n // der Befehl ist verloren, aber der User sieht eh wenige Sekunden sp\u00E4ter\n // dass alles l\u00E4uft. WARN war hier ein false alarm.\n if (device.channels.cloud && !this.cloudClient) {\n this.log.debug(`Command for ${device.name} dropped: Cloud client not ready yet`);\n return;\n }\n\n this.log.warn(`No channel available for ${device.name} (${device.sku})`);\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 if (!this.cloudClient || !device.channels.cloud) {\n this.log.debug(`Cloud not available for generic command on ${device.name}`);\n return;\n }\n\n const shortType = capabilityType.replace(\"devices.capabilities.\", \"\");\n let cloudValue: unknown = value;\n\n if (shortType === \"toggle\") {\n cloudValue = value ? 1 : 0;\n }\n\n const execute = async (): Promise<void> => {\n await this.cloudClient!.controlDevice(\n device.sku,\n device.deviceId,\n capabilityType,\n capabilityInstance,\n cloudValue,\n );\n };\n\n await this.executeRateLimited(execute);\n }\n\n /**\n * Send a batch segment command with pre-parsed data.\n *\n * @param device Target device\n * @param commandStr Original command string (for error messages)\n * @param parsed Pre-parsed batch data (null = invalid command)\n */\n private async sendSegmentBatchParsed(\n device: GoveeDevice,\n commandStr: string,\n parsed: { segments: number[]; color?: number; brightness?: number } | null,\n ): Promise<void> {\n if (!this.cloudClient) {\n return;\n }\n\n if (!parsed) {\n this.log.warn(`Invalid segment command \"${commandStr}\" for ${device.name}`);\n return;\n }\n\n const cap = this.findCapabilityForCommand(device, \"segmentColor:0\");\n if (!cap) {\n this.log.debug(`No segment capability for ${device.name}`);\n return;\n }\n\n if (parsed.color !== undefined) {\n const execute = async (): Promise<void> => {\n await this.cloudClient!.controlDevice(device.sku, device.deviceId, cap.type, cap.instance, {\n segment: parsed.segments,\n rgb: parsed.color,\n });\n };\n await this.executeRateLimited(execute);\n }\n\n if (parsed.brightness !== undefined) {\n const caps = Array.isArray(device.capabilities) ? device.capabilities : [];\n const brightCap = caps.find(\n c =>\n c &&\n typeof c.type === \"string\" &&\n typeof c.instance === \"string\" &&\n c.type.includes(\"segment_color_setting\") &&\n c.instance.toLowerCase().includes(\"brightness\"),\n );\n const execute = async (): Promise<void> => {\n await this.cloudClient!.controlDevice(\n device.sku,\n device.deviceId,\n (brightCap ?? cap).type,\n (brightCap ?? cap).instance,\n { segment: parsed.segments, brightness: parsed.brightness },\n );\n };\n await this.executeRateLimited(execute);\n }\n\n // Update individual segment states to stay in sync\n this.onSegmentBatchUpdate?.(device, parsed);\n }\n\n /**\n * Parse batch segment command string.\n *\n * @param device Target device (for segment count)\n * @param cmd Command string (e.g. \"1-5:#ff0000:20\")\n */\n parseSegmentBatch(\n device: GoveeDevice,\n cmd: string,\n ): {\n segments: number[];\n color?: number;\n brightness?: number;\n } | null {\n // Defensive guard \u2014 non-string input (e.g. from internal caller passing\n // an already-parsed object) would crash cmd.split(). Treat as no-op.\n if (typeof cmd !== \"string\") {\n return null;\n }\n const parts = cmd.split(\":\");\n if (parts.length < 1 || !parts[0]) {\n return null;\n }\n\n // Effective physical segments: honor manual override for cut strips\n const validIndices =\n device.manualMode && Array.isArray(device.manualSegments) && device.manualSegments.length > 0\n ? new Set(device.manualSegments)\n : null;\n const segCount = device.segmentCount ?? 0;\n const isValid = (i: number): boolean => (validIndices ? validIndices.has(i) : i >= 0 && i < segCount);\n\n // Parse segment indices\n const segStr = parts[0].trim();\n let segments: number[];\n\n if (segStr === \"all\") {\n // \"all\" expands to valid physical segments only (skip cut ones)\n segments = validIndices\n ? Array.from(validIndices).sort((a, b) => a - b)\n : Array.from({ length: segCount }, (_, i) => i);\n } else {\n segments = [];\n for (const part of segStr.split(\",\")) {\n const rangeMatch = /^(\\d+)-(\\d+)$/.exec(part.trim());\n if (rangeMatch) {\n const start = parseInt(rangeMatch[1], 10);\n const end = parseInt(rangeMatch[2], 10);\n for (let i = start; i <= end; i++) {\n if (isValid(i)) {\n segments.push(i);\n }\n }\n } else {\n const idx = parseInt(part.trim(), 10);\n if (!isNaN(idx) && isValid(idx)) {\n segments.push(idx);\n }\n }\n }\n }\n\n if (segments.length === 0) {\n return null;\n }\n\n // Parse color (#RRGGBB \u2192 packed int)\n let color: number | undefined;\n if (parts.length >= 2 && parts[1]) {\n const colorStr = parts[1].trim();\n if (/^#?[0-9a-fA-F]{6}$/.test(colorStr)) {\n color = parseInt(colorStr.replace(\"#\", \"\"), 16);\n }\n }\n\n // Parse brightness (0-100)\n let brightness: number | undefined;\n if (parts.length >= 3 && parts[2]) {\n const bri = parseInt(parts[2].trim(), 10);\n if (!isNaN(bri) && bri >= 0 && bri <= 100) {\n brightness = bri;\n }\n }\n\n if (color === undefined && brightness === undefined) {\n return null;\n }\n\n return { segments, color, brightness };\n }\n\n /**\n * Coerce a pre-parsed batch object (from internal callers) to the canonical\n * shape. Returns null if the input is not a valid {segments, ...} object.\n *\n * @param value Candidate object\n */\n private coerceParsedBatch(value: unknown): {\n segments: number[];\n color?: number;\n brightness?: number;\n } | null {\n if (!value || typeof value !== \"object\") {\n return null;\n }\n const v = value as Record<string, unknown>;\n if (!Array.isArray(v.segments) || v.segments.length === 0) {\n return null;\n }\n const segments = v.segments.filter(n => typeof n === \"number\" && Number.isFinite(n) && n >= 0) as number[];\n if (segments.length === 0) {\n return null;\n }\n const color = typeof v.color === \"number\" && Number.isFinite(v.color) ? v.color & 0xffffff : undefined;\n const brightness =\n typeof v.brightness === \"number\" && Number.isFinite(v.brightness)\n ? Math.max(0, Math.min(100, Math.round(v.brightness)))\n : undefined;\n if (color === undefined && brightness === undefined) {\n return null;\n }\n return { segments, color, brightness };\n }\n\n /**\n * Convert adapter value to Cloud API value\n *\n * @param device Target device (for scene/snapshot lookup)\n * @param command Command type\n * @param value Adapter-side value to convert\n */\n toCloudValue(device: GoveeDevice, command: string, value: unknown): unknown {\n switch (command) {\n case \"power\":\n return value ? 1 : 0;\n case \"brightness\":\n return value;\n case \"colorRgb\": {\n const { r, g, b } = hexToRgb(value as string);\n return (r << 16) | (g << 8) | b;\n }\n case \"colorTemperature\":\n return value;\n case \"scene\":\n return value;\n case \"lightScene\": {\n // Value is the dropdown index (string) \u2014 resolve to scene activation payload\n const idx = parseInt(String(value), 10);\n if (isNaN(idx) || idx < 1 || idx > device.scenes.length) {\n this.log.warn(`${device.sku}: invalid light scene index ${String(value)} for cloud`);\n return value;\n }\n return device.scenes[idx - 1].value;\n }\n case \"diyScene\": {\n const idx = parseInt(String(value), 10);\n if (isNaN(idx) || idx < 1 || idx > device.diyScenes.length) {\n this.log.warn(`${device.sku}: invalid DIY scene index ${String(value)} for cloud`);\n return value;\n }\n return device.diyScenes[idx - 1].value;\n }\n case \"snapshot\": {\n const idx = parseInt(String(value), 10);\n if (isNaN(idx) || idx < 1 || idx > device.snapshots.length) {\n this.log.warn(`${device.sku}: invalid snapshot index ${String(value)} for cloud`);\n return value;\n }\n return device.snapshots[idx - 1].value;\n }\n default:\n if (command.startsWith(\"segmentColor:\")) {\n const segIdx = parseInt(command.split(\":\")[1], 10);\n if (isNaN(segIdx) || segIdx < 0) {\n this.log.warn(`${device.sku}: invalid segment index in ${command}`);\n return value;\n }\n const { r, g, b } = hexToRgb(value as string);\n return { segment: [segIdx], rgb: (r << 16) | (g << 8) | b };\n }\n if (command.startsWith(\"segmentBrightness:\")) {\n const segIdx = parseInt(command.split(\":\")[1], 10);\n if (isNaN(segIdx) || segIdx < 0) {\n this.log.warn(`${device.sku}: invalid segment index in ${command}`);\n return value;\n }\n return { segment: [segIdx], brightness: value };\n }\n return value;\n }\n }\n\n /**\n * Find capability matching a command name\n *\n * @param device Target device\n * @param command Command type to find capability for\n */\n findCapabilityForCommand(device: GoveeDevice, command: string): { type: string; instance: string } | undefined {\n const caps = Array.isArray(device.capabilities) ? device.capabilities : [];\n for (const cap of caps) {\n if (!cap || typeof cap.type !== \"string\" || typeof cap.instance !== \"string\") {\n continue;\n }\n const shortType = cap.type.replace(\"devices.capabilities.\", \"\");\n if (command === \"power\" && shortType === \"on_off\") {\n return cap;\n }\n if (command === \"brightness\" && shortType === \"range\" && cap.instance.toLowerCase().includes(\"brightness\")) {\n return cap;\n }\n if (command === \"colorRgb\" && shortType === \"color_setting\" && cap.instance === \"colorRgb\") {\n return cap;\n }\n if (command === \"colorTemperature\" && shortType === \"color_setting\" && cap.instance.includes(\"colorTem\")) {\n return cap;\n }\n if (command === \"scene\" && shortType === \"mode\" && cap.instance === \"presetScene\") {\n return cap;\n }\n if (command === \"lightScene\" && shortType === \"dynamic_scene\" && cap.instance === \"lightScene\") {\n return cap;\n }\n if (command === \"diyScene\" && shortType === \"dynamic_scene\" && cap.instance === \"diyScene\") {\n return cap;\n }\n if (command === \"snapshot\" && shortType === \"dynamic_scene\" && cap.instance === \"snapshot\") {\n return cap;\n }\n if (\n command.startsWith(\"segmentColor:\") &&\n shortType === \"segment_color_setting\" &&\n !cap.instance.toLowerCase().includes(\"brightness\")\n ) {\n return cap;\n }\n if (\n command.startsWith(\"segmentBrightness:\") &&\n shortType === \"segment_color_setting\" &&\n cap.instance.toLowerCase().includes(\"brightness\")\n ) {\n return cap;\n }\n }\n return undefined;\n }\n\n /**\n * Send command via LAN UDP\n *\n * @param device Target device\n * @param command Command type\n * @param value Command value\n */\n private sendLanCommand(device: GoveeDevice, command: string, value: unknown): void {\n if (!device.lanIp || !this.lanClient) {\n return;\n }\n\n switch (command) {\n case \"power\":\n this.lanClient.setPower(device.lanIp, value as boolean);\n break;\n case \"brightness\":\n this.lanClient.setBrightness(device.lanIp, value as number);\n break;\n case \"colorRgb\": {\n const { r, g, b } = hexToRgb(value as string);\n this.lanClient.setColor(device.lanIp, r, g, b);\n break;\n }\n case \"colorTemperature\":\n this.lanClient.setColorTemperature(device.lanIp, value as number);\n break;\n case \"gradientToggle\":\n this.lanClient.setGradient(device.lanIp, value as boolean);\n break;\n case \"diyScene\": {\n // Try ptReal BLE-over-LAN if DIY scene is in library\n const diyIdx = parseInt(String(value), 10);\n if (isNaN(diyIdx) || diyIdx < 1 || diyIdx > device.diyScenes.length) {\n this.log.warn(`${device.sku}: invalid DIY scene index ${String(value)}`);\n return;\n }\n const diyScene = device.diyScenes[diyIdx - 1];\n if (diyScene) {\n const diyLib = device.diyLibrary.find(d => d.name === diyScene.name);\n if (diyLib) {\n this.log.debug(`ptReal DIY: ${diyScene.name} \u2192 code=${diyLib.diyCode}`);\n this.lanClient.setDiyScene(device.lanIp, diyLib.scenceParam ?? \"\");\n return;\n }\n }\n // No library match \u2014 fall through to Cloud\n this.sendCloudCommand(device, command, value).catch(e => {\n this.lastCloudFallbackError = logDedup(\n this.log,\n this.lastCloudFallbackError,\n `Cloud fallback for ${device.name}/${command}`,\n e,\n );\n });\n break;\n }\n case \"lightScene\": {\n // Try ptReal BLE-over-LAN if scene is in scene library\n const idx = parseInt(String(value), 10);\n if (isNaN(idx) || idx < 1 || idx > device.scenes.length) {\n this.log.warn(`${device.sku}: invalid light scene index ${String(value)}`);\n return;\n }\n const scene = device.scenes[idx - 1];\n if (scene) {\n // Match by exact name first, then by base name (strip -A/-B suffix)\n const baseName = scene.name.replace(/-[A-Z]$/, \"\");\n const libEntry =\n device.sceneLibrary.find(s => s.name === scene.name) ?? device.sceneLibrary.find(s => s.name === baseName);\n if (libEntry) {\n const baseParam = libEntry.scenceParam ?? \"\";\n // Devices without segment hardware (e.g. H70B3 Curtain Lights)\n // can't parse the A3-framed multi-packet ptReal protocol that\n // `scenceParam` carries \u2014 those packets describe per-segment\n // animation data. On such devices the packets are silently\n // dropped: the scene activation never happens locally. Fall\n // straight to Cloud activation for them so the scene still\n // works. Simple scenes without scenceParam (older presets like\n // \"Valentine's Day\") still take the ptReal path \u2014 the single\n // activation packet is understood by every Govee light.\n const hasSegments = typeof device.segmentCount === \"number\" && device.segmentCount > 0;\n if (!hasSegments && baseParam.length > 0) {\n this.log.debug(\n `ptReal scene ${scene.name} skipped \u2014 ${device.sku} has no segments, falling through to Cloud`,\n );\n this.sendCloudCommand(device, command, value).catch(e => {\n this.lastCloudFallbackError = logDedup(\n this.log,\n this.lastCloudFallbackError,\n `Cloud fallback for ${device.name}/${command}`,\n e,\n );\n });\n return;\n }\n let param = baseParam;\n if (\n device.sceneSpeed !== undefined &&\n device.sceneSpeed > 0 &&\n libEntry.speedInfo?.supSpeed &&\n libEntry.speedInfo.config\n ) {\n param = applySceneSpeed(param, device.sceneSpeed, libEntry.speedInfo.config);\n }\n this.log.debug(`ptReal: ${scene.name} \u2192 code=${libEntry.sceneCode}`);\n this.lanClient.setScene(device.lanIp, libEntry.sceneCode, param);\n return;\n }\n }\n // Scene not in library \u2014 fall through to Cloud\n this.sendCloudCommand(device, command, value).catch(e => {\n this.lastCloudFallbackError = logDedup(\n this.log,\n this.lastCloudFallbackError,\n `Cloud fallback for ${device.name}/${command}`,\n e,\n );\n });\n break;\n }\n case \"snapshot\": {\n const idx = parseInt(String(value), 10);\n if (isNaN(idx) || idx < 1 || idx > device.snapshots.length) {\n this.log.warn(`${device.sku}: invalid snapshot index ${String(value)}`);\n return;\n }\n const cmdGroups = device.snapshotBleCmds?.[idx - 1];\n if (cmdGroups && cmdGroups.length > 0) {\n const allPackets = cmdGroups.flat();\n if (allPackets.length > 0) {\n this.log.debug(`ptReal Snapshot: ${device.snapshots[idx - 1].name} \u2192 ${allPackets.length} packets`);\n this.lanClient.sendPtReal(device.lanIp, allPackets);\n return;\n }\n }\n // No BLE data \u2014 fall through to Cloud\n this.sendCloudCommand(device, command, value).catch(e => {\n this.lastCloudFallbackError = logDedup(\n this.log,\n this.lastCloudFallbackError,\n `Cloud fallback for ${device.name}/${command}`,\n e,\n );\n });\n break;\n }\n default:\n // LAN doesn't support this command \u2014 fall through to Cloud\n this.sendCloudCommand(device, command, value).catch(e => {\n this.lastCloudFallbackError = logDedup(\n this.log,\n this.lastCloudFallbackError,\n `Cloud fallback for ${device.name}/${command}`,\n e,\n );\n });\n }\n }\n\n /**\n * Send command via Cloud API (rate-limited)\n *\n * @param device Target device\n * @param command Command type\n * @param value Command value\n */\n private async sendCloudCommand(device: GoveeDevice, command: string, value: unknown): Promise<void> {\n // M19 \u2014 Closure capture: lokale Variable nach Guard. Verhindert Race\n // wenn `setCloudClient(null)` zwischen Guard-Check und executeRateLimited\n // l\u00E4uft (z.B. Adapter-Stop mid-await).\n const cloudClient = this.cloudClient;\n if (!cloudClient) {\n return;\n }\n\n // Find the matching capability\n const cap = this.findCapabilityForCommand(device, command);\n if (!cap) {\n // M20 \u2014 dedup-warn statt nur debug. User klickt einen State, kein\n // Channel-Match \u2192 Fehlersuche braucht das Erstauftreten als warn.\n this.lastNoChannelCategory = logDedup(\n this.log,\n this.lastNoChannelCategory,\n `No channel for ${device.name}/${command}`,\n new Error(\"no matching capability\"),\n );\n return;\n }\n\n const cloudValue = this.toCloudValue(device, command, value);\n\n const execute = async (): Promise<void> => {\n await cloudClient.controlDevice(device.sku, device.deviceId, cap.type, cap.instance, cloudValue);\n };\n\n await this.executeRateLimited(execute);\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/build/main.js
CHANGED
|
@@ -209,7 +209,11 @@ class GoveeAdapter extends utils.Adapter {
|
|
|
209
209
|
};
|
|
210
210
|
this.deviceManager.onSegmentBatchUpdate = (device, batch) => {
|
|
211
211
|
const prefix = this.stateManager.devicePrefix(device);
|
|
212
|
+
const cap = typeof device.segmentCount === "number" && device.segmentCount > 0 ? device.segmentCount : 0;
|
|
212
213
|
for (const idx of batch.segments) {
|
|
214
|
+
if (cap === 0 || idx >= cap) {
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
213
217
|
if (batch.color !== void 0) {
|
|
214
218
|
const hex = (0, import_types.rgbIntToHex)(batch.color);
|
|
215
219
|
this.setStateAsync(`${prefix}.segments.${idx}.color`, {
|
|
@@ -229,7 +233,11 @@ class GoveeAdapter extends utils.Adapter {
|
|
|
229
233
|
};
|
|
230
234
|
this.deviceManager.onMqttSegmentUpdate = (device, segments) => {
|
|
231
235
|
const prefix = this.stateManager.devicePrefix(device);
|
|
236
|
+
const cap = typeof device.segmentCount === "number" && device.segmentCount > 0 ? device.segmentCount : 0;
|
|
232
237
|
for (const seg of segments) {
|
|
238
|
+
if (cap === 0 || seg.index >= cap) {
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
233
241
|
this.setStateAsync(`${prefix}.segments.${seg.index}.color`, {
|
|
234
242
|
val: (0, import_types.rgbToHex)(seg.r, seg.g, seg.b),
|
|
235
243
|
ack: true
|
package/build/main.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/main.ts"],
|
|
4
|
-
"sourcesContent": ["import * as utils from \"@iobroker/adapter-core\";\nimport {\n buildDeviceStateDefs,\n getDefaultLanStates,\n mapCloudStateValue,\n planCloudCapabilityWrites,\n} from \"./lib/capability-mapper\";\nimport { getDeviceTier, initDeviceRegistry } from \"./lib/device-registry\";\nimport { DeviceManager, resolveSegmentCount, SEGMENT_HARD_MAX } from \"./lib/device-manager\";\nimport { GoveeApiClient } from \"./lib/govee-api-client\";\nimport { GoveeCloudClient } from \"./lib/govee-cloud-client\";\nimport { GoveeLanClient } from \"./lib/govee-lan-client\";\nimport { GoveeMqttClient } from \"./lib/govee-mqtt-client\";\nimport { GoveeOpenapiMqttClient } from \"./lib/govee-openapi-mqtt-client\";\nimport { LocalSnapshotStore } from \"./lib/local-snapshots\";\nimport { SnapshotHandler, type SnapshotHandlerHost } from \"./lib/snapshot-handler\";\nimport { GroupFanoutHandler, type GroupFanoutHost } from \"./lib/group-fanout\";\nimport { MessageRouter, type MessageRouterHost } from \"./lib/message-router\";\nimport { CloudRetryLoop, type CloudRetryHost } from \"./lib/cloud-retry\";\nimport { RateLimiter } from \"./lib/rate-limiter\";\nimport { SegmentWizard, wizardIdleText, type WizardHost, type WizardResult } from \"./lib/segment-wizard\";\nimport { SkuCache } from \"./lib/sku-cache\";\nimport { StateManager } from \"./lib/state-manager\";\nimport {\n hexToRgb,\n errMessage,\n parseSegmentList,\n resolveStatesValue,\n rgbIntToHex,\n rgbToHex,\n type AdapterConfig,\n type CloudLoadResult,\n type CloudStateCapability,\n type DeviceState,\n type GoveeDevice,\n type PersistedMqttCredentials,\n} from \"./lib/types\";\nimport { APP_API_INITIAL_DELAY_MS, APP_API_POLL_INTERVAL_MS, READY_TIMEOUT_MS } from \"./lib/timing-constants\";\nimport { GOVEE_APP_VERSION } from \"./lib/govee-constants\";\nimport { httpsRequest } from \"./lib/http-client\";\n\n/**\n * Rate limit defaults \u2014 full Cloud API budget (8/min, 9000/day). v2 no\n * longer halves this with govee-appliances because that adapter is\n * deprecated and won't run alongside govee-smart.\n */\nconst FULL_LIMITS = { perMinute: 8, perDay: 9000 };\n\n/**\n * State-suffix \u2192 command-name lookup for writable states. Segment indices\n * are dynamic and handled by regex in stateToCommand \u2014 everything else is\n * a straight string mapping.\n */\nconst STATE_TO_COMMAND: Readonly<Record<string, string>> = {\n \"control.power\": \"power\",\n \"control.brightness\": \"brightness\",\n \"control.colorRgb\": \"colorRgb\",\n \"control.colorTemperature\": \"colorTemperature\",\n \"control.scene\": \"scene\",\n \"control.gradient_toggle\": \"gradientToggle\",\n \"scenes.light_scene\": \"lightScene\",\n \"scenes.diy_scene\": \"diyScene\",\n \"scenes.scene_speed\": \"sceneSpeed\",\n \"music.music_mode\": \"music\",\n \"music.music_sensitivity\": \"music\",\n \"music.music_auto_color\": \"music\",\n \"snapshots.snapshot_cloud\": \"snapshot\",\n \"segments.command\": \"segmentBatch\",\n};\n\nclass GoveeAdapter extends utils.Adapter {\n private deviceManager: DeviceManager | null = null;\n private stateManager: StateManager | null = null;\n private lanClient: GoveeLanClient | null = null;\n private mqttClient: GoveeMqttClient | null = null;\n private openapiMqttClient: GoveeOpenapiMqttClient | null = null;\n private cloudClient: GoveeCloudClient | null = null;\n private rateLimiter: RateLimiter | null = null;\n /** Repeating timer for the App-API poll (sensor-state pull). */\n private appApiPollTimer: ioBroker.Interval | undefined;\n /**\n * One-shot timer for the FIRST app-api poll (5s nach start) \u2014 Handle\n * damit onUnload das wegr\u00E4umen kann bevor es ins Leere feuert.\n */\n private appApiInitialTimer: ioBroker.Timeout | undefined;\n /** One-shot timer for cloud-init 60s safety timeout \u2014 gleiches Pattern. */\n private cloudInitTimer: ioBroker.Timeout | undefined;\n /**\n * Letzter info.connection-Wert \u2014 Cache damit nicht jeder device-update\n * einen unn\u00F6tigen setStateAsync macht (H4).\n */\n private lastConnectionState: boolean | null = null;\n // === Lifecycle-Flags (Adapter-Boot-Sequenz) ===\n // checkAllReady() pr\u00FCft alle 5 Voraussetzungen gleichzeitig \u2014 sie laufen\n // parallel ab, kein lineares STATE_MACHINE-Pattern weil Channels\n // unabh\u00E4ngig connecten.\n /** LAN-Scan-Initial-Wait abgeschlossen (3s nach Start). */\n private lanScanDone = false;\n /** State-Tree-Erstellung f\u00FCr alle Cached/Cloud-Devices fertig. */\n private statesReady = false;\n /** Cloud-Init-Phase abgeschlossen (mit oder ohne Erfolg). */\n private cloudInitDone = false;\n /** True nach dem ersten erfolgreichen App-API-Poll (f\u00FCr Sensoren mit Werten). */\n private appApiInitialPollDone = false;\n /** Verhindert Mehrfach-Ready-Log innerhalb derselben Adapter-Session. */\n private readyLogged = false;\n /** Cloud war mindestens einmal connected \u2014 f\u00FCr \u201Erestored\"-Log nach Down. */\n private cloudWasConnected = false;\n /** T\u00E4gliches Interval f\u00FCr App-Version-Drift-Check gegen App-Store. */\n private appVersionCheckTimer: ioBroker.Interval | undefined;\n // === Sub-Komponenten ===\n private skuCache: SkuCache | null = null;\n private localSnapshots: LocalSnapshotStore | null = null;\n private snapshotHandler: SnapshotHandler | null = null;\n private groupFanout: GroupFanoutHandler | null = null;\n private messageRouter: MessageRouter | null = null;\n private stateCreationQueue: Promise<void>[] = [];\n private lanScanTimer: ioBroker.Timeout | undefined;\n private cleanupTimer: ioBroker.Timeout | undefined;\n private readyTimer: ioBroker.Timeout | undefined;\n private cloudRetry: CloudRetryLoop | null = null;\n private segmentWizard: SegmentWizard | null = null;\n private unhandledRejectionHandler: ((reason: unknown) => void) | null = null;\n private uncaughtExceptionHandler: ((err: Error) => void) | null = null;\n /** Per-device timestamp of the last diagnostics export \u2014 throttle gate */\n private diagnosticsLastRun = new Map<string, number>();\n /** Cached admin language from system.config \u2014 used for wizard UI text */\n private adminLanguage = \"en\";\n /** Last time `requestCode` was triggered via onMessage \u2014 guards against double-click email spam. */\n private lastVerificationRequestMs = 0;\n\n /** @param options Adapter options */\n public constructor(options: Partial<utils.AdapterOptions> = {}) {\n super({ ...options, name: \"govee-smart\" });\n // Per ioBroker rule: async handlers registered on events MUST .catch,\n // otherwise rejections become unhandled \u2192 SIGKILL code 6 \u2192 restart loop.\n this.on(\"ready\", () =>\n this.onReady().catch(e =>\n this.log.error(`onReady crashed: ${e instanceof Error ? (e.stack ?? e.message) : String(e)}`),\n ),\n );\n this.on(\"stateChange\", (id, state) =>\n this.onStateChange(id, state).catch(e => this.log.warn(`onStateChange crashed for ${id}: ${errMessage(e)}`)),\n );\n this.on(\"message\", obj => this.messageRouter?.onMessage(obj));\n this.on(\"unload\", callback => this.onUnload(callback));\n // Last-line-of-defence against unhandled rejections / sync throws from\n // fire-and-forget paths. The per-handler .catch() wrappers cover the\n // direct entry points; this catches whatever slips past them so the\n // adapter logs the cause instead of triggering js-controller SIGKILL.\n this.unhandledRejectionHandler = (reason: unknown) => {\n this.log.error(\n `Unhandled rejection: ${reason instanceof Error ? (reason.stack ?? reason.message) : String(reason)}`,\n );\n };\n this.uncaughtExceptionHandler = (err: Error) => {\n this.log.error(`Uncaught exception: ${err.stack ?? err.message}`);\n };\n process.on(\"unhandledRejection\", this.unhandledRejectionHandler);\n process.on(\"uncaughtException\", this.uncaughtExceptionHandler);\n }\n\n /** Adapter started \u2014 initialize all channels */\n private async onReady(): Promise<void> {\n const config = this.config as unknown as AdapterConfig;\n\n // info channel + states are declared as instanceObjects in\n // io-package.json, so js-controller materialises them on install /\n // upgrade. We only initialise the runtime values here.\n await this.setStateAsync(\"info.connection\", { val: false, ack: true });\n await this.setStateAsync(\"info.mqttConnected\", { val: false, ack: true });\n await this.setStateAsync(\"info.cloudConnected\", { val: false, ack: true });\n await this.setStateAsync(\"info.openapiMqttConnected\", {\n val: false,\n ack: true,\n });\n await this.setStateAsync(\"info.refresh_cloud_data\", {\n val: false,\n ack: true,\n });\n // Load admin language from system.config so wizard prose matches the\n // user's Admin UI. Falls back to English on any lookup failure.\n try {\n const sysConf = await this.getForeignObjectAsync(\"system.config\");\n const lang = (sysConf?.common as { language?: string } | undefined)?.language;\n if (typeof lang === \"string\" && lang.length > 0) {\n this.adminLanguage = lang;\n }\n } catch {\n // Keep default \"en\"\n }\n await this.setStateAsync(\"info.wizardStatus\", {\n val: wizardIdleText(this.adminLanguage),\n ack: true,\n });\n\n this.stateManager = new StateManager(this);\n // General groups online state (reflects Cloud connection)\n await this.stateManager.createGroupsOnlineState(false);\n this.deviceManager = new DeviceManager(this.log, this);\n const dataDir = utils.getAbsoluteInstanceDataDir(this);\n\n // Load device registry from devices.json in the adapter package root.\n // Status filter: verified+reported active by default; seed-status entries\n // require the experimentalQuirks config toggle.\n initDeviceRegistry({\n experimental: config.experimentalQuirks === true,\n log: this.log,\n });\n this.skuCache = new SkuCache(dataDir, this.log);\n this.localSnapshots = new LocalSnapshotStore(dataDir, this.log);\n this.snapshotHandler = new SnapshotHandler(this.buildSnapshotHost());\n this.groupFanout = new GroupFanoutHandler(this.buildGroupFanoutHost());\n this.messageRouter = new MessageRouter(this.buildMessageRouterHost());\n this.deviceManager.setSkuCache(this.skuCache);\n\n // API client for undocumented scene/music/DIY libraries (always available)\n const apiClient = new GoveeApiClient();\n apiClient.setEmail(config.goveeEmail);\n this.deviceManager.setApiClient(apiClient);\n\n this.deviceManager.setCallbacks(\n (device, state) => this.onDeviceStateUpdate(device, state),\n devices => this.onDeviceListChanged(devices),\n );\n\n // Update info.ip when LAN IP changes\n this.deviceManager.onLanIpChanged = (device, ip) => {\n const prefix = this.stateManager!.devicePrefix(device);\n this.setStateAsync(`${prefix}.info.ip`, { val: ip, ack: true }).catch(() => {});\n };\n\n // Sync individual segment states after batch command\n this.deviceManager.onSegmentBatchUpdate = (device, batch) => {\n const prefix = this.stateManager!.devicePrefix(device);\n for (const idx of batch.segments) {\n if (batch.color !== undefined) {\n const hex = rgbIntToHex(batch.color);\n this.setStateAsync(`${prefix}.segments.${idx}.color`, {\n val: hex,\n ack: true,\n }).catch(() => {});\n }\n if (batch.brightness !== undefined) {\n this.setStateAsync(`${prefix}.segments.${idx}.brightness`, {\n val: batch.brightness,\n ack: true,\n }).catch(() => {});\n }\n }\n };\n\n // Sync per-segment states from MQTT BLE status push (AA A5 packets)\n this.deviceManager.onMqttSegmentUpdate = (device, segments) => {\n const prefix = this.stateManager!.devicePrefix(device);\n for (const seg of segments) {\n this.setStateAsync(`${prefix}.segments.${seg.index}.color`, {\n val: rgbToHex(seg.r, seg.g, seg.b),\n ack: true,\n }).catch(() => {});\n this.setStateAsync(`${prefix}.segments.${seg.index}.brightness`, {\n val: seg.brightness,\n ack: true,\n }).catch(() => {});\n }\n };\n\n // When MQTT reveals more segments than the Cloud advertised, rebuild\n // the device's state tree so the extra segments get their datapoints.\n this.deviceManager.onSegmentCountGrown = device => {\n if (!this.stateManager) {\n return;\n }\n this.stateManager.createSegmentStates(device).catch(e => {\n this.log.warn(`Failed to rebuild segment tree for ${device.name} after count growth: ${errMessage(e)}`);\n });\n };\n\n // Log startup with configured channels\n const startChannels: string[] = [\"LAN\"];\n if (config.apiKey) {\n startChannels.push(\"Cloud\");\n }\n if (config.goveeEmail && config.goveePassword) {\n startChannels.push(\"MQTT\");\n }\n this.log.info(`Starting (${startChannels.join(\", \")})`);\n\n // --- LAN (always active) ---\n this.lanClient = new GoveeLanClient(this.log, this);\n this.deviceManager.setLanClient(this.lanClient);\n\n this.lanClient.start(\n lanDevice => {\n this.deviceManager!.handleLanDiscovery(lanDevice);\n // Poll status only when MQTT is unavailable. With an active MQTT\n // subscription Govee pushes state changes authoritatively, so the\n // LAN devStatus request would be duplicate traffic.\n if (!this.mqttClient?.connected) {\n this.lanClient!.requestStatus(lanDevice.ip);\n }\n },\n (sourceIp, status) => {\n this.deviceManager!.handleLanStatus(sourceIp, status);\n },\n 30_000,\n config.networkInterface || \"\",\n );\n\n // Wait for first LAN scan responses (UDP multicast, devices respond within 1-2s)\n this.lanScanTimer = this.setTimeout(() => {\n this.lanScanDone = true;\n this.checkAllReady();\n }, 3_000);\n\n // --- MQTT (if account credentials provided) ---\n // Initialize MQTT before Cloud so scene library can load on first cycle\n if (config.goveeEmail && config.goveePassword) {\n this.mqttClient = new GoveeMqttClient(config.goveeEmail, config.goveePassword, this.log, this);\n\n // Forward every parsed MQTT op.command into the diagnostics ring buffer\n // so diag.export contains the recent packets per device.\n this.mqttClient.setPacketHook((deviceId, topic, hex) => {\n this.deviceManager?.getDiagnostics().addMqttPacket(deviceId, topic, hex);\n });\n\n // 2FA: forward optional code from settings into the next login attempt;\n // clear the field automatically once Govee has accepted it.\n this.mqttClient.setVerificationCode(config.mqttVerificationCode ?? \"\");\n this.mqttClient.setOnVerificationConsumed(() => {\n this.clearVerificationCodeSetting().catch(e => {\n this.log.warn(`Could not clear mqttVerificationCode: ${errMessage(e)}`);\n });\n });\n this.mqttClient.setOnVerificationFailed(reason => {\n // On 'failed' (455 / 454+code-was-sent) blank the code so the user\n // doesn't keep retrying with a stale value. On 'pending' (454 + no\n // code) we leave the field as-is \u2014 the user is about to fill it.\n if (reason === \"failed\") {\n this.clearVerificationCodeSetting().catch(() => {});\n }\n });\n\n // Re-use cached MQTT credentials across restarts. Stored in the\n // info.mqttCredentials state (NOT in adapter native): writing to\n // system.adapter.X.0 native triggers a js-controller adapter\n // restart, which would loop endlessly on every login. States are\n // restart-safe.\n //\n // One-shot: clean up legacy v2.1.0/v2.1.1/v2.1.2 native fields\n // that contained plaintext credentials. Best-effort.\n await this.cleanupLegacyMqttNativeOnce();\n const cachedCreds = await this.loadPersistedCredsFromState();\n if (cachedCreds) {\n this.mqttClient.setPersistedCredentials(cachedCreds);\n }\n this.mqttClient.setOnCredentialsRefresh(creds => {\n this.persistCredsToState(creds).catch(e => {\n this.log.warn(`Could not persist MQTT credentials: ${errMessage(e)}`);\n });\n });\n\n await this.mqttClient.connect(\n update => this.deviceManager!.handleMqttStatus(update),\n connected => {\n this.setStateAsync(\"info.mqttConnected\", {\n val: connected,\n ack: true,\n }).catch(() => {});\n if (connected) {\n this.checkAllReady();\n }\n this.updateConnectionState();\n },\n // Forward every fresh bearer token \u2014 fires on initial login and on\n // each reconnect-login, so the API client never runs with a stale one.\n token => apiClient.setBearerToken(token),\n );\n }\n\n // --- Device data: Cache first, Cloud only on cache miss ---\n const cachedOk = this.deviceManager.loadFromCache();\n\n if (config.apiKey) {\n this.cloudClient = new GoveeCloudClient(config.apiKey, this.log);\n // Capture the most recent Cloud response per (deviceId, endpoint) for\n // diagnostics \u2014 bounded by the DiagnosticsCollector's response slot cap.\n this.cloudClient.setResponseHook((deviceId, endpoint, body) => {\n this.deviceManager?.getDiagnostics().setApiResponse(deviceId, endpoint, body);\n });\n this.deviceManager.setCloudClient(this.cloudClient);\n\n // Bridge synthetic capabilities (App-API, OpenAPI-MQTT events) into the\n // same setState pipeline as polled Cloud state. Keeps mapCloudStateValue\n // as the single source of truth for value coercion + state-id resolution.\n this.deviceManager.setOnCloudCapabilities((device, caps) => {\n this.applyCloudCapabilities(device, caps).catch(e =>\n this.log.warn(`applyCloudCapabilities failed for ${device.sku}: ${errMessage(e)}`),\n );\n });\n\n this.rateLimiter = new RateLimiter(this.log, this, FULL_LIMITS.perMinute, FULL_LIMITS.perDay);\n this.rateLimiter.start();\n this.deviceManager.setRateLimiter(this.rateLimiter);\n\n // OpenAPI-MQTT \u2014 push channel for appliance/sensor events\n // (lackWater, iceFull, bodyAppeared etc.). API key is enough; no\n // separate credentials required. Connection runs in parallel to\n // the AWS-IoT MQTT used for status push of regular devices.\n this.openapiMqttClient = new GoveeOpenapiMqttClient(config.apiKey, this.log, this);\n this.openapiMqttClient.connect(\n event => this.deviceManager?.handleOpenApiEvent(event),\n connected => {\n this.setStateAsync(\"info.openapiMqttConnected\", {\n val: connected,\n ack: true,\n }).catch(() => {});\n },\n );\n\n // App-API poll \u2014 every 2 minutes, pulls state for sensors like H5179\n // where OpenAPI v2 /device/state returns empty. Bearer token comes\n // from the AWS-IoT MQTT login, so a no-op until that succeeds.\n const triggerAppApiPoll = (): void => {\n this.deviceManager\n ?.pollAppApi()\n .then(() => {\n // H2 \u2014 Mark initial-poll-done und re-check Ready damit der\n // Adapter \u201Eready\" loggen kann sobald Sensor-Werte da sind.\n if (!this.appApiInitialPollDone) {\n this.appApiInitialPollDone = true;\n this.checkAllReady();\n }\n })\n .catch(e => this.log.debug(`pollAppApi failed: ${errMessage(e)}`));\n };\n this.appApiPollTimer = this.setInterval(triggerAppApiPoll, APP_API_POLL_INTERVAL_MS);\n // Initial poll: gibt MQTT Zeit f\u00FCr den Bearer-Login. Ohne diesen\n // Sofort-Poll bleiben Sensoren wie H5179 die ersten 2 Minuten nach\n // Start offline (Online-Signal kommt nur via App-API). Handle in\n // Member-Variable damit onUnload den Timer cleart.\n this.appApiInitialTimer = this.setTimeout(triggerAppApiPoll, APP_API_INITIAL_DELAY_MS);\n\n if (!cachedOk) {\n // No cache \u2014 first start, fetch from Cloud with 60s hard-timeout.\n // If Cloud hangs/fails, we don't want to block adapter startup indefinitely.\n const result = await this.cloudInitWithTimeout();\n this.cloudWasConnected = result.ok;\n this.ensureCloudRetry().setConnected(result.ok);\n this.setStateAsync(\"info.cloudConnected\", {\n val: result.ok,\n ack: true,\n }).catch(() => {});\n this.stateManager?.updateGroupsOnline(result.ok).catch(() => {});\n\n if (result.ok) {\n await this.loadCloudStates();\n } else {\n this.handleCloudFailure(result);\n }\n } else {\n this.log.info(\"Using cached device data \u2014 no Cloud calls needed\");\n this.cloudWasConnected = true;\n this.ensureCloudRetry().setConnected(true);\n this.setStateAsync(\"info.cloudConnected\", {\n val: true,\n ack: true,\n }).catch(() => {});\n this.stateManager?.updateGroupsOnline(true).catch(() => {});\n }\n // Load group membership from undocumented API (needs bearer token + device map)\n await this.deviceManager.loadGroupMembers();\n\n this.cloudInitDone = true;\n }\n\n // Wait for all state creation from cache/cloud load to complete.\n // Drain-loop: a callback that fires during the await (e.g. a late LAN\n // discovery) can push fresh promises into the queue \u2014 we need to await\n // those too before flipping statesReady, otherwise the initial state\n // tree would be incomplete on very fast startups.\n while (this.stateCreationQueue.length > 0) {\n const pending = this.stateCreationQueue;\n this.stateCreationQueue = [];\n await Promise.all(pending);\n }\n this.statesReady = true;\n\n // Subscribe to all writable device and group states\n await this.subscribeStatesAsync(\"devices.*\");\n await this.subscribeStatesAsync(\"groups.*\");\n await this.subscribeStatesAsync(\"info.refresh_cloud_data\");\n\n // Cleanup stale devices after initial discovery (30s delay for LAN scan).\n // Reaps devices from every adapter-level map that was keyed on them so the\n // process doesn't leak memory across Cloud-side device turnover.\n this.cleanupTimer = this.setTimeout(() => {\n this.reapStaleDevices().catch(e => this.log.debug(`Device cleanup failed: ${errMessage(e)}`));\n }, 30_000);\n\n // App-Version-Drift-Monitor \u2014 daily check + initial nach 2 min wenn der\n // Adapter-Start ohne MQTT-Login durchgeschlagen ist (z.B. LAN-only).\n this.appVersionCheckTimer = this.setInterval(\n () => {\n this.checkAppVersionDrift().catch(e => this.log.debug(`App version check error: ${errMessage(e)}`));\n },\n 24 * 60 * 60 * 1000,\n );\n this.setTimeout(\n () => {\n this.checkAppVersionDrift().catch(e => this.log.debug(`App version check error: ${errMessage(e)}`));\n },\n 2 * 60 * 1000,\n );\n\n this.updateConnectionState();\n\n // Check if all channels are ready \u2014 may already be true if MQTT connected fast\n this.checkAllReady();\n // Safety timeout: log ready even if a channel takes too long.\n // 60s deckt normalen MQTT-Connect + 1 Reconnect-Attempt ab.\n this.readyTimer = this.setTimeout(() => {\n if (!this.readyLogged) {\n // Safety-Timeout: log ready trotzdem auch wenn ein Channel zu lange\n // braucht. READY_TIMEOUT_MS deckt normalen MQTT-Connect + 1 Reconnect.\n this.readyLogged = true;\n this.logDeviceSummary();\n }\n }, 60_000);\n }\n\n /**\n * Initial Cloud-Load mit 60-Sekunden-Hardtimeout.\n * Blockiert nicht l\u00E4nger \u2014 wenn Cloud h\u00E4ngt, geht Adapter mit LAN+MQTT weiter,\n * und der Retry-Loop probiert's passend zum Fehlergrund erneut.\n */\n private async cloudInitWithTimeout(): Promise<CloudLoadResult> {\n if (!this.deviceManager) {\n return { ok: false, reason: \"transient\" };\n }\n const loadPromise = this.deviceManager.loadFromCloud();\n const timeoutPromise = new Promise<CloudLoadResult>(resolve => {\n this.cloudInitTimer = this.setTimeout(() => resolve({ ok: false, reason: \"transient\" }), READY_TIMEOUT_MS);\n });\n try {\n return await Promise.race([loadPromise, timeoutPromise]);\n } catch {\n return { ok: false, reason: \"transient\" };\n }\n }\n\n /** Build the host object for {@link CloudRetryLoop}. */\n private buildCloudRetryHost(): CloudRetryHost {\n return {\n log: this.log,\n setTimeout: (cb, ms) => this.setTimeout(cb, ms),\n clearTimeout: h => this.clearTimeout(h as ioBroker.Timeout),\n loadFromCloud: () => this.cloudInitWithTimeout(),\n onCloudRestored: async () => {\n this.cloudWasConnected = true;\n this.setStateAsync(\"info.cloudConnected\", {\n val: true,\n ack: true,\n }).catch(() => {});\n this.stateManager?.updateGroupsOnline(true).catch(() => {});\n await this.loadCloudStates();\n },\n };\n }\n\n /** Lazy-initialise the retry loop on first use. */\n private ensureCloudRetry(): CloudRetryLoop {\n if (!this.cloudRetry) {\n this.cloudRetry = new CloudRetryLoop(this.buildCloudRetryHost());\n this.cloudRetry.setConnected(this.cloudWasConnected);\n }\n return this.cloudRetry;\n }\n\n /**\n * React to a Cloud-load outcome \u2014 delegates to {@link CloudRetryLoop}.\n *\n * @param result CloudLoadResult from initial load or retry attempt\n */\n private handleCloudFailure(result: CloudLoadResult): void {\n this.ensureCloudRetry().handleResult(result);\n }\n\n /**\n * React to the user writing `info.refresh_cloud_data = true`. Performs one\n * full Cloud reload cycle so newly created scenes/snapshots from the Govee\n * Home app show up without an adapter restart.\n */\n private async handleManualCloudRefresh(): Promise<void> {\n if (!this.deviceManager || !this.cloudClient) {\n this.log.info(\"Refresh cloud data: no Cloud client configured (API key missing) \u2014 nothing to do\");\n return;\n }\n this.log.info(\"Refresh cloud data: re-fetching scenes and snapshots for all devices\");\n try {\n const changed = await this.deviceManager.refreshSceneData();\n if (changed) {\n await this.loadCloudStates();\n }\n // G2 \u2014 kein \u201Edone\"-Log: User hat den Button gedr\u00FCckt, sieht das\n // Ergebnis am Adapter-Verhalten. \u201Edone\" auf info-level w\u00E4re bei\n // Erfolg redundant (Fehler-Pfad direkt darunter ist warn).\n } catch (e) {\n this.log.warn(`Refresh cloud data failed: ${errMessage(e)}`);\n }\n }\n\n /**\n * Adapter stopping \u2014 MUST be synchronous.\n *\n * @param callback Completion callback\n */\n private onUnload(callback: () => void): void {\n try {\n if (this.lanScanTimer) {\n this.clearTimeout(this.lanScanTimer);\n }\n if (this.cleanupTimer) {\n this.clearTimeout(this.cleanupTimer);\n }\n if (this.readyTimer) {\n this.clearTimeout(this.readyTimer);\n }\n if (this.appApiPollTimer) {\n this.clearInterval(this.appApiPollTimer);\n this.appApiPollTimer = undefined;\n }\n if (this.appApiInitialTimer) {\n this.clearTimeout(this.appApiInitialTimer);\n this.appApiInitialTimer = undefined;\n }\n if (this.cloudInitTimer) {\n this.clearTimeout(this.cloudInitTimer);\n this.cloudInitTimer = undefined;\n }\n if (this.appVersionCheckTimer) {\n this.clearInterval(this.appVersionCheckTimer);\n this.appVersionCheckTimer = undefined;\n }\n this.cloudRetry?.dispose();\n this.segmentWizard?.dispose();\n this.lanClient?.stop();\n this.mqttClient?.disconnect();\n this.openapiMqttClient?.disconnect();\n this.rateLimiter?.stop();\n // Remove process-level handlers so an adapter restart doesn't stack them.\n if (this.unhandledRejectionHandler) {\n process.off(\"unhandledRejection\", this.unhandledRejectionHandler);\n this.unhandledRejectionHandler = null;\n }\n if (this.uncaughtExceptionHandler) {\n process.off(\"uncaughtException\", this.uncaughtExceptionHandler);\n this.uncaughtExceptionHandler = null;\n }\n // onUnload MUST be synchronous \u2014 don't await, but silence potential\n // promise rejection during teardown to avoid \"unhandled rejection\" warnings.\n this.setState(\"info.connection\", { val: false, ack: true }).catch(() => {});\n this.setState(\"info.mqttConnected\", { val: false, ack: true }).catch(() => {});\n this.setState(\"info.openapiMqttConnected\", {\n val: false,\n ack: true,\n }).catch(() => {});\n this.setState(\"info.cloudConnected\", { val: false, ack: true }).catch(() => {});\n } catch {\n // ignore\n }\n callback();\n }\n\n /**\n * Handle state changes from user (write operations).\n *\n * @param id State ID\n * @param state New state value\n */\n private async onStateChange(id: string, state: ioBroker.State | null | undefined): Promise<void> {\n if (!state || state.ack || !this.deviceManager || !this.stateManager) {\n return;\n }\n\n // Global refresh button \u2014 triggers one fresh cloud fetch across all\n // devices and re-builds the state tree. Handy after creating a new\n // snapshot in the Govee Home app without restarting the adapter.\n if (id === `${this.namespace}.info.refresh_cloud_data` && state.val) {\n await this.handleManualCloudRefresh();\n await this.setStateAsync(id, { val: false, ack: true });\n return;\n }\n\n // Find which device this state belongs to\n const localId = id.replace(`${this.namespace}.`, \"\");\n if (!localId.startsWith(\"devices.\") && !localId.startsWith(\"groups.\")) {\n return;\n }\n\n const device = this.findDeviceForState(localId);\n if (!device) {\n return;\n }\n\n // Determine command from state suffix after device prefix\n const prefix = this.stateManager.devicePrefix(device);\n const stateSuffix = localId.slice(prefix.length + 1);\n\n // Resolve dropdown input \u2014 accept Number, numeric String, or label\n // (case-insensitive) against the state's common.states map. Returns\n // the canonical key as String so the rest of the handler sees the\n // same shape it always saw (e.g. \"1\"). Non-dropdown states are\n // passed through unchanged.\n const resolved = await this.resolveDropdownInput(id, state.val);\n if (!resolved.ok) {\n this.log.warn(`Unknown dropdown value for ${id}: ${String(state.val)} \u2014 ignoring`);\n return;\n }\n const val = resolved.val;\n\n // Group fan-out: route commands to each member device\n if (device.sku === \"BaseGroup\" && device.groupMembers) {\n await this.groupFanout!.fanOut(device, stateSuffix, val);\n await this.setStateAsync(id, { val, ack: true });\n if (stateSuffix === \"scenes.light_scene\" || stateSuffix === \"music.music_mode\") {\n await this.resetRelatedDropdowns(prefix, stateSuffix === \"scenes.light_scene\" ? \"lightScene\" : \"music\");\n }\n return;\n }\n\n // Handle local snapshot commands (no Cloud/MQTT needed)\n if (stateSuffix === \"snapshots.snapshot_save\" && typeof val === \"string\" && val.trim()) {\n await this.snapshotHandler!.save(device, val.trim());\n await this.setStateAsync(id, { val: \"\", ack: true });\n return;\n }\n if (stateSuffix === \"snapshots.snapshot_local\") {\n if (val !== \"0\" && val !== 0) {\n await this.snapshotHandler!.restore(device, val);\n await this.resetRelatedDropdowns(prefix, \"snapshotLocal\");\n }\n await this.setStateAsync(id, { val, ack: true });\n return;\n }\n if (stateSuffix === \"snapshots.snapshot_delete\" && typeof val === \"string\" && val.trim()) {\n this.snapshotHandler!.delete(device, val.trim());\n await this.setStateAsync(id, { val: \"\", ack: true });\n return;\n }\n\n // Manual segments toggle/list \u2014 handler owns the ack because a parse\n // error rewrites manual_mode to false, and an outer ack with the\n // raw value would resurrect the rejected entry.\n if (stateSuffix === \"segments.manual_mode\" || stateSuffix === \"segments.manual_list\") {\n await this.handleManualSegmentsChange(device, stateSuffix, val);\n return;\n }\n\n if (stateSuffix === \"diag.export\" && val) {\n await this.handleDiagnosticsExport(device, prefix, id);\n return;\n }\n\n const command = this.stateToCommand(stateSuffix);\n\n if (!command) {\n await this.handleGenericCapabilityCommand(device, id, stateSuffix, val);\n return;\n }\n\n // Dropdown reset to \"---\" (value 0) \u2014 acknowledge without sending command\n if ((command === \"lightScene\" || command === \"diyScene\" || command === \"snapshot\") && (val === \"0\" || val === 0)) {\n await this.setStateAsync(id, { val, ack: true });\n return;\n }\n\n // Scene speed: store on device, applied on next scene activation.\n // Persist to SKU cache so the user's choice survives a restart.\n if (command === \"sceneSpeed\") {\n const level = typeof val === \"number\" ? val : parseInt(String(val), 10);\n if (!isNaN(level)) {\n device.sceneSpeed = level;\n this.deviceManager?.persistDeviceToCache(device);\n }\n await this.setStateAsync(id, { val, ack: true });\n return;\n }\n\n try {\n // Music mode: combine all music states into one STRUCT command\n if (command === \"music\") {\n // music_mode \"---\" (value 0) \u2014 acknowledge without sending command\n if (stateSuffix === \"music.music_mode\" && (val === \"0\" || val === 0)) {\n await this.setStateAsync(id, { val, ack: true });\n return;\n }\n await this.sendMusicCommand(device, prefix, stateSuffix, val);\n await this.setStateAsync(id, { val, ack: true });\n // Reset scene/snapshot dropdowns when activating music mode\n if (stateSuffix === \"music.music_mode\") {\n await this.resetRelatedDropdowns(prefix, \"music\");\n }\n return;\n }\n\n await this.deviceManager.sendCommand(device, command, val);\n // Optimistic ack\n await this.setStateAsync(id, { val, ack: true });\n // Reset related dropdowns when switching modes.\n // Power-off is a special case \u2014 the device is off, so no mode is\n // active anymore; reset every mode dropdown so the UI reflects the\n // reality (scene/music/snapshot selections are now just history).\n if (command === \"power\" && val === false) {\n await this.resetModeDropdowns(prefix, \"\");\n } else {\n await this.resetRelatedDropdowns(prefix, command);\n }\n } catch (err) {\n this.log.warn(`Command failed for ${device.name}: ${errMessage(err)}`);\n }\n }\n\n /**\n * Resolve a dropdown-state input value against the state's common.states\n * map. Returns the canonical key (always String form) so a user can write\n * either the index (\"1\"), the index as a number (1) or the label name\n * (\"Aurora\", case-insensitive) \u2014 all three land at the same canonical\n * value for the rest of the handler.\n *\n * Non-dropdown states (no common.states), reset sentinels (0/\"0\"/\"\") and\n * non-string/number inputs are passed through unchanged. A dropdown input\n * that doesn't match any key or label returns ok=false so the caller can\n * warn and skip the command.\n *\n * @param id Full state id\n * @param raw Raw input value as provided by the user/script\n */\n private async resolveDropdownInput(\n id: string,\n raw: ioBroker.StateValue,\n ): Promise<{ val: ioBroker.StateValue; ok: boolean }> {\n if (raw === null || raw === undefined) {\n return { val: raw, ok: true };\n }\n // Reset sentinels \u2014 let the existing branch handle them.\n if (raw === 0 || raw === \"0\" || raw === \"\") {\n return { val: raw, ok: true };\n }\n // Only dropdown candidates have common.states; non-dropdown inputs\n // can't be resolved here so they pass through.\n if (typeof raw !== \"number\" && typeof raw !== \"string\") {\n return { val: raw, ok: true };\n }\n const obj = await this.getObjectAsync(id);\n const states = obj?.common?.states;\n if (!states || typeof states !== \"object\") {\n return { val: raw, ok: true };\n }\n const resolved = resolveStatesValue(raw, states as Record<string, string>);\n if (resolved) {\n return { val: resolved.key, ok: true };\n }\n return { val: raw, ok: false };\n }\n\n /**\n * Build and send a music_setting STRUCT command.\n * Reads sibling music state values and combines them into one API call.\n *\n * @param device Target device\n * @param prefix Device state prefix\n * @param changedSuffix Which music state was changed\n * @param newValue New value for the changed state\n */\n private async sendMusicCommand(\n device: GoveeDevice,\n prefix: string,\n changedSuffix: string,\n newValue: ioBroker.StateValue,\n ): Promise<void> {\n const musicBase = `${this.namespace}.${prefix}.music`;\n\n // Read current sibling values\n const modeState = await this.getStateAsync(`${musicBase}.music_mode`);\n const sensState = await this.getStateAsync(`${musicBase}.music_sensitivity`);\n const autoState = await this.getStateAsync(`${musicBase}.music_auto_color`);\n\n // Apply the changed value, use siblings for the rest\n const musicMode =\n changedSuffix === \"music.music_mode\" ? parseInt(String(newValue), 10) : parseInt(String(modeState?.val ?? 0), 10);\n const sensitivity =\n changedSuffix === \"music.music_sensitivity\" ? (newValue as number) : ((sensState?.val as number) ?? 100);\n const autoColor = changedSuffix === \"music.music_auto_color\" ? (newValue ? 1 : 0) : autoState?.val ? 1 : 0;\n\n if (!musicMode || musicMode === 0) {\n this.log.debug(\"Music mode not selected, skipping command\");\n return;\n }\n\n // LAN first: send via ptReal BLE if device is on LAN\n if (device.lanIp && this.lanClient) {\n // Read current color for RGB-modes (Spectrum=1, Rolling=2)\n let r = 0,\n g = 0,\n b = 0;\n if (musicMode === 1 || musicMode === 2) {\n const colorState = await this.getStateAsync(`${this.namespace}.${prefix}.control.colorRgb`);\n if (colorState?.val && typeof colorState.val === \"string\") {\n ({ r, g, b } = hexToRgb(colorState.val));\n }\n }\n this.lanClient.setMusicMode(device.lanIp, musicMode, r, g, b);\n return;\n }\n\n // Cloud fallback\n const structValue: Record<string, unknown> = {\n musicMode,\n sensitivity,\n autoColor,\n };\n\n await this.deviceManager!.sendCapabilityCommand(\n device,\n \"devices.capabilities.music_setting\",\n \"musicMode\",\n structValue,\n );\n }\n\n /**\n * Called by device-manager when a device state changes\n *\n * @param device Updated device\n * @param state Changed state values\n */\n private onDeviceStateUpdate(device: GoveeDevice, state: Partial<DeviceState>): void {\n if (this.stateManager) {\n this.stateManager.updateDeviceState(device, state).catch(() => {});\n }\n this.updateConnectionState();\n\n // Update group reachability when member online status changes\n if (state.online !== undefined) {\n this.updateGroupReachability();\n }\n\n // Mirror power-off to mode-dropdown reset. Covers MQTT/LAN-initiated\n // power changes (Govee app or physical remote) so the UI stays honest:\n // a device that's off can't be \"playing Aurora-A\" anymore.\n // L11 \u2014 defensive auch 0 als false akzeptieren (Govee schickt Power\n // theoretisch als boolean, aber MQTT-Boundary k\u00F6nnte 0 durchschleusen).\n const powerOff = state.power === false || (state.power as unknown) === 0;\n if (powerOff && this.stateManager) {\n const prefix = this.stateManager.devicePrefix(device);\n this.resetModeDropdowns(prefix, \"\").catch(() => undefined);\n }\n }\n\n /**\n * Fan out a group command to all member devices.\n * Basic controls (power, brightness, color) are sent directly.\n * Scenes/music are matched by name across members.\n *\n * @param group BaseGroup device\n * @param stateSuffix State path suffix (e.g. \"control.power\")\n * @param value Command value\n */\n /** Construct host object for GroupFanoutHandler. */\n private buildGroupFanoutHost(): GroupFanoutHost {\n return {\n log: this.log,\n namespace: this.namespace,\n getDevices: () => this.deviceManager?.getDevices() ?? [],\n sendCommand: async (device, command, value) => {\n await this.deviceManager?.sendCommand(device, command, value);\n },\n devicePrefix: device => this.stateManager?.devicePrefix(device) ?? \"\",\n stateToCommand: suffix => this.stateToCommand(suffix) ?? undefined,\n getObject: id => this.getObjectAsync(id),\n sendMusicCommand: (device, devicePrefix, stateSuffix, value) =>\n this.sendMusicCommand(device, devicePrefix, stateSuffix, value),\n };\n }\n\n /**\n * Resolve group member references to actual device objects.\n * Bleibt in main.ts weil `updateGroupReachability` (auch in main.ts) das\n * direkt nutzt \u2014 Helper-Pfad ist von der Fan-Out-Klasse unabh\u00E4ngig.\n *\n * @param group BaseGroup device with groupMembers\n * @param devices Full device list to search\n */\n private resolveGroupMembers(group: GoveeDevice, devices: GoveeDevice[]): GoveeDevice[] {\n if (!group.groupMembers) {\n return [];\n }\n return group.groupMembers\n .map(m => devices.find(d => d.sku === m.sku && d.deviceId === m.deviceId))\n .filter((d): d is GoveeDevice => d !== undefined);\n }\n\n /**\n * Recalculate info.membersUnreachable for all groups.\n * Called when any device's online status changes.\n */\n private updateGroupReachability(): void {\n if (!this.deviceManager || !this.stateManager) {\n return;\n }\n const devices = this.deviceManager.getDevices();\n for (const group of devices) {\n if (group.sku !== \"BaseGroup\" || !group.groupMembers) {\n continue;\n }\n const memberDevices = this.resolveGroupMembers(group, devices);\n this.stateManager.updateGroupMembersUnreachable(group, memberDevices).catch(() => {});\n }\n }\n\n /**\n * Rebuild state definitions for one device and feed them into StateManager.\n * Used both from the full-list callback and from targeted refreshes\n * (e.g. after a local snapshot was added or removed \u2014 no reason to rebuild\n * the entire tree for every device then).\n *\n * @param device Target device\n * @param allDevices Full device list (needed to resolve group members)\n */\n private refreshDeviceStates(device: GoveeDevice, allDevices: GoveeDevice[]): void {\n if (!this.stateManager) {\n return;\n }\n const localSnaps = this.localSnapshots?.getSnapshots(device.sku, device.deviceId);\n let memberDevices: GoveeDevice[] | undefined;\n if (device.sku === \"BaseGroup\" && device.groupMembers) {\n memberDevices = this.resolveGroupMembers(device, allDevices);\n }\n const stateDefs = buildDeviceStateDefs(device, localSnaps, memberDevices);\n const p = this.stateManager\n .createDeviceStates(device, stateDefs)\n .then(async () => {\n // v2.1.0 \u2192 v2.1.1 layout migration: drop legacy info.diagnostics_*\n // before publishing the new diag.tier value. Idempotent.\n await this.stateManager?.migrateLegacyDiagnostics(device);\n await this.stateManager?.updateDeviceTier(device, getDeviceTier(device.sku));\n })\n .catch(e => {\n this.log.error(`createDeviceStates failed for ${device.name}: ${errMessage(e)}`);\n });\n // Until ready, collect so onReady can await the whole initial batch.\n // After ready, fire-and-forget \u2014 the queue would otherwise keep growing\n // with resolved promises for the lifetime of the adapter.\n if (!this.statesReady) {\n this.stateCreationQueue.push(p);\n } else {\n void p;\n }\n }\n\n /**\n * Called by device-manager when the device list changes\n *\n * @param devices Current list of all devices\n */\n private onDeviceListChanged(devices: GoveeDevice[]): void {\n if (!this.stateManager) {\n return;\n }\n\n for (const device of devices) {\n this.refreshDeviceStates(device, devices);\n }\n\n this.updateConnectionState();\n // Cache sync happens once after the initial setup completes (see\n // checkAllReady) \u2014 triggering here would fire on every device update\n // and spam the log.\n\n // Keep adapter-level per-device maps (diagnosticsLastRun, ...) aligned\n // with the new device list so removed devices don't leave orphan keys.\n // Skip during the initial boot phase \u2014 the startup cleanupTimer handles\n // that pass with proper LAN-scan-settled timing.\n if (this.statesReady) {\n this.reapStaleDevices().catch(() => undefined);\n }\n }\n\n /**\n * Update global `info.connection` \u2014 der ioBroker-IDC-Indikator.\n *\n * Semantik:\n * - Mit Devices: `connected = true` wenn MIND. ein Device online ist.\n * Wenn alle offline \u2192 false (User sieht: kein Device antwortet).\n * - Ohne Devices: `connected = true` wenn der LAN-Stack l\u00E4uft. Sonst\n * false (z.B. EADDRINUSE oder bind-Fehler).\n *\n * H4 (geplant f\u00FCr Phase H): nur bei tats\u00E4chlichem Wechsel schreiben\n * (cache lastConnectedValue). Aktuell l\u00E4uft updateConnectionState bei\n * jedem device-state-update \u2014 fire-and-forget setStateAsync, nur leichter\n * Overhead.\n */\n private updateConnectionState(): void {\n const devices = this.deviceManager?.getDevices() ?? [];\n const hasDevices = devices.length > 0;\n const anyOnline = devices.some(d => d.state.online);\n const lanRunning = this.lanClient !== null;\n const connected = hasDevices ? anyOnline : lanRunning;\n if (connected !== this.lastConnectionState) {\n this.lastConnectionState = connected;\n this.setStateAsync(\"info.connection\", { val: connected, ack: true }).catch(() => {});\n }\n }\n\n /**\n * Delete ioBroker objects for devices no longer present and drop the same\n * devices from adapter-level maps. Called after the initial-discovery\n * window and every time the device list changes.\n *\n * Scope of \"stale\" today: cleanupDevices compares the ioBroker object tree\n * against the live device-manager registry \u2014 it deletes objects that\n * outlive their entry in `DeviceManager.devices`. In v2.0 that registry is\n * monotonically growing within a single adapter lifetime (entries only\n * leave via cache pruning across restarts), so this primarily catches\n * tree leftovers from a previous adapter version after upgrade. The\n * adapter-level `diagnosticsLastRun` map is also reaped so it can't outlive\n * its devices either.\n *\n * A future stale-pruning step that explicitly retires devices from the\n * device-manager registry should also drop the device from\n * `deviceManager.devices` and call `getDiagnostics().forget(deviceId)` for\n * each retired device \u2014 those reaping APIs come in with the pruning patch,\n * not before (Memory `feedback_kein_phantom_schema`).\n */\n /**\n * App-Version-Drift-Check gegen iTunes-Lookup.\n *\n * Govee's app2.govee.com-Endpoints rejecten manchmal sehr alte\n * User-Agent-Strings. Daily-Check fragt iTunes nach der aktuellen\n * iOS-App-Version + vergleicht mit `GOVEE_APP_VERSION`. Bei Drift > 2\n * minor versions: warn-Log + state `info.appVersionDrift` schreiben.\n *\n * Failures (5xx, network) werden silent debug-geloggt \u2014 kein User-Impact.\n */\n private async checkAppVersionDrift(): Promise<void> {\n try {\n const resp = await httpsRequest<{ resultCount?: number; results?: Array<{ version?: string }> }>({\n method: \"GET\",\n url: \"https://itunes.apple.com/lookup?bundleId=com.ihoment.GoVeeSensor\",\n headers: { \"User-Agent\": \"ioBroker.govee-smart\" },\n timeout: 10_000,\n });\n const liveVersion = resp.results?.[0]?.version;\n if (typeof liveVersion !== \"string\" || liveVersion.length === 0) {\n return;\n }\n const localParts = GOVEE_APP_VERSION.split(\".\").map(Number);\n const liveParts = liveVersion.split(\".\").map(Number);\n // Major + Minor vergleichen \u2014 patch-Differenz ist OK, Govee toleriert\n // bis ~2 minor.\n const localMajor = localParts[0] ?? 0;\n const localMinor = localParts[1] ?? 0;\n const liveMajor = liveParts[0] ?? 0;\n const liveMinor = liveParts[1] ?? 0;\n const liveTotal = liveMajor * 100 + liveMinor;\n const localTotal = localMajor * 100 + localMinor;\n const driftMinor = liveTotal - localTotal;\n const driftMessage =\n driftMinor === 0\n ? `current (live=${liveVersion}, local=${GOVEE_APP_VERSION})`\n : driftMinor <= 2\n ? `minor drift (live=${liveVersion}, local=${GOVEE_APP_VERSION})`\n : `STALE (live=${liveVersion}, local=${GOVEE_APP_VERSION}) \u2014 bump GOVEE_APP_VERSION`;\n await this.setStateAsync(\"info.appVersionDrift\", { val: driftMessage, ack: true }).catch(() => undefined);\n if (driftMinor > 2) {\n this.log.warn(\n `Govee app version drift: live ${liveVersion} vs local ${GOVEE_APP_VERSION} \u2014 undocumented endpoints may start failing. Run sync-govee-app-version.py + release a new adapter version.`,\n );\n } else {\n this.log.debug(`App version: ${driftMessage}`);\n }\n } catch (e) {\n this.log.debug(`App version check failed: ${errMessage(e)}`);\n }\n }\n\n private async reapStaleDevices(): Promise<void> {\n if (!this.stateManager || !this.deviceManager) {\n return;\n }\n const currentDevices = this.deviceManager.getDevices();\n await this.stateManager.cleanupDevices(currentDevices);\n\n // Diagnostics-buffer f\u00FCr entfernte Devices droppen damit Logs/Packets/\n // Responses l\u00E4ngst entfernter Ger\u00E4te nicht im Speicher bleiben.\n const liveDeviceIds = new Set(currentDevices.map(d => d.deviceId));\n this.deviceManager.getDiagnostics().pruneOrphans(liveDeviceIds);\n\n const liveKeys = new Set(currentDevices.map(d => `${d.sku}:${d.deviceId}`));\n for (const key of this.diagnosticsLastRun.keys()) {\n if (!liveKeys.has(key)) {\n this.diagnosticsLastRun.delete(key);\n }\n }\n }\n\n /**\n * Check if all configured channels are initialized and log ready message.\n * Called from MQTT onConnection callback and end of onReady.\n */\n private checkAllReady(): void {\n if (this.readyLogged) {\n return;\n }\n // Wait for first LAN scan (always active)\n if (!this.lanScanDone) {\n return;\n }\n // Wait for initial state creation to complete\n if (!this.statesReady) {\n return;\n }\n // Wait for Cloud init if configured\n if (this.cloudClient && !this.cloudInitDone) {\n return;\n }\n // Wait for MQTT connection if configured\n if (this.mqttClient && !this.mqttClient.connected) {\n return;\n }\n // H1 \u2014 Wait for OpenAPI-MQTT (Cloud-events for sensors/appliances)\n // wenn konfiguriert. Vorher fehlte das \u2014 Adapter war \"ready\" obwohl\n // Sensor-Events-Channel nicht connected war.\n if (this.openapiMqttClient && !this.openapiMqttClient.connected) {\n return;\n }\n // H2 \u2014 Wait for first App-API-Poll wenn ein Sensor-Device angemeldet\n // ist. App-API liefert die Sensor-Werte (H5179 Temp/Humidity/Battery).\n // Ohne diesen Check w\u00E4re der Adapter \"ready\" mit leeren Sensor-Werten\n // f\u00FCr ~2 Minuten.\n if (this.deviceManager?.hasDeviceNeedingAppApi() && !this.appApiInitialPollDone) {\n return;\n }\n this.readyLogged = true;\n this.logDeviceSummary();\n // Persist any learned changes from the initial load (e.g. resolveSegmentCount\n // collapsing Cloud's 15 to the real 10 on H70D1). One-shot on first ready;\n // subsequent mutations persist themselves (MQTT bumps, wizard, manual-mode).\n this.deviceManager?.saveDevicesToCache();\n }\n\n /**\n * Log final ready message with device/group/channel summary.\n */\n private logDeviceSummary(): void {\n if (!this.deviceManager) {\n return;\n }\n const all = this.deviceManager.getDevices();\n const devices = all.filter(d => d.sku !== \"BaseGroup\");\n const groups = all.filter(d => d.sku === \"BaseGroup\");\n\n // H5 \u2014 Ready-Log expliziter: Channel-Status (LAN+Cloud+MQTT+Cloud-events)\n // plus Devices-Online-Count plus Sensor-Initial-Poll-Marker. User soll\n // EINEN Blick auf den Log werfen und sehen was wirklich operational ist.\n const channels: string[] = [\"LAN\"];\n if (this.cloudWasConnected) {\n channels.push(\"Cloud\");\n }\n if (this.mqttClient?.connected) {\n channels.push(\"MQTT\");\n }\n if (this.openapiMqttClient?.connected) {\n channels.push(\"Cloud-events\");\n }\n\n // Device-summary mit Online-Count\n const lightDevices = devices.filter(d => d.type === \"devices.types.light\");\n const onlineDevices = devices.filter(d => d.state.online === true);\n const parts: string[] = [];\n if (devices.length > 0) {\n const onlineLights = lightDevices.filter(d => d.state.online === true).length;\n const totalLights = lightDevices.length;\n if (totalLights > 0) {\n parts.push(\n totalLights === onlineLights\n ? `${totalLights} light${totalLights > 1 ? \"s\" : \"\"} online`\n : `${totalLights} light${totalLights > 1 ? \"s\" : \"\"} (${onlineLights} online, ${totalLights - onlineLights} offline)`,\n );\n }\n const sensors = devices.length - lightDevices.length;\n if (sensors > 0) {\n const onlineSensors = onlineDevices.filter(d => d.type !== \"devices.types.light\").length;\n parts.push(`${sensors} sensor${sensors > 1 ? \"s\" : \"\"} (${onlineSensors} with data)`);\n }\n }\n if (groups.length > 0) {\n parts.push(`${groups.length} group${groups.length > 1 ? \"s\" : \"\"}`);\n }\n const summary = parts.length > 0 ? parts.join(\", \") : \"no devices found\";\n this.log.info(`Govee adapter ready \u2014 ${summary} \u2014 channels: ${channels.join(\"+\")}`);\n\n // Surface configured-but-not-connected channels with a concrete reason.\n // Truthful \u2014 never claim \"still pending\" when the channel actually failed.\n if (this.cloudClient && !this.cloudWasConnected) {\n const reason = this.cloudClient.getFailureReason();\n this.log.warn(reason ? `Cloud not connected: ${reason}` : \"Cloud not connected \u2014 see earlier errors\");\n }\n if (this.mqttClient && !this.mqttClient.connected) {\n const reason = this.mqttClient.getFailureReason();\n this.log.warn(reason ? `MQTT not connected: ${reason}` : \"MQTT not connected \u2014 see earlier errors\");\n }\n }\n\n /**\n * Load current state for all Cloud devices and populate state values.\n * Called once after initial Cloud device list load.\n */\n private async loadCloudStates(): Promise<void> {\n if (!this.cloudClient || !this.deviceManager || !this.stateManager) {\n return;\n }\n\n const devices = this.deviceManager.getDevices();\n // LAN-first: never overwrite LAN states with Cloud values\n const lanStateIds = new Set(getDefaultLanStates().map(s => s.id));\n let loaded = 0;\n\n for (const device of devices) {\n if (!device.channels.cloud || device.capabilities.length === 0) {\n continue;\n }\n\n try {\n const caps = await this.cloudClient.getDeviceState(device.sku, device.deviceId);\n const prefix = this.stateManager.devicePrefix(device);\n\n const writes: Promise<unknown>[] = [];\n for (const cap of caps) {\n const mapped = mapCloudStateValue(cap);\n if (!mapped) {\n continue;\n }\n // Skip LAN-covered states for LAN-capable devices\n if (device.lanIp && lanStateIds.has(mapped.stateId)) {\n continue;\n }\n const statePath = this.stateManager.resolveStatePath(prefix, mapped.stateId);\n // Fire-and-forget \u2014 States are created before loadCloudStates runs;\n // a rejection here means the state was deleted out-of-band and\n // can be safely ignored.\n writes.push(\n this.setStateAsync(statePath, {\n val: mapped.value,\n ack: true,\n }).catch(() => undefined),\n );\n }\n await Promise.all(writes);\n loaded++;\n } catch {\n this.log.debug(`Could not load Cloud state for ${device.name} (${device.sku})`);\n }\n }\n\n if (loaded > 0) {\n this.log.debug(`Cloud states loaded for ${loaded} devices`);\n }\n }\n\n /**\n * Apply a list of synthesized Cloud-state capabilities to a single\n * device \u2014 the App-API poll and OpenAPI-MQTT events both use this path\n * so their values flow through the same `mapCloudStateValue` pipeline\n * that polled Cloud states use.\n *\n * @param device Target Govee device\n * @param caps Capabilities to apply\n */\n private async applyCloudCapabilities(device: GoveeDevice, caps: CloudStateCapability[]): Promise<void> {\n if (!this.stateManager) {\n return;\n }\n const lanStateIds = new Set(getDefaultLanStates().map(s => s.id));\n const prefix = this.stateManager.devicePrefix(device);\n const planned = planCloudCapabilityWrites(caps, Boolean(device.lanIp), lanStateIds);\n // App-API and OpenAPI-MQTT deliver state IDs (battery, temperature,\n // humidity, lackWater, \u2026) that the Cloud-capability pipeline doesn't\n // declare for sensor/appliance SKUs \u2014 the state objects therefore\n // don't exist yet on first write. ensureSyntheticStateObject creates\n // them lazily with the right channel + role + unit.\n for (const mapped of planned) {\n await this.stateManager.ensureSyntheticStateObject(prefix, mapped.stateId);\n }\n const writes = planned.map(mapped => {\n const statePath = this.stateManager!.resolveStatePath(prefix, mapped.stateId);\n return this.setStateAsync(statePath, {\n val: mapped.value,\n ack: true,\n }).catch(() => undefined);\n });\n await Promise.all(writes);\n }\n\n /**\n * Find device for a state ID\n *\n * @param localId Local state ID without namespace prefix\n */\n private findDeviceForState(localId: string): GoveeDevice | undefined {\n if (!this.deviceManager || !this.stateManager) {\n return undefined;\n }\n\n for (const device of this.deviceManager.getDevices()) {\n const prefix = this.stateManager.devicePrefix(device);\n if (localId.startsWith(`${prefix}.`)) {\n return device;\n }\n }\n return undefined;\n }\n\n /**\n * Map state suffix to command name.\n *\n * Simple suffixes live in a lookup table, segment indices need regex\n * extraction because they're dynamic. The three music states all route\n * to the same \"music\" command \u2014 the handler reads sibling values.\n *\n * @param suffix State ID suffix (e.g. \"power\", \"brightness\")\n */\n private stateToCommand(suffix: string): string | null {\n const direct = STATE_TO_COMMAND[suffix];\n if (direct) {\n return direct;\n }\n const segColorMatch = /^segments\\.(\\d+)\\.color$/.exec(suffix);\n if (segColorMatch) {\n return `segmentColor:${segColorMatch[1]}`;\n }\n const segBrightMatch = /^segments\\.(\\d+)\\.brightness$/.exec(suffix);\n if (segBrightMatch) {\n return `segmentBrightness:${segBrightMatch[1]}`;\n }\n return null;\n }\n\n /**\n * Central entry point for manual-segment updates. Sets the device flags,\n * rebuilds the segment tree (which writes manual_mode + manual_list with\n * ack=true), and persists to cache. Both the user state-change handler\n * and the wizard route their final decisions here.\n *\n * @param device Target device\n * @param mode Whether manual mode should be active\n * @param indices Physical indices when mode=true, ignored otherwise\n */\n private async applyManualSegments(device: GoveeDevice, mode: boolean, indices?: number[]): Promise<void> {\n if (!this.stateManager) {\n return;\n }\n device.manualMode = mode;\n device.manualSegments = mode && Array.isArray(indices) && indices.length > 0 ? indices.slice() : undefined;\n await this.stateManager.createSegmentStates(device);\n this.deviceManager?.persistDeviceToCache(device);\n }\n\n /**\n * React to manual-segments state changes \u2014 parses list, forwards to\n * {@link applyManualSegments}. On parse error disables manual mode so the\n * rejected value doesn't survive in the state tree.\n *\n * @param device Target device\n * @param suffix State suffix (either \"segments.manual_mode\" or \"segments.manual_list\")\n * @param newValue Written value\n */\n private async handleManualSegmentsChange(device: GoveeDevice, suffix: string, newValue: unknown): Promise<void> {\n // Peer value that wasn't just written comes from the device object\n // (kept in sync via createSegmentStates), not a separate state read.\n const modeVal = suffix === \"segments.manual_mode\" ? Boolean(newValue) : device.manualMode === true;\n const listVal =\n suffix === \"segments.manual_list\"\n ? typeof newValue === \"string\"\n ? newValue\n : \"\"\n : Array.isArray(device.manualSegments)\n ? device.manualSegments.join(\",\")\n : \"\";\n\n if (!modeVal) {\n this.log.info(`${device.name}: manual segments disabled \u2014 strip treated as contiguous`);\n await this.applyManualSegments(device, false);\n return;\n }\n\n // Upper bound: cap at the real length if known, otherwise the protocol limit.\n // Real length can still grow via MQTT discovery, so SEGMENT_HARD_MAX is the\n // absolute safety net.\n const maxIndex =\n typeof device.segmentCount === \"number\" && device.segmentCount > 0 ? device.segmentCount - 1 : SEGMENT_HARD_MAX;\n const parsed = parseSegmentList(listVal, maxIndex);\n if (parsed.error) {\n this.log.warn(`${device.name}: manual_list invalid (${parsed.error}) \u2014 disabling manual mode`);\n await this.applyManualSegments(device, false);\n return;\n }\n\n this.log.debug(`${device.name}: manual segments active \u2014 ${parsed.indices.length} physical indices (${listVal})`);\n await this.applyManualSegments(device, true, parsed.indices);\n }\n\n // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Segment-Detection-Wizard \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Handle incoming sendTo messages (from jsonConfig).\n *\n * @param obj ioBroker message object\n */\n /** Construct host object for MessageRouter. */\n private buildMessageRouterHost(): MessageRouterHost {\n return {\n log: this.log,\n getConfig: () => {\n const config = this.config as unknown as AdapterConfig;\n return {\n goveeEmail: config.goveeEmail,\n goveePassword: config.goveePassword,\n mqttVerificationCode: config.mqttVerificationCode,\n };\n },\n sendResponse: (obj, data) => this.sendMessageResponse(obj, data),\n createMqttProbeClient: () => {\n const config = this.config as unknown as AdapterConfig;\n return new GoveeMqttClient(config.goveeEmail, config.goveePassword, this.log, this);\n },\n getSegmentDeviceList: () => {\n const devices = this.deviceManager?.getDevices() ?? [];\n return devices\n .filter(d => d.sku !== \"BaseGroup\" && d.state?.online === true && resolveSegmentCount(d) > 0)\n .map(d => ({\n value: this.deviceKeyFor(d),\n label: `${d.name} (${d.sku}, bisher ${resolveSegmentCount(d)} Segmente)`,\n }));\n },\n runWizardStep: (action, deviceKey) => this.runWizardStep(action, deviceKey),\n };\n }\n\n /**\n * Helper: clear `mqttVerificationCode` in adapter native after a successful\n * login or a 455-fail.\n *\n * Idempotent: liest erst den aktuellen Wert, schreibt nur wenn dirty.\n * Verhindert den Adapter-Restart der durch jeden\n * `extendForeignObjectAsync(system.adapter.X, native:...)`-Call ausgel\u00F6st\n * wird (Memory v2.1.3-Bug). Vorher gab es nach jedem 2FA-Login einen\n * unn\u00F6tigen Restart.\n */\n private async clearVerificationCodeSetting(): Promise<void> {\n try {\n const obj = await this.getForeignObjectAsync(`system.adapter.${this.namespace}`);\n const native = (obj?.native ?? {}) as Record<string, unknown>;\n // Skip wenn das Feld eh schon leer (oder undefined) \u2014 kein dirty write,\n // kein Restart-Trigger.\n if (typeof native.mqttVerificationCode !== \"string\" || native.mqttVerificationCode === \"\") {\n return;\n }\n await this.extendForeignObjectAsync(`system.adapter.${this.namespace}`, {\n native: { mqttVerificationCode: \"\" },\n });\n } catch (e) {\n this.log.warn(`Could not clear mqttVerificationCode: ${errMessage(e)}`);\n }\n }\n\n /**\n * Read persisted MQTT credentials from `info.mqttCredentials`. The\n * sensitive fields (bearer + cert + pass) are encrypted with the\n * system secret on save and decrypted here. Returns null if no\n * credentials are stored or the JSON is unparseable.\n *\n * State-based persistence (since v2.1.3) \u2014 writes to a state instead\n * of `system.adapter.X.native` so saving doesn't trigger an adapter\n * restart. The earlier native-based design caused an endless\n * login \u2192 save \u2192 restart \u2192 login loop.\n */\n private async loadPersistedCredsFromState(): Promise<PersistedMqttCredentials | null> {\n try {\n const s = await this.getStateAsync(\"info.mqttCredentials\");\n const raw = typeof s?.val === \"string\" ? s.val : \"\";\n if (!raw) {\n return null;\n }\n const obj = JSON.parse(raw) as {\n bearerToken?: unknown;\n iotEndpoint?: unknown;\n p12Cert?: unknown;\n p12Pass?: unknown;\n accountId?: unknown;\n accountTopic?: unknown;\n tokenExpiresAt?: unknown;\n };\n // typeof-Guards \u2014 JSON.parse liefert raw, this.decrypt() wirft auf\n // non-string-Input. Defensive: wenn das State-Blob durch ein Tool\n // editiert wurde und falsche Typen drin hat, returnen wir null.\n const safeStr = (v: unknown): string => (typeof v === \"string\" ? v : \"\");\n const bearerToken = this.decrypt(safeStr(obj.bearerToken));\n const p12Cert = this.decrypt(safeStr(obj.p12Cert));\n const p12Pass = this.decrypt(safeStr(obj.p12Pass));\n const iotEndpoint = safeStr(obj.iotEndpoint);\n const accountId = safeStr(obj.accountId);\n const accountTopic = safeStr(obj.accountTopic);\n const tokenExpiresAt = typeof obj.tokenExpiresAt === \"number\" ? obj.tokenExpiresAt : 0;\n if (!bearerToken || !iotEndpoint || !p12Cert || !accountId || !accountTopic || !tokenExpiresAt) {\n return null;\n }\n return { bearerToken, iotEndpoint, p12Cert, p12Pass, accountId, accountTopic, tokenExpiresAt };\n } catch {\n return null;\n }\n }\n\n /**\n * Persist freshly-issued MQTT credentials into `info.mqttCredentials`.\n * Sensitive fields go through `this.encrypt()` so the JSON blob is\n * useless without the system secret. State writes do NOT trigger an\n * adapter restart.\n *\n * @param creds The freshly-issued MQTT bundle from a successful login\n */\n private async persistCredsToState(creds: PersistedMqttCredentials): Promise<void> {\n const blob = JSON.stringify({\n bearerToken: this.encrypt(creds.bearerToken),\n iotEndpoint: creds.iotEndpoint,\n p12Cert: this.encrypt(creds.p12Cert),\n p12Pass: this.encrypt(creds.p12Pass),\n accountId: creds.accountId,\n accountTopic: creds.accountTopic,\n tokenExpiresAt: creds.tokenExpiresAt,\n });\n await this.setStateAsync(\"info.mqttCredentials\", { val: blob, ack: true });\n }\n\n /**\n * One-shot cleanup of legacy v2.1.0/v2.1.1/v2.1.2 plaintext credentials\n * sitting in `system.adapter.X.native`.\n *\n * Doppelte Idempotenz:\n * 1. State-Marker `info.legacyMqttCleaned=true` wird NACH erfolgreichem\n * Wipe gesetzt. Bei sp\u00E4teren Starts wird die Funktion \u00FCber den Marker\n * sofort verlassen \u2014 auch wenn das native irgendwie wieder dirty wird.\n * 2. Nur wenn KEINER der Legacy-Marker gesetzt ist UND das native dirty,\n * wird der einmalige extendForeignObjectAsync (Restart-Trigger) gemacht.\n *\n * Dieser Aufruf triggert genau einmal pro Adapter-Lifetime einen\n * Restart \u2014 das ist unvermeidlich, weil die Plaintext-Bytes weg m\u00FCssen.\n * Aber: nach erfolgreichem Cleanup bleibt der Marker, kein erneuter\n * Restart bei flaky writes.\n */\n private async cleanupLegacyMqttNativeOnce(): Promise<void> {\n try {\n // Marker-State zuerst pr\u00FCfen \u2014 wenn schon gecleant, sofort raus.\n const markerState = await this.getStateAsync(\"info.legacyMqttCleaned\");\n if (markerState?.val === true) {\n return;\n }\n const obj = await this.getForeignObjectAsync(`system.adapter.${this.namespace}`);\n const native = (obj?.native ?? {}) as Record<string, unknown>;\n const legacy = [\n \"mqttBearerToken\",\n \"mqttIotEndpoint\",\n \"mqttP12Cert\",\n \"mqttP12Pass\",\n \"mqttAccountId\",\n \"mqttAccountTopic\",\n \"mqttTokenExpiresAt\",\n ];\n const dirty = legacy.some(k => k in native && native[k] !== \"\" && native[k] !== 0);\n if (!dirty) {\n // Native ist clean (z.B. neue Installation oder schon migriert\n // ohne Marker). Marker setzen damit n\u00E4chster Start gar nicht erst\n // den Native-Read macht.\n await this.setStateAsync(\"info.legacyMqttCleaned\", { val: true, ack: true }).catch(() => undefined);\n return;\n }\n this.log.info(\"Removing legacy plaintext MQTT credentials from native (one-time migration)\");\n const wipe: Record<string, unknown> = {};\n for (const k of legacy) {\n wipe[k] = k === \"mqttTokenExpiresAt\" ? 0 : \"\";\n }\n await this.extendForeignObjectAsync(`system.adapter.${this.namespace}`, { native: wipe });\n // Marker setzen NACHDEM der Wipe erfolgreich war \u2014 bei einem etwaigen\n // Crash zwischen extend und setState l\u00E4uft der Cleanup beim n\u00E4chsten\n // Start nochmal (idempotent: native ist dann eh schon clean).\n await this.setStateAsync(\"info.legacyMqttCleaned\", { val: true, ack: true }).catch(() => undefined);\n } catch (e) {\n this.log.debug(`legacy MQTT cleanup skipped: ${errMessage(e)}`);\n }\n }\n\n private sendMessageResponse(obj: ioBroker.Message, data: unknown): void {\n if (obj.callback && obj.from) {\n this.sendTo(obj.from, obj.command, data as Record<string, unknown>, obj.callback);\n }\n }\n\n /**\n * Stable device key for wizard session tracking.\n *\n * @param device Target device\n */\n private deviceKeyFor(device: GoveeDevice): string {\n return `${device.sku}:${device.deviceId}`;\n }\n\n private findDeviceByKey(key: string): GoveeDevice | undefined {\n const devices = this.deviceManager?.getDevices() ?? [];\n return devices.find(d => this.deviceKeyFor(d) === key);\n }\n\n /** Construct the host object passed into SegmentWizard. */\n private buildWizardHost(): WizardHost {\n return {\n log: this.log,\n getState: id => this.getStateAsync(id),\n sendCommand: async (device, command, value) => {\n await this.deviceManager?.sendCommand(device, command, value);\n },\n flashSegmentAtomic: (device, idx) => {\n if (!device.lanIp || !this.lanClient) {\n return Promise.resolve(false);\n }\n this.lanClient.flashSingleSegment(device.lanIp, idx);\n return Promise.resolve(true);\n },\n restoreStripAtomic: (device, total, color, brightness) => {\n if (!device.lanIp || !this.lanClient) {\n return Promise.resolve(false);\n }\n const r = (color >> 16) & 0xff;\n const g = (color >> 8) & 0xff;\n const b = color & 0xff;\n this.lanClient.restoreAllSegments(device.lanIp, total, r, g, b, brightness);\n return Promise.resolve(true);\n },\n findDevice: key => this.findDeviceByKey(key),\n namespace: this.namespace,\n devicePrefix: device => this.stateManager?.devicePrefix(device) ?? \"\",\n setTimeout: (cb, ms) => this.setTimeout(cb, ms),\n clearTimeout: h => this.clearTimeout(h as ioBroker.Timeout),\n applyWizardResult: (device, result) => this.applyWizardResult(device, result),\n getLanguage: () => this.adminLanguage,\n };\n }\n\n /**\n * Apply a finished wizard's measurement: set the real segment count, then\n * route through {@link applyManualSegments} so the same state-tree rebuild\n * and cache-persist path runs for both wizard results and user edits.\n *\n * @param device Target device\n * @param result Wizard's measurement\n */\n private async applyWizardResult(device: GoveeDevice, result: WizardResult): Promise<void> {\n device.segmentCount = result.segmentCount;\n if (result.hasGaps) {\n const parsed = parseSegmentList(result.manualList, result.segmentCount - 1);\n await this.applyManualSegments(device, true, parsed.error ? undefined : parsed.indices);\n } else {\n await this.applyManualSegments(device, false);\n }\n this.log.debug(\n `applyWizardResult: ${device.sku} \u2192 segmentCount=${result.segmentCount}, ` +\n `manualMode=${device.manualMode}, list=\"${result.manualList}\"`,\n );\n }\n\n /**\n * Execute one wizard step (start/yes/no/abort). Delegates to\n * {@link SegmentWizard} \u2014 see `lib/segment-wizard.ts`.\n *\n * @param action \"start\" | \"yes\" | \"no\" | \"abort\"\n * @param deviceKey device identifier (only required for \"start\")\n */\n private async runWizardStep(action: string, deviceKey: string): Promise<Record<string, unknown>> {\n if (!this.segmentWizard) {\n this.segmentWizard = new SegmentWizard(this.buildWizardHost());\n }\n const response = await this.segmentWizard.runStep(action, deviceKey);\n // Mirror the current wizard status into a plain state so admin's\n // `type: \"state\"` component can show it live via state subscription.\n const statusText = this.segmentWizard.getStatusText();\n await this.setStateAsync(\"info.wizardStatus\", {\n val: statusText,\n ack: true,\n });\n return response;\n }\n\n /** Construct host object for SnapshotHandler \u2014 adapter dependencies injected. */\n private buildSnapshotHost(): SnapshotHandlerHost {\n return {\n log: this.log,\n store: this.localSnapshots!,\n namespace: this.namespace,\n devicePrefix: device => this.stateManager?.devicePrefix(device) ?? \"\",\n getState: id => this.getStateAsync(id),\n sendCommand: async (device, command, value) => {\n await this.deviceManager?.sendCommand(device, command, value);\n },\n refreshDeviceStates: device => {\n this.refreshDeviceStates(device, this.deviceManager?.getDevices() ?? []);\n },\n };\n }\n\n /** Dropdowns whose value is a mode-selection \u2014 reset to \"---\" (0) when the mode stops. */\n private static readonly MODE_DROPDOWNS = [\n \"scenes.light_scene\",\n \"scenes.diy_scene\",\n \"snapshots.snapshot_cloud\",\n \"snapshots.snapshot_local\",\n \"music.music_mode\",\n ];\n\n /** Map command \u2192 its own dropdown path (excluded from reset when that mode is the one that was just activated). */\n private static readonly COMMAND_DROPDOWN: Record<string, string> = {\n lightScene: \"scenes.light_scene\",\n diyScene: \"scenes.diy_scene\",\n snapshot: \"snapshots.snapshot_cloud\",\n snapshotLocal: \"snapshots.snapshot_local\",\n music: \"music.music_mode\",\n colorRgb: \"\",\n colorTemperature: \"\",\n };\n\n /**\n * Diagnostics-Export-Button-Handler. Throttled auf 2s pro Device damit\n * wiederholte/Skript-Trigger keinen Burst von JSON-Serialisierungen\n * erzeugen.\n *\n * @param device Govee device\n * @param prefix Device state prefix\n * @param triggerStateId The state-id that triggered the export (so we can ack)\n */\n private async handleDiagnosticsExport(device: GoveeDevice, prefix: string, triggerStateId: string): Promise<void> {\n if (!this.deviceManager) {\n return;\n }\n const deviceKey = `${device.sku}:${device.deviceId}`;\n const now = Date.now();\n const last = this.diagnosticsLastRun.get(deviceKey) ?? 0;\n if (now - last < 2000) {\n this.log.debug(`Diagnostics export throttled for ${device.name} \u2014 last run ${now - last}ms ago`);\n await this.setStateAsync(triggerStateId, { val: false, ack: true });\n return;\n }\n this.diagnosticsLastRun.set(deviceKey, now);\n const diag = this.deviceManager.generateDiagnostics(device, this.version ?? \"unknown\");\n const resultId = `${this.namespace}.${prefix}.diag.result`;\n await this.setStateAsync(resultId, {\n val: JSON.stringify(diag, null, 2),\n ack: true,\n });\n await this.setStateAsync(triggerStateId, { val: false, ack: true });\n this.log.info(`Diagnostics exported for ${device.name} (${device.sku})`);\n }\n\n /**\n * Generischer Capability-Routing-Pfad f\u00FCr States die nicht im STATE_TO_COMMAND\n * Mapping sind. Liest `native.capabilityType`/`capabilityInstance` aus dem\n * State-Object und schickt das via Cloud-API.\n *\n * @param device Target device\n * @param id Full state-id (f\u00FCr ack)\n * @param stateSuffix State-Pfad innerhalb Device (f\u00FCr debug-log)\n * @param val Value zum Senden\n */\n private async handleGenericCapabilityCommand(\n device: GoveeDevice,\n id: string,\n stateSuffix: string,\n val: ioBroker.StateValue,\n ): Promise<void> {\n if (!this.deviceManager) {\n return;\n }\n const obj = await this.getObjectAsync(id);\n const capType = obj?.native?.capabilityType;\n const capInstance = obj?.native?.capabilityInstance;\n if (typeof capType === \"string\" && typeof capInstance === \"string\") {\n try {\n await this.deviceManager.sendCapabilityCommand(device, capType, capInstance, val);\n await this.setStateAsync(id, { val, ack: true });\n } catch (err) {\n this.log.warn(`Command failed for ${device.name}: ${errMessage(err)}`);\n }\n } else {\n this.log.debug(`Unknown writable state: ${stateSuffix}`);\n }\n }\n\n /**\n * Reset related dropdown states when switching between scenes/snapshots/colors.\n * Each mode-switch resets all OTHER mode dropdowns to \"---\" (0).\n *\n * @param prefix Device state prefix\n * @param activeCommand The command that was just executed\n */\n private async resetRelatedDropdowns(prefix: string, activeCommand: string): Promise<void> {\n if (!(activeCommand in GoveeAdapter.COMMAND_DROPDOWN)) {\n return;\n }\n const ownDropdown = GoveeAdapter.COMMAND_DROPDOWN[activeCommand];\n await this.resetModeDropdowns(prefix, ownDropdown);\n }\n\n /**\n * Reset every mode dropdown except `keep` (empty = reset all). Used both for\n * mode-switches (keep the new mode's own dropdown) and for power-off\n * (reset everything \u2014 a device that's off has no active mode).\n *\n * @param prefix Device state prefix\n * @param keep Dropdown path to leave untouched (e.g. \"music.music_mode\"), or \"\" to reset all\n */\n private async resetModeDropdowns(prefix: string, keep: string): Promise<void> {\n await Promise.all(\n GoveeAdapter.MODE_DROPDOWNS.filter(d => d !== keep).map(async dropdown => {\n const stateId = `${this.namespace}.${prefix}.${dropdown}`;\n const current = await this.getStateAsync(stateId);\n if (current?.val && current.val !== \"0\" && current.val !== 0) {\n await this.setStateAsync(stateId, { val: \"0\", ack: true });\n }\n }),\n );\n }\n}\n\nif (require.main !== module) {\n module.exports = (options: Partial<utils.AdapterOptions> | undefined) => new GoveeAdapter(options);\n} else {\n (() => new GoveeAdapter())();\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import * as utils from \"@iobroker/adapter-core\";\nimport {\n buildDeviceStateDefs,\n getDefaultLanStates,\n mapCloudStateValue,\n planCloudCapabilityWrites,\n} from \"./lib/capability-mapper\";\nimport { getDeviceTier, initDeviceRegistry } from \"./lib/device-registry\";\nimport { DeviceManager, resolveSegmentCount, SEGMENT_HARD_MAX } from \"./lib/device-manager\";\nimport { GoveeApiClient } from \"./lib/govee-api-client\";\nimport { GoveeCloudClient } from \"./lib/govee-cloud-client\";\nimport { GoveeLanClient } from \"./lib/govee-lan-client\";\nimport { GoveeMqttClient } from \"./lib/govee-mqtt-client\";\nimport { GoveeOpenapiMqttClient } from \"./lib/govee-openapi-mqtt-client\";\nimport { LocalSnapshotStore } from \"./lib/local-snapshots\";\nimport { SnapshotHandler, type SnapshotHandlerHost } from \"./lib/snapshot-handler\";\nimport { GroupFanoutHandler, type GroupFanoutHost } from \"./lib/group-fanout\";\nimport { MessageRouter, type MessageRouterHost } from \"./lib/message-router\";\nimport { CloudRetryLoop, type CloudRetryHost } from \"./lib/cloud-retry\";\nimport { RateLimiter } from \"./lib/rate-limiter\";\nimport { SegmentWizard, wizardIdleText, type WizardHost, type WizardResult } from \"./lib/segment-wizard\";\nimport { SkuCache } from \"./lib/sku-cache\";\nimport { StateManager } from \"./lib/state-manager\";\nimport {\n hexToRgb,\n errMessage,\n parseSegmentList,\n resolveStatesValue,\n rgbIntToHex,\n rgbToHex,\n type AdapterConfig,\n type CloudLoadResult,\n type CloudStateCapability,\n type DeviceState,\n type GoveeDevice,\n type PersistedMqttCredentials,\n} from \"./lib/types\";\nimport { APP_API_INITIAL_DELAY_MS, APP_API_POLL_INTERVAL_MS, READY_TIMEOUT_MS } from \"./lib/timing-constants\";\nimport { GOVEE_APP_VERSION } from \"./lib/govee-constants\";\nimport { httpsRequest } from \"./lib/http-client\";\n\n/**\n * Rate limit defaults \u2014 full Cloud API budget (8/min, 9000/day). v2 no\n * longer halves this with govee-appliances because that adapter is\n * deprecated and won't run alongside govee-smart.\n */\nconst FULL_LIMITS = { perMinute: 8, perDay: 9000 };\n\n/**\n * State-suffix \u2192 command-name lookup for writable states. Segment indices\n * are dynamic and handled by regex in stateToCommand \u2014 everything else is\n * a straight string mapping.\n */\nconst STATE_TO_COMMAND: Readonly<Record<string, string>> = {\n \"control.power\": \"power\",\n \"control.brightness\": \"brightness\",\n \"control.colorRgb\": \"colorRgb\",\n \"control.colorTemperature\": \"colorTemperature\",\n \"control.scene\": \"scene\",\n \"control.gradient_toggle\": \"gradientToggle\",\n \"scenes.light_scene\": \"lightScene\",\n \"scenes.diy_scene\": \"diyScene\",\n \"scenes.scene_speed\": \"sceneSpeed\",\n \"music.music_mode\": \"music\",\n \"music.music_sensitivity\": \"music\",\n \"music.music_auto_color\": \"music\",\n \"snapshots.snapshot_cloud\": \"snapshot\",\n \"segments.command\": \"segmentBatch\",\n};\n\nclass GoveeAdapter extends utils.Adapter {\n private deviceManager: DeviceManager | null = null;\n private stateManager: StateManager | null = null;\n private lanClient: GoveeLanClient | null = null;\n private mqttClient: GoveeMqttClient | null = null;\n private openapiMqttClient: GoveeOpenapiMqttClient | null = null;\n private cloudClient: GoveeCloudClient | null = null;\n private rateLimiter: RateLimiter | null = null;\n /** Repeating timer for the App-API poll (sensor-state pull). */\n private appApiPollTimer: ioBroker.Interval | undefined;\n /**\n * One-shot timer for the FIRST app-api poll (5s nach start) \u2014 Handle\n * damit onUnload das wegr\u00E4umen kann bevor es ins Leere feuert.\n */\n private appApiInitialTimer: ioBroker.Timeout | undefined;\n /** One-shot timer for cloud-init 60s safety timeout \u2014 gleiches Pattern. */\n private cloudInitTimer: ioBroker.Timeout | undefined;\n /**\n * Letzter info.connection-Wert \u2014 Cache damit nicht jeder device-update\n * einen unn\u00F6tigen setStateAsync macht (H4).\n */\n private lastConnectionState: boolean | null = null;\n // === Lifecycle-Flags (Adapter-Boot-Sequenz) ===\n // checkAllReady() pr\u00FCft alle 5 Voraussetzungen gleichzeitig \u2014 sie laufen\n // parallel ab, kein lineares STATE_MACHINE-Pattern weil Channels\n // unabh\u00E4ngig connecten.\n /** LAN-Scan-Initial-Wait abgeschlossen (3s nach Start). */\n private lanScanDone = false;\n /** State-Tree-Erstellung f\u00FCr alle Cached/Cloud-Devices fertig. */\n private statesReady = false;\n /** Cloud-Init-Phase abgeschlossen (mit oder ohne Erfolg). */\n private cloudInitDone = false;\n /** True nach dem ersten erfolgreichen App-API-Poll (f\u00FCr Sensoren mit Werten). */\n private appApiInitialPollDone = false;\n /** Verhindert Mehrfach-Ready-Log innerhalb derselben Adapter-Session. */\n private readyLogged = false;\n /** Cloud war mindestens einmal connected \u2014 f\u00FCr \u201Erestored\"-Log nach Down. */\n private cloudWasConnected = false;\n /** T\u00E4gliches Interval f\u00FCr App-Version-Drift-Check gegen App-Store. */\n private appVersionCheckTimer: ioBroker.Interval | undefined;\n // === Sub-Komponenten ===\n private skuCache: SkuCache | null = null;\n private localSnapshots: LocalSnapshotStore | null = null;\n private snapshotHandler: SnapshotHandler | null = null;\n private groupFanout: GroupFanoutHandler | null = null;\n private messageRouter: MessageRouter | null = null;\n private stateCreationQueue: Promise<void>[] = [];\n private lanScanTimer: ioBroker.Timeout | undefined;\n private cleanupTimer: ioBroker.Timeout | undefined;\n private readyTimer: ioBroker.Timeout | undefined;\n private cloudRetry: CloudRetryLoop | null = null;\n private segmentWizard: SegmentWizard | null = null;\n private unhandledRejectionHandler: ((reason: unknown) => void) | null = null;\n private uncaughtExceptionHandler: ((err: Error) => void) | null = null;\n /** Per-device timestamp of the last diagnostics export \u2014 throttle gate */\n private diagnosticsLastRun = new Map<string, number>();\n /** Cached admin language from system.config \u2014 used for wizard UI text */\n private adminLanguage = \"en\";\n /** Last time `requestCode` was triggered via onMessage \u2014 guards against double-click email spam. */\n private lastVerificationRequestMs = 0;\n\n /** @param options Adapter options */\n public constructor(options: Partial<utils.AdapterOptions> = {}) {\n super({ ...options, name: \"govee-smart\" });\n // Per ioBroker rule: async handlers registered on events MUST .catch,\n // otherwise rejections become unhandled \u2192 SIGKILL code 6 \u2192 restart loop.\n this.on(\"ready\", () =>\n this.onReady().catch(e =>\n this.log.error(`onReady crashed: ${e instanceof Error ? (e.stack ?? e.message) : String(e)}`),\n ),\n );\n this.on(\"stateChange\", (id, state) =>\n this.onStateChange(id, state).catch(e => this.log.warn(`onStateChange crashed for ${id}: ${errMessage(e)}`)),\n );\n this.on(\"message\", obj => this.messageRouter?.onMessage(obj));\n this.on(\"unload\", callback => this.onUnload(callback));\n // Last-line-of-defence against unhandled rejections / sync throws from\n // fire-and-forget paths. The per-handler .catch() wrappers cover the\n // direct entry points; this catches whatever slips past them so the\n // adapter logs the cause instead of triggering js-controller SIGKILL.\n this.unhandledRejectionHandler = (reason: unknown) => {\n this.log.error(\n `Unhandled rejection: ${reason instanceof Error ? (reason.stack ?? reason.message) : String(reason)}`,\n );\n };\n this.uncaughtExceptionHandler = (err: Error) => {\n this.log.error(`Uncaught exception: ${err.stack ?? err.message}`);\n };\n process.on(\"unhandledRejection\", this.unhandledRejectionHandler);\n process.on(\"uncaughtException\", this.uncaughtExceptionHandler);\n }\n\n /** Adapter started \u2014 initialize all channels */\n private async onReady(): Promise<void> {\n const config = this.config as unknown as AdapterConfig;\n\n // info channel + states are declared as instanceObjects in\n // io-package.json, so js-controller materialises them on install /\n // upgrade. We only initialise the runtime values here.\n await this.setStateAsync(\"info.connection\", { val: false, ack: true });\n await this.setStateAsync(\"info.mqttConnected\", { val: false, ack: true });\n await this.setStateAsync(\"info.cloudConnected\", { val: false, ack: true });\n await this.setStateAsync(\"info.openapiMqttConnected\", {\n val: false,\n ack: true,\n });\n await this.setStateAsync(\"info.refresh_cloud_data\", {\n val: false,\n ack: true,\n });\n // Load admin language from system.config so wizard prose matches the\n // user's Admin UI. Falls back to English on any lookup failure.\n try {\n const sysConf = await this.getForeignObjectAsync(\"system.config\");\n const lang = (sysConf?.common as { language?: string } | undefined)?.language;\n if (typeof lang === \"string\" && lang.length > 0) {\n this.adminLanguage = lang;\n }\n } catch {\n // Keep default \"en\"\n }\n await this.setStateAsync(\"info.wizardStatus\", {\n val: wizardIdleText(this.adminLanguage),\n ack: true,\n });\n\n this.stateManager = new StateManager(this);\n // General groups online state (reflects Cloud connection)\n await this.stateManager.createGroupsOnlineState(false);\n this.deviceManager = new DeviceManager(this.log, this);\n const dataDir = utils.getAbsoluteInstanceDataDir(this);\n\n // Load device registry from devices.json in the adapter package root.\n // Status filter: verified+reported active by default; seed-status entries\n // require the experimentalQuirks config toggle.\n initDeviceRegistry({\n experimental: config.experimentalQuirks === true,\n log: this.log,\n });\n this.skuCache = new SkuCache(dataDir, this.log);\n this.localSnapshots = new LocalSnapshotStore(dataDir, this.log);\n this.snapshotHandler = new SnapshotHandler(this.buildSnapshotHost());\n this.groupFanout = new GroupFanoutHandler(this.buildGroupFanoutHost());\n this.messageRouter = new MessageRouter(this.buildMessageRouterHost());\n this.deviceManager.setSkuCache(this.skuCache);\n\n // API client for undocumented scene/music/DIY libraries (always available)\n const apiClient = new GoveeApiClient();\n apiClient.setEmail(config.goveeEmail);\n this.deviceManager.setApiClient(apiClient);\n\n this.deviceManager.setCallbacks(\n (device, state) => this.onDeviceStateUpdate(device, state),\n devices => this.onDeviceListChanged(devices),\n );\n\n // Update info.ip when LAN IP changes\n this.deviceManager.onLanIpChanged = (device, ip) => {\n const prefix = this.stateManager!.devicePrefix(device);\n this.setStateAsync(`${prefix}.info.ip`, { val: ip, ack: true }).catch(() => {});\n };\n\n // Sync individual segment states after batch command.\n // Wichtig: Wizard sendet `segmentBatch` mit Indizes 0..SEGMENT_HARD_MAX\n // damit das Ger\u00E4t die echte Strip-L\u00E4nge selbst zeigt. Wir d\u00FCrfen das\n // ECHO aber nur in States schreiben die wirklich existieren \u2014 sonst\n // produziert js-controller den \u201Ehas no existing object\"-WARN f\u00FCr\n // jeden index oberhalb der Cap (z.B. segments.51..55 bei 19-Strip).\n this.deviceManager.onSegmentBatchUpdate = (device, batch) => {\n const prefix = this.stateManager!.devicePrefix(device);\n const cap = typeof device.segmentCount === \"number\" && device.segmentCount > 0 ? device.segmentCount : 0;\n for (const idx of batch.segments) {\n if (cap === 0 || idx >= cap) {\n continue;\n }\n if (batch.color !== undefined) {\n const hex = rgbIntToHex(batch.color);\n this.setStateAsync(`${prefix}.segments.${idx}.color`, {\n val: hex,\n ack: true,\n }).catch(() => {});\n }\n if (batch.brightness !== undefined) {\n this.setStateAsync(`${prefix}.segments.${idx}.brightness`, {\n val: batch.brightness,\n ack: true,\n }).catch(() => {});\n }\n }\n };\n\n // Sync per-segment states from MQTT BLE status push (AA A5 packets).\n // Gleicher Cap-Filter wie bei batch \u2014 defensive vor stale Pakete.\n this.deviceManager.onMqttSegmentUpdate = (device, segments) => {\n const prefix = this.stateManager!.devicePrefix(device);\n const cap = typeof device.segmentCount === \"number\" && device.segmentCount > 0 ? device.segmentCount : 0;\n for (const seg of segments) {\n if (cap === 0 || seg.index >= cap) {\n continue;\n }\n this.setStateAsync(`${prefix}.segments.${seg.index}.color`, {\n val: rgbToHex(seg.r, seg.g, seg.b),\n ack: true,\n }).catch(() => {});\n this.setStateAsync(`${prefix}.segments.${seg.index}.brightness`, {\n val: seg.brightness,\n ack: true,\n }).catch(() => {});\n }\n };\n\n // When MQTT reveals more segments than the Cloud advertised, rebuild\n // the device's state tree so the extra segments get their datapoints.\n this.deviceManager.onSegmentCountGrown = device => {\n if (!this.stateManager) {\n return;\n }\n this.stateManager.createSegmentStates(device).catch(e => {\n this.log.warn(`Failed to rebuild segment tree for ${device.name} after count growth: ${errMessage(e)}`);\n });\n };\n\n // Log startup with configured channels\n const startChannels: string[] = [\"LAN\"];\n if (config.apiKey) {\n startChannels.push(\"Cloud\");\n }\n if (config.goveeEmail && config.goveePassword) {\n startChannels.push(\"MQTT\");\n }\n this.log.info(`Starting (${startChannels.join(\", \")})`);\n\n // --- LAN (always active) ---\n this.lanClient = new GoveeLanClient(this.log, this);\n this.deviceManager.setLanClient(this.lanClient);\n\n this.lanClient.start(\n lanDevice => {\n this.deviceManager!.handleLanDiscovery(lanDevice);\n // Poll status only when MQTT is unavailable. With an active MQTT\n // subscription Govee pushes state changes authoritatively, so the\n // LAN devStatus request would be duplicate traffic.\n if (!this.mqttClient?.connected) {\n this.lanClient!.requestStatus(lanDevice.ip);\n }\n },\n (sourceIp, status) => {\n this.deviceManager!.handleLanStatus(sourceIp, status);\n },\n 30_000,\n config.networkInterface || \"\",\n );\n\n // Wait for first LAN scan responses (UDP multicast, devices respond within 1-2s)\n this.lanScanTimer = this.setTimeout(() => {\n this.lanScanDone = true;\n this.checkAllReady();\n }, 3_000);\n\n // --- MQTT (if account credentials provided) ---\n // Initialize MQTT before Cloud so scene library can load on first cycle\n if (config.goveeEmail && config.goveePassword) {\n this.mqttClient = new GoveeMqttClient(config.goveeEmail, config.goveePassword, this.log, this);\n\n // Forward every parsed MQTT op.command into the diagnostics ring buffer\n // so diag.export contains the recent packets per device.\n this.mqttClient.setPacketHook((deviceId, topic, hex) => {\n this.deviceManager?.getDiagnostics().addMqttPacket(deviceId, topic, hex);\n });\n\n // 2FA: forward optional code from settings into the next login attempt;\n // clear the field automatically once Govee has accepted it.\n this.mqttClient.setVerificationCode(config.mqttVerificationCode ?? \"\");\n this.mqttClient.setOnVerificationConsumed(() => {\n this.clearVerificationCodeSetting().catch(e => {\n this.log.warn(`Could not clear mqttVerificationCode: ${errMessage(e)}`);\n });\n });\n this.mqttClient.setOnVerificationFailed(reason => {\n // On 'failed' (455 / 454+code-was-sent) blank the code so the user\n // doesn't keep retrying with a stale value. On 'pending' (454 + no\n // code) we leave the field as-is \u2014 the user is about to fill it.\n if (reason === \"failed\") {\n this.clearVerificationCodeSetting().catch(() => {});\n }\n });\n\n // Re-use cached MQTT credentials across restarts. Stored in the\n // info.mqttCredentials state (NOT in adapter native): writing to\n // system.adapter.X.0 native triggers a js-controller adapter\n // restart, which would loop endlessly on every login. States are\n // restart-safe.\n //\n // One-shot: clean up legacy v2.1.0/v2.1.1/v2.1.2 native fields\n // that contained plaintext credentials. Best-effort.\n await this.cleanupLegacyMqttNativeOnce();\n const cachedCreds = await this.loadPersistedCredsFromState();\n if (cachedCreds) {\n this.mqttClient.setPersistedCredentials(cachedCreds);\n }\n this.mqttClient.setOnCredentialsRefresh(creds => {\n this.persistCredsToState(creds).catch(e => {\n this.log.warn(`Could not persist MQTT credentials: ${errMessage(e)}`);\n });\n });\n\n await this.mqttClient.connect(\n update => this.deviceManager!.handleMqttStatus(update),\n connected => {\n this.setStateAsync(\"info.mqttConnected\", {\n val: connected,\n ack: true,\n }).catch(() => {});\n if (connected) {\n this.checkAllReady();\n }\n this.updateConnectionState();\n },\n // Forward every fresh bearer token \u2014 fires on initial login and on\n // each reconnect-login, so the API client never runs with a stale one.\n token => apiClient.setBearerToken(token),\n );\n }\n\n // --- Device data: Cache first, Cloud only on cache miss ---\n const cachedOk = this.deviceManager.loadFromCache();\n\n if (config.apiKey) {\n this.cloudClient = new GoveeCloudClient(config.apiKey, this.log);\n // Capture the most recent Cloud response per (deviceId, endpoint) for\n // diagnostics \u2014 bounded by the DiagnosticsCollector's response slot cap.\n this.cloudClient.setResponseHook((deviceId, endpoint, body) => {\n this.deviceManager?.getDiagnostics().setApiResponse(deviceId, endpoint, body);\n });\n this.deviceManager.setCloudClient(this.cloudClient);\n\n // Bridge synthetic capabilities (App-API, OpenAPI-MQTT events) into the\n // same setState pipeline as polled Cloud state. Keeps mapCloudStateValue\n // as the single source of truth for value coercion + state-id resolution.\n this.deviceManager.setOnCloudCapabilities((device, caps) => {\n this.applyCloudCapabilities(device, caps).catch(e =>\n this.log.warn(`applyCloudCapabilities failed for ${device.sku}: ${errMessage(e)}`),\n );\n });\n\n this.rateLimiter = new RateLimiter(this.log, this, FULL_LIMITS.perMinute, FULL_LIMITS.perDay);\n this.rateLimiter.start();\n this.deviceManager.setRateLimiter(this.rateLimiter);\n\n // OpenAPI-MQTT \u2014 push channel for appliance/sensor events\n // (lackWater, iceFull, bodyAppeared etc.). API key is enough; no\n // separate credentials required. Connection runs in parallel to\n // the AWS-IoT MQTT used for status push of regular devices.\n this.openapiMqttClient = new GoveeOpenapiMqttClient(config.apiKey, this.log, this);\n this.openapiMqttClient.connect(\n event => this.deviceManager?.handleOpenApiEvent(event),\n connected => {\n this.setStateAsync(\"info.openapiMqttConnected\", {\n val: connected,\n ack: true,\n }).catch(() => {});\n },\n );\n\n // App-API poll \u2014 every 2 minutes, pulls state for sensors like H5179\n // where OpenAPI v2 /device/state returns empty. Bearer token comes\n // from the AWS-IoT MQTT login, so a no-op until that succeeds.\n const triggerAppApiPoll = (): void => {\n this.deviceManager\n ?.pollAppApi()\n .then(() => {\n // H2 \u2014 Mark initial-poll-done und re-check Ready damit der\n // Adapter \u201Eready\" loggen kann sobald Sensor-Werte da sind.\n if (!this.appApiInitialPollDone) {\n this.appApiInitialPollDone = true;\n this.checkAllReady();\n }\n })\n .catch(e => this.log.debug(`pollAppApi failed: ${errMessage(e)}`));\n };\n this.appApiPollTimer = this.setInterval(triggerAppApiPoll, APP_API_POLL_INTERVAL_MS);\n // Initial poll: gibt MQTT Zeit f\u00FCr den Bearer-Login. Ohne diesen\n // Sofort-Poll bleiben Sensoren wie H5179 die ersten 2 Minuten nach\n // Start offline (Online-Signal kommt nur via App-API). Handle in\n // Member-Variable damit onUnload den Timer cleart.\n this.appApiInitialTimer = this.setTimeout(triggerAppApiPoll, APP_API_INITIAL_DELAY_MS);\n\n if (!cachedOk) {\n // No cache \u2014 first start, fetch from Cloud with 60s hard-timeout.\n // If Cloud hangs/fails, we don't want to block adapter startup indefinitely.\n const result = await this.cloudInitWithTimeout();\n this.cloudWasConnected = result.ok;\n this.ensureCloudRetry().setConnected(result.ok);\n this.setStateAsync(\"info.cloudConnected\", {\n val: result.ok,\n ack: true,\n }).catch(() => {});\n this.stateManager?.updateGroupsOnline(result.ok).catch(() => {});\n\n if (result.ok) {\n await this.loadCloudStates();\n } else {\n this.handleCloudFailure(result);\n }\n } else {\n this.log.info(\"Using cached device data \u2014 no Cloud calls needed\");\n this.cloudWasConnected = true;\n this.ensureCloudRetry().setConnected(true);\n this.setStateAsync(\"info.cloudConnected\", {\n val: true,\n ack: true,\n }).catch(() => {});\n this.stateManager?.updateGroupsOnline(true).catch(() => {});\n }\n // Load group membership from undocumented API (needs bearer token + device map)\n await this.deviceManager.loadGroupMembers();\n\n this.cloudInitDone = true;\n }\n\n // Wait for all state creation from cache/cloud load to complete.\n // Drain-loop: a callback that fires during the await (e.g. a late LAN\n // discovery) can push fresh promises into the queue \u2014 we need to await\n // those too before flipping statesReady, otherwise the initial state\n // tree would be incomplete on very fast startups.\n while (this.stateCreationQueue.length > 0) {\n const pending = this.stateCreationQueue;\n this.stateCreationQueue = [];\n await Promise.all(pending);\n }\n this.statesReady = true;\n\n // Subscribe to all writable device and group states\n await this.subscribeStatesAsync(\"devices.*\");\n await this.subscribeStatesAsync(\"groups.*\");\n await this.subscribeStatesAsync(\"info.refresh_cloud_data\");\n\n // Cleanup stale devices after initial discovery (30s delay for LAN scan).\n // Reaps devices from every adapter-level map that was keyed on them so the\n // process doesn't leak memory across Cloud-side device turnover.\n this.cleanupTimer = this.setTimeout(() => {\n this.reapStaleDevices().catch(e => this.log.debug(`Device cleanup failed: ${errMessage(e)}`));\n }, 30_000);\n\n // App-Version-Drift-Monitor \u2014 daily check + initial nach 2 min wenn der\n // Adapter-Start ohne MQTT-Login durchgeschlagen ist (z.B. LAN-only).\n this.appVersionCheckTimer = this.setInterval(\n () => {\n this.checkAppVersionDrift().catch(e => this.log.debug(`App version check error: ${errMessage(e)}`));\n },\n 24 * 60 * 60 * 1000,\n );\n this.setTimeout(\n () => {\n this.checkAppVersionDrift().catch(e => this.log.debug(`App version check error: ${errMessage(e)}`));\n },\n 2 * 60 * 1000,\n );\n\n this.updateConnectionState();\n\n // Check if all channels are ready \u2014 may already be true if MQTT connected fast\n this.checkAllReady();\n // Safety timeout: log ready even if a channel takes too long.\n // 60s deckt normalen MQTT-Connect + 1 Reconnect-Attempt ab.\n this.readyTimer = this.setTimeout(() => {\n if (!this.readyLogged) {\n // Safety-Timeout: log ready trotzdem auch wenn ein Channel zu lange\n // braucht. READY_TIMEOUT_MS deckt normalen MQTT-Connect + 1 Reconnect.\n this.readyLogged = true;\n this.logDeviceSummary();\n }\n }, 60_000);\n }\n\n /**\n * Initial Cloud-Load mit 60-Sekunden-Hardtimeout.\n * Blockiert nicht l\u00E4nger \u2014 wenn Cloud h\u00E4ngt, geht Adapter mit LAN+MQTT weiter,\n * und der Retry-Loop probiert's passend zum Fehlergrund erneut.\n */\n private async cloudInitWithTimeout(): Promise<CloudLoadResult> {\n if (!this.deviceManager) {\n return { ok: false, reason: \"transient\" };\n }\n const loadPromise = this.deviceManager.loadFromCloud();\n const timeoutPromise = new Promise<CloudLoadResult>(resolve => {\n this.cloudInitTimer = this.setTimeout(() => resolve({ ok: false, reason: \"transient\" }), READY_TIMEOUT_MS);\n });\n try {\n return await Promise.race([loadPromise, timeoutPromise]);\n } catch {\n return { ok: false, reason: \"transient\" };\n }\n }\n\n /** Build the host object for {@link CloudRetryLoop}. */\n private buildCloudRetryHost(): CloudRetryHost {\n return {\n log: this.log,\n setTimeout: (cb, ms) => this.setTimeout(cb, ms),\n clearTimeout: h => this.clearTimeout(h as ioBroker.Timeout),\n loadFromCloud: () => this.cloudInitWithTimeout(),\n onCloudRestored: async () => {\n this.cloudWasConnected = true;\n this.setStateAsync(\"info.cloudConnected\", {\n val: true,\n ack: true,\n }).catch(() => {});\n this.stateManager?.updateGroupsOnline(true).catch(() => {});\n await this.loadCloudStates();\n },\n };\n }\n\n /** Lazy-initialise the retry loop on first use. */\n private ensureCloudRetry(): CloudRetryLoop {\n if (!this.cloudRetry) {\n this.cloudRetry = new CloudRetryLoop(this.buildCloudRetryHost());\n this.cloudRetry.setConnected(this.cloudWasConnected);\n }\n return this.cloudRetry;\n }\n\n /**\n * React to a Cloud-load outcome \u2014 delegates to {@link CloudRetryLoop}.\n *\n * @param result CloudLoadResult from initial load or retry attempt\n */\n private handleCloudFailure(result: CloudLoadResult): void {\n this.ensureCloudRetry().handleResult(result);\n }\n\n /**\n * React to the user writing `info.refresh_cloud_data = true`. Performs one\n * full Cloud reload cycle so newly created scenes/snapshots from the Govee\n * Home app show up without an adapter restart.\n */\n private async handleManualCloudRefresh(): Promise<void> {\n if (!this.deviceManager || !this.cloudClient) {\n this.log.info(\"Refresh cloud data: no Cloud client configured (API key missing) \u2014 nothing to do\");\n return;\n }\n this.log.info(\"Refresh cloud data: re-fetching scenes and snapshots for all devices\");\n try {\n const changed = await this.deviceManager.refreshSceneData();\n if (changed) {\n await this.loadCloudStates();\n }\n // G2 \u2014 kein \u201Edone\"-Log: User hat den Button gedr\u00FCckt, sieht das\n // Ergebnis am Adapter-Verhalten. \u201Edone\" auf info-level w\u00E4re bei\n // Erfolg redundant (Fehler-Pfad direkt darunter ist warn).\n } catch (e) {\n this.log.warn(`Refresh cloud data failed: ${errMessage(e)}`);\n }\n }\n\n /**\n * Adapter stopping \u2014 MUST be synchronous.\n *\n * @param callback Completion callback\n */\n private onUnload(callback: () => void): void {\n try {\n if (this.lanScanTimer) {\n this.clearTimeout(this.lanScanTimer);\n }\n if (this.cleanupTimer) {\n this.clearTimeout(this.cleanupTimer);\n }\n if (this.readyTimer) {\n this.clearTimeout(this.readyTimer);\n }\n if (this.appApiPollTimer) {\n this.clearInterval(this.appApiPollTimer);\n this.appApiPollTimer = undefined;\n }\n if (this.appApiInitialTimer) {\n this.clearTimeout(this.appApiInitialTimer);\n this.appApiInitialTimer = undefined;\n }\n if (this.cloudInitTimer) {\n this.clearTimeout(this.cloudInitTimer);\n this.cloudInitTimer = undefined;\n }\n if (this.appVersionCheckTimer) {\n this.clearInterval(this.appVersionCheckTimer);\n this.appVersionCheckTimer = undefined;\n }\n this.cloudRetry?.dispose();\n this.segmentWizard?.dispose();\n this.lanClient?.stop();\n this.mqttClient?.disconnect();\n this.openapiMqttClient?.disconnect();\n this.rateLimiter?.stop();\n // Remove process-level handlers so an adapter restart doesn't stack them.\n if (this.unhandledRejectionHandler) {\n process.off(\"unhandledRejection\", this.unhandledRejectionHandler);\n this.unhandledRejectionHandler = null;\n }\n if (this.uncaughtExceptionHandler) {\n process.off(\"uncaughtException\", this.uncaughtExceptionHandler);\n this.uncaughtExceptionHandler = null;\n }\n // onUnload MUST be synchronous \u2014 don't await, but silence potential\n // promise rejection during teardown to avoid \"unhandled rejection\" warnings.\n this.setState(\"info.connection\", { val: false, ack: true }).catch(() => {});\n this.setState(\"info.mqttConnected\", { val: false, ack: true }).catch(() => {});\n this.setState(\"info.openapiMqttConnected\", {\n val: false,\n ack: true,\n }).catch(() => {});\n this.setState(\"info.cloudConnected\", { val: false, ack: true }).catch(() => {});\n } catch {\n // ignore\n }\n callback();\n }\n\n /**\n * Handle state changes from user (write operations).\n *\n * @param id State ID\n * @param state New state value\n */\n private async onStateChange(id: string, state: ioBroker.State | null | undefined): Promise<void> {\n if (!state || state.ack || !this.deviceManager || !this.stateManager) {\n return;\n }\n\n // Global refresh button \u2014 triggers one fresh cloud fetch across all\n // devices and re-builds the state tree. Handy after creating a new\n // snapshot in the Govee Home app without restarting the adapter.\n if (id === `${this.namespace}.info.refresh_cloud_data` && state.val) {\n await this.handleManualCloudRefresh();\n await this.setStateAsync(id, { val: false, ack: true });\n return;\n }\n\n // Find which device this state belongs to\n const localId = id.replace(`${this.namespace}.`, \"\");\n if (!localId.startsWith(\"devices.\") && !localId.startsWith(\"groups.\")) {\n return;\n }\n\n const device = this.findDeviceForState(localId);\n if (!device) {\n return;\n }\n\n // Determine command from state suffix after device prefix\n const prefix = this.stateManager.devicePrefix(device);\n const stateSuffix = localId.slice(prefix.length + 1);\n\n // Resolve dropdown input \u2014 accept Number, numeric String, or label\n // (case-insensitive) against the state's common.states map. Returns\n // the canonical key as String so the rest of the handler sees the\n // same shape it always saw (e.g. \"1\"). Non-dropdown states are\n // passed through unchanged.\n const resolved = await this.resolveDropdownInput(id, state.val);\n if (!resolved.ok) {\n this.log.warn(`Unknown dropdown value for ${id}: ${String(state.val)} \u2014 ignoring`);\n return;\n }\n const val = resolved.val;\n\n // Group fan-out: route commands to each member device\n if (device.sku === \"BaseGroup\" && device.groupMembers) {\n await this.groupFanout!.fanOut(device, stateSuffix, val);\n await this.setStateAsync(id, { val, ack: true });\n if (stateSuffix === \"scenes.light_scene\" || stateSuffix === \"music.music_mode\") {\n await this.resetRelatedDropdowns(prefix, stateSuffix === \"scenes.light_scene\" ? \"lightScene\" : \"music\");\n }\n return;\n }\n\n // Handle local snapshot commands (no Cloud/MQTT needed)\n if (stateSuffix === \"snapshots.snapshot_save\" && typeof val === \"string\" && val.trim()) {\n await this.snapshotHandler!.save(device, val.trim());\n await this.setStateAsync(id, { val: \"\", ack: true });\n return;\n }\n if (stateSuffix === \"snapshots.snapshot_local\") {\n if (val !== \"0\" && val !== 0) {\n await this.snapshotHandler!.restore(device, val);\n await this.resetRelatedDropdowns(prefix, \"snapshotLocal\");\n }\n await this.setStateAsync(id, { val, ack: true });\n return;\n }\n if (stateSuffix === \"snapshots.snapshot_delete\" && typeof val === \"string\" && val.trim()) {\n this.snapshotHandler!.delete(device, val.trim());\n await this.setStateAsync(id, { val: \"\", ack: true });\n return;\n }\n\n // Manual segments toggle/list \u2014 handler owns the ack because a parse\n // error rewrites manual_mode to false, and an outer ack with the\n // raw value would resurrect the rejected entry.\n if (stateSuffix === \"segments.manual_mode\" || stateSuffix === \"segments.manual_list\") {\n await this.handleManualSegmentsChange(device, stateSuffix, val);\n return;\n }\n\n if (stateSuffix === \"diag.export\" && val) {\n await this.handleDiagnosticsExport(device, prefix, id);\n return;\n }\n\n const command = this.stateToCommand(stateSuffix);\n\n if (!command) {\n await this.handleGenericCapabilityCommand(device, id, stateSuffix, val);\n return;\n }\n\n // Dropdown reset to \"---\" (value 0) \u2014 acknowledge without sending command\n if ((command === \"lightScene\" || command === \"diyScene\" || command === \"snapshot\") && (val === \"0\" || val === 0)) {\n await this.setStateAsync(id, { val, ack: true });\n return;\n }\n\n // Scene speed: store on device, applied on next scene activation.\n // Persist to SKU cache so the user's choice survives a restart.\n if (command === \"sceneSpeed\") {\n const level = typeof val === \"number\" ? val : parseInt(String(val), 10);\n if (!isNaN(level)) {\n device.sceneSpeed = level;\n this.deviceManager?.persistDeviceToCache(device);\n }\n await this.setStateAsync(id, { val, ack: true });\n return;\n }\n\n try {\n // Music mode: combine all music states into one STRUCT command\n if (command === \"music\") {\n // music_mode \"---\" (value 0) \u2014 acknowledge without sending command\n if (stateSuffix === \"music.music_mode\" && (val === \"0\" || val === 0)) {\n await this.setStateAsync(id, { val, ack: true });\n return;\n }\n await this.sendMusicCommand(device, prefix, stateSuffix, val);\n await this.setStateAsync(id, { val, ack: true });\n // Reset scene/snapshot dropdowns when activating music mode\n if (stateSuffix === \"music.music_mode\") {\n await this.resetRelatedDropdowns(prefix, \"music\");\n }\n return;\n }\n\n await this.deviceManager.sendCommand(device, command, val);\n // Optimistic ack\n await this.setStateAsync(id, { val, ack: true });\n // Reset related dropdowns when switching modes.\n // Power-off is a special case \u2014 the device is off, so no mode is\n // active anymore; reset every mode dropdown so the UI reflects the\n // reality (scene/music/snapshot selections are now just history).\n if (command === \"power\" && val === false) {\n await this.resetModeDropdowns(prefix, \"\");\n } else {\n await this.resetRelatedDropdowns(prefix, command);\n }\n } catch (err) {\n this.log.warn(`Command failed for ${device.name}: ${errMessage(err)}`);\n }\n }\n\n /**\n * Resolve a dropdown-state input value against the state's common.states\n * map. Returns the canonical key (always String form) so a user can write\n * either the index (\"1\"), the index as a number (1) or the label name\n * (\"Aurora\", case-insensitive) \u2014 all three land at the same canonical\n * value for the rest of the handler.\n *\n * Non-dropdown states (no common.states), reset sentinels (0/\"0\"/\"\") and\n * non-string/number inputs are passed through unchanged. A dropdown input\n * that doesn't match any key or label returns ok=false so the caller can\n * warn and skip the command.\n *\n * @param id Full state id\n * @param raw Raw input value as provided by the user/script\n */\n private async resolveDropdownInput(\n id: string,\n raw: ioBroker.StateValue,\n ): Promise<{ val: ioBroker.StateValue; ok: boolean }> {\n if (raw === null || raw === undefined) {\n return { val: raw, ok: true };\n }\n // Reset sentinels \u2014 let the existing branch handle them.\n if (raw === 0 || raw === \"0\" || raw === \"\") {\n return { val: raw, ok: true };\n }\n // Only dropdown candidates have common.states; non-dropdown inputs\n // can't be resolved here so they pass through.\n if (typeof raw !== \"number\" && typeof raw !== \"string\") {\n return { val: raw, ok: true };\n }\n const obj = await this.getObjectAsync(id);\n const states = obj?.common?.states;\n if (!states || typeof states !== \"object\") {\n return { val: raw, ok: true };\n }\n const resolved = resolveStatesValue(raw, states as Record<string, string>);\n if (resolved) {\n return { val: resolved.key, ok: true };\n }\n return { val: raw, ok: false };\n }\n\n /**\n * Build and send a music_setting STRUCT command.\n * Reads sibling music state values and combines them into one API call.\n *\n * @param device Target device\n * @param prefix Device state prefix\n * @param changedSuffix Which music state was changed\n * @param newValue New value for the changed state\n */\n private async sendMusicCommand(\n device: GoveeDevice,\n prefix: string,\n changedSuffix: string,\n newValue: ioBroker.StateValue,\n ): Promise<void> {\n const musicBase = `${this.namespace}.${prefix}.music`;\n\n // Read current sibling values\n const modeState = await this.getStateAsync(`${musicBase}.music_mode`);\n const sensState = await this.getStateAsync(`${musicBase}.music_sensitivity`);\n const autoState = await this.getStateAsync(`${musicBase}.music_auto_color`);\n\n // Apply the changed value, use siblings for the rest\n const musicMode =\n changedSuffix === \"music.music_mode\" ? parseInt(String(newValue), 10) : parseInt(String(modeState?.val ?? 0), 10);\n const sensitivity =\n changedSuffix === \"music.music_sensitivity\" ? (newValue as number) : ((sensState?.val as number) ?? 100);\n const autoColor = changedSuffix === \"music.music_auto_color\" ? (newValue ? 1 : 0) : autoState?.val ? 1 : 0;\n\n if (!musicMode || musicMode === 0) {\n this.log.debug(\"Music mode not selected, skipping command\");\n return;\n }\n\n // LAN first: send via ptReal BLE if device is on LAN\n if (device.lanIp && this.lanClient) {\n // Read current color for RGB-modes (Spectrum=1, Rolling=2)\n let r = 0,\n g = 0,\n b = 0;\n if (musicMode === 1 || musicMode === 2) {\n const colorState = await this.getStateAsync(`${this.namespace}.${prefix}.control.colorRgb`);\n if (colorState?.val && typeof colorState.val === \"string\") {\n ({ r, g, b } = hexToRgb(colorState.val));\n }\n }\n this.lanClient.setMusicMode(device.lanIp, musicMode, r, g, b);\n return;\n }\n\n // Cloud fallback\n const structValue: Record<string, unknown> = {\n musicMode,\n sensitivity,\n autoColor,\n };\n\n await this.deviceManager!.sendCapabilityCommand(\n device,\n \"devices.capabilities.music_setting\",\n \"musicMode\",\n structValue,\n );\n }\n\n /**\n * Called by device-manager when a device state changes\n *\n * @param device Updated device\n * @param state Changed state values\n */\n private onDeviceStateUpdate(device: GoveeDevice, state: Partial<DeviceState>): void {\n if (this.stateManager) {\n this.stateManager.updateDeviceState(device, state).catch(() => {});\n }\n this.updateConnectionState();\n\n // Update group reachability when member online status changes\n if (state.online !== undefined) {\n this.updateGroupReachability();\n }\n\n // Mirror power-off to mode-dropdown reset. Covers MQTT/LAN-initiated\n // power changes (Govee app or physical remote) so the UI stays honest:\n // a device that's off can't be \"playing Aurora-A\" anymore.\n // L11 \u2014 defensive auch 0 als false akzeptieren (Govee schickt Power\n // theoretisch als boolean, aber MQTT-Boundary k\u00F6nnte 0 durchschleusen).\n const powerOff = state.power === false || (state.power as unknown) === 0;\n if (powerOff && this.stateManager) {\n const prefix = this.stateManager.devicePrefix(device);\n this.resetModeDropdowns(prefix, \"\").catch(() => undefined);\n }\n }\n\n /**\n * Fan out a group command to all member devices.\n * Basic controls (power, brightness, color) are sent directly.\n * Scenes/music are matched by name across members.\n *\n * @param group BaseGroup device\n * @param stateSuffix State path suffix (e.g. \"control.power\")\n * @param value Command value\n */\n /** Construct host object for GroupFanoutHandler. */\n private buildGroupFanoutHost(): GroupFanoutHost {\n return {\n log: this.log,\n namespace: this.namespace,\n getDevices: () => this.deviceManager?.getDevices() ?? [],\n sendCommand: async (device, command, value) => {\n await this.deviceManager?.sendCommand(device, command, value);\n },\n devicePrefix: device => this.stateManager?.devicePrefix(device) ?? \"\",\n stateToCommand: suffix => this.stateToCommand(suffix) ?? undefined,\n getObject: id => this.getObjectAsync(id),\n sendMusicCommand: (device, devicePrefix, stateSuffix, value) =>\n this.sendMusicCommand(device, devicePrefix, stateSuffix, value),\n };\n }\n\n /**\n * Resolve group member references to actual device objects.\n * Bleibt in main.ts weil `updateGroupReachability` (auch in main.ts) das\n * direkt nutzt \u2014 Helper-Pfad ist von der Fan-Out-Klasse unabh\u00E4ngig.\n *\n * @param group BaseGroup device with groupMembers\n * @param devices Full device list to search\n */\n private resolveGroupMembers(group: GoveeDevice, devices: GoveeDevice[]): GoveeDevice[] {\n if (!group.groupMembers) {\n return [];\n }\n return group.groupMembers\n .map(m => devices.find(d => d.sku === m.sku && d.deviceId === m.deviceId))\n .filter((d): d is GoveeDevice => d !== undefined);\n }\n\n /**\n * Recalculate info.membersUnreachable for all groups.\n * Called when any device's online status changes.\n */\n private updateGroupReachability(): void {\n if (!this.deviceManager || !this.stateManager) {\n return;\n }\n const devices = this.deviceManager.getDevices();\n for (const group of devices) {\n if (group.sku !== \"BaseGroup\" || !group.groupMembers) {\n continue;\n }\n const memberDevices = this.resolveGroupMembers(group, devices);\n this.stateManager.updateGroupMembersUnreachable(group, memberDevices).catch(() => {});\n }\n }\n\n /**\n * Rebuild state definitions for one device and feed them into StateManager.\n * Used both from the full-list callback and from targeted refreshes\n * (e.g. after a local snapshot was added or removed \u2014 no reason to rebuild\n * the entire tree for every device then).\n *\n * @param device Target device\n * @param allDevices Full device list (needed to resolve group members)\n */\n private refreshDeviceStates(device: GoveeDevice, allDevices: GoveeDevice[]): void {\n if (!this.stateManager) {\n return;\n }\n const localSnaps = this.localSnapshots?.getSnapshots(device.sku, device.deviceId);\n let memberDevices: GoveeDevice[] | undefined;\n if (device.sku === \"BaseGroup\" && device.groupMembers) {\n memberDevices = this.resolveGroupMembers(device, allDevices);\n }\n const stateDefs = buildDeviceStateDefs(device, localSnaps, memberDevices);\n const p = this.stateManager\n .createDeviceStates(device, stateDefs)\n .then(async () => {\n // v2.1.0 \u2192 v2.1.1 layout migration: drop legacy info.diagnostics_*\n // before publishing the new diag.tier value. Idempotent.\n await this.stateManager?.migrateLegacyDiagnostics(device);\n await this.stateManager?.updateDeviceTier(device, getDeviceTier(device.sku));\n })\n .catch(e => {\n this.log.error(`createDeviceStates failed for ${device.name}: ${errMessage(e)}`);\n });\n // Until ready, collect so onReady can await the whole initial batch.\n // After ready, fire-and-forget \u2014 the queue would otherwise keep growing\n // with resolved promises for the lifetime of the adapter.\n if (!this.statesReady) {\n this.stateCreationQueue.push(p);\n } else {\n void p;\n }\n }\n\n /**\n * Called by device-manager when the device list changes\n *\n * @param devices Current list of all devices\n */\n private onDeviceListChanged(devices: GoveeDevice[]): void {\n if (!this.stateManager) {\n return;\n }\n\n for (const device of devices) {\n this.refreshDeviceStates(device, devices);\n }\n\n this.updateConnectionState();\n // Cache sync happens once after the initial setup completes (see\n // checkAllReady) \u2014 triggering here would fire on every device update\n // and spam the log.\n\n // Keep adapter-level per-device maps (diagnosticsLastRun, ...) aligned\n // with the new device list so removed devices don't leave orphan keys.\n // Skip during the initial boot phase \u2014 the startup cleanupTimer handles\n // that pass with proper LAN-scan-settled timing.\n if (this.statesReady) {\n this.reapStaleDevices().catch(() => undefined);\n }\n }\n\n /**\n * Update global `info.connection` \u2014 der ioBroker-IDC-Indikator.\n *\n * Semantik:\n * - Mit Devices: `connected = true` wenn MIND. ein Device online ist.\n * Wenn alle offline \u2192 false (User sieht: kein Device antwortet).\n * - Ohne Devices: `connected = true` wenn der LAN-Stack l\u00E4uft. Sonst\n * false (z.B. EADDRINUSE oder bind-Fehler).\n *\n * H4 (geplant f\u00FCr Phase H): nur bei tats\u00E4chlichem Wechsel schreiben\n * (cache lastConnectedValue). Aktuell l\u00E4uft updateConnectionState bei\n * jedem device-state-update \u2014 fire-and-forget setStateAsync, nur leichter\n * Overhead.\n */\n private updateConnectionState(): void {\n const devices = this.deviceManager?.getDevices() ?? [];\n const hasDevices = devices.length > 0;\n const anyOnline = devices.some(d => d.state.online);\n const lanRunning = this.lanClient !== null;\n const connected = hasDevices ? anyOnline : lanRunning;\n if (connected !== this.lastConnectionState) {\n this.lastConnectionState = connected;\n this.setStateAsync(\"info.connection\", { val: connected, ack: true }).catch(() => {});\n }\n }\n\n /**\n * Delete ioBroker objects for devices no longer present and drop the same\n * devices from adapter-level maps. Called after the initial-discovery\n * window and every time the device list changes.\n *\n * Scope of \"stale\" today: cleanupDevices compares the ioBroker object tree\n * against the live device-manager registry \u2014 it deletes objects that\n * outlive their entry in `DeviceManager.devices`. In v2.0 that registry is\n * monotonically growing within a single adapter lifetime (entries only\n * leave via cache pruning across restarts), so this primarily catches\n * tree leftovers from a previous adapter version after upgrade. The\n * adapter-level `diagnosticsLastRun` map is also reaped so it can't outlive\n * its devices either.\n *\n * A future stale-pruning step that explicitly retires devices from the\n * device-manager registry should also drop the device from\n * `deviceManager.devices` and call `getDiagnostics().forget(deviceId)` for\n * each retired device \u2014 those reaping APIs come in with the pruning patch,\n * not before (Memory `feedback_kein_phantom_schema`).\n */\n /**\n * App-Version-Drift-Check gegen iTunes-Lookup.\n *\n * Govee's app2.govee.com-Endpoints rejecten manchmal sehr alte\n * User-Agent-Strings. Daily-Check fragt iTunes nach der aktuellen\n * iOS-App-Version + vergleicht mit `GOVEE_APP_VERSION`. Bei Drift > 2\n * minor versions: warn-Log + state `info.appVersionDrift` schreiben.\n *\n * Failures (5xx, network) werden silent debug-geloggt \u2014 kein User-Impact.\n */\n private async checkAppVersionDrift(): Promise<void> {\n try {\n const resp = await httpsRequest<{ resultCount?: number; results?: Array<{ version?: string }> }>({\n method: \"GET\",\n url: \"https://itunes.apple.com/lookup?bundleId=com.ihoment.GoVeeSensor\",\n headers: { \"User-Agent\": \"ioBroker.govee-smart\" },\n timeout: 10_000,\n });\n const liveVersion = resp.results?.[0]?.version;\n if (typeof liveVersion !== \"string\" || liveVersion.length === 0) {\n return;\n }\n const localParts = GOVEE_APP_VERSION.split(\".\").map(Number);\n const liveParts = liveVersion.split(\".\").map(Number);\n // Major + Minor vergleichen \u2014 patch-Differenz ist OK, Govee toleriert\n // bis ~2 minor.\n const localMajor = localParts[0] ?? 0;\n const localMinor = localParts[1] ?? 0;\n const liveMajor = liveParts[0] ?? 0;\n const liveMinor = liveParts[1] ?? 0;\n const liveTotal = liveMajor * 100 + liveMinor;\n const localTotal = localMajor * 100 + localMinor;\n const driftMinor = liveTotal - localTotal;\n const driftMessage =\n driftMinor === 0\n ? `current (live=${liveVersion}, local=${GOVEE_APP_VERSION})`\n : driftMinor <= 2\n ? `minor drift (live=${liveVersion}, local=${GOVEE_APP_VERSION})`\n : `STALE (live=${liveVersion}, local=${GOVEE_APP_VERSION}) \u2014 bump GOVEE_APP_VERSION`;\n await this.setStateAsync(\"info.appVersionDrift\", { val: driftMessage, ack: true }).catch(() => undefined);\n if (driftMinor > 2) {\n this.log.warn(\n `Govee app version drift: live ${liveVersion} vs local ${GOVEE_APP_VERSION} \u2014 undocumented endpoints may start failing. Run sync-govee-app-version.py + release a new adapter version.`,\n );\n } else {\n this.log.debug(`App version: ${driftMessage}`);\n }\n } catch (e) {\n this.log.debug(`App version check failed: ${errMessage(e)}`);\n }\n }\n\n private async reapStaleDevices(): Promise<void> {\n if (!this.stateManager || !this.deviceManager) {\n return;\n }\n const currentDevices = this.deviceManager.getDevices();\n await this.stateManager.cleanupDevices(currentDevices);\n\n // Diagnostics-buffer f\u00FCr entfernte Devices droppen damit Logs/Packets/\n // Responses l\u00E4ngst entfernter Ger\u00E4te nicht im Speicher bleiben.\n const liveDeviceIds = new Set(currentDevices.map(d => d.deviceId));\n this.deviceManager.getDiagnostics().pruneOrphans(liveDeviceIds);\n\n const liveKeys = new Set(currentDevices.map(d => `${d.sku}:${d.deviceId}`));\n for (const key of this.diagnosticsLastRun.keys()) {\n if (!liveKeys.has(key)) {\n this.diagnosticsLastRun.delete(key);\n }\n }\n }\n\n /**\n * Check if all configured channels are initialized and log ready message.\n * Called from MQTT onConnection callback and end of onReady.\n */\n private checkAllReady(): void {\n if (this.readyLogged) {\n return;\n }\n // Wait for first LAN scan (always active)\n if (!this.lanScanDone) {\n return;\n }\n // Wait for initial state creation to complete\n if (!this.statesReady) {\n return;\n }\n // Wait for Cloud init if configured\n if (this.cloudClient && !this.cloudInitDone) {\n return;\n }\n // Wait for MQTT connection if configured\n if (this.mqttClient && !this.mqttClient.connected) {\n return;\n }\n // H1 \u2014 Wait for OpenAPI-MQTT (Cloud-events for sensors/appliances)\n // wenn konfiguriert. Vorher fehlte das \u2014 Adapter war \"ready\" obwohl\n // Sensor-Events-Channel nicht connected war.\n if (this.openapiMqttClient && !this.openapiMqttClient.connected) {\n return;\n }\n // H2 \u2014 Wait for first App-API-Poll wenn ein Sensor-Device angemeldet\n // ist. App-API liefert die Sensor-Werte (H5179 Temp/Humidity/Battery).\n // Ohne diesen Check w\u00E4re der Adapter \"ready\" mit leeren Sensor-Werten\n // f\u00FCr ~2 Minuten.\n if (this.deviceManager?.hasDeviceNeedingAppApi() && !this.appApiInitialPollDone) {\n return;\n }\n this.readyLogged = true;\n this.logDeviceSummary();\n // Persist any learned changes from the initial load (e.g. resolveSegmentCount\n // collapsing Cloud's 15 to the real 10 on H70D1). One-shot on first ready;\n // subsequent mutations persist themselves (MQTT bumps, wizard, manual-mode).\n this.deviceManager?.saveDevicesToCache();\n }\n\n /**\n * Log final ready message with device/group/channel summary.\n */\n private logDeviceSummary(): void {\n if (!this.deviceManager) {\n return;\n }\n const all = this.deviceManager.getDevices();\n const devices = all.filter(d => d.sku !== \"BaseGroup\");\n const groups = all.filter(d => d.sku === \"BaseGroup\");\n\n // H5 \u2014 Ready-Log expliziter: Channel-Status (LAN+Cloud+MQTT+Cloud-events)\n // plus Devices-Online-Count plus Sensor-Initial-Poll-Marker. User soll\n // EINEN Blick auf den Log werfen und sehen was wirklich operational ist.\n const channels: string[] = [\"LAN\"];\n if (this.cloudWasConnected) {\n channels.push(\"Cloud\");\n }\n if (this.mqttClient?.connected) {\n channels.push(\"MQTT\");\n }\n if (this.openapiMqttClient?.connected) {\n channels.push(\"Cloud-events\");\n }\n\n // Device-summary mit Online-Count\n const lightDevices = devices.filter(d => d.type === \"devices.types.light\");\n const onlineDevices = devices.filter(d => d.state.online === true);\n const parts: string[] = [];\n if (devices.length > 0) {\n const onlineLights = lightDevices.filter(d => d.state.online === true).length;\n const totalLights = lightDevices.length;\n if (totalLights > 0) {\n parts.push(\n totalLights === onlineLights\n ? `${totalLights} light${totalLights > 1 ? \"s\" : \"\"} online`\n : `${totalLights} light${totalLights > 1 ? \"s\" : \"\"} (${onlineLights} online, ${totalLights - onlineLights} offline)`,\n );\n }\n const sensors = devices.length - lightDevices.length;\n if (sensors > 0) {\n const onlineSensors = onlineDevices.filter(d => d.type !== \"devices.types.light\").length;\n parts.push(`${sensors} sensor${sensors > 1 ? \"s\" : \"\"} (${onlineSensors} with data)`);\n }\n }\n if (groups.length > 0) {\n parts.push(`${groups.length} group${groups.length > 1 ? \"s\" : \"\"}`);\n }\n const summary = parts.length > 0 ? parts.join(\", \") : \"no devices found\";\n this.log.info(`Govee adapter ready \u2014 ${summary} \u2014 channels: ${channels.join(\"+\")}`);\n\n // Surface configured-but-not-connected channels with a concrete reason.\n // Truthful \u2014 never claim \"still pending\" when the channel actually failed.\n if (this.cloudClient && !this.cloudWasConnected) {\n const reason = this.cloudClient.getFailureReason();\n this.log.warn(reason ? `Cloud not connected: ${reason}` : \"Cloud not connected \u2014 see earlier errors\");\n }\n if (this.mqttClient && !this.mqttClient.connected) {\n const reason = this.mqttClient.getFailureReason();\n this.log.warn(reason ? `MQTT not connected: ${reason}` : \"MQTT not connected \u2014 see earlier errors\");\n }\n }\n\n /**\n * Load current state for all Cloud devices and populate state values.\n * Called once after initial Cloud device list load.\n */\n private async loadCloudStates(): Promise<void> {\n if (!this.cloudClient || !this.deviceManager || !this.stateManager) {\n return;\n }\n\n const devices = this.deviceManager.getDevices();\n // LAN-first: never overwrite LAN states with Cloud values\n const lanStateIds = new Set(getDefaultLanStates().map(s => s.id));\n let loaded = 0;\n\n for (const device of devices) {\n if (!device.channels.cloud || device.capabilities.length === 0) {\n continue;\n }\n\n try {\n const caps = await this.cloudClient.getDeviceState(device.sku, device.deviceId);\n const prefix = this.stateManager.devicePrefix(device);\n\n const writes: Promise<unknown>[] = [];\n for (const cap of caps) {\n const mapped = mapCloudStateValue(cap);\n if (!mapped) {\n continue;\n }\n // Skip LAN-covered states for LAN-capable devices\n if (device.lanIp && lanStateIds.has(mapped.stateId)) {\n continue;\n }\n const statePath = this.stateManager.resolveStatePath(prefix, mapped.stateId);\n // Fire-and-forget \u2014 States are created before loadCloudStates runs;\n // a rejection here means the state was deleted out-of-band and\n // can be safely ignored.\n writes.push(\n this.setStateAsync(statePath, {\n val: mapped.value,\n ack: true,\n }).catch(() => undefined),\n );\n }\n await Promise.all(writes);\n loaded++;\n } catch {\n this.log.debug(`Could not load Cloud state for ${device.name} (${device.sku})`);\n }\n }\n\n if (loaded > 0) {\n this.log.debug(`Cloud states loaded for ${loaded} devices`);\n }\n }\n\n /**\n * Apply a list of synthesized Cloud-state capabilities to a single\n * device \u2014 the App-API poll and OpenAPI-MQTT events both use this path\n * so their values flow through the same `mapCloudStateValue` pipeline\n * that polled Cloud states use.\n *\n * @param device Target Govee device\n * @param caps Capabilities to apply\n */\n private async applyCloudCapabilities(device: GoveeDevice, caps: CloudStateCapability[]): Promise<void> {\n if (!this.stateManager) {\n return;\n }\n const lanStateIds = new Set(getDefaultLanStates().map(s => s.id));\n const prefix = this.stateManager.devicePrefix(device);\n const planned = planCloudCapabilityWrites(caps, Boolean(device.lanIp), lanStateIds);\n // App-API and OpenAPI-MQTT deliver state IDs (battery, temperature,\n // humidity, lackWater, \u2026) that the Cloud-capability pipeline doesn't\n // declare for sensor/appliance SKUs \u2014 the state objects therefore\n // don't exist yet on first write. ensureSyntheticStateObject creates\n // them lazily with the right channel + role + unit.\n for (const mapped of planned) {\n await this.stateManager.ensureSyntheticStateObject(prefix, mapped.stateId);\n }\n const writes = planned.map(mapped => {\n const statePath = this.stateManager!.resolveStatePath(prefix, mapped.stateId);\n return this.setStateAsync(statePath, {\n val: mapped.value,\n ack: true,\n }).catch(() => undefined);\n });\n await Promise.all(writes);\n }\n\n /**\n * Find device for a state ID\n *\n * @param localId Local state ID without namespace prefix\n */\n private findDeviceForState(localId: string): GoveeDevice | undefined {\n if (!this.deviceManager || !this.stateManager) {\n return undefined;\n }\n\n for (const device of this.deviceManager.getDevices()) {\n const prefix = this.stateManager.devicePrefix(device);\n if (localId.startsWith(`${prefix}.`)) {\n return device;\n }\n }\n return undefined;\n }\n\n /**\n * Map state suffix to command name.\n *\n * Simple suffixes live in a lookup table, segment indices need regex\n * extraction because they're dynamic. The three music states all route\n * to the same \"music\" command \u2014 the handler reads sibling values.\n *\n * @param suffix State ID suffix (e.g. \"power\", \"brightness\")\n */\n private stateToCommand(suffix: string): string | null {\n const direct = STATE_TO_COMMAND[suffix];\n if (direct) {\n return direct;\n }\n const segColorMatch = /^segments\\.(\\d+)\\.color$/.exec(suffix);\n if (segColorMatch) {\n return `segmentColor:${segColorMatch[1]}`;\n }\n const segBrightMatch = /^segments\\.(\\d+)\\.brightness$/.exec(suffix);\n if (segBrightMatch) {\n return `segmentBrightness:${segBrightMatch[1]}`;\n }\n return null;\n }\n\n /**\n * Central entry point for manual-segment updates. Sets the device flags,\n * rebuilds the segment tree (which writes manual_mode + manual_list with\n * ack=true), and persists to cache. Both the user state-change handler\n * and the wizard route their final decisions here.\n *\n * @param device Target device\n * @param mode Whether manual mode should be active\n * @param indices Physical indices when mode=true, ignored otherwise\n */\n private async applyManualSegments(device: GoveeDevice, mode: boolean, indices?: number[]): Promise<void> {\n if (!this.stateManager) {\n return;\n }\n device.manualMode = mode;\n device.manualSegments = mode && Array.isArray(indices) && indices.length > 0 ? indices.slice() : undefined;\n await this.stateManager.createSegmentStates(device);\n this.deviceManager?.persistDeviceToCache(device);\n }\n\n /**\n * React to manual-segments state changes \u2014 parses list, forwards to\n * {@link applyManualSegments}. On parse error disables manual mode so the\n * rejected value doesn't survive in the state tree.\n *\n * @param device Target device\n * @param suffix State suffix (either \"segments.manual_mode\" or \"segments.manual_list\")\n * @param newValue Written value\n */\n private async handleManualSegmentsChange(device: GoveeDevice, suffix: string, newValue: unknown): Promise<void> {\n // Peer value that wasn't just written comes from the device object\n // (kept in sync via createSegmentStates), not a separate state read.\n const modeVal = suffix === \"segments.manual_mode\" ? Boolean(newValue) : device.manualMode === true;\n const listVal =\n suffix === \"segments.manual_list\"\n ? typeof newValue === \"string\"\n ? newValue\n : \"\"\n : Array.isArray(device.manualSegments)\n ? device.manualSegments.join(\",\")\n : \"\";\n\n if (!modeVal) {\n this.log.info(`${device.name}: manual segments disabled \u2014 strip treated as contiguous`);\n await this.applyManualSegments(device, false);\n return;\n }\n\n // Upper bound: cap at the real length if known, otherwise the protocol limit.\n // Real length can still grow via MQTT discovery, so SEGMENT_HARD_MAX is the\n // absolute safety net.\n const maxIndex =\n typeof device.segmentCount === \"number\" && device.segmentCount > 0 ? device.segmentCount - 1 : SEGMENT_HARD_MAX;\n const parsed = parseSegmentList(listVal, maxIndex);\n if (parsed.error) {\n this.log.warn(`${device.name}: manual_list invalid (${parsed.error}) \u2014 disabling manual mode`);\n await this.applyManualSegments(device, false);\n return;\n }\n\n this.log.debug(`${device.name}: manual segments active \u2014 ${parsed.indices.length} physical indices (${listVal})`);\n await this.applyManualSegments(device, true, parsed.indices);\n }\n\n // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Segment-Detection-Wizard \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Handle incoming sendTo messages (from jsonConfig).\n *\n * @param obj ioBroker message object\n */\n /** Construct host object for MessageRouter. */\n private buildMessageRouterHost(): MessageRouterHost {\n return {\n log: this.log,\n getConfig: () => {\n const config = this.config as unknown as AdapterConfig;\n return {\n goveeEmail: config.goveeEmail,\n goveePassword: config.goveePassword,\n mqttVerificationCode: config.mqttVerificationCode,\n };\n },\n sendResponse: (obj, data) => this.sendMessageResponse(obj, data),\n createMqttProbeClient: () => {\n const config = this.config as unknown as AdapterConfig;\n return new GoveeMqttClient(config.goveeEmail, config.goveePassword, this.log, this);\n },\n getSegmentDeviceList: () => {\n const devices = this.deviceManager?.getDevices() ?? [];\n return devices\n .filter(d => d.sku !== \"BaseGroup\" && d.state?.online === true && resolveSegmentCount(d) > 0)\n .map(d => ({\n value: this.deviceKeyFor(d),\n label: `${d.name} (${d.sku}, bisher ${resolveSegmentCount(d)} Segmente)`,\n }));\n },\n runWizardStep: (action, deviceKey) => this.runWizardStep(action, deviceKey),\n };\n }\n\n /**\n * Helper: clear `mqttVerificationCode` in adapter native after a successful\n * login or a 455-fail.\n *\n * Idempotent: liest erst den aktuellen Wert, schreibt nur wenn dirty.\n * Verhindert den Adapter-Restart der durch jeden\n * `extendForeignObjectAsync(system.adapter.X, native:...)`-Call ausgel\u00F6st\n * wird (Memory v2.1.3-Bug). Vorher gab es nach jedem 2FA-Login einen\n * unn\u00F6tigen Restart.\n */\n private async clearVerificationCodeSetting(): Promise<void> {\n try {\n const obj = await this.getForeignObjectAsync(`system.adapter.${this.namespace}`);\n const native = (obj?.native ?? {}) as Record<string, unknown>;\n // Skip wenn das Feld eh schon leer (oder undefined) \u2014 kein dirty write,\n // kein Restart-Trigger.\n if (typeof native.mqttVerificationCode !== \"string\" || native.mqttVerificationCode === \"\") {\n return;\n }\n await this.extendForeignObjectAsync(`system.adapter.${this.namespace}`, {\n native: { mqttVerificationCode: \"\" },\n });\n } catch (e) {\n this.log.warn(`Could not clear mqttVerificationCode: ${errMessage(e)}`);\n }\n }\n\n /**\n * Read persisted MQTT credentials from `info.mqttCredentials`. The\n * sensitive fields (bearer + cert + pass) are encrypted with the\n * system secret on save and decrypted here. Returns null if no\n * credentials are stored or the JSON is unparseable.\n *\n * State-based persistence (since v2.1.3) \u2014 writes to a state instead\n * of `system.adapter.X.native` so saving doesn't trigger an adapter\n * restart. The earlier native-based design caused an endless\n * login \u2192 save \u2192 restart \u2192 login loop.\n */\n private async loadPersistedCredsFromState(): Promise<PersistedMqttCredentials | null> {\n try {\n const s = await this.getStateAsync(\"info.mqttCredentials\");\n const raw = typeof s?.val === \"string\" ? s.val : \"\";\n if (!raw) {\n return null;\n }\n const obj = JSON.parse(raw) as {\n bearerToken?: unknown;\n iotEndpoint?: unknown;\n p12Cert?: unknown;\n p12Pass?: unknown;\n accountId?: unknown;\n accountTopic?: unknown;\n tokenExpiresAt?: unknown;\n };\n // typeof-Guards \u2014 JSON.parse liefert raw, this.decrypt() wirft auf\n // non-string-Input. Defensive: wenn das State-Blob durch ein Tool\n // editiert wurde und falsche Typen drin hat, returnen wir null.\n const safeStr = (v: unknown): string => (typeof v === \"string\" ? v : \"\");\n const bearerToken = this.decrypt(safeStr(obj.bearerToken));\n const p12Cert = this.decrypt(safeStr(obj.p12Cert));\n const p12Pass = this.decrypt(safeStr(obj.p12Pass));\n const iotEndpoint = safeStr(obj.iotEndpoint);\n const accountId = safeStr(obj.accountId);\n const accountTopic = safeStr(obj.accountTopic);\n const tokenExpiresAt = typeof obj.tokenExpiresAt === \"number\" ? obj.tokenExpiresAt : 0;\n if (!bearerToken || !iotEndpoint || !p12Cert || !accountId || !accountTopic || !tokenExpiresAt) {\n return null;\n }\n return { bearerToken, iotEndpoint, p12Cert, p12Pass, accountId, accountTopic, tokenExpiresAt };\n } catch {\n return null;\n }\n }\n\n /**\n * Persist freshly-issued MQTT credentials into `info.mqttCredentials`.\n * Sensitive fields go through `this.encrypt()` so the JSON blob is\n * useless without the system secret. State writes do NOT trigger an\n * adapter restart.\n *\n * @param creds The freshly-issued MQTT bundle from a successful login\n */\n private async persistCredsToState(creds: PersistedMqttCredentials): Promise<void> {\n const blob = JSON.stringify({\n bearerToken: this.encrypt(creds.bearerToken),\n iotEndpoint: creds.iotEndpoint,\n p12Cert: this.encrypt(creds.p12Cert),\n p12Pass: this.encrypt(creds.p12Pass),\n accountId: creds.accountId,\n accountTopic: creds.accountTopic,\n tokenExpiresAt: creds.tokenExpiresAt,\n });\n await this.setStateAsync(\"info.mqttCredentials\", { val: blob, ack: true });\n }\n\n /**\n * One-shot cleanup of legacy v2.1.0/v2.1.1/v2.1.2 plaintext credentials\n * sitting in `system.adapter.X.native`.\n *\n * Doppelte Idempotenz:\n * 1. State-Marker `info.legacyMqttCleaned=true` wird NACH erfolgreichem\n * Wipe gesetzt. Bei sp\u00E4teren Starts wird die Funktion \u00FCber den Marker\n * sofort verlassen \u2014 auch wenn das native irgendwie wieder dirty wird.\n * 2. Nur wenn KEINER der Legacy-Marker gesetzt ist UND das native dirty,\n * wird der einmalige extendForeignObjectAsync (Restart-Trigger) gemacht.\n *\n * Dieser Aufruf triggert genau einmal pro Adapter-Lifetime einen\n * Restart \u2014 das ist unvermeidlich, weil die Plaintext-Bytes weg m\u00FCssen.\n * Aber: nach erfolgreichem Cleanup bleibt der Marker, kein erneuter\n * Restart bei flaky writes.\n */\n private async cleanupLegacyMqttNativeOnce(): Promise<void> {\n try {\n // Marker-State zuerst pr\u00FCfen \u2014 wenn schon gecleant, sofort raus.\n const markerState = await this.getStateAsync(\"info.legacyMqttCleaned\");\n if (markerState?.val === true) {\n return;\n }\n const obj = await this.getForeignObjectAsync(`system.adapter.${this.namespace}`);\n const native = (obj?.native ?? {}) as Record<string, unknown>;\n const legacy = [\n \"mqttBearerToken\",\n \"mqttIotEndpoint\",\n \"mqttP12Cert\",\n \"mqttP12Pass\",\n \"mqttAccountId\",\n \"mqttAccountTopic\",\n \"mqttTokenExpiresAt\",\n ];\n const dirty = legacy.some(k => k in native && native[k] !== \"\" && native[k] !== 0);\n if (!dirty) {\n // Native ist clean (z.B. neue Installation oder schon migriert\n // ohne Marker). Marker setzen damit n\u00E4chster Start gar nicht erst\n // den Native-Read macht.\n await this.setStateAsync(\"info.legacyMqttCleaned\", { val: true, ack: true }).catch(() => undefined);\n return;\n }\n this.log.info(\"Removing legacy plaintext MQTT credentials from native (one-time migration)\");\n const wipe: Record<string, unknown> = {};\n for (const k of legacy) {\n wipe[k] = k === \"mqttTokenExpiresAt\" ? 0 : \"\";\n }\n await this.extendForeignObjectAsync(`system.adapter.${this.namespace}`, { native: wipe });\n // Marker setzen NACHDEM der Wipe erfolgreich war \u2014 bei einem etwaigen\n // Crash zwischen extend und setState l\u00E4uft der Cleanup beim n\u00E4chsten\n // Start nochmal (idempotent: native ist dann eh schon clean).\n await this.setStateAsync(\"info.legacyMqttCleaned\", { val: true, ack: true }).catch(() => undefined);\n } catch (e) {\n this.log.debug(`legacy MQTT cleanup skipped: ${errMessage(e)}`);\n }\n }\n\n private sendMessageResponse(obj: ioBroker.Message, data: unknown): void {\n if (obj.callback && obj.from) {\n this.sendTo(obj.from, obj.command, data as Record<string, unknown>, obj.callback);\n }\n }\n\n /**\n * Stable device key for wizard session tracking.\n *\n * @param device Target device\n */\n private deviceKeyFor(device: GoveeDevice): string {\n return `${device.sku}:${device.deviceId}`;\n }\n\n private findDeviceByKey(key: string): GoveeDevice | undefined {\n const devices = this.deviceManager?.getDevices() ?? [];\n return devices.find(d => this.deviceKeyFor(d) === key);\n }\n\n /** Construct the host object passed into SegmentWizard. */\n private buildWizardHost(): WizardHost {\n return {\n log: this.log,\n getState: id => this.getStateAsync(id),\n sendCommand: async (device, command, value) => {\n await this.deviceManager?.sendCommand(device, command, value);\n },\n flashSegmentAtomic: (device, idx) => {\n if (!device.lanIp || !this.lanClient) {\n return Promise.resolve(false);\n }\n this.lanClient.flashSingleSegment(device.lanIp, idx);\n return Promise.resolve(true);\n },\n restoreStripAtomic: (device, total, color, brightness) => {\n if (!device.lanIp || !this.lanClient) {\n return Promise.resolve(false);\n }\n const r = (color >> 16) & 0xff;\n const g = (color >> 8) & 0xff;\n const b = color & 0xff;\n this.lanClient.restoreAllSegments(device.lanIp, total, r, g, b, brightness);\n return Promise.resolve(true);\n },\n findDevice: key => this.findDeviceByKey(key),\n namespace: this.namespace,\n devicePrefix: device => this.stateManager?.devicePrefix(device) ?? \"\",\n setTimeout: (cb, ms) => this.setTimeout(cb, ms),\n clearTimeout: h => this.clearTimeout(h as ioBroker.Timeout),\n applyWizardResult: (device, result) => this.applyWizardResult(device, result),\n getLanguage: () => this.adminLanguage,\n };\n }\n\n /**\n * Apply a finished wizard's measurement: set the real segment count, then\n * route through {@link applyManualSegments} so the same state-tree rebuild\n * and cache-persist path runs for both wizard results and user edits.\n *\n * @param device Target device\n * @param result Wizard's measurement\n */\n private async applyWizardResult(device: GoveeDevice, result: WizardResult): Promise<void> {\n device.segmentCount = result.segmentCount;\n if (result.hasGaps) {\n const parsed = parseSegmentList(result.manualList, result.segmentCount - 1);\n await this.applyManualSegments(device, true, parsed.error ? undefined : parsed.indices);\n } else {\n await this.applyManualSegments(device, false);\n }\n this.log.debug(\n `applyWizardResult: ${device.sku} \u2192 segmentCount=${result.segmentCount}, ` +\n `manualMode=${device.manualMode}, list=\"${result.manualList}\"`,\n );\n }\n\n /**\n * Execute one wizard step (start/yes/no/abort). Delegates to\n * {@link SegmentWizard} \u2014 see `lib/segment-wizard.ts`.\n *\n * @param action \"start\" | \"yes\" | \"no\" | \"abort\"\n * @param deviceKey device identifier (only required for \"start\")\n */\n private async runWizardStep(action: string, deviceKey: string): Promise<Record<string, unknown>> {\n if (!this.segmentWizard) {\n this.segmentWizard = new SegmentWizard(this.buildWizardHost());\n }\n const response = await this.segmentWizard.runStep(action, deviceKey);\n // Mirror the current wizard status into a plain state so admin's\n // `type: \"state\"` component can show it live via state subscription.\n const statusText = this.segmentWizard.getStatusText();\n await this.setStateAsync(\"info.wizardStatus\", {\n val: statusText,\n ack: true,\n });\n return response;\n }\n\n /** Construct host object for SnapshotHandler \u2014 adapter dependencies injected. */\n private buildSnapshotHost(): SnapshotHandlerHost {\n return {\n log: this.log,\n store: this.localSnapshots!,\n namespace: this.namespace,\n devicePrefix: device => this.stateManager?.devicePrefix(device) ?? \"\",\n getState: id => this.getStateAsync(id),\n sendCommand: async (device, command, value) => {\n await this.deviceManager?.sendCommand(device, command, value);\n },\n refreshDeviceStates: device => {\n this.refreshDeviceStates(device, this.deviceManager?.getDevices() ?? []);\n },\n };\n }\n\n /** Dropdowns whose value is a mode-selection \u2014 reset to \"---\" (0) when the mode stops. */\n private static readonly MODE_DROPDOWNS = [\n \"scenes.light_scene\",\n \"scenes.diy_scene\",\n \"snapshots.snapshot_cloud\",\n \"snapshots.snapshot_local\",\n \"music.music_mode\",\n ];\n\n /** Map command \u2192 its own dropdown path (excluded from reset when that mode is the one that was just activated). */\n private static readonly COMMAND_DROPDOWN: Record<string, string> = {\n lightScene: \"scenes.light_scene\",\n diyScene: \"scenes.diy_scene\",\n snapshot: \"snapshots.snapshot_cloud\",\n snapshotLocal: \"snapshots.snapshot_local\",\n music: \"music.music_mode\",\n colorRgb: \"\",\n colorTemperature: \"\",\n };\n\n /**\n * Diagnostics-Export-Button-Handler. Throttled auf 2s pro Device damit\n * wiederholte/Skript-Trigger keinen Burst von JSON-Serialisierungen\n * erzeugen.\n *\n * @param device Govee device\n * @param prefix Device state prefix\n * @param triggerStateId The state-id that triggered the export (so we can ack)\n */\n private async handleDiagnosticsExport(device: GoveeDevice, prefix: string, triggerStateId: string): Promise<void> {\n if (!this.deviceManager) {\n return;\n }\n const deviceKey = `${device.sku}:${device.deviceId}`;\n const now = Date.now();\n const last = this.diagnosticsLastRun.get(deviceKey) ?? 0;\n if (now - last < 2000) {\n this.log.debug(`Diagnostics export throttled for ${device.name} \u2014 last run ${now - last}ms ago`);\n await this.setStateAsync(triggerStateId, { val: false, ack: true });\n return;\n }\n this.diagnosticsLastRun.set(deviceKey, now);\n const diag = this.deviceManager.generateDiagnostics(device, this.version ?? \"unknown\");\n const resultId = `${this.namespace}.${prefix}.diag.result`;\n await this.setStateAsync(resultId, {\n val: JSON.stringify(diag, null, 2),\n ack: true,\n });\n await this.setStateAsync(triggerStateId, { val: false, ack: true });\n this.log.info(`Diagnostics exported for ${device.name} (${device.sku})`);\n }\n\n /**\n * Generischer Capability-Routing-Pfad f\u00FCr States die nicht im STATE_TO_COMMAND\n * Mapping sind. Liest `native.capabilityType`/`capabilityInstance` aus dem\n * State-Object und schickt das via Cloud-API.\n *\n * @param device Target device\n * @param id Full state-id (f\u00FCr ack)\n * @param stateSuffix State-Pfad innerhalb Device (f\u00FCr debug-log)\n * @param val Value zum Senden\n */\n private async handleGenericCapabilityCommand(\n device: GoveeDevice,\n id: string,\n stateSuffix: string,\n val: ioBroker.StateValue,\n ): Promise<void> {\n if (!this.deviceManager) {\n return;\n }\n const obj = await this.getObjectAsync(id);\n const capType = obj?.native?.capabilityType;\n const capInstance = obj?.native?.capabilityInstance;\n if (typeof capType === \"string\" && typeof capInstance === \"string\") {\n try {\n await this.deviceManager.sendCapabilityCommand(device, capType, capInstance, val);\n await this.setStateAsync(id, { val, ack: true });\n } catch (err) {\n this.log.warn(`Command failed for ${device.name}: ${errMessage(err)}`);\n }\n } else {\n this.log.debug(`Unknown writable state: ${stateSuffix}`);\n }\n }\n\n /**\n * Reset related dropdown states when switching between scenes/snapshots/colors.\n * Each mode-switch resets all OTHER mode dropdowns to \"---\" (0).\n *\n * @param prefix Device state prefix\n * @param activeCommand The command that was just executed\n */\n private async resetRelatedDropdowns(prefix: string, activeCommand: string): Promise<void> {\n if (!(activeCommand in GoveeAdapter.COMMAND_DROPDOWN)) {\n return;\n }\n const ownDropdown = GoveeAdapter.COMMAND_DROPDOWN[activeCommand];\n await this.resetModeDropdowns(prefix, ownDropdown);\n }\n\n /**\n * Reset every mode dropdown except `keep` (empty = reset all). Used both for\n * mode-switches (keep the new mode's own dropdown) and for power-off\n * (reset everything \u2014 a device that's off has no active mode).\n *\n * @param prefix Device state prefix\n * @param keep Dropdown path to leave untouched (e.g. \"music.music_mode\"), or \"\" to reset all\n */\n private async resetModeDropdowns(prefix: string, keep: string): Promise<void> {\n await Promise.all(\n GoveeAdapter.MODE_DROPDOWNS.filter(d => d !== keep).map(async dropdown => {\n const stateId = `${this.namespace}.${prefix}.${dropdown}`;\n const current = await this.getStateAsync(stateId);\n if (current?.val && current.val !== \"0\" && current.val !== 0) {\n await this.setStateAsync(stateId, { val: \"0\", ack: true });\n }\n }),\n );\n }\n}\n\nif (require.main !== module) {\n module.exports = (options: Partial<utils.AdapterOptions> | undefined) => new GoveeAdapter(options);\n} else {\n (() => new GoveeAdapter())();\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["_a"]
|
|
7
7
|
}
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "govee-smart",
|
|
4
|
-
"version": "2.5.
|
|
4
|
+
"version": "2.5.3",
|
|
5
5
|
"news": {
|
|
6
|
+
"2.5.3": {
|
|
7
|
+
"en": "Segment-detection wizard no longer spams 'has no existing object' WARN for indices above the real strip length. Plus: command before cloud-init is silent now (not 'No channel available').",
|
|
8
|
+
"de": "Segment-Erkennungs-Wizard spammt nicht mehr 'has no existing object' fuer Indizes oberhalb der echten Strip-Laenge. Plus: Befehl vor Cloud-Init ist still (nicht 'No channel available').",
|
|
9
|
+
"ru": "Wizard сегментов больше не спамит 'has no existing object' для индексов выше реальной длины ленты. Плюс: команда до cloud-init теперь без WARN.",
|
|
10
|
+
"pt": "O wizard de segmentos nao gera mais o WARN 'has no existing object' para indices acima do comprimento real da fita. Plus: comando antes do cloud-init agora silencioso.",
|
|
11
|
+
"nl": "Segment-detectie wizard spamt 'has no existing object' niet meer voor indices boven de echte strip-lengte. Plus: commando voor cloud-init is nu stil.",
|
|
12
|
+
"fr": "L'assistant de detection de segments ne genere plus le WARN 'has no existing object' pour les indices au-dela de la longueur reelle. Plus : commande avant cloud-init silencieuse.",
|
|
13
|
+
"it": "Il wizard di rilevamento segmenti non spamma piu 'has no existing object' per indici oltre la lunghezza reale. Plus: comando prima di cloud-init ora silenzioso.",
|
|
14
|
+
"es": "El asistente de deteccion de segmentos ya no genera 'has no existing object' WARN para indices fuera del largo real. Plus: comando antes de cloud-init ahora silencioso.",
|
|
15
|
+
"pl": "Wizard wykrywania segmentow nie spamuje juz 'has no existing object' dla indeksow powyzej rzeczywistej dlugosci tasmy. Plus: polecenie przed cloud-init jest teraz ciche.",
|
|
16
|
+
"uk": "Wizard виявлення сегментів більше не спамить 'has no existing object' для індексів понад реальну довжину стрічки. Плюс: команда до cloud-init тепер тиха.",
|
|
17
|
+
"zh-cn": "段检测向导不再为超出真实灯带长度的索引刷 'has no existing object' 警告。加上:cloud-init 完成前的命令现在不再产生 'No channel available' 警告。"
|
|
18
|
+
},
|
|
6
19
|
"2.5.2": {
|
|
7
20
|
"en": "Stops the 'membersUnreachable has no existing object' WARN spam every 2 min — group state stays present with empty value. Plus verified H61A8 Outdoor Neon LED Strip 10m.",
|
|
8
21
|
"de": "Behebt WARN-Spam 'membersUnreachable has no existing object' alle 2 Min — Gruppen-State bleibt existent mit leerem Wert. Plus verifiziert: H61A8 Outdoor Neon LED Strip 10m.",
|
|
@@ -80,19 +93,6 @@
|
|
|
80
93
|
"pl": "Pokrycie testami: smoke testy dla GoveeCloudClient + GoveeMqttClient. Initial-state i setter-checks dodane; pelne sciezki mock w osobnym release.",
|
|
81
94
|
"uk": "Покриття тестами: smoke-тести для GoveeCloudClient + GoveeMqttClient. Initial-state та setter-перевірки додані; повні mock-шляхи окремо.",
|
|
82
95
|
"zh-cn": "测试覆盖:GoveeCloudClient + GoveeMqttClient 的 smoke 测试。初始状态和 setter 检查已加入;完整 mock 路径将在后续发布。"
|
|
83
|
-
},
|
|
84
|
-
"2.3.0": {
|
|
85
|
-
"en": "App-version drift monitor: daily iTunes lookup warns when local Govee-App-Version constant gets too stale. Plus code-hygiene: onStateChange handlers split out, timing-constants module.",
|
|
86
|
-
"de": "App-Version-Drift-Monitor: täglicher iTunes-Lookup warnt wenn lokale Govee-App-Version zu alt ist. Plus Code-Hygiene: onStateChange-Handler aufgeteilt, timing-constants-Modul.",
|
|
87
|
-
"ru": "Монитор дрифта App-версии: ежедневная проверка через iTunes warnt при устаревании локальной Govee-App-Version. Плюс гигиена кода: onStateChange-handlers выделены.",
|
|
88
|
-
"pt": "Monitor de drift da App-version: lookup diario no iTunes alerta quando a Govee-App-Version local fica obsoleta. Plus higiene de codigo: onStateChange handlers separados.",
|
|
89
|
-
"nl": "App-version drift monitor: dagelijkse iTunes-lookup waarschuwt als lokale Govee-App-Version verouderd is. Plus code-hygiene: onStateChange handlers uitgesplitst.",
|
|
90
|
-
"fr": "Moniteur de drift App-version : lookup iTunes quotidien alerte quand la Govee-App-Version locale devient obsolete. Plus hygiene de code : onStateChange handlers separes.",
|
|
91
|
-
"it": "Monitor drift App-version: lookup iTunes giornaliero avvisa quando la Govee-App-Version locale diventa obsoleta. Plus igiene del codice: onStateChange handlers separati.",
|
|
92
|
-
"es": "Monitor drift App-version: lookup diario en iTunes avisa cuando la Govee-App-Version local se queda obsoleta. Plus higiene de codigo: onStateChange handlers separados.",
|
|
93
|
-
"pl": "Monitor driftu App-version: codzienny lookup iTunes ostrzega gdy lokalna Govee-App-Version jest przestarzala. Plus higiena kodu: onStateChange handlers wydzielone.",
|
|
94
|
-
"uk": "Монітор дрейфу App-версії: щоденний iTunes-лукап попереджає коли локальна Govee-App-Version застаріла. Плюс гігієна коду: onStateChange handlers виділені.",
|
|
95
|
-
"zh-cn": "App 版本漂移监控:每日 iTunes 查询,本地 Govee 应用版本过旧时发出警告。代码卫生:onStateChange handlers 拆分。"
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
98
|
"titleLang": {
|