iobroker.govee-smart 2.18.1 → 2.18.2

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,10 @@ 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.18.2 (2026-07-03)
98
+
99
+ - Internal refactoring. No user-facing changes.
100
+
97
101
  ### 2.18.1 (2026-07-03)
98
102
 
99
103
  - Added several new Govee devices to the catalog — 4 more lamps and 3 sensors (including a CO2 monitor). They start as experimental; enable them in the adapter settings to try them.
@@ -146,12 +150,6 @@ This adapter's MQTT authentication and BLE-over-LAN (ptReal) protocol implementa
146
150
  ### 2.16.2 (2026-06-16) — stable
147
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.
148
152
 
149
- ### 2.16.1 (2026-06-11)
150
-
151
- - Running in compact mode no longer intercepts errors from other adapters in the same process — error reporting stays correctly attributed per adapter.
152
- - Cloud commands issued during a long rate-limit window no longer pile up without limit; stale queued calls are dropped with a single warning.
153
- - Device info entries (name, model, IP address) no longer rewrite their timestamps on every refresh — state history stays clean.
154
-
155
153
  [Older changelogs can be found there](CHANGELOG_OLD.md)
156
154
 
157
155
  ## Support
@@ -19,6 +19,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
19
19
  var capability_mapper_exports = {};
20
20
  __export(capability_mapper_exports, {
21
21
  LAN_STATE_IDS: () => LAN_STATE_IDS,
22
+ SENSOR_ROLE_UNIT: () => SENSOR_ROLE_UNIT,
22
23
  applyQuirksToStates: () => applyQuirksToStates,
23
24
  buildCloudStateDefs: () => buildCloudStateDefs,
24
25
  buildLanStateDefs: () => buildLanStateDefs,
@@ -312,23 +313,25 @@ function mapMode(cap) {
312
313
  }
313
314
  ];
314
315
  }
316
+ const SENSOR_ROLE_UNIT = {
317
+ temperature: { role: "value.temperature", unit: "\xB0C" },
318
+ humidity: { role: "value.humidity", unit: "%" },
319
+ battery: { role: "value.battery", unit: "%" },
320
+ co2: { role: "value.co2", unit: "ppm" }
321
+ };
315
322
  function mapProperty(cap) {
316
323
  var _a, _b;
317
324
  const instance = cap.instance.toLowerCase();
318
325
  let role = "value";
319
326
  let unit;
320
327
  if (instance.includes("temperature")) {
321
- role = "value.temperature";
322
- unit = "\xB0C";
328
+ ({ role, unit } = SENSOR_ROLE_UNIT.temperature);
323
329
  } else if (instance.includes("humidity")) {
324
- role = "value.humidity";
325
- unit = "%";
330
+ ({ role, unit } = SENSOR_ROLE_UNIT.humidity);
326
331
  } else if (instance.includes("battery")) {
327
- role = "value.battery";
328
- unit = "%";
332
+ ({ role, unit } = SENSOR_ROLE_UNIT.battery);
329
333
  } else if (instance.includes("co2") || instance.includes("carbondioxide")) {
330
- role = "value.co2";
331
- unit = "ppm";
334
+ ({ role, unit } = SENSOR_ROLE_UNIT.co2);
332
335
  }
333
336
  return [
334
337
  {
@@ -995,6 +998,7 @@ function buildGroupStateDefs(members) {
995
998
  // Annotate the CommonJS export names for ESM import in node:
996
999
  0 && (module.exports = {
997
1000
  LAN_STATE_IDS,
1001
+ SENSOR_ROLE_UNIT,
998
1002
  applyQuirksToStates,
999
1003
  buildCloudStateDefs,
1000
1004
  buildLanStateDefs,
@@ -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 * 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 = \"value.temperature\";\n unit = \"\u00B0C\";\n } else if (instance.includes(\"humidity\")) {\n role = \"value.humidity\";\n unit = \"%\";\n } else if (instance.includes(\"battery\")) {\n role = \"value.battery\";\n unit = \"%\";\n } else if (instance.includes(\"co2\") || instance.includes(\"carbondioxide\")) {\n role = \"value.co2\";\n unit = \"ppm\";\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 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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",
6
6
  "names": []
7
7
  }
@@ -38,12 +38,19 @@ const CHANNEL_NAMES = {
38
38
  info: "Device Information",
39
39
  diag: "Diagnostics"
40
40
  };
41
+ const numSensor = (kind, nameKey) => ({
42
+ type: "number",
43
+ role: import_capability_mapper.SENSOR_ROLE_UNIT[kind].role,
44
+ unit: import_capability_mapper.SENSOR_ROLE_UNIT[kind].unit,
45
+ nameKey,
46
+ channel: "sensor"
47
+ });
41
48
  const SYNTHETIC_STATE_META = {
42
- temperature: { type: "number", role: "value.temperature", unit: "\xB0C", nameKey: "temperature", channel: "sensor" },
43
- humidity: { type: "number", role: "value.humidity", unit: "%", nameKey: "humidity", channel: "sensor" },
44
- battery: { type: "number", role: "value.battery", unit: "%", nameKey: "battery", channel: "sensor" },
45
- co2: { type: "number", role: "value.co2", unit: "ppm", nameKey: "co2", channel: "sensor" },
46
- carbondioxide: { type: "number", role: "value.co2", unit: "ppm", nameKey: "co2", channel: "sensor" },
49
+ temperature: numSensor("temperature", "temperature"),
50
+ humidity: numSensor("humidity", "humidity"),
51
+ battery: numSensor("battery", "battery"),
52
+ co2: numSensor("co2", "co2"),
53
+ carbondioxide: numSensor("co2", "co2"),
47
54
  online: { type: "boolean", role: "indicator.connected", nameKey: "online", channel: "sensor" },
48
55
  lackwater: { type: "boolean", role: "indicator.maintenance", nameKey: "lackOfWater", channel: "events" },
49
56
  lackwaterevent: { type: "boolean", role: "indicator.maintenance", nameKey: "lackOfWater", channel: "events" },
@@ -51,15 +58,9 @@ const SYNTHETIC_STATE_META = {
51
58
  icefullevent: { type: "boolean", role: "indicator.maintenance", nameKey: "iceBucketFull", channel: "events" },
52
59
  bodyappeared: { type: "boolean", role: "sensor.motion", nameKey: "bodyDetected", channel: "events" },
53
60
  dirtdetected: { type: "boolean", role: "indicator.maintenance", nameKey: "dirtDetected", channel: "events" },
54
- sensor_temperature: {
55
- type: "number",
56
- role: "value.temperature",
57
- unit: "\xB0C",
58
- nameKey: "temperature",
59
- channel: "sensor"
60
- },
61
- sensor_humidity: { type: "number", role: "value.humidity", unit: "%", nameKey: "humidity", channel: "sensor" },
62
- sensor_battery: { type: "number", role: "value.battery", unit: "%", nameKey: "battery", channel: "sensor" },
61
+ sensor_temperature: numSensor("temperature", "temperature"),
62
+ sensor_humidity: numSensor("humidity", "humidity"),
63
+ sensor_battery: numSensor("battery", "battery"),
63
64
  lack_water: { type: "boolean", role: "indicator.maintenance", nameKey: "lackOfWater", channel: "events" },
64
65
  lack_water_event: { type: "boolean", role: "indicator.maintenance", nameKey: "lackOfWater", channel: "events" },
65
66
  ice_full: { type: "boolean", role: "indicator.maintenance", nameKey: "iceBucketFull", channel: "events" },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/state-manager.ts"],
4
- "sourcesContent": ["import type * as utils from \"@iobroker/adapter-core\";\nimport { buildLanStateDefs, LAN_STATE_IDS, type StateDefinition } from \"./capability-mapper\";\nimport { GROUP_ICON, iconForGoveeType, shortenGoveeType } from \"./device-icons\";\nimport { resolveSegmentCount } from \"./device-manager\";\nimport { GOVEE_DEVICE_TYPE } from \"./govee-constants\";\nimport type { I18nKey } from \"./i18n\";\nimport { tDesc, tName } from \"./i18n\";\nimport type { DeviceState, GoveeDevice } from \"./types\";\nimport { mapKey, treeKey } from \"./device-key\";\n\n/**\n * Channels whose state-set is fully described by capability-driven stateDefs.\n * Only these get the stale-state cleanup pass \u2014 `info` is intentionally absent\n * because it mixes capability-driven states (diagnostics_export/result) with\n * adapter-managed ones (online, model, serial, ip, members) that come from\n * ensureState instead of stateDefs. Cleaning `info` by stateDef-set would\n * delete the adapter-managed ones.\n */\nconst MANAGED_CHANNELS = [\"control\", \"scenes\", \"music\", \"snapshots\", \"sensor\", \"events\"];\n/**\n * Display names used when the channel object is (re-)created. `info` is\n * listed here even though it's not in MANAGED_CHANNELS \u2014 capability-mapper\n * emits states with `channel: \"info\"`, and without this entry the create\n * path would overwrite the original \"Device Information\" name with the\n * literal \"info\".\n */\nconst CHANNEL_NAMES: Record<string, string> = {\n control: \"Controls\",\n scenes: \"Scenes\",\n music: \"Music\",\n snapshots: \"Snapshots\",\n sensor: \"Sensor Data\",\n events: \"Events\",\n info: \"Device Information\",\n diag: \"Diagnostics\",\n};\n\n/**\n * Synthetic capabilities written by the App-API poll and OpenAPI-MQTT\n * handler arrive as `(stateId, value)` pairs without a channel hint \u2014\n * `mapCloudStateValue` returns only what the Govee response carries.\n *\n * For lights the LAN/Cloud-state pipeline pre-populates `stateChannelMap`\n * via `createDeviceStates`, so `resolveStatePath` finds the channel.\n * For thermometer/appliance state IDs that are *only* delivered via the\n * App-API path (battery, temperature, humidity, CO\u2082, lackWater, \u2026) the map\n * is empty and the lookup would default to \"control\" \u2014 visibly wrong:\n * `info: control.battery has no existing object`.\n *\n * This routing table assigns those IDs to their semantic channel without\n * needing a separate `createDeviceStates` pass for sensor-only devices.\n * Keep IDs lowercase; resolveStatePath calls this on the raw stateId.\n */\n// Two spellings per ID below \u2014 the \"raw\" form (e.g. `temperature`) for instances\n// named exactly that, and the sanitizeId output (e.g. `sensor_temperature`) for\n// camelCase instances that capability-mapper's sanitizeId converted to\n// snake_case. Without the alias the sanitized variant would fall back to the\n// safe default \"control\" and its state would be unreachable.\n\n/**\n * Per-stateId metadata for synthetic states (App-API/OpenAPI-MQTT pipe). The\n * `channel` is the single source of truth for {@link inferChannelFromStateId},\n * so the routing and the state definition never drift apart.\n */\ninterface SyntheticStateMeta {\n type: \"boolean\" | \"number\";\n role: string;\n unit?: string;\n nameKey: I18nKey;\n /** Semantic channel \u2014 sensor readings vs. device events. */\n channel: \"sensor\" | \"events\";\n}\nconst SYNTHETIC_STATE_META: Record<string, SyntheticStateMeta> = {\n temperature: { type: \"number\", role: \"value.temperature\", unit: \"\u00B0C\", nameKey: \"temperature\", channel: \"sensor\" },\n humidity: { type: \"number\", role: \"value.humidity\", unit: \"%\", nameKey: \"humidity\", channel: \"sensor\" },\n battery: { type: \"number\", role: \"value.battery\", unit: \"%\", nameKey: \"battery\", channel: \"sensor\" },\n co2: { type: \"number\", role: \"value.co2\", unit: \"ppm\", nameKey: \"co2\", channel: \"sensor\" },\n carbondioxide: { type: \"number\", role: \"value.co2\", unit: \"ppm\", nameKey: \"co2\", channel: \"sensor\" },\n online: { type: \"boolean\", role: \"indicator.connected\", nameKey: \"online\", channel: \"sensor\" },\n lackwater: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"lackOfWater\", channel: \"events\" },\n lackwaterevent: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"lackOfWater\", channel: \"events\" },\n icefull: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"iceBucketFull\", channel: \"events\" },\n icefullevent: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"iceBucketFull\", channel: \"events\" },\n bodyappeared: { type: \"boolean\", role: \"sensor.motion\", nameKey: \"bodyDetected\", channel: \"events\" },\n dirtdetected: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"dirtDetected\", channel: \"events\" },\n sensor_temperature: {\n type: \"number\",\n role: \"value.temperature\",\n unit: \"\u00B0C\",\n nameKey: \"temperature\",\n channel: \"sensor\",\n },\n sensor_humidity: { type: \"number\", role: \"value.humidity\", unit: \"%\", nameKey: \"humidity\", channel: \"sensor\" },\n sensor_battery: { type: \"number\", role: \"value.battery\", unit: \"%\", nameKey: \"battery\", channel: \"sensor\" },\n lack_water: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"lackOfWater\", channel: \"events\" },\n lack_water_event: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"lackOfWater\", channel: \"events\" },\n ice_full: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"iceBucketFull\", channel: \"events\" },\n ice_full_event: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"iceBucketFull\", channel: \"events\" },\n body_appeared: { type: \"boolean\", role: \"sensor.motion\", nameKey: \"bodyDetected\", channel: \"events\" },\n dirt_detected: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"dirtDetected\", channel: \"events\" },\n};\n\n/**\n * Best-effort channel routing for a synthetic state ID with no stateChannelMap\n * entry yet (App-API caps before createDeviceStates). Reads the channel straight\n * from {@link SYNTHETIC_STATE_META}; an unknown ID falls back to the safe default\n * \"control\". Keep IDs lowercase \u2014 resolveStatePath calls this on the raw stateId.\n *\n * @param stateId The raw state ID (e.g. \"battery\", \"lackWater\")\n */\nfunction inferChannelFromStateId(stateId: string): string {\n return SYNTHETIC_STATE_META[stateId.toLowerCase()]?.channel ?? \"control\";\n}\n\n/** Manages ioBroker state creation and updates for Govee devices */\nexport class StateManager {\n private readonly adapter: utils.AdapterInstance;\n /** Maps deviceKey (sku_deviceId) \u2192 current object prefix */\n private readonly prefixMap = new Map<string, string>();\n /** Maps \"prefix.stateId\" \u2192 channel name (populated during createDeviceStates) */\n private readonly stateChannelMap = new Map<string, string>();\n /**\n * Cache of state IDs already created via {@link ensureState} \u2014 skips the\n * `extendObject` round-trip on the hot path. Refreshed on\n * {@link removeDevice}/{@link forgetPrefix} so a re-pair doesn't reuse stale\n * cache entries.\n */\n private readonly ensuredStates = new Set<string>();\n /**\n * \"prefix.stateId\" keys already handled by {@link removeSyntheticStateOnce} \u2014\n * bounds the phantom-state cleanup to one existence-check per adapter run.\n */\n private readonly cleanedSyntheticStates = new Set<string>();\n\n /** @param adapter The ioBroker adapter instance */\n constructor(adapter: utils.AdapterInstance) {\n this.adapter = adapter;\n }\n\n /**\n * Force-replace `common.states` on a persisted state object if any existing\n * value is non-string (= translation object from older releases).\n *\n * `extendObject` deep-merges and CANNOT replace an object-value with a\n * string. Only a full `setObjectAsync` replaces. Same fix-pattern as\n * hassemu v1.27.2 (URL-dropdown) and v1.28.4 (mode-dropdown). Admin\n * renders states-values as React children \u2014 a translation object triggers\n * React Error #31 \u2192 fatal \"Error in GUI\" on dropdown open (write:true) or\n * any render path (write:false like diag.tier).\n *\n * @param id Full state path.\n * @param fresh Plain-string `common.states` map to write.\n */\n private async repairCommonStatesIfBuggy(id: string, fresh: Record<string, string>): Promise<void> {\n const existing = await this.adapter.getObjectAsync(id).catch(() => null);\n if (!existing) {\n return;\n }\n const states = existing.common?.states;\n if (!states || typeof states !== \"object\") {\n return;\n }\n const buggy = Object.values(states as Record<string, unknown>).some(v => typeof v !== \"string\");\n if (!buggy) {\n return;\n }\n await this.adapter.extendObject(id, { common: { states: fresh } }).catch(() => undefined);\n }\n\n /**\n * @param id Voller State-Pfad (`devices.X.info.Y`)\n */\n private async safeDeleteState(id: string): Promise<void> {\n const obj = await this.adapter.getObjectAsync(id).catch(() => null);\n if (!obj) {\n return;\n }\n await this.adapter.delStateAsync(id).catch(() => undefined);\n await this.adapter.delObjectAsync(id).catch(() => undefined);\n }\n\n /**\n * Remove a synthetic App-API/OpenAPI-MQTT sensor state (e.g. a phantom\n * `sensor_humidity` on a temp-only thermometer) at most once per adapter run.\n * These states are created ad-hoc by the App-API pipe and are NOT covered by\n * any def-driven cleanup, so a datapoint that should no longer exist (Govee's\n * `hum:0` sentinel on a device without a humidity capability, #31) would\n * otherwise linger forever. Existence-checked via {@link safeDeleteState}, so\n * it's a silent no-op on installs that never had the state.\n *\n * @param prefix Device object ID prefix\n * @param stateId Synthetic state ID (e.g. \"sensor_humidity\")\n */\n async removeSyntheticStateOnce(prefix: string, stateId: string): Promise<void> {\n const key = `${prefix}.${stateId}`;\n if (this.cleanedSyntheticStates.has(key)) {\n return;\n }\n this.cleanedSyntheticStates.add(key);\n await this.safeDeleteState(this.resolveStatePath(prefix, stateId));\n }\n\n /**\n * Push the device's trust tier (verified/reported/seed/unknown) into\n * the user-visible `diag.tier` state. Called after every device-state\n * refresh so the value tracks any registry change between adapter\n * restarts (e.g. seed \u2192 verified once a tester confirms). No-op for\n * groups (BaseGroup has no per-device tier).\n *\n * @param device Govee device\n * @param tier Canonical tier label\n */\n async updateDeviceTier(device: GoveeDevice, tier: string): Promise<void> {\n if (device.sku === \"BaseGroup\") {\n return;\n }\n const prefix = this.devicePrefix(device);\n await this.adapter.setState(`${prefix}.diag.tier`, { val: tier, ack: true }).catch(() => undefined);\n }\n\n /**\n * Migrate v2.1.0 layout (`info.diagnostics_*`) to v2.1.1 layout\n * (`diag.*`). Deletes the three old objects + states; the new ones get\n * created by the regular `createDeviceStates` pass. Idempotent \u2014 calling\n * twice is a no-op once the old objects are gone.\n *\n * @param device Govee device\n */\n async migrateLegacyDiagnostics(device: GoveeDevice): Promise<void> {\n if (device.sku === \"BaseGroup\") {\n return;\n }\n const prefix = this.devicePrefix(device);\n for (const stale of [\"diagnostics_export\", \"diagnostics_result\", \"diagnostics_tier\"]) {\n await this.safeDeleteState(`${prefix}.info.${stale}`);\n this.stateChannelMap.delete(`${prefix}.${stale}`);\n }\n }\n\n /**\n * B2 hard-cut migration: the LAN control colour states were renamed from\n * camelCase (`control.colorRgb` / `control.colorTemperature`) to snake_case\n * (`control.color_rgb` / `control.color_temperature`) for a consistent\n * state-tree spelling. Delete the old objects on upgraded installs so they\n * don't linger as dead duplicates next to the freshly-created snake_case\n * states. Existence-checked + idempotent (a no-op once migrated / on fresh\n * installs); works for devices AND groups (both carry these control states).\n * Users must update scripts that referenced the old ids.\n *\n * @param device Govee device or group\n */\n async migrateLegacyColorStateIds(device: GoveeDevice): Promise<void> {\n const prefix = this.devicePrefix(device);\n await this.safeDeleteState(`${prefix}.control.colorRgb`);\n await this.safeDeleteState(`${prefix}.control.colorTemperature`);\n }\n\n /**\n * Resolve full state path for a given device prefix and state ID.\n * Routes the state to the correct channel (control, scenes, music, snapshots).\n *\n * @param prefix Device object ID prefix\n * @param stateId State ID suffix\n */\n resolveStatePath(prefix: string, stateId: string): string {\n const channel = this.stateChannelMap.get(`${prefix}.${stateId}`) ?? inferChannelFromStateId(stateId);\n return `${prefix}.${channel}.${stateId}`;\n }\n\n /**\n * Lazily create the channel + state object for synthetic state IDs the\n * App-API poll and OpenAPI-MQTT pipeline write. Cloud-capability defs\n * for sensor SKUs (e.g. H5179) are often empty in OpenAPI v2, so the\n * usual `createDeviceStates` pass would not declare battery / temperature\n * / events.* \u2014 without this helper the first write logs\n * `info: <id> has no existing object`.\n *\n * Idempotent: skips when the meta table doesn't know the stateId, and\n * `setObjectNotExistsAsync` is itself a no-op for existing objects.\n *\n * @param prefix Device prefix (e.g. \"devices.h5179_aabb\")\n * @param stateId State ID without channel (e.g. \"battery\")\n */\n async ensureSyntheticStateObject(prefix: string, stateId: string): Promise<void> {\n const meta = SYNTHETIC_STATE_META[stateId.toLowerCase()];\n if (!meta) {\n return;\n }\n const channel = inferChannelFromStateId(stateId);\n // Channel object first \u2014 sensors land in the new sensor/ subtree, events\n // in events/. Without an extendObject the channel parent stays missing\n // and Admin shows the state directly under the device root.\n await this.adapter\n .extendObject(\n `${prefix}.${channel}`,\n {\n type: \"channel\",\n common: { name: CHANNEL_NAMES[channel] ?? channel },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n )\n .catch(() => undefined);\n await this.adapter\n .extendObject(\n `${prefix}.${channel}.${stateId}`,\n {\n type: \"state\",\n common: {\n name: tName(meta.nameKey),\n type: meta.type,\n role: meta.role,\n read: true,\n write: false,\n ...(meta.unit !== undefined ? { unit: meta.unit } : {}),\n def: meta.type === \"boolean\" ? false : 0,\n },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n )\n .catch(() => undefined);\n this.stateChannelMap.set(`${prefix}.${stateId}`, channel);\n }\n\n /**\n * Phase 1 \u2014 Info-States. Always-existing device metadata: info.name,\n * info.online (per-device for lights, global for groups), info.model,\n * info.serial, info.ip, info.type. For groups: info.members + cleanup\n * of legacy device-level info states.\n *\n * Idempotent. Called from every phase callback (LAN-Phase + Cloud-Phase\n * + Group-Phase) \u2014 extendObject de-duplicates so the cost is small.\n *\n * Never deletes states from MANAGED_CHANNELS. The info channel is not in\n * MANAGED_CHANNELS, so cleanup never touches its content.\n *\n * @param device Govee device\n */\n async createInfoStates(device: GoveeDevice): Promise<void> {\n const key = this.deviceKey(device);\n const newPrefix = this.devicePrefix(device);\n const oldPrefix = this.prefixMap.get(key);\n\n // Migrate if prefix changed (e.g., old naming scheme)\n if (oldPrefix && oldPrefix !== newPrefix) {\n this.adapter.log.debug(`Migrating device ${device.sku}: ${oldPrefix} \u2192 ${newPrefix}`);\n await this.adapter.delObjectAsync(oldPrefix, { recursive: true });\n // Drop stale channel-map entries under the old prefix so they don't\n // shadow resolveStatePath lookups after the rename.\n const oldChannelKey = `${oldPrefix}.`;\n for (const mapKey of this.stateChannelMap.keys()) {\n if (mapKey.startsWith(oldChannelKey)) {\n this.stateChannelMap.delete(mapKey);\n }\n }\n }\n this.prefixMap.set(key, newPrefix);\n\n const prefix = newPrefix;\n const isGroup = device.sku === \"BaseGroup\";\n\n // Device object with online status indicator + type-aware icon.\n // Groups use the general groups.info.online state instead of per-group online.\n const onlineId = isGroup\n ? `${this.adapter.namespace}.groups.info.online`\n : `${this.adapter.namespace}.${prefix}.info.online`;\n const icon = isGroup ? GROUP_ICON : iconForGoveeType(device.type);\n await this.adapter.extendObject(\n prefix,\n {\n type: \"device\",\n common: {\n name: device.name,\n icon,\n statusStates: { onlineId },\n },\n native: {\n sku: device.sku,\n deviceId: device.deviceId,\n },\n },\n { preserve: { common: [\"name\"] } },\n );\n\n // Info channel \u2014 groups only get name (no individual online)\n await this.adapter.extendObject(\n `${prefix}.info`,\n {\n type: \"channel\",\n common: { name: tName(\"deviceInformation\") },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n\n // setStateChangedAsync (not setState) for the info metadata below:\n // createInfoStates re-runs on every phase callback (cloud refresh,\n // snapshot save/delete, \u2026) with mostly identical values \u2014 only a real\n // change should write and bump the timestamp. No consumer reads the\n // ts/lc of these states as a freshness signal (grep-verified).\n await this.ensureState(`${prefix}.info.name`, \"Name\", \"string\", \"text\", false);\n await this.adapter.setStateChangedAsync(`${prefix}.info.name`, {\n val: device.name,\n ack: true,\n });\n\n if (!isGroup) {\n await this.ensureState(\n `${prefix}.info.online`,\n \"Online\",\n \"boolean\",\n \"indicator.reachable\",\n false,\n undefined,\n false,\n );\n // info.online is written via syncInfoOnline (resolver-based, no\n // ts-rewrite-spam). The initial sync happens right after this method\n // returns \u2014 see syncInfoOnline. Direct write here was the source of\n // periodic false\u2192true bounces (captured 2026-05-13).\n await this.ensureState(`${prefix}.info.model`, \"Model\", \"string\", \"text\", false, undefined, \"\");\n await this.ensureState(`${prefix}.info.serial`, \"Serial Number\", \"string\", \"text\", false, undefined, \"\");\n await this.ensureState(`${prefix}.info.ip`, \"IP Address\", \"string\", \"info.ip\", false, undefined, \"\");\n // Device-type marker \u2014 short label like \"light\", \"thermometer\",\n // \"heater\" (Govee API type without the \"devices.types.\" prefix).\n // Lets scripts filter `*.info.type === \"light\"` without parsing.\n await this.ensureState(`${prefix}.info.type`, \"Device Type\", \"string\", \"text\", false, undefined, \"\");\n await this.adapter.setStateChangedAsync(`${prefix}.info.model`, {\n val: device.sku,\n ack: true,\n });\n await this.adapter.setStateChangedAsync(`${prefix}.info.serial`, {\n val: device.deviceId,\n ack: true,\n });\n await this.adapter.setStateChangedAsync(`${prefix}.info.ip`, {\n val: device.lanIp ?? \"\",\n ack: true,\n });\n await this.adapter.setStateChangedAsync(`${prefix}.info.type`, {\n val: shortenGoveeType(device.type),\n ack: true,\n });\n // Initial info.online sync \u2014 see syncInfoOnline for the resolver.\n // Subsequent updates come from the periodic sync timer in main.ts\n // and from direct calls in onDeviceStateUpdate when state.online\n // arrives via the existing event paths.\n await this.syncInfoOnline(device);\n } else {\n // Group members: comma-separated device prefix IDs\n const memberIds = (device.groupMembers ?? []).map(m => treeKey(m.sku, m.deviceId)).join(\", \");\n await this.ensureState(`${prefix}.info.members`, \"Members\", \"string\", \"text\", false);\n await this.adapter.setStateChangedAsync(`${prefix}.info.members`, {\n val: memberIds,\n ack: true,\n });\n\n // Legacy cleanup \u2014 groups never carry device-level info states or\n // diagnostics, but older installs had them. Drop any leftovers so the\n // tree reflects the current layout.\n for (const staleId of [\n \"online\",\n \"model\",\n \"serial\",\n \"ip\",\n \"diagnostics_export\",\n \"diagnostics_result\",\n \"diagnostics_tier\",\n ]) {\n await this.safeDeleteState(`${prefix}.info.${staleId}`);\n }\n // Groups never had a `diag` channel \u2014 drop any leftover from migrated installs.\n await this.adapter.delObjectAsync(`${prefix}.diag`, { recursive: true }).catch(() => {});\n }\n }\n\n /**\n * Phase 2 \u2014 LAN-States. Power, brightness, colorRgb, colorTemperature\n * (the LAN-default set defined by getDefaultLanStates). Pure additive:\n * never deletes from MANAGED_CHANNELS, no cleanup at end. Devices without\n * lanIp get no states (sensors/appliances/groups skip silently).\n *\n * @param device Govee device\n */\n async createLanStates(device: GoveeDevice): Promise<void> {\n const stateDefs = buildLanStateDefs(device, this.adapter.log);\n if (stateDefs.length === 0) {\n this.adapter.log.debug(\n `buildLanStateDefs for ${device.sku} ${device.deviceId}: 0 states (no LAN IP / not a light) \u2014 LAN phase skipped`,\n );\n return;\n }\n this.adapter.log.debug(\n `buildLanStateDefs for ${device.sku} ${device.deviceId}: ${stateDefs.length} state(s) \u2192 writing to LAN channel`,\n );\n const prefix = this.devicePrefix(device);\n await this.writeStateDefsToChannels(prefix, stateDefs, \"LAN\");\n }\n\n /**\n * Phase 3 \u2014 Cloud-States. Capability-derived states (scenes, music,\n * snapshots, sensor, events, segments, cloud-only control toggles) plus\n * synthetic local states (diagnostics, refresh_cloud, snapshot_local/...).\n * Runs cleanupCloudOwnedStates at the end to remove states no longer\n * present in stateDefs \u2014 but LAN-default ids in the control channel are\n * preserved via the LAN_STATE_IDS skip.\n *\n * @param device Govee device\n * @param stateDefs Cloud-owned state definitions from buildCloudStateDefs\n */\n async createCloudStates(device: GoveeDevice, stateDefs: StateDefinition[]): Promise<void> {\n const prefix = this.devicePrefix(device);\n\n // Drop _segment_ marker entries \u2014 segments have their own dedicated\n // createSegmentStates pass (per-segment color/brightness states).\n const nonSegmentDefs = stateDefs.filter(d => !d.id.startsWith(\"_segment_\"));\n await this.writeStateDefsToChannels(prefix, nonSegmentDefs, `Cloud ${device.sku}`);\n\n // Remove states no longer present in this Cloud-phase build. LAN_STATE_IDS\n // protects the LAN-default ids in the control channel \u2014 the LAN phase\n // owns those.\n await this.cleanupCloudOwnedStates(prefix, nonSegmentDefs);\n\n // Segment channel if device has segment caps\n if (stateDefs.some(d => d.id.startsWith(\"_segment_\"))) {\n await this.createSegmentStates(device);\n }\n }\n\n /**\n * Shared inner loop \u2014 group stateDefs by channel, create the channel\n * object once, then create each state. Called from createLanStates and\n * createCloudStates. Idempotent (extendObject).\n *\n * @param prefix Device prefix (e.g. \"devices.h6172_abcd\")\n * @param stateDefs State definitions to write\n * @param logTag Short tag for the per-phase debug log line\n */\n private async writeStateDefsToChannels(prefix: string, stateDefs: StateDefinition[], logTag: string): Promise<void> {\n const channelGroups = new Map<string, StateDefinition[]>();\n for (const def of stateDefs) {\n const channel = def.channel ?? \"control\";\n this.stateChannelMap.set(`${prefix}.${def.id}`, channel);\n if (!channelGroups.has(channel)) {\n channelGroups.set(channel, []);\n }\n channelGroups.get(channel)!.push(def);\n }\n\n this.adapter.log.debug(\n `createStates [${logTag}] ${prefix}: ${stateDefs.length} states in ${channelGroups.size} channel(s)`,\n );\n\n for (const [channel, defs] of channelGroups) {\n await this.adapter.extendObject(\n `${prefix}.${channel}`,\n {\n type: \"channel\",\n common: { name: CHANNEL_NAMES[channel] ?? channel },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n\n for (const def of defs) {\n const common: Partial<ioBroker.StateCommon> = {\n name: def.name as ioBroker.StringOrTranslated,\n type: def.type,\n role: def.role,\n read: true,\n write: def.write,\n };\n\n if (def.unit) {\n common.unit = def.unit;\n }\n if (def.min !== undefined) {\n common.min = def.min;\n }\n if (def.max !== undefined) {\n common.max = def.max;\n }\n if (def.states) {\n common.states = def.states;\n }\n if (def.def !== undefined) {\n common.def = def.def;\n }\n if (def.desc) {\n common.desc = def.desc as ioBroker.StringOrTranslated;\n }\n\n await this.adapter.extendObject(\n `${prefix}.${channel}.${def.id}`,\n {\n type: \"state\",\n common: common,\n native: {\n capabilityType: def.capabilityType,\n capabilityInstance: def.capabilityInstance,\n },\n },\n { preserve: { common: [\"name\"] } },\n );\n\n // Existing diag.tier datapoints from v2.6.0+ may carry translation-object\n // VALUES in common.states (the old buildCloudStateDefs wrote tLabel(...)\n // directly). extendObject deep-merges and cannot replace an\n // object-value with a string. Force-replace via setObjectAsync when\n // any persisted state value is non-string. Pattern proven in hassemu\n // v1.27.2 / v1.28.4. React Error #31 would otherwise fatal-crash Admin\n // on dropdown open (write:true states) or any view that renders the\n // value (write:false states like diag.tier).\n if (def.states) {\n await this.repairCommonStatesIfBuggy(`${prefix}.${channel}.${def.id}`, def.states);\n }\n\n // Initialize or validate state value\n if (def.def !== undefined) {\n const current = await this.adapter.getStateAsync(`${prefix}.${channel}.${def.id}`);\n if (!current || current.val === null || current.val === undefined) {\n // Set default value for new states\n await this.adapter.setState(`${prefix}.${channel}.${def.id}`, {\n val: def.def,\n ack: true,\n });\n } else if (def.states && !(String(current.val) in def.states)) {\n // Reset dropdown to default if current value is no longer valid\n this.adapter.log.debug(\n `Resetting stale dropdown: ${prefix}.${channel}.${def.id} = \"${String(current.val)}\" \u2192 \"${String(def.def)}\"`,\n );\n await this.adapter.setState(`${prefix}.${channel}.${def.id}`, {\n val: def.def,\n ack: true,\n });\n }\n }\n }\n }\n }\n\n /**\n * Create segment channel with per-segment color + brightness states.\n *\n * @param device Govee device\n */\n async createSegmentStates(device: GoveeDevice): Promise<void> {\n const prefix = this.devicePrefix(device);\n\n await this.adapter.extendObject(\n `${prefix}.segments`,\n {\n type: \"channel\",\n common: { name: tName(\"ledSegments\") },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n\n // Resolve the authoritative count: cache/MQTT-learned wins over Cloud\n // capabilities. A manual list can only grow the count (never shrink it)\n // so users editing manual_list can reveal hidden indices without losing\n // the already-learned total.\n const resolved = resolveSegmentCount(device);\n const manualMax =\n Array.isArray(device.manualSegments) && device.manualSegments.length > 0\n ? Math.max(...device.manualSegments) + 1\n : 0;\n const segmentCount = Math.max(resolved, manualMax);\n device.segmentCount = segmentCount;\n\n // Effective segment list \u2014 honor manual override if active (cut-strip support)\n const validIndices =\n device.manualMode && Array.isArray(device.manualSegments) && device.manualSegments.length > 0\n ? device.manualSegments.slice().sort((a, b) => a - b)\n : Array.from({ length: segmentCount }, (_, i) => i);\n const reportedCount = validIndices.length;\n\n await this.ensureState(`${prefix}.segments.count`, tName(\"segmentCount\"), \"number\", \"value\", false);\n await this.adapter.setState(`${prefix}.segments.count`, {\n val: reportedCount,\n ack: true,\n });\n\n // Manual-mode toggle and list \u2014 user-writable for cut-strip overrides\n await this.adapter.extendObject(\n `${prefix}.segments.manual_mode`,\n {\n type: \"state\",\n common: {\n name: tName(\"manualSegmentsActive\"),\n type: \"boolean\",\n role: \"switch\",\n read: true,\n write: true,\n def: false,\n desc: tDesc(\"manualSegmentsDesc\"),\n },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n await this.adapter.extendObject(\n `${prefix}.segments.manual_list`,\n {\n type: \"state\",\n common: {\n name: tName(\"manualSegmentList\"),\n type: \"string\",\n role: \"text\",\n read: true,\n write: true,\n def: \"\",\n desc: tDesc(\"manualListDesc\"),\n },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n\n // Sync manual_mode / manual_list states back from the runtime device\n // (restored from cache on startup, or updated by the wizard). Using\n // ack=true keeps this out of the user-change handler path.\n const manualModeVal = device.manualMode === true;\n const manualListVal =\n device.manualMode && Array.isArray(device.manualSegments) && device.manualSegments.length > 0\n ? device.manualSegments.join(\",\")\n : \"\";\n await this.adapter.setState(`${prefix}.segments.manual_mode`, {\n val: manualModeVal,\n ack: true,\n });\n await this.adapter.setState(`${prefix}.segments.manual_list`, {\n val: manualListVal,\n ack: true,\n });\n\n for (const i of validIndices) {\n await this.adapter.extendObject(\n `${prefix}.segments.${i}`,\n {\n type: \"channel\",\n common: { name: `Segment ${i}` },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n\n await this.adapter.extendObject(\n `${prefix}.segments.${i}.color`,\n {\n type: \"state\",\n common: {\n name: tName(\"color\"),\n type: \"string\",\n role: \"level.color.rgb\",\n read: true,\n write: true,\n def: \"#000000\", // avoid null in vis until the first write (LAN-only tier) \u2014 B6\n },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n\n await this.adapter.extendObject(\n `${prefix}.segments.${i}.brightness`,\n {\n type: \"state\",\n common: {\n name: tName(\"brightness\"),\n type: \"number\",\n role: \"level.brightness\",\n read: true,\n write: true,\n min: 0,\n max: 100,\n unit: \"%\",\n def: 0, // avoid null in vis until the first write (LAN-only tier) \u2014 B6\n },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n }\n\n // Comfort command state for batch segment control\n await this.adapter.extendObject(\n `${prefix}.segments.command`,\n {\n type: \"state\",\n common: {\n name: tName(\"batchSegmentCommand\"),\n type: \"string\",\n role: \"text\",\n read: false,\n write: true,\n desc: tDesc(\"batchCommandDesc\"),\n },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n\n // Remove segment channels that aren't in the valid list (supports gaps for manual mode)\n await this.cleanupExcessSegments(prefix, validIndices);\n }\n\n /**\n * Remove segment sub-channels that are not in the valid-indices list.\n * Supports gaps (e.g. manual list \"0-8,10-14\" \u2192 segment 9 channel gets removed).\n *\n * @param prefix Device prefix\n * @param validIndices Valid segment indices (all others will be deleted)\n */\n private async cleanupExcessSegments(prefix: string, validIndices: number[]): Promise<void> {\n const valid = new Set(validIndices);\n const segPrefix = `${this.adapter.namespace}.${prefix}.segments.`;\n const existing = await this.adapter.getObjectViewAsync(\"system\", \"channel\", {\n startkey: segPrefix,\n endkey: `${segPrefix}\\u9999`,\n });\n\n if (!existing?.rows) {\n return;\n }\n\n for (const row of existing.rows) {\n const localId = row.id.replace(`${this.adapter.namespace}.`, \"\");\n const segPart = localId.replace(`${prefix}.segments.`, \"\");\n const segIdx = parseInt(segPart, 10);\n if (!isNaN(segIdx) && !valid.has(segIdx)) {\n this.adapter.log.debug(`Removing excess segment: ${localId}`);\n // Drop orphan state values too \u2014 `delObjectAsync(recursive)` removes\n // the object tree but leaves the state-table values for color and\n // brightness behind. Without these explicit `delStateAsync` calls,\n // historical values would resurrect into a re-created segment after\n // a length change.\n await this.adapter.delStateAsync(`${localId}.color`).catch(() => undefined);\n await this.adapter.delStateAsync(`${localId}.brightness`).catch(() => undefined);\n await this.adapter.delObjectAsync(localId, { recursive: true });\n }\n }\n }\n\n /**\n * Update device state from any source (LAN, MQTT, Cloud).\n *\n * Writes are fire-and-forget and run in parallel \u2014 they're independent,\n * and the \"does this state exist?\" check that used to guard each write\n * was an extra object-read on the hot path (one MQTT push = one update\n * call). createDeviceStates has already run before any update lands,\n * so the states are guaranteed to exist; if one disappears (manual\n * deletion), the setState will reject and we swallow it.\n *\n * @param device Govee device\n * @param state Partial state update\n */\n async updateDeviceState(device: GoveeDevice, state: Partial<DeviceState>): Promise<void> {\n const prefix = this.devicePrefix(device);\n const writes: Promise<unknown>[] = [];\n\n const set = (id: string, val: ioBroker.StateValue): void => {\n // setStateChangedAsync (not setState): the LAN devStatus poll (every\n // 30 s when no MQTT is connected) re-delivers identical power / brightness\n // / colorRgb / colorTemperature each cycle \u2014 only write (and bump the\n // timestamp) on a real value change. Freshness lives on the device object\n // (lastLanReplyAt / lastSeenOnNetwork), not on these control-state ts.\n writes.push(this.adapter.setStateChangedAsync(id, { val, ack: true }).catch(() => undefined));\n };\n\n // info.online for Lights is owned by syncInfoOnline \u2014 direct writes here\n // would re-introduce the ts-rewrite-spam (every 2 min same value) and\n // the false-positive `true` writes from Cloud/MQTT paths. For Sensors/\n // Appliances the existing flow stays (applyOnlineCap \u2192 onDeviceUpdate \u2192\n // here \u2192 info.online).\n if (state.online !== undefined && device.type !== GOVEE_DEVICE_TYPE.LIGHT) {\n set(`${prefix}.info.online`, state.online);\n }\n if (state.power !== undefined) {\n set(`${prefix}.control.power`, state.power);\n }\n if (state.brightness !== undefined) {\n set(`${prefix}.control.brightness`, state.brightness);\n }\n if (state.colorRgb !== undefined) {\n set(`${prefix}.control.color_rgb`, state.colorRgb);\n }\n if (state.colorTemperature !== undefined) {\n set(`${prefix}.control.color_temperature`, state.colorTemperature);\n }\n if (state.scene !== undefined) {\n set(`${prefix}.control.scene`, state.scene);\n }\n\n await Promise.all(writes);\n }\n\n /**\n * Create the general groups.info.online state (reflects Cloud connection).\n *\n * @param online Initial online value\n */\n async createGroupsOnlineState(online: boolean): Promise<void> {\n await this.adapter.extendObject(\n \"groups\",\n {\n type: \"folder\",\n common: { name: tName(\"groups\") },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n await this.adapter.extendObject(\n \"groups.info\",\n {\n type: \"channel\",\n common: { name: tName(\"groupsStatus\") },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n await this.ensureState(\"groups.info.online\", \"Cloud Online\", \"boolean\", \"indicator.reachable\", false);\n await this.adapter.setState(\"groups.info.online\", {\n val: online,\n ack: true,\n });\n }\n\n /**\n * Update the general groups online state.\n *\n * @param online Cloud connection status\n */\n async updateGroupsOnline(online: boolean): Promise<void> {\n await this.adapter.setState(\"groups.info.online\", { val: online, ack: true }).catch(() => undefined);\n }\n\n /**\n * Update info.membersUnreachable for a group.\n *\n * Always keeps the state (existing) and writes a comma-separated list of the\n * unreachable members, or an empty string when all are online. Previously we\n * deleted the object on \"all reachable\" \u2014 but that produced a js-controller\n * WARN \"State 'X.membersUnreachable' has no existing object\" every ~2 minutes,\n * because parallel updateGroupReachability calls (LAN+MQTT status updates fire\n * almost simultaneously) could trigger a race condition between setState\n * (object exists) and safeDeleteState (object gone). Always keeping the state\n * present avoids that entirely.\n *\n * @param group BaseGroup device\n * @param memberDevices Resolved member devices\n */\n async updateGroupMembersUnreachable(group: GoveeDevice, memberDevices: GoveeDevice[]): Promise<void> {\n const prefix = this.devicePrefix(group);\n const stateId = `${prefix}.info.membersUnreachable`;\n\n const unreachable = memberDevices.filter(m => !m.state.online).map(m => treeKey(m.sku, m.deviceId));\n\n await this.ensureState(stateId, \"Unreachable Members\", \"string\", \"text\", false);\n await this.adapter.setState(stateId, {\n val: unreachable.join(\", \"),\n ack: true,\n });\n }\n\n /**\n * Cleanup stale devices that no longer exist.\n *\n * Returns the prefixes of removed devices so callers (DeviceManager,\n * adapter-level maps) can drop their own entries for the same devices\n * and prevent unbounded map growth across the adapter's lifetime.\n *\n * @param currentDevices Current device list\n * @returns Prefixes of removed devices (e.g. \"devices.h61be_1d6f\")\n */\n async cleanupDevices(currentDevices: GoveeDevice[]): Promise<string[]> {\n const currentPrefixes = new Set(currentDevices.map(d => this.devicePrefix(d)));\n const removed: string[] = [];\n\n // Cleanup both devices/ and groups/ folders\n for (const folder of [\"devices\", \"groups\"]) {\n // getObjectViewAsync can throw on transient js-controller hiccups \\u2014\n // wrapping it lets cleanupDevices proceed with the other folder\n // instead of bailing out of the whole cleanup pass.\n let existingObjects;\n try {\n existingObjects = await this.adapter.getObjectViewAsync(\"system\", \"device\", {\n startkey: `${this.adapter.namespace}.${folder}.`,\n endkey: `${this.adapter.namespace}.${folder}.\\u9999`,\n });\n } catch (e) {\n this.adapter.log.debug(\n `cleanupDevices: getObjectViewAsync failed for ${folder}: ${e instanceof Error ? e.message : String(e)}`,\n );\n continue;\n }\n\n if (!existingObjects?.rows) {\n continue;\n }\n\n for (const row of existingObjects.rows) {\n const localId = row.id.replace(`${this.adapter.namespace}.`, \"\");\n if (!currentPrefixes.has(localId)) {\n this.adapter.log.debug(`Removing stale device: ${localId}`);\n // Recursive delObject removes the object tree but can leave\n // orphan state values in the state-table \u2014 clean those too so\n // historical values don't survive a device removal.\n const stateRows = await this.adapter\n .getObjectViewAsync(\"system\", \"state\", {\n startkey: `${row.id}.`,\n endkey: `${row.id}.\u9999`,\n })\n .catch(() => undefined);\n if (stateRows?.rows) {\n for (const stateRow of stateRows.rows) {\n const stateLocalId = stateRow.id.replace(`${this.adapter.namespace}.`, \"\");\n await this.adapter.delStateAsync(stateLocalId).catch(() => undefined);\n }\n }\n await this.adapter.delObjectAsync(localId, { recursive: true });\n this.forgetPrefix(localId);\n removed.push(localId);\n }\n }\n }\n return removed;\n }\n\n /**\n * Phase 3 cleanup \u2014 remove Cloud-owned states that are no longer in the\n * current Cloud-phase stateDefs. Respects LAN_STATE_IDS so the LAN phase's\n * states in the control channel never get touched.\n *\n * The Cloud-owned channels (scenes, music, snapshots, sensor, events) are\n * 100% Cloud territory \u2014 anything not in cloudStateDefs there is stale.\n * The control channel is mixed: LAN-default ids (power, brightness, \u2026)\n * belong to the LAN phase and are skipped via the LAN_STATE_IDS constant.\n *\n * Public for the v2.8.0 migration shot in main.ts.onReady \u2014 pure-LAN\n * devices need a one-time cleanupCloudOwnedStates(prefix, []) to wipe\n * scene/music/snapshot leftovers from prior versions.\n *\n * @param prefix Device prefix\n * @param cloudStateDefs Current Cloud-phase state definitions (non-segment)\n */\n async cleanupCloudOwnedStates(prefix: string, cloudStateDefs: StateDefinition[]): Promise<void> {\n // Build expected state set per channel\n const expectedByChannel = new Map<string, Set<string>>();\n for (const def of cloudStateDefs) {\n const channel = def.channel ?? \"control\";\n if (!expectedByChannel.has(channel)) {\n expectedByChannel.set(channel, new Set());\n }\n expectedByChannel.get(channel)!.add(def.id);\n }\n\n const devicePrefix = `${this.adapter.namespace}.${prefix}.`;\n const existing = await this.adapter.getObjectViewAsync(\"system\", \"state\", {\n startkey: devicePrefix,\n endkey: `${devicePrefix}\\u9999`,\n });\n if (!existing?.rows) {\n return;\n }\n\n const totalsPerChannel = new Map<string, { seen: number; deleted: number }>();\n for (const row of existing.rows) {\n const rest = row.id.replace(devicePrefix, \"\");\n const dotIdx = rest.indexOf(\".\");\n if (dotIdx < 0) {\n continue;\n }\n const channel = rest.slice(0, dotIdx);\n const stateId = rest.slice(dotIdx + 1);\n if (!MANAGED_CHANNELS.includes(channel)) {\n continue;\n }\n // In the control channel, LAN-default ids belong to the LAN phase \u2014\n // Cloud cleanup must not touch them. Other MANAGED_CHANNELS are\n // wholly Cloud territory. Count them as seen-but-not-deleted survivors so\n // the \"empty channel\" removal below doesn't delete the control channel\n // object out from under its surviving LAN states (L9).\n if (channel === \"control\" && LAN_STATE_IDS.has(stateId)) {\n const survivors = totalsPerChannel.get(channel) ?? { seen: 0, deleted: 0 };\n survivors.seen++;\n totalsPerChannel.set(channel, survivors);\n continue;\n }\n const totals = totalsPerChannel.get(channel) ?? { seen: 0, deleted: 0 };\n totals.seen++;\n const validIds = expectedByChannel.get(channel) ?? new Set<string>();\n if (!validIds.has(stateId)) {\n const localId = row.id.replace(`${this.adapter.namespace}.`, \"\");\n this.adapter.log.debug(`Removing stale state: ${localId}`);\n await this.adapter.delObjectAsync(localId);\n await this.adapter.delStateAsync(localId).catch(() => {});\n totals.deleted++;\n }\n totalsPerChannel.set(channel, totals);\n }\n\n // Remove empty channel objects \u2014 no surviving states for this channel\n for (const [channel, totals] of totalsPerChannel) {\n if (totals.deleted > 0 && totals.deleted === totals.seen) {\n this.adapter.log.debug(`Removing empty channel: ${prefix}.${channel}`);\n await this.adapter.delObjectAsync(`${prefix}.${channel}`).catch(() => undefined);\n }\n }\n }\n\n /**\n * Get device object ID prefix \u2014 stable SKU + short device ID.\n * Groups (BaseGroup) go under groups/, devices under devices/.\n * Human-readable name is in common.name, not in the object ID.\n *\n * @param device Govee device\n */\n devicePrefix(device: GoveeDevice): string {\n const folder = device.sku === \"BaseGroup\" ? \"groups\" : \"devices\";\n return `${folder}.${treeKey(device.sku, device.deviceId)}`;\n }\n\n /**\n * Drop prefix + stateChannel entries for a device that was removed.\n * Prevents the maps from growing indefinitely across adapter lifetime.\n *\n * @param prefix Device prefix that was removed\n */\n private forgetPrefix(prefix: string): void {\n for (const key of this.prefixMap.keys()) {\n if (this.prefixMap.get(key) === prefix) {\n this.prefixMap.delete(key);\n }\n }\n const stalePrefix = `${prefix}.`;\n for (const key of this.stateChannelMap.keys()) {\n if (key.startsWith(stalePrefix)) {\n this.stateChannelMap.delete(key);\n }\n }\n // Drop ensureState cache for this device too \u2014 a re-pair must run the\n // full extendObject path again so the new device's name/type get\n // applied (cache hit would skip the round-trip and keep stale common.*).\n // ensureState stores namespace-less ids (extendObject takes a relative\n // id), so match the same way as stateChannelMap above \u2014 the old namespaced\n // match never fired, so re-paired devices skipped info-state creation (M4).\n for (const id of this.ensuredStates) {\n if (id === prefix || id.startsWith(stalePrefix)) {\n this.ensuredStates.delete(id);\n }\n }\n }\n\n /**\n * Unique key for internal tracking (not used as object ID).\n *\n * @param device Govee device\n */\n private deviceKey(device: GoveeDevice): string {\n return mapKey(device.sku, device.deviceId);\n }\n\n /**\n * Create a state if it doesn't exist. Cached after the first successful\n * `extendObject` so hot-path callers (e.g. `updateGroupMembersUnreachable`\n * fires per status update) skip the Redis round-trip.\n *\n * @param id State object ID\n * @param name Display name\n * @param type Value type\n * @param role ioBroker role\n * @param write Whether state is writable\n * @param unit Optional unit of measurement\n * @param def Optional default value \u2014 set so the state has a sensible\n * initial value before the first writeback (avoids `null`\n * display in admin between create and first setState).\n */\n private async ensureState(\n id: string,\n name: ioBroker.StringOrTranslated,\n type: ioBroker.CommonType,\n role: string,\n write: boolean,\n unit?: string,\n def?: ioBroker.StateValue,\n ): Promise<void> {\n if (this.ensuredStates.has(id)) {\n return;\n }\n const common: Partial<ioBroker.StateCommon> = {\n name,\n type,\n role,\n read: true,\n write,\n };\n if (unit) {\n common.unit = unit;\n }\n if (def !== undefined) {\n common.def = def;\n }\n await this.adapter.extendObject(\n id,\n {\n type: \"state\",\n common: common,\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n this.ensuredStates.add(id);\n }\n\n /**\n * Resolver-based info.online sync.\n *\n * For LAN-capable LED Lights (`type === light` AND `lanIp` set) the\n * truth-source is exclusively `device.lastLanReplyAt` \u2014 set when the device\n * replies to a LAN-Discovery multicast or LAN-Unicast devStatus. The 90 s\n * freshness window tolerates 3 missed 30 s scans against UDP packet loss but\n * still flips offline reasonably fast on a real outage.\n *\n * For cloud-only Lights (a light whose owner never enabled the local API, so\n * `lanIp` is null) and for Sensors/Appliances there is no LAN signal:\n * `device.state.online` \u2014 set by `applyOnlineCap` from App-API / OpenAPI-MQTT\n * \u2014 is read straight through here. Local-first stays, local-only does not.\n *\n * Writes `info.online` only when the resolved value differs from the\n * current state \u2014 kills the 2-min ts-rewrite-spam captured 2026-05-13.\n *\n * For Lights: when the resolved online value changes, the internal\n * `device.state.online` is also updated so downstream consumers\n * (`updateGroupReachability`, `handleLanDiscovery` wasOffline check)\n * stay in sync. Returns `true` in that case so the caller can fire\n * the group-fanout reachability refresh.\n *\n * Skips BaseGroup devices \u2014 groups have a global `groups.info.online`\n * managed elsewhere.\n *\n * @param device Govee device to sync\n * @returns true if a Light's resolved online state changed (caller should\n * refresh group-reachability), false otherwise\n */\n async syncInfoOnline(device: GoveeDevice): Promise<boolean> {\n if (device.sku === \"BaseGroup\") {\n return false;\n }\n const prefix = this.devicePrefix(device);\n const stateId = `${prefix}.info.online`;\n\n let desiredOnline: boolean;\n if (device.type === GOVEE_DEVICE_TYPE.LIGHT && device.lanIp) {\n // LAN-capable light: LAN-reply freshness is the truth-source (v2.9.0 \u2014 the\n // Cloud cache lagged real reachability and wrote 2\u00D7 false-positive `true`\n // during the 2026-05-13 outage capture).\n desiredOnline = !!(device.lastLanReplyAt && Date.now() - device.lastLanReplyAt < 90_000);\n } else {\n // Cloud-only light (no local API, lanIp === null) + sensors/appliances:\n // no LAN signal to trust, so the cloud-reported device online\n // (`state.online`, fed by applyOnlineCap from App-API / OpenAPI-MQTT) is\n // the right source. Local-first stays \u2014 local-only does not.\n desiredOnline = device.state.online === true;\n }\n\n const current = await this.adapter.getStateAsync(stateId).catch(() => null);\n if (!current || current.val !== desiredOnline) {\n await this.adapter.setState(stateId, { val: desiredOnline, ack: true }).catch(() => undefined);\n }\n\n let lightOnlineChanged = false;\n if (device.type === GOVEE_DEVICE_TYPE.LIGHT && device.state.online !== desiredOnline) {\n device.state.online = desiredOnline;\n lightOnlineChanged = true;\n }\n\n return lightOnlineChanged;\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type * as utils from \"@iobroker/adapter-core\";\nimport { buildLanStateDefs, LAN_STATE_IDS, SENSOR_ROLE_UNIT, type StateDefinition } from \"./capability-mapper\";\nimport { GROUP_ICON, iconForGoveeType, shortenGoveeType } from \"./device-icons\";\nimport { resolveSegmentCount } from \"./device-manager\";\nimport { GOVEE_DEVICE_TYPE } from \"./govee-constants\";\nimport type { I18nKey } from \"./i18n\";\nimport { tDesc, tName } from \"./i18n\";\nimport type { DeviceState, GoveeDevice } from \"./types\";\nimport { mapKey, treeKey } from \"./device-key\";\n\n/**\n * Channels whose state-set is fully described by capability-driven stateDefs.\n * Only these get the stale-state cleanup pass \u2014 `info` is intentionally absent\n * because it mixes capability-driven states (diagnostics_export/result) with\n * adapter-managed ones (online, model, serial, ip, members) that come from\n * ensureState instead of stateDefs. Cleaning `info` by stateDef-set would\n * delete the adapter-managed ones.\n */\nconst MANAGED_CHANNELS = [\"control\", \"scenes\", \"music\", \"snapshots\", \"sensor\", \"events\"];\n/**\n * Display names used when the channel object is (re-)created. `info` is\n * listed here even though it's not in MANAGED_CHANNELS \u2014 capability-mapper\n * emits states with `channel: \"info\"`, and without this entry the create\n * path would overwrite the original \"Device Information\" name with the\n * literal \"info\".\n */\nconst CHANNEL_NAMES: Record<string, string> = {\n control: \"Controls\",\n scenes: \"Scenes\",\n music: \"Music\",\n snapshots: \"Snapshots\",\n sensor: \"Sensor Data\",\n events: \"Events\",\n info: \"Device Information\",\n diag: \"Diagnostics\",\n};\n\n/**\n * Synthetic capabilities written by the App-API poll and OpenAPI-MQTT\n * handler arrive as `(stateId, value)` pairs without a channel hint \u2014\n * `mapCloudStateValue` returns only what the Govee response carries.\n *\n * For lights the LAN/Cloud-state pipeline pre-populates `stateChannelMap`\n * via `createDeviceStates`, so `resolveStatePath` finds the channel.\n * For thermometer/appliance state IDs that are *only* delivered via the\n * App-API path (battery, temperature, humidity, CO\u2082, lackWater, \u2026) the map\n * is empty and the lookup would default to \"control\" \u2014 visibly wrong:\n * `info: control.battery has no existing object`.\n *\n * This routing table assigns those IDs to their semantic channel without\n * needing a separate `createDeviceStates` pass for sensor-only devices.\n * Keep IDs lowercase; resolveStatePath calls this on the raw stateId.\n */\n// Two spellings per ID below \u2014 the \"raw\" form (e.g. `temperature`) for instances\n// named exactly that, and the sanitizeId output (e.g. `sensor_temperature`) for\n// camelCase instances that capability-mapper's sanitizeId converted to\n// snake_case. Without the alias the sanitized variant would fall back to the\n// safe default \"control\" and its state would be unreachable.\n\n/**\n * Per-stateId metadata for synthetic states (App-API/OpenAPI-MQTT pipe). The\n * `channel` is the single source of truth for {@link inferChannelFromStateId},\n * so the routing and the state definition never drift apart.\n */\ninterface SyntheticStateMeta {\n type: \"boolean\" | \"number\";\n role: string;\n unit?: string;\n nameKey: I18nKey;\n /** Semantic channel \u2014 sensor readings vs. device events. */\n channel: \"sensor\" | \"events\";\n}\n/**\n * Numeric-sensor meta with role + unit pulled from the shared\n * {@link SENSOR_ROLE_UNIT}, so the App-API/MQTT path can't drift from the\n * Cloud-capability path. Booleans/events stay inline \u2014 they're heterogeneous.\n *\n * @param kind Sensor kind keying into {@link SENSOR_ROLE_UNIT}\n * @param nameKey i18n key for the state's display name\n */\nconst numSensor = (kind: keyof typeof SENSOR_ROLE_UNIT, nameKey: I18nKey): SyntheticStateMeta => ({\n type: \"number\",\n role: SENSOR_ROLE_UNIT[kind].role,\n unit: SENSOR_ROLE_UNIT[kind].unit,\n nameKey,\n channel: \"sensor\",\n});\nconst SYNTHETIC_STATE_META: Record<string, SyntheticStateMeta> = {\n temperature: numSensor(\"temperature\", \"temperature\"),\n humidity: numSensor(\"humidity\", \"humidity\"),\n battery: numSensor(\"battery\", \"battery\"),\n co2: numSensor(\"co2\", \"co2\"),\n carbondioxide: numSensor(\"co2\", \"co2\"),\n online: { type: \"boolean\", role: \"indicator.connected\", nameKey: \"online\", channel: \"sensor\" },\n lackwater: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"lackOfWater\", channel: \"events\" },\n lackwaterevent: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"lackOfWater\", channel: \"events\" },\n icefull: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"iceBucketFull\", channel: \"events\" },\n icefullevent: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"iceBucketFull\", channel: \"events\" },\n bodyappeared: { type: \"boolean\", role: \"sensor.motion\", nameKey: \"bodyDetected\", channel: \"events\" },\n dirtdetected: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"dirtDetected\", channel: \"events\" },\n sensor_temperature: numSensor(\"temperature\", \"temperature\"),\n sensor_humidity: numSensor(\"humidity\", \"humidity\"),\n sensor_battery: numSensor(\"battery\", \"battery\"),\n lack_water: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"lackOfWater\", channel: \"events\" },\n lack_water_event: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"lackOfWater\", channel: \"events\" },\n ice_full: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"iceBucketFull\", channel: \"events\" },\n ice_full_event: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"iceBucketFull\", channel: \"events\" },\n body_appeared: { type: \"boolean\", role: \"sensor.motion\", nameKey: \"bodyDetected\", channel: \"events\" },\n dirt_detected: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"dirtDetected\", channel: \"events\" },\n};\n\n/**\n * Best-effort channel routing for a synthetic state ID with no stateChannelMap\n * entry yet (App-API caps before createDeviceStates). Reads the channel straight\n * from {@link SYNTHETIC_STATE_META}; an unknown ID falls back to the safe default\n * \"control\". Keep IDs lowercase \u2014 resolveStatePath calls this on the raw stateId.\n *\n * @param stateId The raw state ID (e.g. \"battery\", \"lackWater\")\n */\nfunction inferChannelFromStateId(stateId: string): string {\n return SYNTHETIC_STATE_META[stateId.toLowerCase()]?.channel ?? \"control\";\n}\n\n/** Manages ioBroker state creation and updates for Govee devices */\nexport class StateManager {\n private readonly adapter: utils.AdapterInstance;\n /** Maps deviceKey (sku_deviceId) \u2192 current object prefix */\n private readonly prefixMap = new Map<string, string>();\n /** Maps \"prefix.stateId\" \u2192 channel name (populated during createDeviceStates) */\n private readonly stateChannelMap = new Map<string, string>();\n /**\n * Cache of state IDs already created via {@link ensureState} \u2014 skips the\n * `extendObject` round-trip on the hot path. Refreshed on\n * {@link removeDevice}/{@link forgetPrefix} so a re-pair doesn't reuse stale\n * cache entries.\n */\n private readonly ensuredStates = new Set<string>();\n /**\n * \"prefix.stateId\" keys already handled by {@link removeSyntheticStateOnce} \u2014\n * bounds the phantom-state cleanup to one existence-check per adapter run.\n */\n private readonly cleanedSyntheticStates = new Set<string>();\n\n /** @param adapter The ioBroker adapter instance */\n constructor(adapter: utils.AdapterInstance) {\n this.adapter = adapter;\n }\n\n /**\n * Force-replace `common.states` on a persisted state object if any existing\n * value is non-string (= translation object from older releases).\n *\n * `extendObject` deep-merges and CANNOT replace an object-value with a\n * string. Only a full `setObjectAsync` replaces. Same fix-pattern as\n * hassemu v1.27.2 (URL-dropdown) and v1.28.4 (mode-dropdown). Admin\n * renders states-values as React children \u2014 a translation object triggers\n * React Error #31 \u2192 fatal \"Error in GUI\" on dropdown open (write:true) or\n * any render path (write:false like diag.tier).\n *\n * @param id Full state path.\n * @param fresh Plain-string `common.states` map to write.\n */\n private async repairCommonStatesIfBuggy(id: string, fresh: Record<string, string>): Promise<void> {\n const existing = await this.adapter.getObjectAsync(id).catch(() => null);\n if (!existing) {\n return;\n }\n const states = existing.common?.states;\n if (!states || typeof states !== \"object\") {\n return;\n }\n const buggy = Object.values(states as Record<string, unknown>).some(v => typeof v !== \"string\");\n if (!buggy) {\n return;\n }\n await this.adapter.extendObject(id, { common: { states: fresh } }).catch(() => undefined);\n }\n\n /**\n * @param id Voller State-Pfad (`devices.X.info.Y`)\n */\n private async safeDeleteState(id: string): Promise<void> {\n const obj = await this.adapter.getObjectAsync(id).catch(() => null);\n if (!obj) {\n return;\n }\n await this.adapter.delStateAsync(id).catch(() => undefined);\n await this.adapter.delObjectAsync(id).catch(() => undefined);\n }\n\n /**\n * Remove a synthetic App-API/OpenAPI-MQTT sensor state (e.g. a phantom\n * `sensor_humidity` on a temp-only thermometer) at most once per adapter run.\n * These states are created ad-hoc by the App-API pipe and are NOT covered by\n * any def-driven cleanup, so a datapoint that should no longer exist (Govee's\n * `hum:0` sentinel on a device without a humidity capability, #31) would\n * otherwise linger forever. Existence-checked via {@link safeDeleteState}, so\n * it's a silent no-op on installs that never had the state.\n *\n * @param prefix Device object ID prefix\n * @param stateId Synthetic state ID (e.g. \"sensor_humidity\")\n */\n async removeSyntheticStateOnce(prefix: string, stateId: string): Promise<void> {\n const key = `${prefix}.${stateId}`;\n if (this.cleanedSyntheticStates.has(key)) {\n return;\n }\n this.cleanedSyntheticStates.add(key);\n await this.safeDeleteState(this.resolveStatePath(prefix, stateId));\n }\n\n /**\n * Push the device's trust tier (verified/reported/seed/unknown) into\n * the user-visible `diag.tier` state. Called after every device-state\n * refresh so the value tracks any registry change between adapter\n * restarts (e.g. seed \u2192 verified once a tester confirms). No-op for\n * groups (BaseGroup has no per-device tier).\n *\n * @param device Govee device\n * @param tier Canonical tier label\n */\n async updateDeviceTier(device: GoveeDevice, tier: string): Promise<void> {\n if (device.sku === \"BaseGroup\") {\n return;\n }\n const prefix = this.devicePrefix(device);\n await this.adapter.setState(`${prefix}.diag.tier`, { val: tier, ack: true }).catch(() => undefined);\n }\n\n /**\n * Migrate v2.1.0 layout (`info.diagnostics_*`) to v2.1.1 layout\n * (`diag.*`). Deletes the three old objects + states; the new ones get\n * created by the regular `createDeviceStates` pass. Idempotent \u2014 calling\n * twice is a no-op once the old objects are gone.\n *\n * @param device Govee device\n */\n async migrateLegacyDiagnostics(device: GoveeDevice): Promise<void> {\n if (device.sku === \"BaseGroup\") {\n return;\n }\n const prefix = this.devicePrefix(device);\n for (const stale of [\"diagnostics_export\", \"diagnostics_result\", \"diagnostics_tier\"]) {\n await this.safeDeleteState(`${prefix}.info.${stale}`);\n this.stateChannelMap.delete(`${prefix}.${stale}`);\n }\n }\n\n /**\n * B2 hard-cut migration: the LAN control colour states were renamed from\n * camelCase (`control.colorRgb` / `control.colorTemperature`) to snake_case\n * (`control.color_rgb` / `control.color_temperature`) for a consistent\n * state-tree spelling. Delete the old objects on upgraded installs so they\n * don't linger as dead duplicates next to the freshly-created snake_case\n * states. Existence-checked + idempotent (a no-op once migrated / on fresh\n * installs); works for devices AND groups (both carry these control states).\n * Users must update scripts that referenced the old ids.\n *\n * @param device Govee device or group\n */\n async migrateLegacyColorStateIds(device: GoveeDevice): Promise<void> {\n const prefix = this.devicePrefix(device);\n await this.safeDeleteState(`${prefix}.control.colorRgb`);\n await this.safeDeleteState(`${prefix}.control.colorTemperature`);\n }\n\n /**\n * Resolve full state path for a given device prefix and state ID.\n * Routes the state to the correct channel (control, scenes, music, snapshots).\n *\n * @param prefix Device object ID prefix\n * @param stateId State ID suffix\n */\n resolveStatePath(prefix: string, stateId: string): string {\n const channel = this.stateChannelMap.get(`${prefix}.${stateId}`) ?? inferChannelFromStateId(stateId);\n return `${prefix}.${channel}.${stateId}`;\n }\n\n /**\n * Lazily create the channel + state object for synthetic state IDs the\n * App-API poll and OpenAPI-MQTT pipeline write. Cloud-capability defs\n * for sensor SKUs (e.g. H5179) are often empty in OpenAPI v2, so the\n * usual `createDeviceStates` pass would not declare battery / temperature\n * / events.* \u2014 without this helper the first write logs\n * `info: <id> has no existing object`.\n *\n * Idempotent: skips when the meta table doesn't know the stateId, and\n * `setObjectNotExistsAsync` is itself a no-op for existing objects.\n *\n * @param prefix Device prefix (e.g. \"devices.h5179_aabb\")\n * @param stateId State ID without channel (e.g. \"battery\")\n */\n async ensureSyntheticStateObject(prefix: string, stateId: string): Promise<void> {\n const meta = SYNTHETIC_STATE_META[stateId.toLowerCase()];\n if (!meta) {\n return;\n }\n const channel = inferChannelFromStateId(stateId);\n // Channel object first \u2014 sensors land in the new sensor/ subtree, events\n // in events/. Without an extendObject the channel parent stays missing\n // and Admin shows the state directly under the device root.\n await this.adapter\n .extendObject(\n `${prefix}.${channel}`,\n {\n type: \"channel\",\n common: { name: CHANNEL_NAMES[channel] ?? channel },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n )\n .catch(() => undefined);\n await this.adapter\n .extendObject(\n `${prefix}.${channel}.${stateId}`,\n {\n type: \"state\",\n common: {\n name: tName(meta.nameKey),\n type: meta.type,\n role: meta.role,\n read: true,\n write: false,\n ...(meta.unit !== undefined ? { unit: meta.unit } : {}),\n def: meta.type === \"boolean\" ? false : 0,\n },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n )\n .catch(() => undefined);\n this.stateChannelMap.set(`${prefix}.${stateId}`, channel);\n }\n\n /**\n * Phase 1 \u2014 Info-States. Always-existing device metadata: info.name,\n * info.online (per-device for lights, global for groups), info.model,\n * info.serial, info.ip, info.type. For groups: info.members + cleanup\n * of legacy device-level info states.\n *\n * Idempotent. Called from every phase callback (LAN-Phase + Cloud-Phase\n * + Group-Phase) \u2014 extendObject de-duplicates so the cost is small.\n *\n * Never deletes states from MANAGED_CHANNELS. The info channel is not in\n * MANAGED_CHANNELS, so cleanup never touches its content.\n *\n * @param device Govee device\n */\n async createInfoStates(device: GoveeDevice): Promise<void> {\n const key = this.deviceKey(device);\n const newPrefix = this.devicePrefix(device);\n const oldPrefix = this.prefixMap.get(key);\n\n // Migrate if prefix changed (e.g., old naming scheme)\n if (oldPrefix && oldPrefix !== newPrefix) {\n this.adapter.log.debug(`Migrating device ${device.sku}: ${oldPrefix} \u2192 ${newPrefix}`);\n await this.adapter.delObjectAsync(oldPrefix, { recursive: true });\n // Drop stale channel-map entries under the old prefix so they don't\n // shadow resolveStatePath lookups after the rename.\n const oldChannelKey = `${oldPrefix}.`;\n for (const mapKey of this.stateChannelMap.keys()) {\n if (mapKey.startsWith(oldChannelKey)) {\n this.stateChannelMap.delete(mapKey);\n }\n }\n }\n this.prefixMap.set(key, newPrefix);\n\n const prefix = newPrefix;\n const isGroup = device.sku === \"BaseGroup\";\n\n // Device object with online status indicator + type-aware icon.\n // Groups use the general groups.info.online state instead of per-group online.\n const onlineId = isGroup\n ? `${this.adapter.namespace}.groups.info.online`\n : `${this.adapter.namespace}.${prefix}.info.online`;\n const icon = isGroup ? GROUP_ICON : iconForGoveeType(device.type);\n await this.adapter.extendObject(\n prefix,\n {\n type: \"device\",\n common: {\n name: device.name,\n icon,\n statusStates: { onlineId },\n },\n native: {\n sku: device.sku,\n deviceId: device.deviceId,\n },\n },\n { preserve: { common: [\"name\"] } },\n );\n\n // Info channel \u2014 groups only get name (no individual online)\n await this.adapter.extendObject(\n `${prefix}.info`,\n {\n type: \"channel\",\n common: { name: tName(\"deviceInformation\") },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n\n // setStateChangedAsync (not setState) for the info metadata below:\n // createInfoStates re-runs on every phase callback (cloud refresh,\n // snapshot save/delete, \u2026) with mostly identical values \u2014 only a real\n // change should write and bump the timestamp. No consumer reads the\n // ts/lc of these states as a freshness signal (grep-verified).\n await this.ensureState(`${prefix}.info.name`, \"Name\", \"string\", \"text\", false);\n await this.adapter.setStateChangedAsync(`${prefix}.info.name`, {\n val: device.name,\n ack: true,\n });\n\n if (!isGroup) {\n await this.ensureState(\n `${prefix}.info.online`,\n \"Online\",\n \"boolean\",\n \"indicator.reachable\",\n false,\n undefined,\n false,\n );\n // info.online is written via syncInfoOnline (resolver-based, no\n // ts-rewrite-spam). The initial sync happens right after this method\n // returns \u2014 see syncInfoOnline. Direct write here was the source of\n // periodic false\u2192true bounces (captured 2026-05-13).\n await this.ensureState(`${prefix}.info.model`, \"Model\", \"string\", \"text\", false, undefined, \"\");\n await this.ensureState(`${prefix}.info.serial`, \"Serial Number\", \"string\", \"text\", false, undefined, \"\");\n await this.ensureState(`${prefix}.info.ip`, \"IP Address\", \"string\", \"info.ip\", false, undefined, \"\");\n // Device-type marker \u2014 short label like \"light\", \"thermometer\",\n // \"heater\" (Govee API type without the \"devices.types.\" prefix).\n // Lets scripts filter `*.info.type === \"light\"` without parsing.\n await this.ensureState(`${prefix}.info.type`, \"Device Type\", \"string\", \"text\", false, undefined, \"\");\n await this.adapter.setStateChangedAsync(`${prefix}.info.model`, {\n val: device.sku,\n ack: true,\n });\n await this.adapter.setStateChangedAsync(`${prefix}.info.serial`, {\n val: device.deviceId,\n ack: true,\n });\n await this.adapter.setStateChangedAsync(`${prefix}.info.ip`, {\n val: device.lanIp ?? \"\",\n ack: true,\n });\n await this.adapter.setStateChangedAsync(`${prefix}.info.type`, {\n val: shortenGoveeType(device.type),\n ack: true,\n });\n // Initial info.online sync \u2014 see syncInfoOnline for the resolver.\n // Subsequent updates come from the periodic sync timer in main.ts\n // and from direct calls in onDeviceStateUpdate when state.online\n // arrives via the existing event paths.\n await this.syncInfoOnline(device);\n } else {\n // Group members: comma-separated device prefix IDs\n const memberIds = (device.groupMembers ?? []).map(m => treeKey(m.sku, m.deviceId)).join(\", \");\n await this.ensureState(`${prefix}.info.members`, \"Members\", \"string\", \"text\", false);\n await this.adapter.setStateChangedAsync(`${prefix}.info.members`, {\n val: memberIds,\n ack: true,\n });\n\n // Legacy cleanup \u2014 groups never carry device-level info states or\n // diagnostics, but older installs had them. Drop any leftovers so the\n // tree reflects the current layout.\n for (const staleId of [\n \"online\",\n \"model\",\n \"serial\",\n \"ip\",\n \"diagnostics_export\",\n \"diagnostics_result\",\n \"diagnostics_tier\",\n ]) {\n await this.safeDeleteState(`${prefix}.info.${staleId}`);\n }\n // Groups never had a `diag` channel \u2014 drop any leftover from migrated installs.\n await this.adapter.delObjectAsync(`${prefix}.diag`, { recursive: true }).catch(() => {});\n }\n }\n\n /**\n * Phase 2 \u2014 LAN-States. Power, brightness, colorRgb, colorTemperature\n * (the LAN-default set defined by getDefaultLanStates). Pure additive:\n * never deletes from MANAGED_CHANNELS, no cleanup at end. Devices without\n * lanIp get no states (sensors/appliances/groups skip silently).\n *\n * @param device Govee device\n */\n async createLanStates(device: GoveeDevice): Promise<void> {\n const stateDefs = buildLanStateDefs(device, this.adapter.log);\n if (stateDefs.length === 0) {\n this.adapter.log.debug(\n `buildLanStateDefs for ${device.sku} ${device.deviceId}: 0 states (no LAN IP / not a light) \u2014 LAN phase skipped`,\n );\n return;\n }\n this.adapter.log.debug(\n `buildLanStateDefs for ${device.sku} ${device.deviceId}: ${stateDefs.length} state(s) \u2192 writing to LAN channel`,\n );\n const prefix = this.devicePrefix(device);\n await this.writeStateDefsToChannels(prefix, stateDefs, \"LAN\");\n }\n\n /**\n * Phase 3 \u2014 Cloud-States. Capability-derived states (scenes, music,\n * snapshots, sensor, events, segments, cloud-only control toggles) plus\n * synthetic local states (diagnostics, refresh_cloud, snapshot_local/...).\n * Runs cleanupCloudOwnedStates at the end to remove states no longer\n * present in stateDefs \u2014 but LAN-default ids in the control channel are\n * preserved via the LAN_STATE_IDS skip.\n *\n * @param device Govee device\n * @param stateDefs Cloud-owned state definitions from buildCloudStateDefs\n */\n async createCloudStates(device: GoveeDevice, stateDefs: StateDefinition[]): Promise<void> {\n const prefix = this.devicePrefix(device);\n\n // Drop _segment_ marker entries \u2014 segments have their own dedicated\n // createSegmentStates pass (per-segment color/brightness states).\n const nonSegmentDefs = stateDefs.filter(d => !d.id.startsWith(\"_segment_\"));\n await this.writeStateDefsToChannels(prefix, nonSegmentDefs, `Cloud ${device.sku}`);\n\n // Remove states no longer present in this Cloud-phase build. LAN_STATE_IDS\n // protects the LAN-default ids in the control channel \u2014 the LAN phase\n // owns those.\n await this.cleanupCloudOwnedStates(prefix, nonSegmentDefs);\n\n // Segment channel if device has segment caps\n if (stateDefs.some(d => d.id.startsWith(\"_segment_\"))) {\n await this.createSegmentStates(device);\n }\n }\n\n /**\n * Shared inner loop \u2014 group stateDefs by channel, create the channel\n * object once, then create each state. Called from createLanStates and\n * createCloudStates. Idempotent (extendObject).\n *\n * @param prefix Device prefix (e.g. \"devices.h6172_abcd\")\n * @param stateDefs State definitions to write\n * @param logTag Short tag for the per-phase debug log line\n */\n private async writeStateDefsToChannels(prefix: string, stateDefs: StateDefinition[], logTag: string): Promise<void> {\n const channelGroups = new Map<string, StateDefinition[]>();\n for (const def of stateDefs) {\n const channel = def.channel ?? \"control\";\n this.stateChannelMap.set(`${prefix}.${def.id}`, channel);\n if (!channelGroups.has(channel)) {\n channelGroups.set(channel, []);\n }\n channelGroups.get(channel)!.push(def);\n }\n\n this.adapter.log.debug(\n `createStates [${logTag}] ${prefix}: ${stateDefs.length} states in ${channelGroups.size} channel(s)`,\n );\n\n for (const [channel, defs] of channelGroups) {\n await this.adapter.extendObject(\n `${prefix}.${channel}`,\n {\n type: \"channel\",\n common: { name: CHANNEL_NAMES[channel] ?? channel },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n\n for (const def of defs) {\n const common: Partial<ioBroker.StateCommon> = {\n name: def.name as ioBroker.StringOrTranslated,\n type: def.type,\n role: def.role,\n read: true,\n write: def.write,\n };\n\n if (def.unit) {\n common.unit = def.unit;\n }\n if (def.min !== undefined) {\n common.min = def.min;\n }\n if (def.max !== undefined) {\n common.max = def.max;\n }\n if (def.states) {\n common.states = def.states;\n }\n if (def.def !== undefined) {\n common.def = def.def;\n }\n if (def.desc) {\n common.desc = def.desc as ioBroker.StringOrTranslated;\n }\n\n await this.adapter.extendObject(\n `${prefix}.${channel}.${def.id}`,\n {\n type: \"state\",\n common: common,\n native: {\n capabilityType: def.capabilityType,\n capabilityInstance: def.capabilityInstance,\n },\n },\n { preserve: { common: [\"name\"] } },\n );\n\n // Existing diag.tier datapoints from v2.6.0+ may carry translation-object\n // VALUES in common.states (the old buildCloudStateDefs wrote tLabel(...)\n // directly). extendObject deep-merges and cannot replace an\n // object-value with a string. Force-replace via setObjectAsync when\n // any persisted state value is non-string. Pattern proven in hassemu\n // v1.27.2 / v1.28.4. React Error #31 would otherwise fatal-crash Admin\n // on dropdown open (write:true states) or any view that renders the\n // value (write:false states like diag.tier).\n if (def.states) {\n await this.repairCommonStatesIfBuggy(`${prefix}.${channel}.${def.id}`, def.states);\n }\n\n // Initialize or validate state value\n if (def.def !== undefined) {\n const current = await this.adapter.getStateAsync(`${prefix}.${channel}.${def.id}`);\n if (!current || current.val === null || current.val === undefined) {\n // Set default value for new states\n await this.adapter.setState(`${prefix}.${channel}.${def.id}`, {\n val: def.def,\n ack: true,\n });\n } else if (def.states && !(String(current.val) in def.states)) {\n // Reset dropdown to default if current value is no longer valid\n this.adapter.log.debug(\n `Resetting stale dropdown: ${prefix}.${channel}.${def.id} = \"${String(current.val)}\" \u2192 \"${String(def.def)}\"`,\n );\n await this.adapter.setState(`${prefix}.${channel}.${def.id}`, {\n val: def.def,\n ack: true,\n });\n }\n }\n }\n }\n }\n\n /**\n * Create segment channel with per-segment color + brightness states.\n *\n * @param device Govee device\n */\n async createSegmentStates(device: GoveeDevice): Promise<void> {\n const prefix = this.devicePrefix(device);\n\n await this.adapter.extendObject(\n `${prefix}.segments`,\n {\n type: \"channel\",\n common: { name: tName(\"ledSegments\") },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n\n // Resolve the authoritative count: cache/MQTT-learned wins over Cloud\n // capabilities. A manual list can only grow the count (never shrink it)\n // so users editing manual_list can reveal hidden indices without losing\n // the already-learned total.\n const resolved = resolveSegmentCount(device);\n const manualMax =\n Array.isArray(device.manualSegments) && device.manualSegments.length > 0\n ? Math.max(...device.manualSegments) + 1\n : 0;\n const segmentCount = Math.max(resolved, manualMax);\n device.segmentCount = segmentCount;\n\n // Effective segment list \u2014 honor manual override if active (cut-strip support)\n const validIndices =\n device.manualMode && Array.isArray(device.manualSegments) && device.manualSegments.length > 0\n ? device.manualSegments.slice().sort((a, b) => a - b)\n : Array.from({ length: segmentCount }, (_, i) => i);\n const reportedCount = validIndices.length;\n\n await this.ensureState(`${prefix}.segments.count`, tName(\"segmentCount\"), \"number\", \"value\", false);\n await this.adapter.setState(`${prefix}.segments.count`, {\n val: reportedCount,\n ack: true,\n });\n\n // Manual-mode toggle and list \u2014 user-writable for cut-strip overrides\n await this.adapter.extendObject(\n `${prefix}.segments.manual_mode`,\n {\n type: \"state\",\n common: {\n name: tName(\"manualSegmentsActive\"),\n type: \"boolean\",\n role: \"switch\",\n read: true,\n write: true,\n def: false,\n desc: tDesc(\"manualSegmentsDesc\"),\n },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n await this.adapter.extendObject(\n `${prefix}.segments.manual_list`,\n {\n type: \"state\",\n common: {\n name: tName(\"manualSegmentList\"),\n type: \"string\",\n role: \"text\",\n read: true,\n write: true,\n def: \"\",\n desc: tDesc(\"manualListDesc\"),\n },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n\n // Sync manual_mode / manual_list states back from the runtime device\n // (restored from cache on startup, or updated by the wizard). Using\n // ack=true keeps this out of the user-change handler path.\n const manualModeVal = device.manualMode === true;\n const manualListVal =\n device.manualMode && Array.isArray(device.manualSegments) && device.manualSegments.length > 0\n ? device.manualSegments.join(\",\")\n : \"\";\n await this.adapter.setState(`${prefix}.segments.manual_mode`, {\n val: manualModeVal,\n ack: true,\n });\n await this.adapter.setState(`${prefix}.segments.manual_list`, {\n val: manualListVal,\n ack: true,\n });\n\n for (const i of validIndices) {\n await this.adapter.extendObject(\n `${prefix}.segments.${i}`,\n {\n type: \"channel\",\n common: { name: `Segment ${i}` },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n\n await this.adapter.extendObject(\n `${prefix}.segments.${i}.color`,\n {\n type: \"state\",\n common: {\n name: tName(\"color\"),\n type: \"string\",\n role: \"level.color.rgb\",\n read: true,\n write: true,\n def: \"#000000\", // avoid null in vis until the first write (LAN-only tier) \u2014 B6\n },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n\n await this.adapter.extendObject(\n `${prefix}.segments.${i}.brightness`,\n {\n type: \"state\",\n common: {\n name: tName(\"brightness\"),\n type: \"number\",\n role: \"level.brightness\",\n read: true,\n write: true,\n min: 0,\n max: 100,\n unit: \"%\",\n def: 0, // avoid null in vis until the first write (LAN-only tier) \u2014 B6\n },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n }\n\n // Comfort command state for batch segment control\n await this.adapter.extendObject(\n `${prefix}.segments.command`,\n {\n type: \"state\",\n common: {\n name: tName(\"batchSegmentCommand\"),\n type: \"string\",\n role: \"text\",\n read: false,\n write: true,\n desc: tDesc(\"batchCommandDesc\"),\n },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n\n // Remove segment channels that aren't in the valid list (supports gaps for manual mode)\n await this.cleanupExcessSegments(prefix, validIndices);\n }\n\n /**\n * Remove segment sub-channels that are not in the valid-indices list.\n * Supports gaps (e.g. manual list \"0-8,10-14\" \u2192 segment 9 channel gets removed).\n *\n * @param prefix Device prefix\n * @param validIndices Valid segment indices (all others will be deleted)\n */\n private async cleanupExcessSegments(prefix: string, validIndices: number[]): Promise<void> {\n const valid = new Set(validIndices);\n const segPrefix = `${this.adapter.namespace}.${prefix}.segments.`;\n const existing = await this.adapter.getObjectViewAsync(\"system\", \"channel\", {\n startkey: segPrefix,\n endkey: `${segPrefix}\\u9999`,\n });\n\n if (!existing?.rows) {\n return;\n }\n\n for (const row of existing.rows) {\n const localId = row.id.replace(`${this.adapter.namespace}.`, \"\");\n const segPart = localId.replace(`${prefix}.segments.`, \"\");\n const segIdx = parseInt(segPart, 10);\n if (!isNaN(segIdx) && !valid.has(segIdx)) {\n this.adapter.log.debug(`Removing excess segment: ${localId}`);\n // Drop orphan state values too \u2014 `delObjectAsync(recursive)` removes\n // the object tree but leaves the state-table values for color and\n // brightness behind. Without these explicit `delStateAsync` calls,\n // historical values would resurrect into a re-created segment after\n // a length change.\n await this.adapter.delStateAsync(`${localId}.color`).catch(() => undefined);\n await this.adapter.delStateAsync(`${localId}.brightness`).catch(() => undefined);\n await this.adapter.delObjectAsync(localId, { recursive: true });\n }\n }\n }\n\n /**\n * Update device state from any source (LAN, MQTT, Cloud).\n *\n * Writes are fire-and-forget and run in parallel \u2014 they're independent,\n * and the \"does this state exist?\" check that used to guard each write\n * was an extra object-read on the hot path (one MQTT push = one update\n * call). createDeviceStates has already run before any update lands,\n * so the states are guaranteed to exist; if one disappears (manual\n * deletion), the setState will reject and we swallow it.\n *\n * @param device Govee device\n * @param state Partial state update\n */\n async updateDeviceState(device: GoveeDevice, state: Partial<DeviceState>): Promise<void> {\n const prefix = this.devicePrefix(device);\n const writes: Promise<unknown>[] = [];\n\n const set = (id: string, val: ioBroker.StateValue): void => {\n // setStateChangedAsync (not setState): the LAN devStatus poll (every\n // 30 s when no MQTT is connected) re-delivers identical power / brightness\n // / colorRgb / colorTemperature each cycle \u2014 only write (and bump the\n // timestamp) on a real value change. Freshness lives on the device object\n // (lastLanReplyAt / lastSeenOnNetwork), not on these control-state ts.\n writes.push(this.adapter.setStateChangedAsync(id, { val, ack: true }).catch(() => undefined));\n };\n\n // info.online for Lights is owned by syncInfoOnline \u2014 direct writes here\n // would re-introduce the ts-rewrite-spam (every 2 min same value) and\n // the false-positive `true` writes from Cloud/MQTT paths. For Sensors/\n // Appliances the existing flow stays (applyOnlineCap \u2192 onDeviceUpdate \u2192\n // here \u2192 info.online).\n if (state.online !== undefined && device.type !== GOVEE_DEVICE_TYPE.LIGHT) {\n set(`${prefix}.info.online`, state.online);\n }\n if (state.power !== undefined) {\n set(`${prefix}.control.power`, state.power);\n }\n if (state.brightness !== undefined) {\n set(`${prefix}.control.brightness`, state.brightness);\n }\n if (state.colorRgb !== undefined) {\n set(`${prefix}.control.color_rgb`, state.colorRgb);\n }\n if (state.colorTemperature !== undefined) {\n set(`${prefix}.control.color_temperature`, state.colorTemperature);\n }\n if (state.scene !== undefined) {\n set(`${prefix}.control.scene`, state.scene);\n }\n\n await Promise.all(writes);\n }\n\n /**\n * Create the general groups.info.online state (reflects Cloud connection).\n *\n * @param online Initial online value\n */\n async createGroupsOnlineState(online: boolean): Promise<void> {\n await this.adapter.extendObject(\n \"groups\",\n {\n type: \"folder\",\n common: { name: tName(\"groups\") },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n await this.adapter.extendObject(\n \"groups.info\",\n {\n type: \"channel\",\n common: { name: tName(\"groupsStatus\") },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n await this.ensureState(\"groups.info.online\", \"Cloud Online\", \"boolean\", \"indicator.reachable\", false);\n await this.adapter.setState(\"groups.info.online\", {\n val: online,\n ack: true,\n });\n }\n\n /**\n * Update the general groups online state.\n *\n * @param online Cloud connection status\n */\n async updateGroupsOnline(online: boolean): Promise<void> {\n await this.adapter.setState(\"groups.info.online\", { val: online, ack: true }).catch(() => undefined);\n }\n\n /**\n * Update info.membersUnreachable for a group.\n *\n * Always keeps the state (existing) and writes a comma-separated list of the\n * unreachable members, or an empty string when all are online. Previously we\n * deleted the object on \"all reachable\" \u2014 but that produced a js-controller\n * WARN \"State 'X.membersUnreachable' has no existing object\" every ~2 minutes,\n * because parallel updateGroupReachability calls (LAN+MQTT status updates fire\n * almost simultaneously) could trigger a race condition between setState\n * (object exists) and safeDeleteState (object gone). Always keeping the state\n * present avoids that entirely.\n *\n * @param group BaseGroup device\n * @param memberDevices Resolved member devices\n */\n async updateGroupMembersUnreachable(group: GoveeDevice, memberDevices: GoveeDevice[]): Promise<void> {\n const prefix = this.devicePrefix(group);\n const stateId = `${prefix}.info.membersUnreachable`;\n\n const unreachable = memberDevices.filter(m => !m.state.online).map(m => treeKey(m.sku, m.deviceId));\n\n await this.ensureState(stateId, \"Unreachable Members\", \"string\", \"text\", false);\n await this.adapter.setState(stateId, {\n val: unreachable.join(\", \"),\n ack: true,\n });\n }\n\n /**\n * Cleanup stale devices that no longer exist.\n *\n * Returns the prefixes of removed devices so callers (DeviceManager,\n * adapter-level maps) can drop their own entries for the same devices\n * and prevent unbounded map growth across the adapter's lifetime.\n *\n * @param currentDevices Current device list\n * @returns Prefixes of removed devices (e.g. \"devices.h61be_1d6f\")\n */\n async cleanupDevices(currentDevices: GoveeDevice[]): Promise<string[]> {\n const currentPrefixes = new Set(currentDevices.map(d => this.devicePrefix(d)));\n const removed: string[] = [];\n\n // Cleanup both devices/ and groups/ folders\n for (const folder of [\"devices\", \"groups\"]) {\n // getObjectViewAsync can throw on transient js-controller hiccups \\u2014\n // wrapping it lets cleanupDevices proceed with the other folder\n // instead of bailing out of the whole cleanup pass.\n let existingObjects;\n try {\n existingObjects = await this.adapter.getObjectViewAsync(\"system\", \"device\", {\n startkey: `${this.adapter.namespace}.${folder}.`,\n endkey: `${this.adapter.namespace}.${folder}.\\u9999`,\n });\n } catch (e) {\n this.adapter.log.debug(\n `cleanupDevices: getObjectViewAsync failed for ${folder}: ${e instanceof Error ? e.message : String(e)}`,\n );\n continue;\n }\n\n if (!existingObjects?.rows) {\n continue;\n }\n\n for (const row of existingObjects.rows) {\n const localId = row.id.replace(`${this.adapter.namespace}.`, \"\");\n if (!currentPrefixes.has(localId)) {\n this.adapter.log.debug(`Removing stale device: ${localId}`);\n // Recursive delObject removes the object tree but can leave\n // orphan state values in the state-table \u2014 clean those too so\n // historical values don't survive a device removal.\n const stateRows = await this.adapter\n .getObjectViewAsync(\"system\", \"state\", {\n startkey: `${row.id}.`,\n endkey: `${row.id}.\u9999`,\n })\n .catch(() => undefined);\n if (stateRows?.rows) {\n for (const stateRow of stateRows.rows) {\n const stateLocalId = stateRow.id.replace(`${this.adapter.namespace}.`, \"\");\n await this.adapter.delStateAsync(stateLocalId).catch(() => undefined);\n }\n }\n await this.adapter.delObjectAsync(localId, { recursive: true });\n this.forgetPrefix(localId);\n removed.push(localId);\n }\n }\n }\n return removed;\n }\n\n /**\n * Phase 3 cleanup \u2014 remove Cloud-owned states that are no longer in the\n * current Cloud-phase stateDefs. Respects LAN_STATE_IDS so the LAN phase's\n * states in the control channel never get touched.\n *\n * The Cloud-owned channels (scenes, music, snapshots, sensor, events) are\n * 100% Cloud territory \u2014 anything not in cloudStateDefs there is stale.\n * The control channel is mixed: LAN-default ids (power, brightness, \u2026)\n * belong to the LAN phase and are skipped via the LAN_STATE_IDS constant.\n *\n * Public for the v2.8.0 migration shot in main.ts.onReady \u2014 pure-LAN\n * devices need a one-time cleanupCloudOwnedStates(prefix, []) to wipe\n * scene/music/snapshot leftovers from prior versions.\n *\n * @param prefix Device prefix\n * @param cloudStateDefs Current Cloud-phase state definitions (non-segment)\n */\n async cleanupCloudOwnedStates(prefix: string, cloudStateDefs: StateDefinition[]): Promise<void> {\n // Build expected state set per channel\n const expectedByChannel = new Map<string, Set<string>>();\n for (const def of cloudStateDefs) {\n const channel = def.channel ?? \"control\";\n if (!expectedByChannel.has(channel)) {\n expectedByChannel.set(channel, new Set());\n }\n expectedByChannel.get(channel)!.add(def.id);\n }\n\n const devicePrefix = `${this.adapter.namespace}.${prefix}.`;\n const existing = await this.adapter.getObjectViewAsync(\"system\", \"state\", {\n startkey: devicePrefix,\n endkey: `${devicePrefix}\\u9999`,\n });\n if (!existing?.rows) {\n return;\n }\n\n const totalsPerChannel = new Map<string, { seen: number; deleted: number }>();\n for (const row of existing.rows) {\n const rest = row.id.replace(devicePrefix, \"\");\n const dotIdx = rest.indexOf(\".\");\n if (dotIdx < 0) {\n continue;\n }\n const channel = rest.slice(0, dotIdx);\n const stateId = rest.slice(dotIdx + 1);\n if (!MANAGED_CHANNELS.includes(channel)) {\n continue;\n }\n // In the control channel, LAN-default ids belong to the LAN phase \u2014\n // Cloud cleanup must not touch them. Other MANAGED_CHANNELS are\n // wholly Cloud territory. Count them as seen-but-not-deleted survivors so\n // the \"empty channel\" removal below doesn't delete the control channel\n // object out from under its surviving LAN states (L9).\n if (channel === \"control\" && LAN_STATE_IDS.has(stateId)) {\n const survivors = totalsPerChannel.get(channel) ?? { seen: 0, deleted: 0 };\n survivors.seen++;\n totalsPerChannel.set(channel, survivors);\n continue;\n }\n const totals = totalsPerChannel.get(channel) ?? { seen: 0, deleted: 0 };\n totals.seen++;\n const validIds = expectedByChannel.get(channel) ?? new Set<string>();\n if (!validIds.has(stateId)) {\n const localId = row.id.replace(`${this.adapter.namespace}.`, \"\");\n this.adapter.log.debug(`Removing stale state: ${localId}`);\n await this.adapter.delObjectAsync(localId);\n await this.adapter.delStateAsync(localId).catch(() => {});\n totals.deleted++;\n }\n totalsPerChannel.set(channel, totals);\n }\n\n // Remove empty channel objects \u2014 no surviving states for this channel\n for (const [channel, totals] of totalsPerChannel) {\n if (totals.deleted > 0 && totals.deleted === totals.seen) {\n this.adapter.log.debug(`Removing empty channel: ${prefix}.${channel}`);\n await this.adapter.delObjectAsync(`${prefix}.${channel}`).catch(() => undefined);\n }\n }\n }\n\n /**\n * Get device object ID prefix \u2014 stable SKU + short device ID.\n * Groups (BaseGroup) go under groups/, devices under devices/.\n * Human-readable name is in common.name, not in the object ID.\n *\n * @param device Govee device\n */\n devicePrefix(device: GoveeDevice): string {\n const folder = device.sku === \"BaseGroup\" ? \"groups\" : \"devices\";\n return `${folder}.${treeKey(device.sku, device.deviceId)}`;\n }\n\n /**\n * Drop prefix + stateChannel entries for a device that was removed.\n * Prevents the maps from growing indefinitely across adapter lifetime.\n *\n * @param prefix Device prefix that was removed\n */\n private forgetPrefix(prefix: string): void {\n for (const key of this.prefixMap.keys()) {\n if (this.prefixMap.get(key) === prefix) {\n this.prefixMap.delete(key);\n }\n }\n const stalePrefix = `${prefix}.`;\n for (const key of this.stateChannelMap.keys()) {\n if (key.startsWith(stalePrefix)) {\n this.stateChannelMap.delete(key);\n }\n }\n // Drop ensureState cache for this device too \u2014 a re-pair must run the\n // full extendObject path again so the new device's name/type get\n // applied (cache hit would skip the round-trip and keep stale common.*).\n // ensureState stores namespace-less ids (extendObject takes a relative\n // id), so match the same way as stateChannelMap above \u2014 the old namespaced\n // match never fired, so re-paired devices skipped info-state creation (M4).\n for (const id of this.ensuredStates) {\n if (id === prefix || id.startsWith(stalePrefix)) {\n this.ensuredStates.delete(id);\n }\n }\n }\n\n /**\n * Unique key for internal tracking (not used as object ID).\n *\n * @param device Govee device\n */\n private deviceKey(device: GoveeDevice): string {\n return mapKey(device.sku, device.deviceId);\n }\n\n /**\n * Create a state if it doesn't exist. Cached after the first successful\n * `extendObject` so hot-path callers (e.g. `updateGroupMembersUnreachable`\n * fires per status update) skip the Redis round-trip.\n *\n * @param id State object ID\n * @param name Display name\n * @param type Value type\n * @param role ioBroker role\n * @param write Whether state is writable\n * @param unit Optional unit of measurement\n * @param def Optional default value \u2014 set so the state has a sensible\n * initial value before the first writeback (avoids `null`\n * display in admin between create and first setState).\n */\n private async ensureState(\n id: string,\n name: ioBroker.StringOrTranslated,\n type: ioBroker.CommonType,\n role: string,\n write: boolean,\n unit?: string,\n def?: ioBroker.StateValue,\n ): Promise<void> {\n if (this.ensuredStates.has(id)) {\n return;\n }\n const common: Partial<ioBroker.StateCommon> = {\n name,\n type,\n role,\n read: true,\n write,\n };\n if (unit) {\n common.unit = unit;\n }\n if (def !== undefined) {\n common.def = def;\n }\n await this.adapter.extendObject(\n id,\n {\n type: \"state\",\n common: common,\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n this.ensuredStates.add(id);\n }\n\n /**\n * Resolver-based info.online sync.\n *\n * For LAN-capable LED Lights (`type === light` AND `lanIp` set) the\n * truth-source is exclusively `device.lastLanReplyAt` \u2014 set when the device\n * replies to a LAN-Discovery multicast or LAN-Unicast devStatus. The 90 s\n * freshness window tolerates 3 missed 30 s scans against UDP packet loss but\n * still flips offline reasonably fast on a real outage.\n *\n * For cloud-only Lights (a light whose owner never enabled the local API, so\n * `lanIp` is null) and for Sensors/Appliances there is no LAN signal:\n * `device.state.online` \u2014 set by `applyOnlineCap` from App-API / OpenAPI-MQTT\n * \u2014 is read straight through here. Local-first stays, local-only does not.\n *\n * Writes `info.online` only when the resolved value differs from the\n * current state \u2014 kills the 2-min ts-rewrite-spam captured 2026-05-13.\n *\n * For Lights: when the resolved online value changes, the internal\n * `device.state.online` is also updated so downstream consumers\n * (`updateGroupReachability`, `handleLanDiscovery` wasOffline check)\n * stay in sync. Returns `true` in that case so the caller can fire\n * the group-fanout reachability refresh.\n *\n * Skips BaseGroup devices \u2014 groups have a global `groups.info.online`\n * managed elsewhere.\n *\n * @param device Govee device to sync\n * @returns true if a Light's resolved online state changed (caller should\n * refresh group-reachability), false otherwise\n */\n async syncInfoOnline(device: GoveeDevice): Promise<boolean> {\n if (device.sku === \"BaseGroup\") {\n return false;\n }\n const prefix = this.devicePrefix(device);\n const stateId = `${prefix}.info.online`;\n\n let desiredOnline: boolean;\n if (device.type === GOVEE_DEVICE_TYPE.LIGHT && device.lanIp) {\n // LAN-capable light: LAN-reply freshness is the truth-source (v2.9.0 \u2014 the\n // Cloud cache lagged real reachability and wrote 2\u00D7 false-positive `true`\n // during the 2026-05-13 outage capture).\n desiredOnline = !!(device.lastLanReplyAt && Date.now() - device.lastLanReplyAt < 90_000);\n } else {\n // Cloud-only light (no local API, lanIp === null) + sensors/appliances:\n // no LAN signal to trust, so the cloud-reported device online\n // (`state.online`, fed by applyOnlineCap from App-API / OpenAPI-MQTT) is\n // the right source. Local-first stays \u2014 local-only does not.\n desiredOnline = device.state.online === true;\n }\n\n const current = await this.adapter.getStateAsync(stateId).catch(() => null);\n if (!current || current.val !== desiredOnline) {\n await this.adapter.setState(stateId, { val: desiredOnline, ack: true }).catch(() => undefined);\n }\n\n let lightOnlineChanged = false;\n if (device.type === GOVEE_DEVICE_TYPE.LIGHT && device.state.online !== desiredOnline) {\n device.state.online = desiredOnline;\n lightOnlineChanged = true;\n }\n\n return lightOnlineChanged;\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["mapKey"]
7
7
  }
package/io-package.json CHANGED
@@ -1,8 +1,21 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "govee-smart",
4
- "version": "2.18.1",
4
+ "version": "2.18.2",
5
5
  "news": {
6
+ "2.18.2": {
7
+ "en": "Internal refactoring. No user-facing changes.",
8
+ "de": "Interne Überarbeitung. Keine für Nutzer sichtbaren Änderungen.",
9
+ "ru": "Внутренний рефакторинг. Никаких изменений для пользователя.",
10
+ "pt": "Refatoração interna. Nenhuma alteração visível para o utilizador.",
11
+ "nl": "Interne refactoring. Geen zichtbare wijzigingen voor gebruikers.",
12
+ "fr": "Refactorisation interne. Aucun changement visible pour l'utilisateur.",
13
+ "it": "Refactoring interno. Nessuna modifica visibile all'utente.",
14
+ "es": "Refactorización interna. Sin cambios visibles para el usuario.",
15
+ "pl": "Wewnętrzny refaktoring. Brak zmian widocznych dla użytkownika.",
16
+ "uk": "Внутрішній рефакторинг. Жодних змін для користувача.",
17
+ "zh-cn": "内部重构。无用户可见的更改。"
18
+ },
6
19
  "2.18.1": {
7
20
  "en": "New Govee devices in the catalog: more lamps and sensors, including a CO2 monitor. They start as experimental — enable them in the adapter settings to try them.",
8
21
  "de": "Neue Govee-Geräte im Katalog: mehr Lampen und Sensoren, darunter ein CO2-Monitor. Sie starten experimentell — in den Adapter-Einstellungen aktivieren zum Ausprobieren.",
@@ -80,19 +93,6 @@
80
93
  "pl": "Światłami i urządzeniami bez połączenia lokalnego można teraz sterować przez Cloud, plus czytelniejsze jednorazowe powiadomienia i więcej obsługiwanych urządzeń.",
81
94
  "uk": "Освітлення та пристрої без локального підключення тепер керуються через хмару, плюс зрозуміліші одноразові сповіщення та більше підтримуваних пристроїв.",
82
95
  "zh-cn": "无本地连接的灯和设备现在可通过云端控制,并提供更清晰的一次性提醒和更多受支持的设备。"
83
- },
84
- "2.15.0": {
85
- "en": "Added optional Sentry error reporting: crashes are sent to the developer so issues get fixed faster. Active only with ioBroker diagnostics enabled; anonymous.",
86
- "de": "Optionale Fehlermeldung über Sentry hinzugefügt: Abstürze werden an den Entwickler gesendet, damit Probleme schneller behoben werden. Nur aktiv bei eingeschalteter ioBroker-Diagnose; anonym.",
87
- "ru": "Добавлена необязательная отправка ошибок через Sentry: сбои отправляются разработчику, чтобы быстрее их устранять. Работает только при включённой диагностике ioBroker; анонимно.",
88
- "pt": "Adicionado relatório de erros opcional via Sentry: as falhas são enviadas ao programador para corrigir problemas mais rápido. Ativo apenas com o diagnóstico do ioBroker ligado; anónimo.",
89
- "nl": "Optionele foutrapportage via Sentry toegevoegd: crashes worden naar de ontwikkelaar gestuurd om problemen sneller op te lossen. Alleen actief met ioBroker-diagnostiek ingeschakeld; anoniem.",
90
- "fr": "Rapport d'erreurs optionnel via Sentry ajouté : les plantages sont envoyés au développeur pour corriger plus vite. Actif seulement si le diagnostic ioBroker est activé ; anonyme.",
91
- "it": "Aggiunta segnalazione errori opzionale via Sentry: gli arresti vengono inviati allo sviluppatore per risolvere prima i problemi. Attivo solo con la diagnostica ioBroker attiva; anonimo.",
92
- "es": "Añadido informe de errores opcional mediante Sentry: los fallos se envían al desarrollador para solucionar problemas más rápido. Activo solo con el diagnóstico de ioBroker activado; anónimo.",
93
- "pl": "Dodano opcjonalne raportowanie błędów przez Sentry: awarie są wysyłane do dewelopera, aby szybciej rozwiązywać problemy. Aktywne tylko przy włączonej diagnostyce ioBroker; anonimowo.",
94
- "uk": "Додано необов'язкову відправку помилок через Sentry: збої надсилаються розробнику, щоб швидше їх виправляти. Працює лише з увімкненою діагностикою ioBroker; анонімно.",
95
- "zh-cn": "新增可选的 Sentry 错误上报:崩溃信息会发送给开发者以更快修复问题。仅在启用 ioBroker 诊断时生效;匿名。"
96
96
  }
97
97
  },
98
98
  "plugins": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.govee-smart",
3
- "version": "2.18.1",
3
+ "version": "2.18.2",
4
4
  "description": "Control Govee WiFi devices via LAN, MQTT and Cloud.",
5
5
  "author": {
6
6
  "name": "krobi",