iobroker.govee-smart 2.18.2 → 2.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -94,6 +94,11 @@ This adapter's MQTT authentication and BLE-over-LAN (ptReal) protocol implementa
94
94
  Placeholder for the next version (at the beginning of the line):
95
95
  ### **WORK IN PROGRESS**
96
96
  -->
97
+ ### 2.19.0 (2026-07-04)
98
+
99
+ - Device log lines now consistently name devices as "name (model)" — cache maintenance, command errors and wizard messages included, no more bare model/address labels
100
+ - The credentials entry disappears from the object tree — login tokens stay saved in encrypted form, just no longer visible as an object
101
+
97
102
  ### 2.18.2 (2026-07-03)
98
103
 
99
104
  - Internal refactoring. No user-facing changes.
@@ -147,9 +152,6 @@ This adapter's MQTT authentication and BLE-over-LAN (ptReal) protocol implementa
147
152
  - Fixed: temperature-only sensors no longer keep a phantom humidity datapoint stuck at 0 — a device with no humidity sensor drops it, while a real thermo-hygrometer keeps its humidity.
148
153
  - Fixed: a rejected Govee API key is now always reported as "API key rejected — check Govee API key" and stops the retry loop, instead of a generic error and retrying a bad key forever.
149
154
 
150
- ### 2.16.2 (2026-06-16) — stable
151
- - On hosts with multiple network interfaces, LAN device discovery now uses the selected interface for outgoing traffic, so it no longer misses devices by scanning on the wrong one.
152
-
153
155
  [Older changelogs can be found there](CHANGELOG_OLD.md)
154
156
 
155
157
  ## Support
@@ -69,7 +69,6 @@
69
69
  "infoCloudConnected": "Cloud-API verbunden",
70
70
  "infoConnection": "Adapter verbunden",
71
71
  "infoMqttConnected": "MQTT verbunden",
72
- "infoMqttCredentials": "Gespeicherte MQTT-Anmeldedaten",
73
72
  "infoOpenapiMqttConnected": "Govee OpenAPI MQTT verbunden",
74
73
  "infoWizardStatus": "Segment-Wizard Status",
75
74
  "information": "Information",
@@ -69,7 +69,6 @@
69
69
  "infoCloudConnected": "Cloud API connected",
70
70
  "infoConnection": "Adapter connected",
71
71
  "infoMqttConnected": "MQTT connected",
72
- "infoMqttCredentials": "Persisted MQTT credentials",
73
72
  "infoOpenapiMqttConnected": "Govee OpenAPI MQTT connected",
74
73
  "infoWizardStatus": "Segment-Wizard status",
75
74
  "information": "Information",
@@ -69,7 +69,6 @@
69
69
  "infoCloudConnected": "Cloud API conectado",
70
70
  "infoConnection": "Adaptador conectado",
71
71
  "infoMqttConnected": "MQTT conectado",
72
- "infoMqttCredentials": "Credenciales MQTT guardadas",
73
72
  "infoOpenapiMqttConnected": "Govee OpenAPI MQTT conectado",
74
73
  "infoWizardStatus": "Estado del asistente de segmentos",
75
74
  "information": "Información",
@@ -69,7 +69,6 @@
69
69
  "infoCloudConnected": "API Cloud connectée",
70
70
  "infoConnection": "Adaptateur connecté",
71
71
  "infoMqttConnected": "MQTT connecté",
72
- "infoMqttCredentials": "Identifiants MQTT enregistrés",
73
72
  "infoOpenapiMqttConnected": "Govee OpenAPI MQTT connecté",
74
73
  "infoWizardStatus": "État de l'assistant de segments",
75
74
  "information": "Information",
@@ -69,7 +69,6 @@
69
69
  "infoCloudConnected": "Cloud API connesso",
70
70
  "infoConnection": "Adattatore connesso",
71
71
  "infoMqttConnected": "MQTT connesso",
72
- "infoMqttCredentials": "Credenziali MQTT salvate",
73
72
  "infoOpenapiMqttConnected": "Govee OpenAPI MQTT connesso",
74
73
  "infoWizardStatus": "Stato del wizard segmenti",
75
74
  "information": "Informazioni",
@@ -69,7 +69,6 @@
69
69
  "infoCloudConnected": "Cloud-API verbonden",
70
70
  "infoConnection": "Adapter verbonden",
71
71
  "infoMqttConnected": "MQTT verbonden",
72
- "infoMqttCredentials": "Opgeslagen MQTT-referenties",
73
72
  "infoOpenapiMqttConnected": "Govee OpenAPI MQTT verbonden",
74
73
  "infoWizardStatus": "Segment-wizard status",
75
74
  "information": "Informatie",
@@ -69,7 +69,6 @@
69
69
  "infoCloudConnected": "Cloud API połączony",
70
70
  "infoConnection": "Adapter połączony",
71
71
  "infoMqttConnected": "MQTT połączony",
72
- "infoMqttCredentials": "Zapisane dane uwierzytelniające MQTT",
73
72
  "infoOpenapiMqttConnected": "Govee OpenAPI MQTT połączony",
74
73
  "infoWizardStatus": "Status kreatora segmentów",
75
74
  "information": "Informacja",
@@ -69,7 +69,6 @@
69
69
  "infoCloudConnected": "Cloud API conectada",
70
70
  "infoConnection": "Adaptador conectado",
71
71
  "infoMqttConnected": "MQTT conectado",
72
- "infoMqttCredentials": "Credenciais MQTT persistidas",
73
72
  "infoOpenapiMqttConnected": "Govee OpenAPI MQTT conectado",
74
73
  "infoWizardStatus": "Estado do assistente de segmentos",
75
74
  "information": "Informação",
@@ -69,7 +69,6 @@
69
69
  "infoCloudConnected": "Cloud API подключён",
70
70
  "infoConnection": "Адаптер подключён",
71
71
  "infoMqttConnected": "MQTT подключён",
72
- "infoMqttCredentials": "Сохранённые учётные данные MQTT",
73
72
  "infoOpenapiMqttConnected": "Govee OpenAPI MQTT подключён",
74
73
  "infoWizardStatus": "Статус мастера сегментов",
75
74
  "information": "Информация",
@@ -69,7 +69,6 @@
69
69
  "infoCloudConnected": "Cloud API підключено",
70
70
  "infoConnection": "Адаптер підключено",
71
71
  "infoMqttConnected": "MQTT підключено",
72
- "infoMqttCredentials": "Збережені облікові дані MQTT",
73
72
  "infoOpenapiMqttConnected": "Govee OpenAPI MQTT підключено",
74
73
  "infoWizardStatus": "Статус майстра сегментів",
75
74
  "information": "Інформація",
@@ -69,7 +69,6 @@
69
69
  "infoCloudConnected": "云 API 已连接",
70
70
  "infoConnection": "适配器已连接",
71
71
  "infoMqttConnected": "MQTT 已连接",
72
- "infoMqttCredentials": "已保存的 MQTT 凭据",
73
72
  "infoOpenapiMqttConnected": "Govee OpenAPI MQTT 已连接",
74
73
  "infoWizardStatus": "分段向导状态",
75
74
  "information": "信息",
@@ -852,7 +852,7 @@ function buildCloudStateDefs(device, log, localSnapshots, memberDevices) {
852
852
  }
853
853
  }
854
854
  } catch (e) {
855
- log.debug(`${device.sku}: speed-config parse failed for scene "${entry.name}": ${(0, import_types.errMessage)(e)}`);
855
+ log.debug(`${(0, import_types.deviceLabel)(device)}: speed-config parse failed for scene "${entry.name}": ${(0, import_types.errMessage)(e)}`);
856
856
  }
857
857
  }
858
858
  return max;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/capability-mapper.ts"],
4
- "sourcesContent": ["import {\n buildUniqueLabelMap,\n capMatchesControl,\n errMessage,\n rgbToHex,\n type CapabilityOption,\n type CloudCapability,\n type CloudStateCapability,\n type ControlKind,\n type GoveeDevice,\n} from \"./types\";\nimport { applyColorTempQuirk, getDeviceQuirks } from \"./device-registry\";\nimport { GOVEE_CAP_TYPE, GOVEE_DEVICE_TYPE } from \"./govee-constants\";\nimport { resolveLabel, tDesc, tName } from \"./i18n\";\n\n/** ioBroker state definition derived from a Govee capability */\nexport interface StateDefinition {\n /** State ID suffix (e.g. \"power\", \"brightness\", \"color_rgb\") */\n id: string;\n /**\n * Display name. Plain string for capability-derived names (e.g. from\n * `humanize(cap.instance)` of an unknown Govee capability \u2014 those aren't\n * predictable). For known states, a translation object `{en, de, ru, ...}`\n * built via `tName()` \u2014 Admin/vis/Object-Browser pick the user's language\n * automatically.\n */\n name: string | Record<string, string>;\n /**\n * Human-readable description shown in the object browser \u2014 used to clarify\n * ambiguous state names (e.g. cloud vs local snapshots) where the id alone\n * isn't enough for a user to know what the state does.\n */\n desc?: string | Record<string, string>;\n /** ioBroker value type */\n type: ioBroker.CommonType;\n /** ioBroker role */\n role: string;\n /** Whether state is writable */\n write: boolean;\n /** Unit string */\n unit?: string;\n /** Min value for numbers */\n min?: number;\n /** Max value for numbers */\n max?: number;\n /**\n * Predefined values for a select (value \u2192 label).\n *\n * **Labels MUST be plain-string** \u2014 Admin renders states-values as React\n * children and a `{en, de, \u2026}` translation object triggers React Error #31\n * \u2192 fatal \"Error in GUI\" on dropdown open (verified 2026-05-12). For\n * localized labels, resolve via {@link resolveLabel} with the adapter's\n * `system.config.language` value once.\n */\n states?: Record<string, string>;\n /** Default value for new states */\n def?: ioBroker.StateValue;\n /** Original capability type */\n capabilityType: string;\n /** Original capability instance */\n capabilityInstance: string;\n /** Target channel (control, scenes, music, snapshots). Defaults to \"control\". */\n channel?: string;\n}\n\n/**\n * Coerce arbitrary value to boolean. Accepts true/1/\"1\"/\"true\" as truthy.\n *\n * @param v Raw value from API\n */\nfunction coerceBool(v: unknown): boolean {\n return v === true || v === 1 || v === \"1\" || v === \"true\";\n}\n\n/**\n * Stringify an unknown raw API value for an ioBroker state. Objects /\n * functions go through JSON.stringify (so we don't get `[object Object]`);\n * everything else takes the primitive `String()` path. Centralised to keep\n * the no-base-to-string lint rule happy at the call sites without\n * sprinkling type assertions all over.\n *\n * @param v Raw value from API\n */\nfunction safeStringify(v: unknown): string {\n switch (typeof v) {\n case \"string\":\n return v;\n case \"number\":\n case \"bigint\":\n case \"boolean\":\n case \"symbol\":\n return v.toString();\n case \"undefined\":\n return \"undefined\";\n default:\n // object, function, null\n return JSON.stringify(v);\n }\n}\n\n/**\n * Coerce arbitrary value to finite number, or null if not parseable.\n *\n * @param v Raw value from API\n */\nfunction coerceNum(v: unknown): number | null {\n if (typeof v === \"number\" && Number.isFinite(v)) {\n return v;\n }\n if (typeof v === \"string\" && v.trim() !== \"\") {\n const n = Number(v);\n if (Number.isFinite(n)) {\n return n;\n }\n }\n return null;\n}\n\n/**\n * Maps Govee Cloud API capabilities to ioBroker state definitions.\n * Pure function \u2014 no side effects, easily testable.\n *\n * @param capabilities Device capabilities from Cloud API\n * @param log Adapter logger \u2014 per-cap skip-decisions land on debug.\n */\nexport function mapCapabilities(capabilities: CloudCapability[], log: ioBroker.Logger): StateDefinition[] {\n const states: StateDefinition[] = [];\n\n if (!Array.isArray(capabilities)) {\n return states;\n }\n\n let mapped = 0;\n let skipped = 0;\n for (const cap of capabilities) {\n const result = mapSingleCapability(cap);\n if (result) {\n states.push(...result);\n mapped++;\n } else {\n skipped++;\n // Unknown / unhandled cap shape \u2014 log so bug reports can see what\n // Govee sent that we didn't know what to do with. Includes type+\n // instance because that's enough to grep against device-registry\n // entries when adding support for a new SKU.\n log.debug(\n `Cap skipped: type=${cap?.type ?? \"?\"} instance=${cap?.instance ?? \"?\"} \u2014 no mapping (capability not handled or malformed)`,\n );\n }\n }\n log.debug(`mapCapabilities: ${mapped} mapped, ${skipped} skipped, ${states.length} state def(s) produced`);\n\n return states;\n}\n\n/**\n * Probe `capabilities` for a `devices.capabilities.dynamic_scene` entry of\n * the given instance (lightScene / diyScene / snapshot). Used to gate the\n * scene/snapshot dropdowns capability-driven instead of data-driven \u2014 a\n * device that exposes the cap should always show the dropdown, even if\n * the scene list hasn't been fetched yet.\n *\n * @param capabilities Device capabilities from Cloud API\n * @param instance The dynamic_scene instance to look up\n */\nexport function hasDynamicSceneCapability(\n capabilities: CloudCapability[],\n instance: \"lightScene\" | \"diyScene\" | \"snapshot\",\n): boolean {\n if (!Array.isArray(capabilities)) {\n return false;\n }\n return capabilities.some(\n cap =>\n typeof cap?.type === \"string\" &&\n typeof cap?.instance === \"string\" &&\n (cap.type === GOVEE_CAP_TYPE.DYNAMIC_SCENE || cap.type === \"dynamic_scene\") &&\n cap.instance === instance,\n );\n}\n\n/**\n * Single source of truth for \"this state-id belongs to LAN territory\". Used by:\n * - cleanupCloudOwnedStates \u2192 skip these ids when wiping cloud-owned states in the control channel\n * - buildCloudStateDefs \u2192 dedup capability-derived defs against LAN ownership (prevents double-create)\n * - cloud-state-loader \u2192 filter out LAN-state-ids when applying cloud values\n *\n * Adding a new LAN-default state means: extend this set AND add the entry in getDefaultLanStates.\n * The capability-tag-invariant test enforces both stay in lock-step.\n */\nexport const LAN_STATE_IDS: ReadonlySet<string> = new Set([\"power\", \"brightness\", \"color_rgb\", \"color_temperature\"]);\n\n/**\n * Default state definitions for LAN-only devices (no Cloud capabilities).\n * All LAN-capable Govee lights support: power, brightness, color, color temperature.\n *\n * State IDs MUST match LAN_STATE_IDS above. Invariant test in capability-mapper.test.ts\n * fails if these drift apart.\n */\nexport function getDefaultLanStates(): StateDefinition[] {\n return [\n {\n id: \"power\",\n name: tName(\"power\"),\n type: \"boolean\",\n role: \"switch\",\n write: true,\n def: false,\n capabilityType: \"lan\",\n capabilityInstance: \"powerSwitch\",\n },\n {\n id: \"brightness\",\n name: tName(\"brightness\"),\n type: \"number\",\n role: \"level.brightness\",\n write: true,\n min: 0,\n max: 100,\n unit: \"%\",\n def: 0,\n capabilityType: \"lan\",\n capabilityInstance: \"brightness\",\n },\n {\n id: \"color_rgb\",\n name: tName(\"colorRgb\"),\n type: \"string\",\n role: \"level.color.rgb\",\n write: true,\n def: \"#000000\",\n capabilityType: \"lan\",\n capabilityInstance: \"colorRgb\",\n },\n {\n id: \"color_temperature\",\n name: tName(\"colorTemperature\"),\n type: \"number\",\n role: \"level.color.temperature\",\n write: true,\n min: 2000,\n max: 9000,\n unit: \"K\",\n def: 2000,\n capabilityType: \"lan\",\n capabilityInstance: \"colorTemperatureK\",\n },\n ];\n}\n\n/**\n * Map a single capability to state definition(s)\n *\n * @param cap Cloud capability to map\n */\nfunction mapSingleCapability(cap: CloudCapability): StateDefinition[] | null {\n if (!cap || typeof cap.type !== \"string\" || typeof cap.instance !== \"string\") {\n return null;\n }\n const shortType = cap.type.replace(\"devices.capabilities.\", \"\");\n\n switch (shortType) {\n case \"on_off\":\n return [\n {\n id: \"power\",\n name: tName(\"power\"),\n type: \"boolean\",\n role: \"switch\",\n write: true,\n def: false,\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n },\n ];\n\n case \"range\":\n return mapRange(cap);\n\n case \"color_setting\":\n return mapColorSetting(cap);\n\n case \"toggle\":\n return [\n {\n id: sanitizeId(cap.instance),\n name: humanize(cap.instance),\n type: \"boolean\",\n role: \"switch\",\n write: true,\n def: false,\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n },\n ];\n\n case \"mode\":\n return mapMode(cap);\n\n case \"property\":\n return mapProperty(cap);\n\n case \"online\":\n // Handled separately \u2014 not a regular state\n return null;\n\n case \"segment_color_setting\":\n // Segments are handled specially by state-manager\n return [\n {\n id: `_segment_${sanitizeId(cap.instance)}`,\n name: humanize(cap.instance),\n type: \"string\",\n role: \"json\",\n write: true,\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n },\n ];\n\n case \"dynamic_scene\":\n // lightScene / diyScene / snapshot get real dropdowns built later in\n // buildCloudStateDefs from the scenes/snapshots arrays \u2014 skip the\n // generic stub here so we don't create and immediately delete it.\n if (cap.instance === \"lightScene\" || cap.instance === \"diyScene\" || cap.instance === \"snapshot\") {\n return null;\n }\n return [\n {\n id: sanitizeId(cap.instance),\n name: humanize(cap.instance),\n type: \"string\",\n role: \"json\",\n write: true,\n def: \"\",\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n },\n ];\n\n case \"work_mode\":\n return mapWorkMode(cap);\n\n case \"temperature_setting\":\n return mapTemperatureSetting(cap);\n\n case \"event\":\n return mapEvent(cap);\n\n case \"music_setting\":\n return mapMusicSetting(cap);\n\n default:\n return null;\n }\n}\n\n/**\n * Map range capability (brightness, humidity, etc.)\n *\n * @param cap Cloud range capability\n */\nfunction mapRange(cap: CloudCapability): StateDefinition[] {\n const range = cap.parameters?.range;\n const isBrightness = cap.instance.toLowerCase().includes(\"brightness\");\n\n return [\n {\n id: sanitizeId(cap.instance),\n name: humanize(cap.instance),\n type: \"number\",\n role: isBrightness ? \"level.brightness\" : \"level\",\n write: true,\n min: range?.min ?? 0,\n max: range?.max ?? 100,\n unit: normalizeUnit(cap.parameters?.unit),\n def: range?.min ?? 0,\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n },\n ];\n}\n\n/**\n * Map color_setting capability (RGB or color temperature)\n *\n * @param cap Cloud color setting capability\n */\nfunction mapColorSetting(cap: CloudCapability): StateDefinition[] {\n if (cap.instance === \"colorRgb\") {\n return [\n {\n id: \"color_rgb\",\n name: tName(\"colorRgb\"),\n type: \"string\",\n role: \"level.color.rgb\",\n write: true,\n def: \"#000000\",\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n },\n ];\n }\n\n if (cap.instance.includes(\"colorTem\")) {\n const range = cap.parameters?.range;\n return [\n {\n id: \"color_temperature\",\n name: tName(\"colorTemperature\"),\n type: \"number\",\n role: \"level.color.temperature\",\n write: true,\n min: range?.min ?? 2000,\n max: range?.max ?? 9000,\n unit: \"K\",\n def: range?.min ?? 2000,\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n },\n ];\n }\n\n return [];\n}\n\n/**\n * Map mode capability (scenes with ENUM options)\n *\n * @param cap Cloud mode capability\n */\nfunction mapMode(cap: CloudCapability): StateDefinition[] {\n if (cap.instance !== \"presetScene\" || !Array.isArray(cap.parameters?.options)) {\n return [];\n }\n\n const states: Record<string, string> = {};\n for (const opt of cap.parameters.options) {\n if (!opt || typeof opt.name !== \"string\") {\n continue;\n }\n const val = safeStringify(opt.value);\n states[val] = opt.name;\n }\n\n return [\n {\n id: \"scene\",\n name: tName(\"scene\"),\n type: \"mixed\",\n role: \"state\",\n write: true,\n states,\n def: \"\",\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n },\n ];\n}\n\n/**\n * Canonical (role, unit) for the numeric Govee sensor kinds \u2014 the single\n * source of truth shared between {@link mapProperty} (Cloud property caps) and\n * state-manager's synthetic-state table, so a reading gets the same role + unit\n * whether it arrives via the Cloud-capability path or the App-API/MQTT path.\n */\nexport const SENSOR_ROLE_UNIT = {\n temperature: { role: \"value.temperature\", unit: \"\u00B0C\" },\n humidity: { role: \"value.humidity\", unit: \"%\" },\n battery: { role: \"value.battery\", unit: \"%\" },\n co2: { role: \"value.co2\", unit: \"ppm\" },\n} as const;\n\n/**\n * Map property capability (read-only sensors). Routes to the `sensor`\n * channel so a Heater's temperature reading sits cleanly next to other\n * sensor-style states instead of in `control`.\n *\n * @param cap Cloud property capability\n */\nfunction mapProperty(cap: CloudCapability): StateDefinition[] {\n const instance = cap.instance.toLowerCase();\n let role = \"value\";\n let unit: string | undefined;\n\n if (instance.includes(\"temperature\")) {\n ({ role, unit } = SENSOR_ROLE_UNIT.temperature);\n } else if (instance.includes(\"humidity\")) {\n ({ role, unit } = SENSOR_ROLE_UNIT.humidity);\n } else if (instance.includes(\"battery\")) {\n ({ role, unit } = SENSOR_ROLE_UNIT.battery);\n } else if (instance.includes(\"co2\") || instance.includes(\"carbondioxide\")) {\n ({ role, unit } = SENSOR_ROLE_UNIT.co2);\n }\n\n return [\n {\n id: sanitizeId(cap.instance),\n name: humanize(cap.instance),\n type: \"number\",\n role,\n write: false,\n unit: normalizeUnit(cap.parameters?.unit) ?? unit,\n def: 0, // avoid null in vis until the first sensor reading (consistency \u2014 B11)\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n channel: \"sensor\",\n },\n ];\n}\n\n/**\n * Map work_mode capability (STRUCT \u2014 Govee Heater/Humidifier/Fan/...).\n *\n * Two states max:\n * - `work_mode` \u2014 main mode dropdown (mixed type so users can write\n * either the numeric mode value or the label name)\n * - `mode_value` \u2014 secondary parameter (e.g. fan-speed level for the\n * \"manual\" mode); only created if the API actually exposes one\n *\n * @param cap Cloud work_mode capability\n */\nfunction mapWorkMode(cap: CloudCapability): StateDefinition[] {\n const fields = cap.parameters?.fields;\n if (!fields || fields.length === 0) {\n return [\n {\n id: \"work_mode\",\n name: tName(\"workMode\"),\n type: \"mixed\",\n role: \"level.mode.work\",\n write: true,\n def: \"\",\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n },\n ];\n }\n\n const states: StateDefinition[] = [];\n const modeField = fields.find(f => f && f.fieldName === \"workMode\");\n if (modeField?.options && modeField.options.length > 0) {\n const modeStates: Record<string, string> = {};\n for (const opt of modeField.options) {\n if (opt && typeof opt.name === \"string\") {\n modeStates[safeStringify(opt.value)] = opt.name;\n }\n }\n states.push({\n id: \"work_mode\",\n name: tName(\"workMode\"),\n type: \"mixed\",\n role: \"level.mode.work\",\n write: true,\n states: modeStates,\n def: modeField.options[0] ? safeStringify(modeField.options[0].value) : \"\",\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n });\n }\n\n const valueField = fields.find(f => f && f.fieldName === \"modeValue\");\n if (valueField) {\n if (valueField.options && valueField.options.length > 0) {\n const valStates: Record<string, string> = {};\n for (const opt of valueField.options) {\n if (opt && typeof opt.name === \"string\") {\n valStates[safeStringify(opt.value)] = opt.name;\n }\n }\n states.push({\n id: \"mode_value\",\n name: tName(\"modeValue\"),\n type: \"mixed\",\n role: \"state\",\n write: true,\n states: valStates,\n def: valueField.options[0] ? safeStringify(valueField.options[0].value) : \"\",\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n });\n } else if (valueField.range) {\n states.push({\n id: \"mode_value\",\n name: tName(\"modeValue\"),\n type: \"number\",\n role: \"level\",\n write: true,\n min: valueField.range.min,\n max: valueField.range.max,\n def: valueField.range.min,\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n });\n }\n }\n\n return states;\n}\n\n/**\n * Map temperature_setting capability \u2014 Heater target-temp slider.\n * Honours the unit reported by the API (\u00B0F or \u00B0C); falls back to \u00B0F\n * because that's the more common Govee Heater default.\n *\n * @param cap Cloud temperature_setting capability\n */\nfunction mapTemperatureSetting(cap: CloudCapability): StateDefinition[] {\n const fields = cap.parameters?.fields;\n if (Array.isArray(fields) && fields.length > 0) {\n const tempField = fields.find(f => {\n if (!f || typeof f.fieldName !== \"string\") {\n return false;\n }\n if (f.fieldName === \"targetTemperature\") {\n return true;\n }\n return f.fieldName.toLowerCase().includes(\"temperature\");\n });\n if (tempField?.range) {\n const unit = normalizeUnit(cap.parameters?.unit) ?? \"\u00B0F\";\n return [\n {\n id: \"target_temperature\",\n name: tName(\"targetTemperature\"),\n type: \"number\",\n role: \"level.temperature\",\n write: true,\n min: tempField.range.min,\n max: tempField.range.max,\n unit,\n def: tempField.range.min,\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n },\n ];\n }\n }\n\n const range = cap.parameters?.range;\n if (range) {\n const unit = normalizeUnit(cap.parameters?.unit) ?? \"\u00B0F\";\n return [\n {\n id: \"target_temperature\",\n name: tName(\"targetTemperature\"),\n type: \"number\",\n role: \"level.temperature\",\n write: true,\n min: range.min,\n max: range.max,\n unit,\n def: range.min,\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n },\n ];\n }\n\n // No usable schema \u2014 expose the raw payload so the user at least sees\n // the attempt and can report it. Stays JSON to avoid pretending we\n // understand the structure.\n return [\n {\n id: \"target_temperature\",\n name: tName(\"targetTemperature\"),\n type: \"string\",\n role: \"json\",\n write: true,\n def: \"\",\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n },\n ];\n}\n\n/**\n * Map event capability (asynchronous OpenAPI-MQTT alarms \u2014 read-only).\n * Each event becomes a boolean indicator in the events/ channel\n * (lackWater, iceFull, bodyAppeared, dirtDetected, \u2026).\n *\n * @param cap Cloud event capability\n */\nfunction mapEvent(cap: CloudCapability): StateDefinition[] {\n return [\n {\n id: sanitizeId(cap.instance),\n name: humanize(cap.instance),\n type: \"boolean\",\n role: \"indicator.alarm\",\n write: false,\n def: false,\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n channel: \"events\",\n },\n ];\n}\n\n/**\n * Map music_setting capability to user-friendly states.\n * Parses STRUCT fields into: mode dropdown, sensitivity slider, auto-color toggle.\n *\n * @param cap Cloud music_setting capability\n */\n/**\n * Whether a music mode carries a custom RGB colour in the ptReal LAN packet\n * (`33 05 01 <mode> R G B`). Spectrum + Rolling are colour-reactive; Energic,\n * Rhythm and any other mode use the device's own auto-colours.\n *\n * Keyed on the mode NAME, not its numeric value: Govee's music-mode values are\n * SKU-specific (the fleet has 0-based and 1-based SKUs \u2014 see A1), so a\n * value-based gate would append/withhold RGB on the wrong mode for a SKU whose\n * Spectrum/Rolling isn't at value 1/2. An unknown mode name (e.g. \"Sprouting\")\n * returns false \u2014 the safe default is to send no RGB rather than push bytes a\n * mode may not expect.\n *\n * @param name Music mode name from the capability option\n */\nexport function musicModeNameUsesRgb(name: string | undefined): boolean {\n if (typeof name !== \"string\") {\n return false;\n }\n const n = name.trim().toLowerCase();\n return n === \"spectrum\" || n === \"rolling\";\n}\n\n/**\n * Ordered list of valid music-mode options from a music_setting capability.\n * Both the dropdown builder ({@link mapMusicSetting}) and the send path\n * (`sendMusicCommand` in state-change-router) resolve music modes through THIS\n * function, so the index\u2192value mapping can never drift between what the user\n * selects and what is sent to the device.\n *\n * @param cap A music_setting capability\n * @returns The `musicMode` field's options (string name only), in API order\n */\nexport function getMusicModeOptions(cap: CloudCapability): CapabilityOption[] {\n const fields = cap.parameters?.fields;\n if (!Array.isArray(fields)) {\n return [];\n }\n const modeField = fields.find(f => f && typeof f.fieldName === \"string\" && f.fieldName === \"musicMode\");\n if (!modeField?.options || !Array.isArray(modeField.options)) {\n return [];\n }\n return modeField.options.filter(o => !!o && typeof o.name === \"string\");\n}\n\nfunction mapMusicSetting(cap: CloudCapability): StateDefinition[] {\n const fields = cap.parameters?.fields;\n if (!Array.isArray(fields) || fields.length === 0) {\n // No field details from API \u2014 can't create usable states\n return [];\n }\n\n const states: StateDefinition[] = [];\n\n // Mode dropdown \u2014 only if API provides actual mode options. Index-based\n // (0 = \"---\" sentinel), consistent with every other dropdown; the device\n // value for index N is modeOptions[N-1].value, resolved at send time through\n // the SAME getMusicModeOptions() list so build and send never drift. The old\n // value-keyed scheme collided with the sentinel on 0-based SKUs, making the\n // first music mode unreachable (A1).\n const modeOptions = getMusicModeOptions(cap);\n if (modeOptions.length > 0) {\n states.push({\n id: \"music_mode\",\n name: tName(\"musicMode\"),\n type: \"mixed\",\n role: \"state\",\n write: true,\n states: buildUniqueLabelMap(modeOptions),\n def: \"0\",\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n });\n }\n\n // Sensitivity slider\n const sensField = fields.find(f => f && typeof f.fieldName === \"string\" && f.fieldName === \"sensitivity\");\n if (sensField?.range) {\n states.push({\n id: \"music_sensitivity\",\n name: tName(\"musicSensitivity\"),\n type: \"number\",\n role: \"level\",\n write: true,\n min: sensField.range.min,\n max: sensField.range.max,\n unit: \"%\",\n def: sensField.range.max,\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n });\n }\n\n // Auto color toggle\n const autoColorField = fields.find(f => f && typeof f.fieldName === \"string\" && f.fieldName === \"autoColor\");\n if (autoColorField) {\n states.push({\n id: \"music_auto_color\",\n name: tName(\"musicAutoColor\"),\n type: \"boolean\",\n role: \"switch\",\n write: true,\n def: true,\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n });\n }\n\n // All music states belong to the music channel\n for (const s of states) {\n s.channel = \"music\";\n }\n return states;\n}\n\n/**\n * Apply device quirks to mapped state definitions.\n * Corrects wrong API data (e.g. color temperature range) for specific SKUs.\n *\n * @param sku Device model (e.g. \"H60A1\")\n * @param states State definitions to adjust\n * @param log Adapter logger \u2014 quirk-applied events land on debug.\n */\nexport function applyQuirksToStates(sku: string, states: StateDefinition[], log: ioBroker.Logger): StateDefinition[] {\n for (const state of states) {\n if (state.id === \"color_temperature\" && state.min != null && state.max != null) {\n const corrected = applyColorTempQuirk(sku, state.min, state.max);\n if (corrected.min !== state.min || corrected.max !== state.max) {\n log.debug(\n `Quirk applied for ${sku}: color_temperature range ${state.min}-${state.max}K \u2192 ${corrected.min}-${corrected.max}K`,\n );\n }\n state.min = corrected.min;\n state.max = corrected.max;\n state.def = corrected.min;\n }\n }\n return states;\n}\n\n/** Known Govee API unit strings \u2192 ioBroker units */\nconst UNIT_MAP: Record<string, string> = {\n \"unit.percent\": \"%\",\n \"unit.kelvin\": \"K\",\n \"unit.celsius\": \"\u00B0C\",\n \"unit.fahrenheit\": \"\u00B0F\",\n};\n\n/**\n * Normalize Govee API unit string to ioBroker standard\n *\n * @param unit Raw unit string from API\n */\nfunction normalizeUnit(unit?: string): string | undefined {\n if (!unit) {\n return undefined;\n }\n return UNIT_MAP[unit] ?? unit;\n}\n\n/**\n * Sanitize a string for use as ioBroker state ID\n *\n * @param str Input string to sanitize\n */\nfunction sanitizeId(str: string): string {\n return str\n .replace(/([a-z])([A-Z])/g, \"$1_$2\")\n .replace(/[^a-zA-Z0-9_]/g, \"_\")\n .toLowerCase();\n}\n\n/**\n * Convert camelCase to human-readable name\n *\n * @param str camelCase input string\n */\nfunction humanize(str: string): string {\n // Order: first underscore \u2192 space, then camelCase split, then trim +\n // uppercase the first character. Previously leading-underscore IDs\n // (e.g. `_segment_color`) became ` segment color` with a leading space\n // and no capitalization (^\\w matches a space, not a word char).\n return str\n .replace(/_/g, \" \")\n .replace(/([a-z])([A-Z])/g, \"$1 $2\")\n .trim()\n .replace(/^./, c => c.toUpperCase());\n}\n\n/** Mapped Cloud state value: state ID + converted value */\nexport interface CloudStateValue {\n /** State ID in control/ channel (e.g. \"power\", \"brightness\", \"gradient_toggle\") */\n stateId: string;\n /** Converted value ready for ioBroker setState */\n value: ioBroker.StateValue;\n}\n\n/**\n * Map a Cloud device state capability to a state ID + converted value.\n * Uses the same ID logic as mapCapabilities so IDs always match.\n *\n * @param cap Cloud state capability with current value\n */\nexport function mapCloudStateValue(cap: CloudStateCapability): CloudStateValue | null {\n if (!cap || typeof cap.type !== \"string\" || typeof cap.instance !== \"string\") {\n return null;\n }\n const shortType = cap.type.replace(\"devices.capabilities.\", \"\");\n const raw = cap.state?.value;\n if (raw === undefined || raw === null) {\n return null;\n }\n\n switch (shortType) {\n case \"on_off\":\n return { stateId: \"power\", value: coerceBool(raw) };\n\n case \"range\": {\n const n = coerceNum(raw);\n if (n === null) {\n return null;\n }\n return { stateId: sanitizeId(cap.instance), value: n };\n }\n\n case \"color_setting\":\n if (cap.instance === \"colorRgb\") {\n const num = coerceNum(raw) ?? 0;\n return {\n stateId: \"color_rgb\",\n value: rgbToHex((num >> 16) & 0xff, (num >> 8) & 0xff, num & 0xff),\n };\n }\n if (cap.instance.includes(\"colorTem\")) {\n const n = coerceNum(raw);\n if (n === null) {\n return null;\n }\n return { stateId: \"color_temperature\", value: n };\n }\n return null;\n\n case \"toggle\":\n return { stateId: sanitizeId(cap.instance), value: coerceBool(raw) };\n\n case \"mode\":\n if (cap.instance === \"presetScene\") {\n return {\n stateId: \"scene\",\n value: safeStringify(raw),\n };\n }\n return null;\n\n case \"dynamic_scene\":\n // snapshot is an action-only dropdown (activate a saved snapshot) \u2014 there\n // is no persistent \"current snapshot\" status, and its real state id is\n // snapshot_cloud (not \"snapshot\"), so a cloud-state write here only\n // produced a \"has no existing object\" warning (B12). Skip it. lightScene /\n // diyScene DO have a meaningful active value and sanitizeId maps them to\n // light_scene / diy_scene, which match their real states.\n if (cap.instance === \"snapshot\") {\n return null;\n }\n return {\n stateId: sanitizeId(cap.instance),\n value: safeStringify(raw),\n };\n\n case \"work_mode\": {\n // STRUCT: { workMode: <number>, modeValue?: <number> }. Cloud\n // /device/state only returns the primary mode here \u2014 mode_value\n // (sub-parameter) follows via MQTT status push when the device\n // reports it, so we don't lose it just because it isn't in the\n // initial state response.\n if (typeof raw === \"object\" && raw !== null) {\n const struct = raw as Record<string, unknown>;\n const n = coerceNum(struct.workMode);\n if (n !== null) {\n return { stateId: \"work_mode\", value: n };\n }\n }\n const direct = coerceNum(raw);\n if (direct !== null) {\n return { stateId: \"work_mode\", value: direct };\n }\n return null;\n }\n\n case \"temperature_setting\": {\n // STRUCT: { targetTemperature: <number>, temperatureUnit?: ... }\n // \u2014 fall back to direct number for adapters that simplify.\n const direct = coerceNum(raw);\n if (direct !== null) {\n return { stateId: \"target_temperature\", value: direct };\n }\n if (typeof raw === \"object\" && raw !== null) {\n const struct = raw as Record<string, unknown>;\n const temp = struct.targetTemperature ?? struct.temperature ?? struct.temp;\n const n = coerceNum(temp);\n if (n !== null) {\n return { stateId: \"target_temperature\", value: n };\n }\n }\n return null;\n }\n\n case \"event\":\n return {\n stateId: sanitizeId(cap.instance),\n value: coerceBool(raw),\n };\n\n case \"music_setting\":\n // Extract mode value from STRUCT state\n if (typeof raw === \"object\" && raw !== null) {\n const struct = raw as Record<string, unknown>;\n const mode = coerceNum(struct.musicMode);\n return {\n stateId: \"music_mode\",\n value: mode !== null ? String(mode) : \"0\",\n };\n }\n return null;\n\n case \"property\": {\n const n = coerceNum(raw);\n if (n === null) {\n return null;\n }\n return { stateId: sanitizeId(cap.instance), value: n };\n }\n\n default:\n return null;\n }\n}\n\n/**\n * Plan the per-state writes for a list of synthesised Cloud-state\n * capabilities. Used by the App-API poll and the OpenAPI-MQTT event\n * handler (both call into `applyCloudCapabilities` on the adapter side).\n *\n * Returns the resolved `(stateId, value)` pairs for capabilities that:\n * - decode via `mapCloudStateValue` to a non-null result, AND\n * - aren't shadowed by the LAN-state set when the device is LAN-capable\n * (lights with `lanIp` shouldn't have their LAN sub-second updates\n * overwritten by a Cloud-source value).\n *\n * Pure function \u2014 no adapter state, no I/O \u2014 so the LAN-shadow logic is\n * unit-testable independent of the live state-write pipeline.\n *\n * @param caps Capabilities to consider\n * @param hasLanIp Whether the target device has a known LAN IP\n * @param lanStateIds Default-LAN state IDs that LAN delivers authoritatively\n */\nexport function planCloudCapabilityWrites(\n caps: CloudStateCapability[],\n hasLanIp: boolean,\n lanStateIds: ReadonlySet<string>,\n): CloudStateValue[] {\n const writes: CloudStateValue[] = [];\n if (!Array.isArray(caps)) {\n return writes;\n }\n for (const cap of caps) {\n const mapped = mapCloudStateValue(cap);\n if (!mapped) {\n continue;\n }\n if (hasLanIp && lanStateIds.has(mapped.stateId)) {\n continue;\n }\n writes.push(mapped);\n }\n return writes;\n}\n\n/**\n * Scene-dropdown rules \u2014 three states with identical shape (dropdown over a\n * device-content array, capabilityType `dynamic_scene`). Adding a new\n * scene-style dropdown means: append one entry here. The 8 other Cloud-derived\n * states (scene_speed, refresh_cloud, snapshot_local/save/delete, diag.*)\n * have genuinely different shapes and stay inline \u2014 not the same anti-pattern.\n */\nconst SCENE_DROPDOWN_RULES: ReadonlyArray<{\n id: string;\n cap: \"lightScene\" | \"diyScene\" | \"snapshot\";\n nameKey: Parameters<typeof tName>[0];\n descKey?: Parameters<typeof tDesc>[0];\n channel: \"scenes\" | \"snapshots\";\n source: (d: GoveeDevice) => { name: string }[];\n}> = [\n { id: \"light_scene\", cap: \"lightScene\", nameKey: \"lightScene\", channel: \"scenes\", source: d => d.scenes },\n { id: \"diy_scene\", cap: \"diyScene\", nameKey: \"diyScene\", channel: \"scenes\", source: d => d.diyScenes },\n {\n id: \"snapshot_cloud\",\n cap: \"snapshot\",\n nameKey: \"cloudSnapshot\",\n descKey: \"cloudSnapshotDesc\",\n channel: \"snapshots\",\n source: d => d.snapshots,\n },\n];\n\n/**\n * Build LAN-owned state definitions for a device. Returns the four\n * lan-default states (power/brightness/colorRgb/colorTemperature) with quirks\n * applied, or [] for devices without a LAN address (sensors, appliances,\n * groups).\n *\n * Phase architecture: belongs to the LAN phase. Called when a device becomes\n * visible via LAN discovery or is loaded with a lanIp from the cache.\n *\n * @param device Govee device\n * @param log Adapter logger \u2014 forwarded to applyQuirksToStates.\n */\nexport function buildLanStateDefs(device: GoveeDevice, log: ioBroker.Logger): StateDefinition[] {\n if (!device.lanIp) {\n return [];\n }\n const stateDefs = getDefaultLanStates();\n applyQuirksToStates(device.sku, stateDefs, log);\n return stateDefs;\n}\n\n/**\n * The three diagnostics states (export button, JSON result, trust tier).\n * Shared by buildCloudStateDefs (tier default \"unknown\") and\n * buildGroupStateDefs (tier default \"verified\") \u2014 only the tier default differs.\n *\n * @param tierDef Initial value for the diag.tier state\n */\nfunction buildDiagStateDefs(tierDef: string | null): StateDefinition[] {\n const defs: StateDefinition[] = [\n {\n id: \"export\",\n name: tName(\"exportDiagnostics\"),\n type: \"boolean\",\n role: \"button\",\n write: true,\n def: false,\n capabilityType: \"local\",\n capabilityInstance: \"diagnosticsExport\",\n channel: \"diag\",\n },\n {\n id: \"result\",\n name: tName(\"diagnosticsJson\"),\n type: \"string\",\n role: \"json\",\n write: false,\n def: \"\",\n capabilityType: \"local\",\n capabilityInstance: \"diagnosticsResult\",\n channel: \"diag\",\n },\n ];\n // The trust tier is a per-SKU catalog attribute \u2014 meaningless for a BaseGroup\n // (not a real device), which used to show a hard-coded \"verified\". Groups pass\n // null and get no tier state at all (B4).\n if (tierDef !== null) {\n defs.push({\n id: \"tier\",\n name: tName(\"deviceTier\"),\n type: \"string\",\n role: \"text\",\n write: false,\n def: tierDef,\n states: {\n verified: resolveLabel(\"deviceTierVerified\"),\n reported: resolveLabel(\"deviceTierReported\"),\n seed: resolveLabel(\"deviceTierSeed\"),\n unknown: resolveLabel(\"deviceTierUnknown\"),\n },\n capabilityType: \"local\",\n capabilityInstance: \"diagnosticsTier\",\n channel: \"diag\",\n });\n }\n return defs;\n}\n\n/**\n * Build Cloud-owned state definitions for a device \u2014 everything that needs\n * Cloud capabilities or local synthetic decoration. Excludes LAN-default IDs\n * (the LAN phase owns those). Returns intersection state for BaseGroup\n * devices.\n *\n * Phase architecture: belongs to the Cloud phase. Called when capabilities for\n * a device become available from the cache or a fresh cloud load.\n *\n * @param device Govee device\n * @param log Adapter logger \u2014 forwarded to mapCapabilities / applyQuirksToStates.\n * @param localSnapshots Optional local snapshot names\n * @param memberDevices Resolved member devices (only for BaseGroup)\n */\nexport function buildCloudStateDefs(\n device: GoveeDevice,\n log: ioBroker.Logger,\n localSnapshots?: { name: string }[],\n memberDevices?: GoveeDevice[],\n): StateDefinition[] {\n if (device.sku === \"BaseGroup\") {\n return buildGroupStateDefs(memberDevices || []);\n }\n\n // Per-SKU quirk: brokenPlatformApi \u2192 don't trust the platform-cap tree.\n // Skip capability-derived defs AND the scene/snapshot dropdowns (they're\n // gated by hasDynamicSceneCapability which reads device.capabilities, i.e.\n // the same untrusted source). LAN-phase still creates power / brightness /\n // colorRgb / colorTemperature defaults so the device stays controllable.\n const quirks = getDeviceQuirks(device.sku);\n const skipCapabilities = quirks?.brokenPlatformApi === true;\n\n // Capability-derived states. A LAN-owning light (lanIp set) lets the LAN\n // phase own the four control ids (power/brightness/colorRgb/colorTemperature)\n // \u2014 they are filtered out here so they are not double-created in the same\n // channel. Without a lanIp there is NO LAN phase to provide them and nothing\n // to double-create, so keep the cap-derived control states: a cloud-only\n // light (owner never enabled the local API) stays controllable via the Cloud,\n // and appliances (which never have a LAN phase) keep their on/off too. The\n // command-router routes these via Cloud (`no-lan` / `light-no-lan-fallback`).\n // Local-first stays, local-only does not. Sensors carry no such caps; groups\n // returned earlier. LAN lights are unchanged.\n const noLanPhase = !device.lanIp;\n const stateDefs: StateDefinition[] = skipCapabilities\n ? []\n : mapCapabilities(device.capabilities, log).filter(d => noLanPhase || !LAN_STATE_IDS.has(d.id));\n\n if (skipCapabilities) {\n log.debug(`${device.sku}: brokenPlatformApi quirk active \u2014 skipping capability-derived states + dropdowns`);\n }\n\n applyQuirksToStates(device.sku, stateDefs, log);\n\n // Light-only synthetic state defs \u2014 scenes / snapshots / music / scene_speed\n // only make sense for lights. Sensors and appliances would otherwise see\n // empty snapshot dropdowns and a useless save/delete button pair.\n const isLight = device.type === GOVEE_DEVICE_TYPE.LIGHT;\n\n // Three structurally-identical Cloud dropdowns \u2014 collapsed into one loop.\n for (const r of SCENE_DROPDOWN_RULES) {\n if (skipCapabilities || !isLight || !hasDynamicSceneCapability(device.capabilities, r.cap)) {\n continue;\n }\n stateDefs.push({\n id: r.id,\n name: tName(r.nameKey),\n desc: r.descKey ? tDesc(r.descKey) : undefined,\n // mixed lets users write the index (\"1\"), the index as number (1),\n // or the entry name (\"Aurora\") \u2014 the onStateChange handler resolves\n // all three forms via the common.states map.\n type: \"mixed\",\n role: \"state\",\n write: true,\n states: buildUniqueLabelMap(r.source(device)),\n def: \"0\",\n capabilityType: GOVEE_CAP_TYPE.DYNAMIC_SCENE,\n capabilityInstance: r.cap,\n channel: r.channel,\n });\n }\n\n // Scene speed slider \u2014 only if any scene supports speed adjustment.\n // Stays inline: depends on a computed maxSpeedLevel that doesn't fit the\n // dropdown-rule shape.\n const maxSpeedLevel = device.sceneLibrary.reduce((max, entry) => {\n if (entry.speedInfo?.supSpeed && entry.speedInfo.config) {\n try {\n const parsed = JSON.parse(entry.speedInfo.config) as unknown;\n // Config can drift \u2014 if not an array, skip this entry silently\n if (!Array.isArray(parsed)) {\n return max;\n }\n for (const cfg of parsed as Array<{ moveIn?: number[] }>) {\n if (cfg && Array.isArray(cfg.moveIn) && cfg.moveIn.length - 1 > max) {\n max = cfg.moveIn.length - 1;\n }\n }\n } catch (e) {\n log.debug(`${device.sku}: speed-config parse failed for scene \"${entry.name}\": ${errMessage(e)}`);\n }\n }\n return max;\n }, -1);\n if (isLight && maxSpeedLevel > 0) {\n stateDefs.push({\n id: \"scene_speed\",\n name: tName(\"sceneSpeed\"),\n type: \"number\",\n role: \"level\",\n write: true,\n min: 0,\n max: maxSpeedLevel,\n def: 0,\n capabilityType: \"local\",\n capabilityInstance: \"sceneSpeed\",\n channel: \"scenes\",\n });\n }\n\n // Per-device refresh button \u2014 gated on ANY dynamic-scene capability.\n // OR-gate over three caps doesn't fit a rules-table.\n // brokenPlatformApi gate skips this too: the refresh action would post a\n // user-facing button for a Cloud endpoint that wasn't trustable to begin\n // with.\n if (\n !skipCapabilities &&\n isLight &&\n (hasDynamicSceneCapability(device.capabilities, \"lightScene\") ||\n hasDynamicSceneCapability(device.capabilities, \"diyScene\") ||\n hasDynamicSceneCapability(device.capabilities, \"snapshot\"))\n ) {\n stateDefs.push({\n id: \"refresh_cloud\",\n name: tName(\"refreshCloud\"),\n desc: tDesc(\"refreshCloudDesc\"),\n type: \"boolean\",\n role: \"button\",\n write: true,\n def: false,\n capabilityType: \"local\",\n capabilityInstance: \"refreshCloud\",\n channel: \"snapshots\",\n });\n }\n\n // Local snapshots \u2014 three states with different shapes (mixed dropdown vs\n // plain string-write fields). Inline because a rules-table would need a\n // discriminator field with no payoff.\n if (isLight) {\n stateDefs.push({\n id: \"snapshot_local\",\n name: tName(\"localSnapshot\"),\n desc: tDesc(\"localSnapshotDesc\"),\n type: \"mixed\",\n role: \"state\",\n write: true,\n states: buildUniqueLabelMap(localSnapshots ?? []),\n def: \"0\",\n capabilityType: \"local\",\n capabilityInstance: \"snapshotLocal\",\n channel: \"snapshots\",\n });\n stateDefs.push({\n id: \"snapshot_save\",\n name: tName(\"saveLocalSnapshot\"),\n desc: tDesc(\"saveLocalSnapshotDesc\"),\n type: \"string\",\n role: \"text\",\n write: true,\n def: \"\",\n capabilityType: \"local\",\n capabilityInstance: \"snapshotSave\",\n channel: \"snapshots\",\n });\n stateDefs.push({\n id: \"snapshot_delete\",\n name: tName(\"deleteLocalSnapshot\"),\n desc: tDesc(\"deleteLocalSnapshotDesc\"),\n type: \"string\",\n role: \"text\",\n write: true,\n def: \"\",\n capabilityType: \"local\",\n capabilityInstance: \"snapshotDelete\",\n channel: \"snapshots\",\n });\n }\n\n // Diagnostics \u2014 export button, JSON result, trust tier. Default tier\n // \"unknown\" for a real device (its actual tier comes from the registry).\n stateDefs.push(...buildDiagStateDefs(\"unknown\"));\n\n return stateDefs;\n}\n\n/** Maps the snake_case LAN control state-id (getDefaultLanStates ids) to its cloud-instance kind. */\nconst CONTROL_STATE_ID_TO_KIND: Record<string, ControlKind> = {\n power: \"power\",\n brightness: \"brightness\",\n color_rgb: \"colorRgb\",\n color_temperature: \"colorTemperature\",\n};\n\n/**\n * Check if a member device supports a given control state.\n * LAN-capable devices support all basic controls.\n *\n * @param member Group member device\n * @param stateId Control state ID (e.g. \"power\", \"brightness\")\n */\nfunction memberHasControlState(member: GoveeDevice, stateId: string): boolean {\n if (member.lanIp) {\n return true;\n }\n const kind = CONTROL_STATE_ID_TO_KIND[stateId];\n if (!kind) {\n return false;\n }\n const caps = Array.isArray(member.capabilities) ? member.capabilities : [];\n return caps.some(c => capMatchesControl(c, kind));\n}\n\n/**\n * Build state definitions for a BaseGroup device.\n * Capabilities = intersection of controllable member devices.\n * No snapshots, no segments; diag-states (export/result/tier) included since v2.9.1.\n *\n * @param members Resolved member devices\n */\nfunction buildGroupStateDefs(members: GoveeDevice[]): StateDefinition[] {\n const controllable = members.filter(m => m.lanIp || m.channels.cloud);\n if (controllable.length === 0) {\n return [];\n }\n\n const stateDefs: StateDefinition[] = [];\n\n // Control states: intersection of member capabilities\n for (const ld of getDefaultLanStates()) {\n if (controllable.every(m => memberHasControlState(m, ld.id))) {\n stateDefs.push(ld);\n }\n }\n\n // Scenes: intersection of member scene names\n if (controllable.every(m => m.scenes.length > 0)) {\n const firstNames = controllable[0].scenes.map(s => s.name);\n const commonNames = firstNames.filter(name => controllable.every(m => m.scenes.some(s => s.name === name)));\n if (commonNames.length > 0) {\n stateDefs.push({\n id: \"light_scene\",\n name: tName(\"lightScene\"),\n type: \"mixed\",\n role: \"state\",\n write: true,\n states: buildUniqueLabelMap(commonNames.map(name => ({ name }))),\n def: \"0\",\n capabilityType: GOVEE_CAP_TYPE.DYNAMIC_SCENE,\n capabilityInstance: \"lightScene\",\n channel: \"scenes\",\n });\n }\n }\n\n // Music: intersection of member music libraries\n if (controllable.every(m => m.musicLibrary.length > 0)) {\n const firstNames = controllable[0].musicLibrary.map(m => m.name);\n const commonNames = firstNames.filter(name => controllable.every(m => m.musicLibrary.some(ml => ml.name === name)));\n if (commonNames.length > 0) {\n stateDefs.push({\n id: \"music_mode\",\n name: tName(\"musicMode\"),\n type: \"mixed\",\n role: \"state\",\n write: true,\n states: buildUniqueLabelMap(commonNames.map(name => ({ name }))),\n def: \"0\",\n capabilityType: GOVEE_CAP_TYPE.MUSIC_SETTING,\n capabilityInstance: \"musicMode\",\n channel: \"music\",\n });\n }\n }\n\n // v2.9.1 \u2014 BaseGroups get the same three diag states. Tier defaults to\n // \"verified\" because BaseGroup isn't a real SKU and has no quirks entry \u2014\n // the diag-button just renders consistently.\n stateDefs.push(...buildDiagStateDefs(null));\n\n return stateDefs;\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import {\n buildUniqueLabelMap,\n capMatchesControl,\n deviceLabel,\n errMessage,\n rgbToHex,\n type CapabilityOption,\n type CloudCapability,\n type CloudStateCapability,\n type ControlKind,\n type GoveeDevice,\n} from \"./types\";\nimport { applyColorTempQuirk, getDeviceQuirks } from \"./device-registry\";\nimport { GOVEE_CAP_TYPE, GOVEE_DEVICE_TYPE } from \"./govee-constants\";\nimport { resolveLabel, tDesc, tName } from \"./i18n\";\n\n/** ioBroker state definition derived from a Govee capability */\nexport interface StateDefinition {\n /** State ID suffix (e.g. \"power\", \"brightness\", \"color_rgb\") */\n id: string;\n /**\n * Display name. Plain string for capability-derived names (e.g. from\n * `humanize(cap.instance)` of an unknown Govee capability \u2014 those aren't\n * predictable). For known states, a translation object `{en, de, ru, ...}`\n * built via `tName()` \u2014 Admin/vis/Object-Browser pick the user's language\n * automatically.\n */\n name: string | Record<string, string>;\n /**\n * Human-readable description shown in the object browser \u2014 used to clarify\n * ambiguous state names (e.g. cloud vs local snapshots) where the id alone\n * isn't enough for a user to know what the state does.\n */\n desc?: string | Record<string, string>;\n /** ioBroker value type */\n type: ioBroker.CommonType;\n /** ioBroker role */\n role: string;\n /** Whether state is writable */\n write: boolean;\n /** Unit string */\n unit?: string;\n /** Min value for numbers */\n min?: number;\n /** Max value for numbers */\n max?: number;\n /**\n * Predefined values for a select (value \u2192 label).\n *\n * **Labels MUST be plain-string** \u2014 Admin renders states-values as React\n * children and a `{en, de, \u2026}` translation object triggers React Error #31\n * \u2192 fatal \"Error in GUI\" on dropdown open (verified 2026-05-12). For\n * localized labels, resolve via {@link resolveLabel} with the adapter's\n * `system.config.language` value once.\n */\n states?: Record<string, string>;\n /** Default value for new states */\n def?: ioBroker.StateValue;\n /** Original capability type */\n capabilityType: string;\n /** Original capability instance */\n capabilityInstance: string;\n /** Target channel (control, scenes, music, snapshots). Defaults to \"control\". */\n channel?: string;\n}\n\n/**\n * Coerce arbitrary value to boolean. Accepts true/1/\"1\"/\"true\" as truthy.\n *\n * @param v Raw value from API\n */\nfunction coerceBool(v: unknown): boolean {\n return v === true || v === 1 || v === \"1\" || v === \"true\";\n}\n\n/**\n * Stringify an unknown raw API value for an ioBroker state. Objects /\n * functions go through JSON.stringify (so we don't get `[object Object]`);\n * everything else takes the primitive `String()` path. Centralised to keep\n * the no-base-to-string lint rule happy at the call sites without\n * sprinkling type assertions all over.\n *\n * @param v Raw value from API\n */\nfunction safeStringify(v: unknown): string {\n switch (typeof v) {\n case \"string\":\n return v;\n case \"number\":\n case \"bigint\":\n case \"boolean\":\n case \"symbol\":\n return v.toString();\n case \"undefined\":\n return \"undefined\";\n default:\n // object, function, null\n return JSON.stringify(v);\n }\n}\n\n/**\n * Coerce arbitrary value to finite number, or null if not parseable.\n *\n * @param v Raw value from API\n */\nfunction coerceNum(v: unknown): number | null {\n if (typeof v === \"number\" && Number.isFinite(v)) {\n return v;\n }\n if (typeof v === \"string\" && v.trim() !== \"\") {\n const n = Number(v);\n if (Number.isFinite(n)) {\n return n;\n }\n }\n return null;\n}\n\n/**\n * Maps Govee Cloud API capabilities to ioBroker state definitions.\n * Pure function \u2014 no side effects, easily testable.\n *\n * @param capabilities Device capabilities from Cloud API\n * @param log Adapter logger \u2014 per-cap skip-decisions land on debug.\n */\nexport function mapCapabilities(capabilities: CloudCapability[], log: ioBroker.Logger): StateDefinition[] {\n const states: StateDefinition[] = [];\n\n if (!Array.isArray(capabilities)) {\n return states;\n }\n\n let mapped = 0;\n let skipped = 0;\n for (const cap of capabilities) {\n const result = mapSingleCapability(cap);\n if (result) {\n states.push(...result);\n mapped++;\n } else {\n skipped++;\n // Unknown / unhandled cap shape \u2014 log so bug reports can see what\n // Govee sent that we didn't know what to do with. Includes type+\n // instance because that's enough to grep against device-registry\n // entries when adding support for a new SKU.\n log.debug(\n `Cap skipped: type=${cap?.type ?? \"?\"} instance=${cap?.instance ?? \"?\"} \u2014 no mapping (capability not handled or malformed)`,\n );\n }\n }\n log.debug(`mapCapabilities: ${mapped} mapped, ${skipped} skipped, ${states.length} state def(s) produced`);\n\n return states;\n}\n\n/**\n * Probe `capabilities` for a `devices.capabilities.dynamic_scene` entry of\n * the given instance (lightScene / diyScene / snapshot). Used to gate the\n * scene/snapshot dropdowns capability-driven instead of data-driven \u2014 a\n * device that exposes the cap should always show the dropdown, even if\n * the scene list hasn't been fetched yet.\n *\n * @param capabilities Device capabilities from Cloud API\n * @param instance The dynamic_scene instance to look up\n */\nexport function hasDynamicSceneCapability(\n capabilities: CloudCapability[],\n instance: \"lightScene\" | \"diyScene\" | \"snapshot\",\n): boolean {\n if (!Array.isArray(capabilities)) {\n return false;\n }\n return capabilities.some(\n cap =>\n typeof cap?.type === \"string\" &&\n typeof cap?.instance === \"string\" &&\n (cap.type === GOVEE_CAP_TYPE.DYNAMIC_SCENE || cap.type === \"dynamic_scene\") &&\n cap.instance === instance,\n );\n}\n\n/**\n * Single source of truth for \"this state-id belongs to LAN territory\". Used by:\n * - cleanupCloudOwnedStates \u2192 skip these ids when wiping cloud-owned states in the control channel\n * - buildCloudStateDefs \u2192 dedup capability-derived defs against LAN ownership (prevents double-create)\n * - cloud-state-loader \u2192 filter out LAN-state-ids when applying cloud values\n *\n * Adding a new LAN-default state means: extend this set AND add the entry in getDefaultLanStates.\n * The capability-tag-invariant test enforces both stay in lock-step.\n */\nexport const LAN_STATE_IDS: ReadonlySet<string> = new Set([\"power\", \"brightness\", \"color_rgb\", \"color_temperature\"]);\n\n/**\n * Default state definitions for LAN-only devices (no Cloud capabilities).\n * All LAN-capable Govee lights support: power, brightness, color, color temperature.\n *\n * State IDs MUST match LAN_STATE_IDS above. Invariant test in capability-mapper.test.ts\n * fails if these drift apart.\n */\nexport function getDefaultLanStates(): StateDefinition[] {\n return [\n {\n id: \"power\",\n name: tName(\"power\"),\n type: \"boolean\",\n role: \"switch\",\n write: true,\n def: false,\n capabilityType: \"lan\",\n capabilityInstance: \"powerSwitch\",\n },\n {\n id: \"brightness\",\n name: tName(\"brightness\"),\n type: \"number\",\n role: \"level.brightness\",\n write: true,\n min: 0,\n max: 100,\n unit: \"%\",\n def: 0,\n capabilityType: \"lan\",\n capabilityInstance: \"brightness\",\n },\n {\n id: \"color_rgb\",\n name: tName(\"colorRgb\"),\n type: \"string\",\n role: \"level.color.rgb\",\n write: true,\n def: \"#000000\",\n capabilityType: \"lan\",\n capabilityInstance: \"colorRgb\",\n },\n {\n id: \"color_temperature\",\n name: tName(\"colorTemperature\"),\n type: \"number\",\n role: \"level.color.temperature\",\n write: true,\n min: 2000,\n max: 9000,\n unit: \"K\",\n def: 2000,\n capabilityType: \"lan\",\n capabilityInstance: \"colorTemperatureK\",\n },\n ];\n}\n\n/**\n * Map a single capability to state definition(s)\n *\n * @param cap Cloud capability to map\n */\nfunction mapSingleCapability(cap: CloudCapability): StateDefinition[] | null {\n if (!cap || typeof cap.type !== \"string\" || typeof cap.instance !== \"string\") {\n return null;\n }\n const shortType = cap.type.replace(\"devices.capabilities.\", \"\");\n\n switch (shortType) {\n case \"on_off\":\n return [\n {\n id: \"power\",\n name: tName(\"power\"),\n type: \"boolean\",\n role: \"switch\",\n write: true,\n def: false,\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n },\n ];\n\n case \"range\":\n return mapRange(cap);\n\n case \"color_setting\":\n return mapColorSetting(cap);\n\n case \"toggle\":\n return [\n {\n id: sanitizeId(cap.instance),\n name: humanize(cap.instance),\n type: \"boolean\",\n role: \"switch\",\n write: true,\n def: false,\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n },\n ];\n\n case \"mode\":\n return mapMode(cap);\n\n case \"property\":\n return mapProperty(cap);\n\n case \"online\":\n // Handled separately \u2014 not a regular state\n return null;\n\n case \"segment_color_setting\":\n // Segments are handled specially by state-manager\n return [\n {\n id: `_segment_${sanitizeId(cap.instance)}`,\n name: humanize(cap.instance),\n type: \"string\",\n role: \"json\",\n write: true,\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n },\n ];\n\n case \"dynamic_scene\":\n // lightScene / diyScene / snapshot get real dropdowns built later in\n // buildCloudStateDefs from the scenes/snapshots arrays \u2014 skip the\n // generic stub here so we don't create and immediately delete it.\n if (cap.instance === \"lightScene\" || cap.instance === \"diyScene\" || cap.instance === \"snapshot\") {\n return null;\n }\n return [\n {\n id: sanitizeId(cap.instance),\n name: humanize(cap.instance),\n type: \"string\",\n role: \"json\",\n write: true,\n def: \"\",\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n },\n ];\n\n case \"work_mode\":\n return mapWorkMode(cap);\n\n case \"temperature_setting\":\n return mapTemperatureSetting(cap);\n\n case \"event\":\n return mapEvent(cap);\n\n case \"music_setting\":\n return mapMusicSetting(cap);\n\n default:\n return null;\n }\n}\n\n/**\n * Map range capability (brightness, humidity, etc.)\n *\n * @param cap Cloud range capability\n */\nfunction mapRange(cap: CloudCapability): StateDefinition[] {\n const range = cap.parameters?.range;\n const isBrightness = cap.instance.toLowerCase().includes(\"brightness\");\n\n return [\n {\n id: sanitizeId(cap.instance),\n name: humanize(cap.instance),\n type: \"number\",\n role: isBrightness ? \"level.brightness\" : \"level\",\n write: true,\n min: range?.min ?? 0,\n max: range?.max ?? 100,\n unit: normalizeUnit(cap.parameters?.unit),\n def: range?.min ?? 0,\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n },\n ];\n}\n\n/**\n * Map color_setting capability (RGB or color temperature)\n *\n * @param cap Cloud color setting capability\n */\nfunction mapColorSetting(cap: CloudCapability): StateDefinition[] {\n if (cap.instance === \"colorRgb\") {\n return [\n {\n id: \"color_rgb\",\n name: tName(\"colorRgb\"),\n type: \"string\",\n role: \"level.color.rgb\",\n write: true,\n def: \"#000000\",\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n },\n ];\n }\n\n if (cap.instance.includes(\"colorTem\")) {\n const range = cap.parameters?.range;\n return [\n {\n id: \"color_temperature\",\n name: tName(\"colorTemperature\"),\n type: \"number\",\n role: \"level.color.temperature\",\n write: true,\n min: range?.min ?? 2000,\n max: range?.max ?? 9000,\n unit: \"K\",\n def: range?.min ?? 2000,\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n },\n ];\n }\n\n return [];\n}\n\n/**\n * Map mode capability (scenes with ENUM options)\n *\n * @param cap Cloud mode capability\n */\nfunction mapMode(cap: CloudCapability): StateDefinition[] {\n if (cap.instance !== \"presetScene\" || !Array.isArray(cap.parameters?.options)) {\n return [];\n }\n\n const states: Record<string, string> = {};\n for (const opt of cap.parameters.options) {\n if (!opt || typeof opt.name !== \"string\") {\n continue;\n }\n const val = safeStringify(opt.value);\n states[val] = opt.name;\n }\n\n return [\n {\n id: \"scene\",\n name: tName(\"scene\"),\n type: \"mixed\",\n role: \"state\",\n write: true,\n states,\n def: \"\",\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n },\n ];\n}\n\n/**\n * Canonical (role, unit) for the numeric Govee sensor kinds \u2014 the single\n * source of truth shared between {@link mapProperty} (Cloud property caps) and\n * state-manager's synthetic-state table, so a reading gets the same role + unit\n * whether it arrives via the Cloud-capability path or the App-API/MQTT path.\n */\nexport const SENSOR_ROLE_UNIT = {\n temperature: { role: \"value.temperature\", unit: \"\u00B0C\" },\n humidity: { role: \"value.humidity\", unit: \"%\" },\n battery: { role: \"value.battery\", unit: \"%\" },\n co2: { role: \"value.co2\", unit: \"ppm\" },\n} as const;\n\n/**\n * Map property capability (read-only sensors). Routes to the `sensor`\n * channel so a Heater's temperature reading sits cleanly next to other\n * sensor-style states instead of in `control`.\n *\n * @param cap Cloud property capability\n */\nfunction mapProperty(cap: CloudCapability): StateDefinition[] {\n const instance = cap.instance.toLowerCase();\n let role = \"value\";\n let unit: string | undefined;\n\n if (instance.includes(\"temperature\")) {\n ({ role, unit } = SENSOR_ROLE_UNIT.temperature);\n } else if (instance.includes(\"humidity\")) {\n ({ role, unit } = SENSOR_ROLE_UNIT.humidity);\n } else if (instance.includes(\"battery\")) {\n ({ role, unit } = SENSOR_ROLE_UNIT.battery);\n } else if (instance.includes(\"co2\") || instance.includes(\"carbondioxide\")) {\n ({ role, unit } = SENSOR_ROLE_UNIT.co2);\n }\n\n return [\n {\n id: sanitizeId(cap.instance),\n name: humanize(cap.instance),\n type: \"number\",\n role,\n write: false,\n unit: normalizeUnit(cap.parameters?.unit) ?? unit,\n def: 0, // avoid null in vis until the first sensor reading (consistency \u2014 B11)\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n channel: \"sensor\",\n },\n ];\n}\n\n/**\n * Map work_mode capability (STRUCT \u2014 Govee Heater/Humidifier/Fan/...).\n *\n * Two states max:\n * - `work_mode` \u2014 main mode dropdown (mixed type so users can write\n * either the numeric mode value or the label name)\n * - `mode_value` \u2014 secondary parameter (e.g. fan-speed level for the\n * \"manual\" mode); only created if the API actually exposes one\n *\n * @param cap Cloud work_mode capability\n */\nfunction mapWorkMode(cap: CloudCapability): StateDefinition[] {\n const fields = cap.parameters?.fields;\n if (!fields || fields.length === 0) {\n return [\n {\n id: \"work_mode\",\n name: tName(\"workMode\"),\n type: \"mixed\",\n role: \"level.mode.work\",\n write: true,\n def: \"\",\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n },\n ];\n }\n\n const states: StateDefinition[] = [];\n const modeField = fields.find(f => f && f.fieldName === \"workMode\");\n if (modeField?.options && modeField.options.length > 0) {\n const modeStates: Record<string, string> = {};\n for (const opt of modeField.options) {\n if (opt && typeof opt.name === \"string\") {\n modeStates[safeStringify(opt.value)] = opt.name;\n }\n }\n states.push({\n id: \"work_mode\",\n name: tName(\"workMode\"),\n type: \"mixed\",\n role: \"level.mode.work\",\n write: true,\n states: modeStates,\n def: modeField.options[0] ? safeStringify(modeField.options[0].value) : \"\",\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n });\n }\n\n const valueField = fields.find(f => f && f.fieldName === \"modeValue\");\n if (valueField) {\n if (valueField.options && valueField.options.length > 0) {\n const valStates: Record<string, string> = {};\n for (const opt of valueField.options) {\n if (opt && typeof opt.name === \"string\") {\n valStates[safeStringify(opt.value)] = opt.name;\n }\n }\n states.push({\n id: \"mode_value\",\n name: tName(\"modeValue\"),\n type: \"mixed\",\n role: \"state\",\n write: true,\n states: valStates,\n def: valueField.options[0] ? safeStringify(valueField.options[0].value) : \"\",\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n });\n } else if (valueField.range) {\n states.push({\n id: \"mode_value\",\n name: tName(\"modeValue\"),\n type: \"number\",\n role: \"level\",\n write: true,\n min: valueField.range.min,\n max: valueField.range.max,\n def: valueField.range.min,\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n });\n }\n }\n\n return states;\n}\n\n/**\n * Map temperature_setting capability \u2014 Heater target-temp slider.\n * Honours the unit reported by the API (\u00B0F or \u00B0C); falls back to \u00B0F\n * because that's the more common Govee Heater default.\n *\n * @param cap Cloud temperature_setting capability\n */\nfunction mapTemperatureSetting(cap: CloudCapability): StateDefinition[] {\n const fields = cap.parameters?.fields;\n if (Array.isArray(fields) && fields.length > 0) {\n const tempField = fields.find(f => {\n if (!f || typeof f.fieldName !== \"string\") {\n return false;\n }\n if (f.fieldName === \"targetTemperature\") {\n return true;\n }\n return f.fieldName.toLowerCase().includes(\"temperature\");\n });\n if (tempField?.range) {\n const unit = normalizeUnit(cap.parameters?.unit) ?? \"\u00B0F\";\n return [\n {\n id: \"target_temperature\",\n name: tName(\"targetTemperature\"),\n type: \"number\",\n role: \"level.temperature\",\n write: true,\n min: tempField.range.min,\n max: tempField.range.max,\n unit,\n def: tempField.range.min,\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n },\n ];\n }\n }\n\n const range = cap.parameters?.range;\n if (range) {\n const unit = normalizeUnit(cap.parameters?.unit) ?? \"\u00B0F\";\n return [\n {\n id: \"target_temperature\",\n name: tName(\"targetTemperature\"),\n type: \"number\",\n role: \"level.temperature\",\n write: true,\n min: range.min,\n max: range.max,\n unit,\n def: range.min,\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n },\n ];\n }\n\n // No usable schema \u2014 expose the raw payload so the user at least sees\n // the attempt and can report it. Stays JSON to avoid pretending we\n // understand the structure.\n return [\n {\n id: \"target_temperature\",\n name: tName(\"targetTemperature\"),\n type: \"string\",\n role: \"json\",\n write: true,\n def: \"\",\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n },\n ];\n}\n\n/**\n * Map event capability (asynchronous OpenAPI-MQTT alarms \u2014 read-only).\n * Each event becomes a boolean indicator in the events/ channel\n * (lackWater, iceFull, bodyAppeared, dirtDetected, \u2026).\n *\n * @param cap Cloud event capability\n */\nfunction mapEvent(cap: CloudCapability): StateDefinition[] {\n return [\n {\n id: sanitizeId(cap.instance),\n name: humanize(cap.instance),\n type: \"boolean\",\n role: \"indicator.alarm\",\n write: false,\n def: false,\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n channel: \"events\",\n },\n ];\n}\n\n/**\n * Map music_setting capability to user-friendly states.\n * Parses STRUCT fields into: mode dropdown, sensitivity slider, auto-color toggle.\n *\n * @param cap Cloud music_setting capability\n */\n/**\n * Whether a music mode carries a custom RGB colour in the ptReal LAN packet\n * (`33 05 01 <mode> R G B`). Spectrum + Rolling are colour-reactive; Energic,\n * Rhythm and any other mode use the device's own auto-colours.\n *\n * Keyed on the mode NAME, not its numeric value: Govee's music-mode values are\n * SKU-specific (the fleet has 0-based and 1-based SKUs \u2014 see A1), so a\n * value-based gate would append/withhold RGB on the wrong mode for a SKU whose\n * Spectrum/Rolling isn't at value 1/2. An unknown mode name (e.g. \"Sprouting\")\n * returns false \u2014 the safe default is to send no RGB rather than push bytes a\n * mode may not expect.\n *\n * @param name Music mode name from the capability option\n */\nexport function musicModeNameUsesRgb(name: string | undefined): boolean {\n if (typeof name !== \"string\") {\n return false;\n }\n const n = name.trim().toLowerCase();\n return n === \"spectrum\" || n === \"rolling\";\n}\n\n/**\n * Ordered list of valid music-mode options from a music_setting capability.\n * Both the dropdown builder ({@link mapMusicSetting}) and the send path\n * (`sendMusicCommand` in state-change-router) resolve music modes through THIS\n * function, so the index\u2192value mapping can never drift between what the user\n * selects and what is sent to the device.\n *\n * @param cap A music_setting capability\n * @returns The `musicMode` field's options (string name only), in API order\n */\nexport function getMusicModeOptions(cap: CloudCapability): CapabilityOption[] {\n const fields = cap.parameters?.fields;\n if (!Array.isArray(fields)) {\n return [];\n }\n const modeField = fields.find(f => f && typeof f.fieldName === \"string\" && f.fieldName === \"musicMode\");\n if (!modeField?.options || !Array.isArray(modeField.options)) {\n return [];\n }\n return modeField.options.filter(o => !!o && typeof o.name === \"string\");\n}\n\nfunction mapMusicSetting(cap: CloudCapability): StateDefinition[] {\n const fields = cap.parameters?.fields;\n if (!Array.isArray(fields) || fields.length === 0) {\n // No field details from API \u2014 can't create usable states\n return [];\n }\n\n const states: StateDefinition[] = [];\n\n // Mode dropdown \u2014 only if API provides actual mode options. Index-based\n // (0 = \"---\" sentinel), consistent with every other dropdown; the device\n // value for index N is modeOptions[N-1].value, resolved at send time through\n // the SAME getMusicModeOptions() list so build and send never drift. The old\n // value-keyed scheme collided with the sentinel on 0-based SKUs, making the\n // first music mode unreachable (A1).\n const modeOptions = getMusicModeOptions(cap);\n if (modeOptions.length > 0) {\n states.push({\n id: \"music_mode\",\n name: tName(\"musicMode\"),\n type: \"mixed\",\n role: \"state\",\n write: true,\n states: buildUniqueLabelMap(modeOptions),\n def: \"0\",\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n });\n }\n\n // Sensitivity slider\n const sensField = fields.find(f => f && typeof f.fieldName === \"string\" && f.fieldName === \"sensitivity\");\n if (sensField?.range) {\n states.push({\n id: \"music_sensitivity\",\n name: tName(\"musicSensitivity\"),\n type: \"number\",\n role: \"level\",\n write: true,\n min: sensField.range.min,\n max: sensField.range.max,\n unit: \"%\",\n def: sensField.range.max,\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n });\n }\n\n // Auto color toggle\n const autoColorField = fields.find(f => f && typeof f.fieldName === \"string\" && f.fieldName === \"autoColor\");\n if (autoColorField) {\n states.push({\n id: \"music_auto_color\",\n name: tName(\"musicAutoColor\"),\n type: \"boolean\",\n role: \"switch\",\n write: true,\n def: true,\n capabilityType: cap.type,\n capabilityInstance: cap.instance,\n });\n }\n\n // All music states belong to the music channel\n for (const s of states) {\n s.channel = \"music\";\n }\n return states;\n}\n\n/**\n * Apply device quirks to mapped state definitions.\n * Corrects wrong API data (e.g. color temperature range) for specific SKUs.\n *\n * @param sku Device model (e.g. \"H60A1\")\n * @param states State definitions to adjust\n * @param log Adapter logger \u2014 quirk-applied events land on debug.\n */\nexport function applyQuirksToStates(sku: string, states: StateDefinition[], log: ioBroker.Logger): StateDefinition[] {\n for (const state of states) {\n if (state.id === \"color_temperature\" && state.min != null && state.max != null) {\n const corrected = applyColorTempQuirk(sku, state.min, state.max);\n if (corrected.min !== state.min || corrected.max !== state.max) {\n log.debug(\n `Quirk applied for ${sku}: color_temperature range ${state.min}-${state.max}K \u2192 ${corrected.min}-${corrected.max}K`,\n );\n }\n state.min = corrected.min;\n state.max = corrected.max;\n state.def = corrected.min;\n }\n }\n return states;\n}\n\n/** Known Govee API unit strings \u2192 ioBroker units */\nconst UNIT_MAP: Record<string, string> = {\n \"unit.percent\": \"%\",\n \"unit.kelvin\": \"K\",\n \"unit.celsius\": \"\u00B0C\",\n \"unit.fahrenheit\": \"\u00B0F\",\n};\n\n/**\n * Normalize Govee API unit string to ioBroker standard\n *\n * @param unit Raw unit string from API\n */\nfunction normalizeUnit(unit?: string): string | undefined {\n if (!unit) {\n return undefined;\n }\n return UNIT_MAP[unit] ?? unit;\n}\n\n/**\n * Sanitize a string for use as ioBroker state ID\n *\n * @param str Input string to sanitize\n */\nfunction sanitizeId(str: string): string {\n return str\n .replace(/([a-z])([A-Z])/g, \"$1_$2\")\n .replace(/[^a-zA-Z0-9_]/g, \"_\")\n .toLowerCase();\n}\n\n/**\n * Convert camelCase to human-readable name\n *\n * @param str camelCase input string\n */\nfunction humanize(str: string): string {\n // Order: first underscore \u2192 space, then camelCase split, then trim +\n // uppercase the first character. Previously leading-underscore IDs\n // (e.g. `_segment_color`) became ` segment color` with a leading space\n // and no capitalization (^\\w matches a space, not a word char).\n return str\n .replace(/_/g, \" \")\n .replace(/([a-z])([A-Z])/g, \"$1 $2\")\n .trim()\n .replace(/^./, c => c.toUpperCase());\n}\n\n/** Mapped Cloud state value: state ID + converted value */\nexport interface CloudStateValue {\n /** State ID in control/ channel (e.g. \"power\", \"brightness\", \"gradient_toggle\") */\n stateId: string;\n /** Converted value ready for ioBroker setState */\n value: ioBroker.StateValue;\n}\n\n/**\n * Map a Cloud device state capability to a state ID + converted value.\n * Uses the same ID logic as mapCapabilities so IDs always match.\n *\n * @param cap Cloud state capability with current value\n */\nexport function mapCloudStateValue(cap: CloudStateCapability): CloudStateValue | null {\n if (!cap || typeof cap.type !== \"string\" || typeof cap.instance !== \"string\") {\n return null;\n }\n const shortType = cap.type.replace(\"devices.capabilities.\", \"\");\n const raw = cap.state?.value;\n if (raw === undefined || raw === null) {\n return null;\n }\n\n switch (shortType) {\n case \"on_off\":\n return { stateId: \"power\", value: coerceBool(raw) };\n\n case \"range\": {\n const n = coerceNum(raw);\n if (n === null) {\n return null;\n }\n return { stateId: sanitizeId(cap.instance), value: n };\n }\n\n case \"color_setting\":\n if (cap.instance === \"colorRgb\") {\n const num = coerceNum(raw) ?? 0;\n return {\n stateId: \"color_rgb\",\n value: rgbToHex((num >> 16) & 0xff, (num >> 8) & 0xff, num & 0xff),\n };\n }\n if (cap.instance.includes(\"colorTem\")) {\n const n = coerceNum(raw);\n if (n === null) {\n return null;\n }\n return { stateId: \"color_temperature\", value: n };\n }\n return null;\n\n case \"toggle\":\n return { stateId: sanitizeId(cap.instance), value: coerceBool(raw) };\n\n case \"mode\":\n if (cap.instance === \"presetScene\") {\n return {\n stateId: \"scene\",\n value: safeStringify(raw),\n };\n }\n return null;\n\n case \"dynamic_scene\":\n // snapshot is an action-only dropdown (activate a saved snapshot) \u2014 there\n // is no persistent \"current snapshot\" status, and its real state id is\n // snapshot_cloud (not \"snapshot\"), so a cloud-state write here only\n // produced a \"has no existing object\" warning (B12). Skip it. lightScene /\n // diyScene DO have a meaningful active value and sanitizeId maps them to\n // light_scene / diy_scene, which match their real states.\n if (cap.instance === \"snapshot\") {\n return null;\n }\n return {\n stateId: sanitizeId(cap.instance),\n value: safeStringify(raw),\n };\n\n case \"work_mode\": {\n // STRUCT: { workMode: <number>, modeValue?: <number> }. Cloud\n // /device/state only returns the primary mode here \u2014 mode_value\n // (sub-parameter) follows via MQTT status push when the device\n // reports it, so we don't lose it just because it isn't in the\n // initial state response.\n if (typeof raw === \"object\" && raw !== null) {\n const struct = raw as Record<string, unknown>;\n const n = coerceNum(struct.workMode);\n if (n !== null) {\n return { stateId: \"work_mode\", value: n };\n }\n }\n const direct = coerceNum(raw);\n if (direct !== null) {\n return { stateId: \"work_mode\", value: direct };\n }\n return null;\n }\n\n case \"temperature_setting\": {\n // STRUCT: { targetTemperature: <number>, temperatureUnit?: ... }\n // \u2014 fall back to direct number for adapters that simplify.\n const direct = coerceNum(raw);\n if (direct !== null) {\n return { stateId: \"target_temperature\", value: direct };\n }\n if (typeof raw === \"object\" && raw !== null) {\n const struct = raw as Record<string, unknown>;\n const temp = struct.targetTemperature ?? struct.temperature ?? struct.temp;\n const n = coerceNum(temp);\n if (n !== null) {\n return { stateId: \"target_temperature\", value: n };\n }\n }\n return null;\n }\n\n case \"event\":\n return {\n stateId: sanitizeId(cap.instance),\n value: coerceBool(raw),\n };\n\n case \"music_setting\":\n // Extract mode value from STRUCT state\n if (typeof raw === \"object\" && raw !== null) {\n const struct = raw as Record<string, unknown>;\n const mode = coerceNum(struct.musicMode);\n return {\n stateId: \"music_mode\",\n value: mode !== null ? String(mode) : \"0\",\n };\n }\n return null;\n\n case \"property\": {\n const n = coerceNum(raw);\n if (n === null) {\n return null;\n }\n return { stateId: sanitizeId(cap.instance), value: n };\n }\n\n default:\n return null;\n }\n}\n\n/**\n * Plan the per-state writes for a list of synthesised Cloud-state\n * capabilities. Used by the App-API poll and the OpenAPI-MQTT event\n * handler (both call into `applyCloudCapabilities` on the adapter side).\n *\n * Returns the resolved `(stateId, value)` pairs for capabilities that:\n * - decode via `mapCloudStateValue` to a non-null result, AND\n * - aren't shadowed by the LAN-state set when the device is LAN-capable\n * (lights with `lanIp` shouldn't have their LAN sub-second updates\n * overwritten by a Cloud-source value).\n *\n * Pure function \u2014 no adapter state, no I/O \u2014 so the LAN-shadow logic is\n * unit-testable independent of the live state-write pipeline.\n *\n * @param caps Capabilities to consider\n * @param hasLanIp Whether the target device has a known LAN IP\n * @param lanStateIds Default-LAN state IDs that LAN delivers authoritatively\n */\nexport function planCloudCapabilityWrites(\n caps: CloudStateCapability[],\n hasLanIp: boolean,\n lanStateIds: ReadonlySet<string>,\n): CloudStateValue[] {\n const writes: CloudStateValue[] = [];\n if (!Array.isArray(caps)) {\n return writes;\n }\n for (const cap of caps) {\n const mapped = mapCloudStateValue(cap);\n if (!mapped) {\n continue;\n }\n if (hasLanIp && lanStateIds.has(mapped.stateId)) {\n continue;\n }\n writes.push(mapped);\n }\n return writes;\n}\n\n/**\n * Scene-dropdown rules \u2014 three states with identical shape (dropdown over a\n * device-content array, capabilityType `dynamic_scene`). Adding a new\n * scene-style dropdown means: append one entry here. The 8 other Cloud-derived\n * states (scene_speed, refresh_cloud, snapshot_local/save/delete, diag.*)\n * have genuinely different shapes and stay inline \u2014 not the same anti-pattern.\n */\nconst SCENE_DROPDOWN_RULES: ReadonlyArray<{\n id: string;\n cap: \"lightScene\" | \"diyScene\" | \"snapshot\";\n nameKey: Parameters<typeof tName>[0];\n descKey?: Parameters<typeof tDesc>[0];\n channel: \"scenes\" | \"snapshots\";\n source: (d: GoveeDevice) => { name: string }[];\n}> = [\n { id: \"light_scene\", cap: \"lightScene\", nameKey: \"lightScene\", channel: \"scenes\", source: d => d.scenes },\n { id: \"diy_scene\", cap: \"diyScene\", nameKey: \"diyScene\", channel: \"scenes\", source: d => d.diyScenes },\n {\n id: \"snapshot_cloud\",\n cap: \"snapshot\",\n nameKey: \"cloudSnapshot\",\n descKey: \"cloudSnapshotDesc\",\n channel: \"snapshots\",\n source: d => d.snapshots,\n },\n];\n\n/**\n * Build LAN-owned state definitions for a device. Returns the four\n * lan-default states (power/brightness/colorRgb/colorTemperature) with quirks\n * applied, or [] for devices without a LAN address (sensors, appliances,\n * groups).\n *\n * Phase architecture: belongs to the LAN phase. Called when a device becomes\n * visible via LAN discovery or is loaded with a lanIp from the cache.\n *\n * @param device Govee device\n * @param log Adapter logger \u2014 forwarded to applyQuirksToStates.\n */\nexport function buildLanStateDefs(device: GoveeDevice, log: ioBroker.Logger): StateDefinition[] {\n if (!device.lanIp) {\n return [];\n }\n const stateDefs = getDefaultLanStates();\n applyQuirksToStates(device.sku, stateDefs, log);\n return stateDefs;\n}\n\n/**\n * The three diagnostics states (export button, JSON result, trust tier).\n * Shared by buildCloudStateDefs (tier default \"unknown\") and\n * buildGroupStateDefs (tier default \"verified\") \u2014 only the tier default differs.\n *\n * @param tierDef Initial value for the diag.tier state\n */\nfunction buildDiagStateDefs(tierDef: string | null): StateDefinition[] {\n const defs: StateDefinition[] = [\n {\n id: \"export\",\n name: tName(\"exportDiagnostics\"),\n type: \"boolean\",\n role: \"button\",\n write: true,\n def: false,\n capabilityType: \"local\",\n capabilityInstance: \"diagnosticsExport\",\n channel: \"diag\",\n },\n {\n id: \"result\",\n name: tName(\"diagnosticsJson\"),\n type: \"string\",\n role: \"json\",\n write: false,\n def: \"\",\n capabilityType: \"local\",\n capabilityInstance: \"diagnosticsResult\",\n channel: \"diag\",\n },\n ];\n // The trust tier is a per-SKU catalog attribute \u2014 meaningless for a BaseGroup\n // (not a real device), which used to show a hard-coded \"verified\". Groups pass\n // null and get no tier state at all (B4).\n if (tierDef !== null) {\n defs.push({\n id: \"tier\",\n name: tName(\"deviceTier\"),\n type: \"string\",\n role: \"text\",\n write: false,\n def: tierDef,\n states: {\n verified: resolveLabel(\"deviceTierVerified\"),\n reported: resolveLabel(\"deviceTierReported\"),\n seed: resolveLabel(\"deviceTierSeed\"),\n unknown: resolveLabel(\"deviceTierUnknown\"),\n },\n capabilityType: \"local\",\n capabilityInstance: \"diagnosticsTier\",\n channel: \"diag\",\n });\n }\n return defs;\n}\n\n/**\n * Build Cloud-owned state definitions for a device \u2014 everything that needs\n * Cloud capabilities or local synthetic decoration. Excludes LAN-default IDs\n * (the LAN phase owns those). Returns intersection state for BaseGroup\n * devices.\n *\n * Phase architecture: belongs to the Cloud phase. Called when capabilities for\n * a device become available from the cache or a fresh cloud load.\n *\n * @param device Govee device\n * @param log Adapter logger \u2014 forwarded to mapCapabilities / applyQuirksToStates.\n * @param localSnapshots Optional local snapshot names\n * @param memberDevices Resolved member devices (only for BaseGroup)\n */\nexport function buildCloudStateDefs(\n device: GoveeDevice,\n log: ioBroker.Logger,\n localSnapshots?: { name: string }[],\n memberDevices?: GoveeDevice[],\n): StateDefinition[] {\n if (device.sku === \"BaseGroup\") {\n return buildGroupStateDefs(memberDevices || []);\n }\n\n // Per-SKU quirk: brokenPlatformApi \u2192 don't trust the platform-cap tree.\n // Skip capability-derived defs AND the scene/snapshot dropdowns (they're\n // gated by hasDynamicSceneCapability which reads device.capabilities, i.e.\n // the same untrusted source). LAN-phase still creates power / brightness /\n // colorRgb / colorTemperature defaults so the device stays controllable.\n const quirks = getDeviceQuirks(device.sku);\n const skipCapabilities = quirks?.brokenPlatformApi === true;\n\n // Capability-derived states. A LAN-owning light (lanIp set) lets the LAN\n // phase own the four control ids (power/brightness/colorRgb/colorTemperature)\n // \u2014 they are filtered out here so they are not double-created in the same\n // channel. Without a lanIp there is NO LAN phase to provide them and nothing\n // to double-create, so keep the cap-derived control states: a cloud-only\n // light (owner never enabled the local API) stays controllable via the Cloud,\n // and appliances (which never have a LAN phase) keep their on/off too. The\n // command-router routes these via Cloud (`no-lan` / `light-no-lan-fallback`).\n // Local-first stays, local-only does not. Sensors carry no such caps; groups\n // returned earlier. LAN lights are unchanged.\n const noLanPhase = !device.lanIp;\n const stateDefs: StateDefinition[] = skipCapabilities\n ? []\n : mapCapabilities(device.capabilities, log).filter(d => noLanPhase || !LAN_STATE_IDS.has(d.id));\n\n if (skipCapabilities) {\n log.debug(`${device.sku}: brokenPlatformApi quirk active \u2014 skipping capability-derived states + dropdowns`);\n }\n\n applyQuirksToStates(device.sku, stateDefs, log);\n\n // Light-only synthetic state defs \u2014 scenes / snapshots / music / scene_speed\n // only make sense for lights. Sensors and appliances would otherwise see\n // empty snapshot dropdowns and a useless save/delete button pair.\n const isLight = device.type === GOVEE_DEVICE_TYPE.LIGHT;\n\n // Three structurally-identical Cloud dropdowns \u2014 collapsed into one loop.\n for (const r of SCENE_DROPDOWN_RULES) {\n if (skipCapabilities || !isLight || !hasDynamicSceneCapability(device.capabilities, r.cap)) {\n continue;\n }\n stateDefs.push({\n id: r.id,\n name: tName(r.nameKey),\n desc: r.descKey ? tDesc(r.descKey) : undefined,\n // mixed lets users write the index (\"1\"), the index as number (1),\n // or the entry name (\"Aurora\") \u2014 the onStateChange handler resolves\n // all three forms via the common.states map.\n type: \"mixed\",\n role: \"state\",\n write: true,\n states: buildUniqueLabelMap(r.source(device)),\n def: \"0\",\n capabilityType: GOVEE_CAP_TYPE.DYNAMIC_SCENE,\n capabilityInstance: r.cap,\n channel: r.channel,\n });\n }\n\n // Scene speed slider \u2014 only if any scene supports speed adjustment.\n // Stays inline: depends on a computed maxSpeedLevel that doesn't fit the\n // dropdown-rule shape.\n const maxSpeedLevel = device.sceneLibrary.reduce((max, entry) => {\n if (entry.speedInfo?.supSpeed && entry.speedInfo.config) {\n try {\n const parsed = JSON.parse(entry.speedInfo.config) as unknown;\n // Config can drift \u2014 if not an array, skip this entry silently\n if (!Array.isArray(parsed)) {\n return max;\n }\n for (const cfg of parsed as Array<{ moveIn?: number[] }>) {\n if (cfg && Array.isArray(cfg.moveIn) && cfg.moveIn.length - 1 > max) {\n max = cfg.moveIn.length - 1;\n }\n }\n } catch (e) {\n log.debug(`${deviceLabel(device)}: speed-config parse failed for scene \"${entry.name}\": ${errMessage(e)}`);\n }\n }\n return max;\n }, -1);\n if (isLight && maxSpeedLevel > 0) {\n stateDefs.push({\n id: \"scene_speed\",\n name: tName(\"sceneSpeed\"),\n type: \"number\",\n role: \"level\",\n write: true,\n min: 0,\n max: maxSpeedLevel,\n def: 0,\n capabilityType: \"local\",\n capabilityInstance: \"sceneSpeed\",\n channel: \"scenes\",\n });\n }\n\n // Per-device refresh button \u2014 gated on ANY dynamic-scene capability.\n // OR-gate over three caps doesn't fit a rules-table.\n // brokenPlatformApi gate skips this too: the refresh action would post a\n // user-facing button for a Cloud endpoint that wasn't trustable to begin\n // with.\n if (\n !skipCapabilities &&\n isLight &&\n (hasDynamicSceneCapability(device.capabilities, \"lightScene\") ||\n hasDynamicSceneCapability(device.capabilities, \"diyScene\") ||\n hasDynamicSceneCapability(device.capabilities, \"snapshot\"))\n ) {\n stateDefs.push({\n id: \"refresh_cloud\",\n name: tName(\"refreshCloud\"),\n desc: tDesc(\"refreshCloudDesc\"),\n type: \"boolean\",\n role: \"button\",\n write: true,\n def: false,\n capabilityType: \"local\",\n capabilityInstance: \"refreshCloud\",\n channel: \"snapshots\",\n });\n }\n\n // Local snapshots \u2014 three states with different shapes (mixed dropdown vs\n // plain string-write fields). Inline because a rules-table would need a\n // discriminator field with no payoff.\n if (isLight) {\n stateDefs.push({\n id: \"snapshot_local\",\n name: tName(\"localSnapshot\"),\n desc: tDesc(\"localSnapshotDesc\"),\n type: \"mixed\",\n role: \"state\",\n write: true,\n states: buildUniqueLabelMap(localSnapshots ?? []),\n def: \"0\",\n capabilityType: \"local\",\n capabilityInstance: \"snapshotLocal\",\n channel: \"snapshots\",\n });\n stateDefs.push({\n id: \"snapshot_save\",\n name: tName(\"saveLocalSnapshot\"),\n desc: tDesc(\"saveLocalSnapshotDesc\"),\n type: \"string\",\n role: \"text\",\n write: true,\n def: \"\",\n capabilityType: \"local\",\n capabilityInstance: \"snapshotSave\",\n channel: \"snapshots\",\n });\n stateDefs.push({\n id: \"snapshot_delete\",\n name: tName(\"deleteLocalSnapshot\"),\n desc: tDesc(\"deleteLocalSnapshotDesc\"),\n type: \"string\",\n role: \"text\",\n write: true,\n def: \"\",\n capabilityType: \"local\",\n capabilityInstance: \"snapshotDelete\",\n channel: \"snapshots\",\n });\n }\n\n // Diagnostics \u2014 export button, JSON result, trust tier. Default tier\n // \"unknown\" for a real device (its actual tier comes from the registry).\n stateDefs.push(...buildDiagStateDefs(\"unknown\"));\n\n return stateDefs;\n}\n\n/** Maps the snake_case LAN control state-id (getDefaultLanStates ids) to its cloud-instance kind. */\nconst CONTROL_STATE_ID_TO_KIND: Record<string, ControlKind> = {\n power: \"power\",\n brightness: \"brightness\",\n color_rgb: \"colorRgb\",\n color_temperature: \"colorTemperature\",\n};\n\n/**\n * Check if a member device supports a given control state.\n * LAN-capable devices support all basic controls.\n *\n * @param member Group member device\n * @param stateId Control state ID (e.g. \"power\", \"brightness\")\n */\nfunction memberHasControlState(member: GoveeDevice, stateId: string): boolean {\n if (member.lanIp) {\n return true;\n }\n const kind = CONTROL_STATE_ID_TO_KIND[stateId];\n if (!kind) {\n return false;\n }\n const caps = Array.isArray(member.capabilities) ? member.capabilities : [];\n return caps.some(c => capMatchesControl(c, kind));\n}\n\n/**\n * Build state definitions for a BaseGroup device.\n * Capabilities = intersection of controllable member devices.\n * No snapshots, no segments; diag-states (export/result/tier) included since v2.9.1.\n *\n * @param members Resolved member devices\n */\nfunction buildGroupStateDefs(members: GoveeDevice[]): StateDefinition[] {\n const controllable = members.filter(m => m.lanIp || m.channels.cloud);\n if (controllable.length === 0) {\n return [];\n }\n\n const stateDefs: StateDefinition[] = [];\n\n // Control states: intersection of member capabilities\n for (const ld of getDefaultLanStates()) {\n if (controllable.every(m => memberHasControlState(m, ld.id))) {\n stateDefs.push(ld);\n }\n }\n\n // Scenes: intersection of member scene names\n if (controllable.every(m => m.scenes.length > 0)) {\n const firstNames = controllable[0].scenes.map(s => s.name);\n const commonNames = firstNames.filter(name => controllable.every(m => m.scenes.some(s => s.name === name)));\n if (commonNames.length > 0) {\n stateDefs.push({\n id: \"light_scene\",\n name: tName(\"lightScene\"),\n type: \"mixed\",\n role: \"state\",\n write: true,\n states: buildUniqueLabelMap(commonNames.map(name => ({ name }))),\n def: \"0\",\n capabilityType: GOVEE_CAP_TYPE.DYNAMIC_SCENE,\n capabilityInstance: \"lightScene\",\n channel: \"scenes\",\n });\n }\n }\n\n // Music: intersection of member music libraries\n if (controllable.every(m => m.musicLibrary.length > 0)) {\n const firstNames = controllable[0].musicLibrary.map(m => m.name);\n const commonNames = firstNames.filter(name => controllable.every(m => m.musicLibrary.some(ml => ml.name === name)));\n if (commonNames.length > 0) {\n stateDefs.push({\n id: \"music_mode\",\n name: tName(\"musicMode\"),\n type: \"mixed\",\n role: \"state\",\n write: true,\n states: buildUniqueLabelMap(commonNames.map(name => ({ name }))),\n def: \"0\",\n capabilityType: GOVEE_CAP_TYPE.MUSIC_SETTING,\n capabilityInstance: \"musicMode\",\n channel: \"music\",\n });\n }\n }\n\n // v2.9.1 \u2014 BaseGroups get the same three diag states. Tier defaults to\n // \"verified\" because BaseGroup isn't a real SKU and has no quirks entry \u2014\n // the diag-button just renders consistently.\n stateDefs.push(...buildDiagStateDefs(null));\n\n return stateDefs;\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -236,10 +236,10 @@ class CommandRouter {
236
236
  return;
237
237
  }
238
238
  if (device.channels.cloud && !this.cloudClient) {
239
- this.log.debug(`Command for ${device.name} dropped: Cloud client not ready yet`);
239
+ this.log.debug(`Command for ${(0, import_types.deviceLabel)(device)} dropped: Cloud client not ready yet`);
240
240
  return;
241
241
  }
242
- this.log.warn(`No channel available for ${device.name} (${device.sku})`);
242
+ this.log.warn(`No channel available for ${(0, import_types.deviceLabel)(device)}`);
243
243
  }
244
244
  /**
245
245
  * Send a command to a device. Routing is decided up-front by
@@ -375,7 +375,7 @@ class CommandRouter {
375
375
  */
376
376
  async sendCapabilityCommand(device, capabilityType, capabilityInstance, value) {
377
377
  if (!this.cloudClient || !device.channels.cloud) {
378
- this.log.debug(`Cloud not available for generic command on ${device.name}`);
378
+ this.log.debug(`Cloud not available for generic command on ${(0, import_types.deviceLabel)(device)}`);
379
379
  return;
380
380
  }
381
381
  const shortType = capabilityType.replace("devices.capabilities.", "");
@@ -406,12 +406,12 @@ class CommandRouter {
406
406
  return;
407
407
  }
408
408
  if (!parsed) {
409
- this.log.warn(`Invalid segment command "${commandStr}" for ${device.name}`);
409
+ this.log.warn(`Invalid segment command "${commandStr}" for ${(0, import_types.deviceLabel)(device)}`);
410
410
  return;
411
411
  }
412
412
  const cap = this.findCapabilityForCommand(device, "segmentColor:0");
413
413
  if (!cap) {
414
- this.log.debug(`No segment capability for ${device.name}`);
414
+ this.log.debug(`No segment capability for ${(0, import_types.deviceLabel)(device)}`);
415
415
  return;
416
416
  }
417
417
  if (parsed.color !== void 0) {
@@ -552,7 +552,7 @@ class CommandRouter {
552
552
  case "lightScene": {
553
553
  const idx = parseInt(String(value), 10);
554
554
  if (isNaN(idx) || idx < 1 || idx > device.scenes.length) {
555
- this.log.warn(`${device.sku}: invalid scene index ${String(value)}`);
555
+ this.log.warn(`${(0, import_types.deviceLabel)(device)}: invalid scene index ${String(value)}`);
556
556
  return value;
557
557
  }
558
558
  return device.scenes[idx - 1].value;
@@ -560,7 +560,7 @@ class CommandRouter {
560
560
  case "diyScene": {
561
561
  const idx = parseInt(String(value), 10);
562
562
  if (isNaN(idx) || idx < 1 || idx > device.diyScenes.length) {
563
- this.log.warn(`${device.sku}: invalid scene index ${String(value)}`);
563
+ this.log.warn(`${(0, import_types.deviceLabel)(device)}: invalid scene index ${String(value)}`);
564
564
  return value;
565
565
  }
566
566
  return device.diyScenes[idx - 1].value;
@@ -568,7 +568,7 @@ class CommandRouter {
568
568
  case "snapshot": {
569
569
  const idx = parseInt(String(value), 10);
570
570
  if (isNaN(idx) || idx < 1 || idx > device.snapshots.length) {
571
- this.log.warn(`${device.sku}: invalid snapshot index ${String(value)}`);
571
+ this.log.warn(`${(0, import_types.deviceLabel)(device)}: invalid snapshot index ${String(value)}`);
572
572
  return value;
573
573
  }
574
574
  return device.snapshots[idx - 1].value;
@@ -577,7 +577,7 @@ class CommandRouter {
577
577
  if (command.startsWith("segmentColor:")) {
578
578
  const segIdx = parseInt(command.split(":")[1], 10);
579
579
  if (isNaN(segIdx) || segIdx < 0) {
580
- this.log.warn(`${device.sku}: invalid segment index in ${command}`);
580
+ this.log.warn(`${(0, import_types.deviceLabel)(device)}: invalid segment index in ${command}`);
581
581
  return value;
582
582
  }
583
583
  const { r, g, b } = (0, import_types.hexToRgb)(value);
@@ -586,7 +586,7 @@ class CommandRouter {
586
586
  if (command.startsWith("segmentBrightness:")) {
587
587
  const segIdx = parseInt(command.split(":")[1], 10);
588
588
  if (isNaN(segIdx) || segIdx < 0) {
589
- this.log.warn(`${device.sku}: invalid segment index in ${command}`);
589
+ this.log.warn(`${(0, import_types.deviceLabel)(device)}: invalid segment index in ${command}`);
590
590
  return value;
591
591
  }
592
592
  return { segment: [segIdx], brightness: value };
@@ -667,7 +667,7 @@ class CommandRouter {
667
667
  case "diyScene": {
668
668
  const diyIdx = parseInt(String(value), 10);
669
669
  if (isNaN(diyIdx) || diyIdx < 1 || diyIdx > device.diyScenes.length) {
670
- this.log.warn(`${device.sku}: invalid scene index ${String(value)}`);
670
+ this.log.warn(`${(0, import_types.deviceLabel)(device)}: invalid scene index ${String(value)}`);
671
671
  return;
672
672
  }
673
673
  const diyScene = device.diyScenes[diyIdx - 1];
@@ -685,7 +685,7 @@ class CommandRouter {
685
685
  case "lightScene": {
686
686
  const idx = parseInt(String(value), 10);
687
687
  if (isNaN(idx) || idx < 1 || idx > device.scenes.length) {
688
- this.log.warn(`${device.sku}: invalid scene index ${String(value)}`);
688
+ this.log.warn(`${(0, import_types.deviceLabel)(device)}: invalid scene index ${String(value)}`);
689
689
  return;
690
690
  }
691
691
  const scene = device.scenes[idx - 1];
@@ -709,7 +709,7 @@ class CommandRouter {
709
709
  case "snapshot": {
710
710
  const idx = parseInt(String(value), 10);
711
711
  if (isNaN(idx) || idx < 1 || idx > device.snapshots.length) {
712
- this.log.warn(`${device.sku}: invalid snapshot index ${String(value)}`);
712
+ this.log.warn(`${(0, import_types.deviceLabel)(device)}: invalid snapshot index ${String(value)}`);
713
713
  return;
714
714
  }
715
715
  const cmdGroups = (_e = device.snapshotBleCmds) == null ? void 0 : _e[idx - 1];