iobroker.govee-smart 2.8.3 → 2.8.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/build/lib/capability-mapper.js +5 -5
- package/build/lib/capability-mapper.js.map +2 -2
- package/build/lib/handlers/device-events.js +3 -3
- package/build/lib/handlers/device-events.js.map +2 -2
- package/build/lib/i18n-states.js +11 -0
- package/build/lib/i18n-states.js.map +2 -2
- package/build/lib/state-manager.js +34 -0
- package/build/lib/state-manager.js.map +2 -2
- package/io-package.json +14 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -128,6 +128,10 @@ This adapter's MQTT authentication and BLE-over-LAN (ptReal) protocol implementa
|
|
|
128
128
|
Placeholder for the next version (at the beginning of the line):
|
|
129
129
|
### **WORK IN PROGRESS**
|
|
130
130
|
-->
|
|
131
|
+
### 2.8.4 (2026-05-12)
|
|
132
|
+
|
|
133
|
+
- The device trust tier state under each device no longer carries a multi-language label object that would crash the admin with "Error in GUI" if rendered as a dropdown.
|
|
134
|
+
|
|
131
135
|
### 2.8.3 (2026-05-12)
|
|
132
136
|
|
|
133
137
|
- Debug log now covers every adapter path: App and Cloud API calls (with body snippets on null), cache load/save with age, state-writes, capability mapping, LAN command timing, MQTT login server reply.
|
|
@@ -148,10 +152,6 @@ This adapter's MQTT authentication and BLE-over-LAN (ptReal) protocol implementa
|
|
|
148
152
|
- Restart no longer briefly removes and re-creates scene, music and snapshot datapoints.
|
|
149
153
|
- Lights without API key no longer have empty scene/snapshot dropdowns left over from earlier versions.
|
|
150
154
|
|
|
151
|
-
### 2.7.1 (2026-05-10)
|
|
152
|
-
|
|
153
|
-
- Cleaner startup log: the first line now tells you to wait for the "ready" message, and one redundant connection-info line is gone.
|
|
154
|
-
|
|
155
155
|
Older entries are in [CHANGELOG_OLD.md](CHANGELOG_OLD.md).
|
|
156
156
|
|
|
157
157
|
## Support
|
|
@@ -735,7 +735,7 @@ function buildLanStateDefs(device, log) {
|
|
|
735
735
|
applyQuirksToStates(device.sku, stateDefs, log);
|
|
736
736
|
return stateDefs;
|
|
737
737
|
}
|
|
738
|
-
function buildCloudStateDefs(device, log, localSnapshots, memberDevices) {
|
|
738
|
+
function buildCloudStateDefs(device, log, localSnapshots, memberDevices, lang = "en") {
|
|
739
739
|
if (device.sku === "BaseGroup") {
|
|
740
740
|
return buildGroupStateDefs(memberDevices || []);
|
|
741
741
|
}
|
|
@@ -879,10 +879,10 @@ function buildCloudStateDefs(device, log, localSnapshots, memberDevices) {
|
|
|
879
879
|
write: false,
|
|
880
880
|
def: "unknown",
|
|
881
881
|
states: {
|
|
882
|
-
verified: (0, import_i18n_states.
|
|
883
|
-
reported: (0, import_i18n_states.
|
|
884
|
-
seed: (0, import_i18n_states.
|
|
885
|
-
unknown: (0, import_i18n_states.
|
|
882
|
+
verified: (0, import_i18n_states.resolveLabel)("deviceTierVerified", lang),
|
|
883
|
+
reported: (0, import_i18n_states.resolveLabel)("deviceTierReported", lang),
|
|
884
|
+
seed: (0, import_i18n_states.resolveLabel)("deviceTierSeed", lang),
|
|
885
|
+
unknown: (0, import_i18n_states.resolveLabel)("deviceTierUnknown", lang)
|
|
886
886
|
},
|
|
887
887
|
capabilityType: "local",
|
|
888
888
|
capabilityInstance: "diagnosticsTier",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/capability-mapper.ts"],
|
|
4
|
-
"sourcesContent": ["import {\n buildUniqueLabelMap,\n rgbToHex,\n type CloudCapability,\n type CloudStateCapability,\n type GoveeDevice,\n} from \"./types\";\nimport { applyColorTempQuirk } from \"./device-registry\";\nimport { tDesc, tLabel, tName } from \"./i18n-states\";\n\n/** ioBroker state definition derived from a Govee capability */\nexport interface StateDefinition {\n /** State ID suffix (e.g. \"power\", \"brightness\", \"colorRgb\") */\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()` from `i18n-states.ts` \u2014 Admin/vis/Object-Browser\n * pick the user's language 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). Label can be a plain\n * string or a `{en, de, ...}` translation object (ioBroker Admin v6+).\n */\n states?: Record<string, string | 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 === \"devices.capabilities.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\", \"colorRgb\", \"colorTemperature\"]);\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: \"colorRgb\",\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: \"colorTemperature\",\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: \"colorRgb\",\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 === \"colorTemperatureK\" || cap.instance.includes(\"colorTem\")) {\n const range = cap.parameters?.range;\n return [\n {\n id: \"colorTemperature\",\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 = typeof opt.value === \"object\" ? JSON.stringify(opt.value) : String(opt.value);\n states[val] = opt.name;\n }\n\n return [\n {\n id: \"scene\",\n name: tName(\"scene\"),\n type: \"mixed\",\n role: \"text\",\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 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\",\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[typeof opt.value === \"object\" ? JSON.stringify(opt.value) : String(opt.value)] = opt.name;\n }\n }\n states.push({\n id: \"work_mode\",\n name: tName(\"workMode\"),\n type: \"mixed\",\n role: \"level.mode\",\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[typeof opt.value === \"object\" ? JSON.stringify(opt.value) : String(opt.value)] = opt.name;\n }\n }\n states.push({\n id: \"mode_value\",\n name: tName(\"modeValue\"),\n type: \"mixed\",\n role: \"level\",\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 */\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\n const modeField = fields.find(f => f && typeof f.fieldName === \"string\" && f.fieldName === \"musicMode\");\n if (modeField?.options && Array.isArray(modeField.options) && modeField.options.length > 0) {\n const modeStates: Record<string, string> = { 0: \"---\" };\n for (const opt of modeField.options) {\n if (!opt || typeof opt.name !== \"string\") {\n continue;\n }\n modeStates[typeof opt.value === \"object\" ? JSON.stringify(opt.value) : String(opt.value)] = opt.name;\n }\n states.push({\n id: \"music_mode\",\n name: tName(\"musicMode\"),\n type: \"mixed\",\n role: \"text\",\n write: true,\n states: modeStates,\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 === \"colorTemperature\" && 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}: colorTemperature 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 // Reihenfolge: erst Underscore \u2192 Space, dann camelCase-split, dann\n // trim + erstes Zeichen uppercase. Vorher: leading-underscore-IDs\n // (z.B. `_segment_color`) wurden zu ` segment color` mit leading\n // Space und ohne Capitalization (^\\w matched space, nicht 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 setStateAsync */\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: \"colorRgb\",\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: \"colorTemperature\", 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 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-Architektur: geh\u00F6rt zur LAN-Phase. Wird gerufen wenn ein Ger\u00E4t per\n * LAN-Discovery sichtbar wird oder mit lanIp aus dem Cache geladen wird.\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 * 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-Architektur: geh\u00F6rt zur Cloud-Phase. Wird gerufen wenn capabilities\n * f\u00FCr ein Ger\u00E4t aus dem Cache oder einem frischen Cloud-Load verf\u00FCgbar sind.\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 // Capability-derived states with LAN-default IDs filtered out \u2014 the LAN\n // phase owns those, capability mapper duplicates would land in the same\n // channel and confuse cleanup. Single source of truth: LAN_STATE_IDS.\n const stateDefs: StateDefinition[] = mapCapabilities(device.capabilities, log).filter(d => !LAN_STATE_IDS.has(d.id));\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 === \"devices.types.light\";\n\n // Three structurally-identical Cloud dropdowns \u2014 collapsed into one loop.\n for (const r of SCENE_DROPDOWN_RULES) {\n if (!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: \"text\",\n write: true,\n states: buildUniqueLabelMap(r.source(device)),\n def: \"0\",\n capabilityType: \"devices.capabilities.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 {\n /* ignore invalid config JSON */\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 if (\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: \"text\",\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 three states with different shapes (boolean button vs\n // read-only string vs string with fixed states map). Inline for same\n // reason as local snapshots.\n stateDefs.push({\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 stateDefs.push({\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 // Trust tier for the user \u2014 verified/reported/seed/unknown. Tells\n // power users whether the experimental toggle would buy them anything,\n // and lets unknown-SKU users see at a glance that their device isn't\n // in the catalogue yet.\n stateDefs.push({\n id: \"tier\",\n name: tName(\"deviceTier\"),\n type: \"string\",\n role: \"text\",\n write: false,\n def: \"unknown\",\n states: {\n verified: tLabel(\"deviceTierVerified\"),\n reported: tLabel(\"deviceTierReported\"),\n seed: tLabel(\"deviceTierSeed\"),\n unknown: tLabel(\"deviceTierUnknown\"),\n },\n capabilityType: \"local\",\n capabilityInstance: \"diagnosticsTier\",\n channel: \"diag\",\n });\n\n return stateDefs;\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 caps = Array.isArray(member.capabilities) ? member.capabilities : [];\n switch (stateId) {\n case \"power\":\n return caps.some(c => c && typeof c.type === \"string\" && c.type.endsWith(\"on_off\"));\n case \"brightness\":\n return caps.some(\n c =>\n c &&\n typeof c.type === \"string\" &&\n typeof c.instance === \"string\" &&\n c.type.endsWith(\"range\") &&\n c.instance === \"brightness\",\n );\n case \"colorRgb\":\n return caps.some(\n c =>\n c &&\n typeof c.type === \"string\" &&\n typeof c.instance === \"string\" &&\n c.type.endsWith(\"color_setting\") &&\n c.instance === \"colorRgb\",\n );\n case \"colorTemperature\":\n return caps.some(\n c =>\n c &&\n typeof c.type === \"string\" &&\n typeof c.instance === \"string\" &&\n c.type.endsWith(\"color_setting\") &&\n (c.instance === \"colorTem\" || c.instance === \"colorTemperatureK\"),\n );\n default:\n return false;\n }\n}\n\n/**\n * Build state definitions for a BaseGroup device.\n * Capabilities = intersection of controllable member devices.\n * No snapshots, no diagnostics, no segments.\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: \"text\",\n write: true,\n states: buildUniqueLabelMap(commonNames.map(name => ({ name }))),\n def: \"0\",\n capabilityType: \"devices.capabilities.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: \"text\",\n write: true,\n states: buildUniqueLabelMap(commonNames.map(name => ({ name }))),\n def: \"0\",\n capabilityType: \"devices.capabilities.music_setting\",\n capabilityInstance: \"musicMode\",\n channel: \"music\",\n });\n }\n }\n\n return stateDefs;\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import {\n buildUniqueLabelMap,\n rgbToHex,\n type CloudCapability,\n type CloudStateCapability,\n type GoveeDevice,\n} from \"./types\";\nimport { applyColorTempQuirk } from \"./device-registry\";\nimport { resolveLabel, tDesc, tName } from \"./i18n-states\";\n\n/** ioBroker state definition derived from a Govee capability */\nexport interface StateDefinition {\n /** State ID suffix (e.g. \"power\", \"brightness\", \"colorRgb\") */\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()` from `i18n-states.ts` \u2014 Admin/vis/Object-Browser\n * pick the user's language 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 === \"devices.capabilities.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\", \"colorRgb\", \"colorTemperature\"]);\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: \"colorRgb\",\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: \"colorTemperature\",\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: \"colorRgb\",\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 === \"colorTemperatureK\" || cap.instance.includes(\"colorTem\")) {\n const range = cap.parameters?.range;\n return [\n {\n id: \"colorTemperature\",\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 = typeof opt.value === \"object\" ? JSON.stringify(opt.value) : String(opt.value);\n states[val] = opt.name;\n }\n\n return [\n {\n id: \"scene\",\n name: tName(\"scene\"),\n type: \"mixed\",\n role: \"text\",\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 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\",\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[typeof opt.value === \"object\" ? JSON.stringify(opt.value) : String(opt.value)] = opt.name;\n }\n }\n states.push({\n id: \"work_mode\",\n name: tName(\"workMode\"),\n type: \"mixed\",\n role: \"level.mode\",\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[typeof opt.value === \"object\" ? JSON.stringify(opt.value) : String(opt.value)] = opt.name;\n }\n }\n states.push({\n id: \"mode_value\",\n name: tName(\"modeValue\"),\n type: \"mixed\",\n role: \"level\",\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 */\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\n const modeField = fields.find(f => f && typeof f.fieldName === \"string\" && f.fieldName === \"musicMode\");\n if (modeField?.options && Array.isArray(modeField.options) && modeField.options.length > 0) {\n const modeStates: Record<string, string> = { 0: \"---\" };\n for (const opt of modeField.options) {\n if (!opt || typeof opt.name !== \"string\") {\n continue;\n }\n modeStates[typeof opt.value === \"object\" ? JSON.stringify(opt.value) : String(opt.value)] = opt.name;\n }\n states.push({\n id: \"music_mode\",\n name: tName(\"musicMode\"),\n type: \"mixed\",\n role: \"text\",\n write: true,\n states: modeStates,\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 === \"colorTemperature\" && 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}: colorTemperature 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 // Reihenfolge: erst Underscore \u2192 Space, dann camelCase-split, dann\n // trim + erstes Zeichen uppercase. Vorher: leading-underscore-IDs\n // (z.B. `_segment_color`) wurden zu ` segment color` mit leading\n // Space und ohne Capitalization (^\\w matched space, nicht 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 setStateAsync */\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: \"colorRgb\",\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: \"colorTemperature\", 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 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-Architektur: geh\u00F6rt zur LAN-Phase. Wird gerufen wenn ein Ger\u00E4t per\n * LAN-Discovery sichtbar wird oder mit lanIp aus dem Cache geladen wird.\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 * 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-Architektur: geh\u00F6rt zur Cloud-Phase. Wird gerufen wenn capabilities\n * f\u00FCr ein Ger\u00E4t aus dem Cache oder einem frischen Cloud-Load verf\u00FCgbar sind.\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 * @param lang Two-letter ISO language code (e.g. `adapter.language ?? \"en\"`).\n * Resolves `common.states` VALUES that must be plain-string for Admin.\n */\nexport function buildCloudStateDefs(\n device: GoveeDevice,\n log: ioBroker.Logger,\n localSnapshots?: { name: string }[],\n memberDevices?: GoveeDevice[],\n lang: string = \"en\",\n): StateDefinition[] {\n if (device.sku === \"BaseGroup\") {\n return buildGroupStateDefs(memberDevices || []);\n }\n\n // Capability-derived states with LAN-default IDs filtered out \u2014 the LAN\n // phase owns those, capability mapper duplicates would land in the same\n // channel and confuse cleanup. Single source of truth: LAN_STATE_IDS.\n const stateDefs: StateDefinition[] = mapCapabilities(device.capabilities, log).filter(d => !LAN_STATE_IDS.has(d.id));\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 === \"devices.types.light\";\n\n // Three structurally-identical Cloud dropdowns \u2014 collapsed into one loop.\n for (const r of SCENE_DROPDOWN_RULES) {\n if (!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: \"text\",\n write: true,\n states: buildUniqueLabelMap(r.source(device)),\n def: \"0\",\n capabilityType: \"devices.capabilities.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 {\n /* ignore invalid config JSON */\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 if (\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: \"text\",\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 three states with different shapes (boolean button vs\n // read-only string vs string with fixed states map). Inline for same\n // reason as local snapshots.\n stateDefs.push({\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 stateDefs.push({\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 // Trust tier for the user \u2014 verified/reported/seed/unknown. Tells\n // power users whether the experimental toggle would buy them anything,\n // and lets unknown-SKU users see at a glance that their device isn't\n // in the catalogue yet.\n stateDefs.push({\n id: \"tier\",\n name: tName(\"deviceTier\"),\n type: \"string\",\n role: \"text\",\n write: false,\n def: \"unknown\",\n states: {\n verified: resolveLabel(\"deviceTierVerified\", lang),\n reported: resolveLabel(\"deviceTierReported\", lang),\n seed: resolveLabel(\"deviceTierSeed\", lang),\n unknown: resolveLabel(\"deviceTierUnknown\", lang),\n },\n capabilityType: \"local\",\n capabilityInstance: \"diagnosticsTier\",\n channel: \"diag\",\n });\n\n return stateDefs;\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 caps = Array.isArray(member.capabilities) ? member.capabilities : [];\n switch (stateId) {\n case \"power\":\n return caps.some(c => c && typeof c.type === \"string\" && c.type.endsWith(\"on_off\"));\n case \"brightness\":\n return caps.some(\n c =>\n c &&\n typeof c.type === \"string\" &&\n typeof c.instance === \"string\" &&\n c.type.endsWith(\"range\") &&\n c.instance === \"brightness\",\n );\n case \"colorRgb\":\n return caps.some(\n c =>\n c &&\n typeof c.type === \"string\" &&\n typeof c.instance === \"string\" &&\n c.type.endsWith(\"color_setting\") &&\n c.instance === \"colorRgb\",\n );\n case \"colorTemperature\":\n return caps.some(\n c =>\n c &&\n typeof c.type === \"string\" &&\n typeof c.instance === \"string\" &&\n c.type.endsWith(\"color_setting\") &&\n (c.instance === \"colorTem\" || c.instance === \"colorTemperatureK\"),\n );\n default:\n return false;\n }\n}\n\n/**\n * Build state definitions for a BaseGroup device.\n * Capabilities = intersection of controllable member devices.\n * No snapshots, no diagnostics, no segments.\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: \"text\",\n write: true,\n states: buildUniqueLabelMap(commonNames.map(name => ({ name }))),\n def: \"0\",\n capabilityType: \"devices.capabilities.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: \"text\",\n write: true,\n states: buildUniqueLabelMap(commonNames.map(name => ({ name }))),\n def: \"0\",\n capabilityType: \"devices.capabilities.music_setting\",\n capabilityInstance: \"musicMode\",\n channel: \"music\",\n });\n }\n }\n\n return stateDefs;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -77,7 +77,7 @@ function onLanDeviceReady(adapter, device, _allDevices) {
|
|
|
77
77
|
connectionState.updateConnectionState(adapter);
|
|
78
78
|
}
|
|
79
79
|
function onCloudDataReady(adapter, device, allDevices) {
|
|
80
|
-
var _a, _b;
|
|
80
|
+
var _a, _b, _c;
|
|
81
81
|
if (!adapter.stateManager) {
|
|
82
82
|
return;
|
|
83
83
|
}
|
|
@@ -87,7 +87,7 @@ function onCloudDataReady(adapter, device, allDevices) {
|
|
|
87
87
|
if (device.sku === "BaseGroup" && device.groupMembers) {
|
|
88
88
|
memberDevices = groupFanoutHandler.resolveGroupMembers(device, allDevices);
|
|
89
89
|
}
|
|
90
|
-
const cloudDefs = (0, import_capability_mapper.buildCloudStateDefs)(device, adapter.log, localSnaps, memberDevices);
|
|
90
|
+
const cloudDefs = (0, import_capability_mapper.buildCloudStateDefs)(device, adapter.log, localSnaps, memberDevices, (_b = adapter.language) != null ? _b : "en");
|
|
91
91
|
const capN = Array.isArray(device.capabilities) ? device.capabilities.length : 0;
|
|
92
92
|
adapter.log.debug(
|
|
93
93
|
`buildCloudStateDefs for ${device.sku} ${device.deviceId}: ${capN} cap(s) in \u2192 ${cloudDefs.length} state def(s) out`
|
|
@@ -104,7 +104,7 @@ function onCloudDataReady(adapter, device, allDevices) {
|
|
|
104
104
|
trackStateCreation(adapter, p);
|
|
105
105
|
connectionState.updateConnectionState(adapter);
|
|
106
106
|
if (adapter.statesReady) {
|
|
107
|
-
(
|
|
107
|
+
(_c = adapter.reapStaleDevices) == null ? void 0 : _c.call(adapter).catch(() => void 0);
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
function onGroupMembersReady(adapter, group, allDevices) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/handlers/device-events.ts"],
|
|
4
|
-
"sourcesContent": ["import { buildCloudStateDefs } from \"../capability-mapper\";\nimport type { DeviceManager } from \"../device-manager\";\nimport { getDeviceTier } from \"../device-registry\";\nimport type { LocalSnapshotStore } from \"../local-snapshots\";\nimport type { StateManager } from \"../state-manager\";\nimport { errMessage, type DeviceState, type GoveeDevice } from \"../types\";\nimport * as connectionState from \"./connection-state\";\nimport * as groupFanoutHandler from \"./group-fanout-handler\";\nimport * as groupStateHelpers from \"./group-state-helpers\";\n\n/**\n * Adapter surface required by the device-event helpers \u2014 covers the\n * onDeviceStateUpdate + onDeviceListChanged + refreshDeviceStates path.\n *\n * Composes ConnectionStateAdapter (for updateConnectionState) plus\n * GroupFanoutHandlerAdapter and GroupStateHelpersAdapter via duck-typing\n * \u2014 the calling adapter implements all three sets implicitly.\n */\nexport interface DeviceEventsAdapter {\n readonly log: ioBroker.Logger;\n readonly namespace: string;\n readonly deviceManager: DeviceManager | null;\n readonly stateManager: StateManager | null;\n readonly localSnapshots: LocalSnapshotStore | null;\n readonly statesReady: boolean;\n readonly stateCreationQueue: Promise<void>[];\n /** Re-fired into stateManager + connection-state + groupFanout-reachability. */\n setStateAsync(id: string, state: ioBroker.SettableState | ioBroker.StateValue): Promise<unknown>;\n /** Optional reapStaleDevices delegate \u2014 owned by main.ts because it touches diagnosticsLastRun. */\n reapStaleDevices?(): Promise<void>;\n}\n\n/**\n * Called by device-manager when a device's per-state values change. Mirrors\n * the updates into stateManager, refreshes the global connection-state\n * indicator, updates group reachability, and resets all mode dropdowns\n * when the device just powered off (the user shouldn't see \"playing\n * Aurora-A\" on a device that's off).\n *\n */\nexport function onDeviceStateUpdate<\n T extends DeviceEventsAdapter &\n connectionState.ConnectionStateAdapter &\n groupFanoutHandler.GroupFanoutHandlerAdapter &\n groupStateHelpers.GroupStateHelpersAdapter,\n>(adapter: T, device: GoveeDevice, state: Partial<DeviceState>): void {\n if (adapter.stateManager) {\n adapter.stateManager.updateDeviceState(device, state).catch(() => {});\n }\n connectionState.updateConnectionState(adapter);\n\n if (state.online !== undefined) {\n groupFanoutHandler.updateGroupReachability(adapter);\n }\n\n // Mirror power-off to mode-dropdown reset. Covers MQTT/LAN-initiated\n // power changes (Govee app or physical remote) so the UI stays honest:\n // a device that's off can't be \"playing Aurora-A\" anymore.\n // L11 \u2014 defensive auch 0 als false akzeptieren (Govee schickt Power\n // theoretisch als boolean, aber MQTT-Boundary k\u00F6nnte 0 durchschleusen).\n const powerOff = state.power === false || (state.power as unknown) === 0;\n if (powerOff && adapter.stateManager) {\n const prefix = adapter.stateManager.devicePrefix(device);\n groupStateHelpers.resetModeDropdowns(adapter, prefix, \"\").catch(() => undefined);\n }\n}\n\n/**\n * Internal \u2014 schedule a state-creation promise. Until adapter.statesReady,\n * promises accumulate in stateCreationQueue so onReady can await the full\n * initial batch. After ready, fire-and-forget.\n */\nfunction trackStateCreation(adapter: DeviceEventsAdapter, p: Promise<void>): void {\n if (!adapter.statesReady) {\n adapter.stateCreationQueue.push(p);\n } else {\n void p;\n }\n}\n\n/**\n * Phase 1 callback \u2014 LAN-Discovery has found a device. Creates info-channel\n * states (always-existing metadata) plus LAN-default control states (power,\n * brightness, colorRgb, colorTemperature).\n *\n * Does NOT create scenes/music/snapshots \u2014 those need Cloud data. If the\n * device later gets cloud capabilities, onCloudDataReady will fill them in\n * additively.\n *\n */\nexport function onLanDeviceReady<T extends DeviceEventsAdapter & connectionState.ConnectionStateAdapter>(\n adapter: T,\n device: GoveeDevice,\n _allDevices: GoveeDevice[],\n): void {\n if (!adapter.stateManager) {\n return;\n }\n const sm = adapter.stateManager;\n const p = (async () => {\n await sm.createInfoStates(device);\n await sm.createLanStates(device);\n })().catch(e => {\n adapter.log.error(`onLanDeviceReady failed for ${device.name}: ${errMessage(e)}`);\n });\n trackStateCreation(adapter, p);\n connectionState.updateConnectionState(adapter);\n}\n\n/**\n * Phase 2 callback \u2014 Cloud-Data is available for a device (from cache-merge,\n * loadFromCloud success, refreshSceneDataForDevice, snapshot save/delete, or\n * wizard-apply). Creates the full state-tree: info + LAN + Cloud states.\n *\n * createInfoStates and createLanStates are idempotent \u2014 calling them again\n * after a LAN-phase has run only updates `info.online`/`info.ip` values.\n *\n */\nexport function onCloudDataReady<T extends DeviceEventsAdapter & connectionState.ConnectionStateAdapter>(\n adapter: T,\n device: GoveeDevice,\n allDevices: GoveeDevice[],\n): void {\n if (!adapter.stateManager) {\n return;\n }\n const sm = adapter.stateManager;\n const localSnaps = adapter.localSnapshots?.getSnapshots(device.sku, device.deviceId);\n let memberDevices: GoveeDevice[] | undefined;\n if (device.sku === \"BaseGroup\" && device.groupMembers) {\n memberDevices = groupFanoutHandler.resolveGroupMembers(device, allDevices);\n }\n const cloudDefs = buildCloudStateDefs(device, adapter.log, localSnaps, memberDevices);\n const capN = Array.isArray(device.capabilities) ? device.capabilities.length : 0;\n adapter.log.debug(\n `buildCloudStateDefs for ${device.sku} ${device.deviceId}: ${capN} cap(s) in \u2192 ${cloudDefs.length} state def(s) out`,\n );\n const p = (async () => {\n await sm.createInfoStates(device);\n await sm.createLanStates(device);\n await sm.createCloudStates(device, cloudDefs);\n await sm.migrateLegacyDiagnostics(device);\n await sm.updateDeviceTier(device, getDeviceTier(device.sku));\n })().catch(e => {\n adapter.log.error(`onCloudDataReady failed for ${device.name}: ${errMessage(e)}`);\n });\n trackStateCreation(adapter, p);\n connectionState.updateConnectionState(adapter);\n if (adapter.statesReady) {\n adapter.reapStaleDevices?.().catch(() => undefined);\n }\n}\n\n/**\n * Phase 3 callback \u2014 Group members have been resolved (loadGroupMembers\n * success). Rebuilds the BaseGroup state-tree with the intersection of\n * member device capabilities.\n *\n * Member devices fire their own onLanDeviceReady / onCloudDataReady\n * independently \u2014 this callback only handles the group itself.\n *\n */\nexport function onGroupMembersReady<T extends DeviceEventsAdapter & connectionState.ConnectionStateAdapter>(\n adapter: T,\n group: GoveeDevice,\n allDevices: GoveeDevice[],\n): void {\n // BaseGroups go through the same Cloud-data path \u2014 group state-defs are\n // intersection of member capabilities, which is Cloud-derived.\n onCloudDataReady(adapter, group, allDevices);\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,+BAAoC;AAEpC,6BAA8B;AAG9B,mBAA+D;AAC/D,sBAAiC;AACjC,yBAAoC;AACpC,wBAAmC;
|
|
4
|
+
"sourcesContent": ["import { buildCloudStateDefs } from \"../capability-mapper\";\nimport type { DeviceManager } from \"../device-manager\";\nimport { getDeviceTier } from \"../device-registry\";\nimport type { LocalSnapshotStore } from \"../local-snapshots\";\nimport type { StateManager } from \"../state-manager\";\nimport { errMessage, type DeviceState, type GoveeDevice } from \"../types\";\nimport * as connectionState from \"./connection-state\";\nimport * as groupFanoutHandler from \"./group-fanout-handler\";\nimport * as groupStateHelpers from \"./group-state-helpers\";\n\n/**\n * Adapter surface required by the device-event helpers \u2014 covers the\n * onDeviceStateUpdate + onDeviceListChanged + refreshDeviceStates path.\n *\n * Composes ConnectionStateAdapter (for updateConnectionState) plus\n * GroupFanoutHandlerAdapter and GroupStateHelpersAdapter via duck-typing\n * \u2014 the calling adapter implements all three sets implicitly.\n */\nexport interface DeviceEventsAdapter {\n readonly log: ioBroker.Logger;\n readonly namespace: string;\n readonly language?: ioBroker.Languages;\n readonly deviceManager: DeviceManager | null;\n readonly stateManager: StateManager | null;\n readonly localSnapshots: LocalSnapshotStore | null;\n readonly statesReady: boolean;\n readonly stateCreationQueue: Promise<void>[];\n /** Re-fired into stateManager + connection-state + groupFanout-reachability. */\n setStateAsync(id: string, state: ioBroker.SettableState | ioBroker.StateValue): Promise<unknown>;\n /** Optional reapStaleDevices delegate \u2014 owned by main.ts because it touches diagnosticsLastRun. */\n reapStaleDevices?(): Promise<void>;\n}\n\n/**\n * Called by device-manager when a device's per-state values change. Mirrors\n * the updates into stateManager, refreshes the global connection-state\n * indicator, updates group reachability, and resets all mode dropdowns\n * when the device just powered off (the user shouldn't see \"playing\n * Aurora-A\" on a device that's off).\n *\n */\nexport function onDeviceStateUpdate<\n T extends DeviceEventsAdapter &\n connectionState.ConnectionStateAdapter &\n groupFanoutHandler.GroupFanoutHandlerAdapter &\n groupStateHelpers.GroupStateHelpersAdapter,\n>(adapter: T, device: GoveeDevice, state: Partial<DeviceState>): void {\n if (adapter.stateManager) {\n adapter.stateManager.updateDeviceState(device, state).catch(() => {});\n }\n connectionState.updateConnectionState(adapter);\n\n if (state.online !== undefined) {\n groupFanoutHandler.updateGroupReachability(adapter);\n }\n\n // Mirror power-off to mode-dropdown reset. Covers MQTT/LAN-initiated\n // power changes (Govee app or physical remote) so the UI stays honest:\n // a device that's off can't be \"playing Aurora-A\" anymore.\n // L11 \u2014 defensive auch 0 als false akzeptieren (Govee schickt Power\n // theoretisch als boolean, aber MQTT-Boundary k\u00F6nnte 0 durchschleusen).\n const powerOff = state.power === false || (state.power as unknown) === 0;\n if (powerOff && adapter.stateManager) {\n const prefix = adapter.stateManager.devicePrefix(device);\n groupStateHelpers.resetModeDropdowns(adapter, prefix, \"\").catch(() => undefined);\n }\n}\n\n/**\n * Internal \u2014 schedule a state-creation promise. Until adapter.statesReady,\n * promises accumulate in stateCreationQueue so onReady can await the full\n * initial batch. After ready, fire-and-forget.\n */\nfunction trackStateCreation(adapter: DeviceEventsAdapter, p: Promise<void>): void {\n if (!adapter.statesReady) {\n adapter.stateCreationQueue.push(p);\n } else {\n void p;\n }\n}\n\n/**\n * Phase 1 callback \u2014 LAN-Discovery has found a device. Creates info-channel\n * states (always-existing metadata) plus LAN-default control states (power,\n * brightness, colorRgb, colorTemperature).\n *\n * Does NOT create scenes/music/snapshots \u2014 those need Cloud data. If the\n * device later gets cloud capabilities, onCloudDataReady will fill them in\n * additively.\n *\n */\nexport function onLanDeviceReady<T extends DeviceEventsAdapter & connectionState.ConnectionStateAdapter>(\n adapter: T,\n device: GoveeDevice,\n _allDevices: GoveeDevice[],\n): void {\n if (!adapter.stateManager) {\n return;\n }\n const sm = adapter.stateManager;\n const p = (async () => {\n await sm.createInfoStates(device);\n await sm.createLanStates(device);\n })().catch(e => {\n adapter.log.error(`onLanDeviceReady failed for ${device.name}: ${errMessage(e)}`);\n });\n trackStateCreation(adapter, p);\n connectionState.updateConnectionState(adapter);\n}\n\n/**\n * Phase 2 callback \u2014 Cloud-Data is available for a device (from cache-merge,\n * loadFromCloud success, refreshSceneDataForDevice, snapshot save/delete, or\n * wizard-apply). Creates the full state-tree: info + LAN + Cloud states.\n *\n * createInfoStates and createLanStates are idempotent \u2014 calling them again\n * after a LAN-phase has run only updates `info.online`/`info.ip` values.\n *\n */\nexport function onCloudDataReady<T extends DeviceEventsAdapter & connectionState.ConnectionStateAdapter>(\n adapter: T,\n device: GoveeDevice,\n allDevices: GoveeDevice[],\n): void {\n if (!adapter.stateManager) {\n return;\n }\n const sm = adapter.stateManager;\n const localSnaps = adapter.localSnapshots?.getSnapshots(device.sku, device.deviceId);\n let memberDevices: GoveeDevice[] | undefined;\n if (device.sku === \"BaseGroup\" && device.groupMembers) {\n memberDevices = groupFanoutHandler.resolveGroupMembers(device, allDevices);\n }\n const cloudDefs = buildCloudStateDefs(device, adapter.log, localSnaps, memberDevices, adapter.language ?? \"en\");\n const capN = Array.isArray(device.capabilities) ? device.capabilities.length : 0;\n adapter.log.debug(\n `buildCloudStateDefs for ${device.sku} ${device.deviceId}: ${capN} cap(s) in \u2192 ${cloudDefs.length} state def(s) out`,\n );\n const p = (async () => {\n await sm.createInfoStates(device);\n await sm.createLanStates(device);\n await sm.createCloudStates(device, cloudDefs);\n await sm.migrateLegacyDiagnostics(device);\n await sm.updateDeviceTier(device, getDeviceTier(device.sku));\n })().catch(e => {\n adapter.log.error(`onCloudDataReady failed for ${device.name}: ${errMessage(e)}`);\n });\n trackStateCreation(adapter, p);\n connectionState.updateConnectionState(adapter);\n if (adapter.statesReady) {\n adapter.reapStaleDevices?.().catch(() => undefined);\n }\n}\n\n/**\n * Phase 3 callback \u2014 Group members have been resolved (loadGroupMembers\n * success). Rebuilds the BaseGroup state-tree with the intersection of\n * member device capabilities.\n *\n * Member devices fire their own onLanDeviceReady / onCloudDataReady\n * independently \u2014 this callback only handles the group itself.\n *\n */\nexport function onGroupMembersReady<T extends DeviceEventsAdapter & connectionState.ConnectionStateAdapter>(\n adapter: T,\n group: GoveeDevice,\n allDevices: GoveeDevice[],\n): void {\n // BaseGroups go through the same Cloud-data path \u2014 group state-defs are\n // intersection of member capabilities, which is Cloud-derived.\n onCloudDataReady(adapter, group, allDevices);\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,+BAAoC;AAEpC,6BAA8B;AAG9B,mBAA+D;AAC/D,sBAAiC;AACjC,yBAAoC;AACpC,wBAAmC;AAiC5B,SAAS,oBAKd,SAAY,QAAqB,OAAmC;AACpE,MAAI,QAAQ,cAAc;AACxB,YAAQ,aAAa,kBAAkB,QAAQ,KAAK,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACtE;AACA,kBAAgB,sBAAsB,OAAO;AAE7C,MAAI,MAAM,WAAW,QAAW;AAC9B,uBAAmB,wBAAwB,OAAO;AAAA,EACpD;AAOA,QAAM,WAAW,MAAM,UAAU,SAAU,MAAM,UAAsB;AACvE,MAAI,YAAY,QAAQ,cAAc;AACpC,UAAM,SAAS,QAAQ,aAAa,aAAa,MAAM;AACvD,sBAAkB,mBAAmB,SAAS,QAAQ,EAAE,EAAE,MAAM,MAAM,MAAS;AAAA,EACjF;AACF;AAOA,SAAS,mBAAmB,SAA8B,GAAwB;AAChF,MAAI,CAAC,QAAQ,aAAa;AACxB,YAAQ,mBAAmB,KAAK,CAAC;AAAA,EACnC,OAAO;AACL,SAAK;AAAA,EACP;AACF;AAYO,SAAS,iBACd,SACA,QACA,aACM;AACN,MAAI,CAAC,QAAQ,cAAc;AACzB;AAAA,EACF;AACA,QAAM,KAAK,QAAQ;AACnB,QAAM,KAAK,YAAY;AACrB,UAAM,GAAG,iBAAiB,MAAM;AAChC,UAAM,GAAG,gBAAgB,MAAM;AAAA,EACjC,GAAG,EAAE,MAAM,OAAK;AACd,YAAQ,IAAI,MAAM,+BAA+B,OAAO,IAAI,SAAK,yBAAW,CAAC,CAAC,EAAE;AAAA,EAClF,CAAC;AACD,qBAAmB,SAAS,CAAC;AAC7B,kBAAgB,sBAAsB,OAAO;AAC/C;AAWO,SAAS,iBACd,SACA,QACA,YACM;AA3HR;AA4HE,MAAI,CAAC,QAAQ,cAAc;AACzB;AAAA,EACF;AACA,QAAM,KAAK,QAAQ;AACnB,QAAM,cAAa,aAAQ,mBAAR,mBAAwB,aAAa,OAAO,KAAK,OAAO;AAC3E,MAAI;AACJ,MAAI,OAAO,QAAQ,eAAe,OAAO,cAAc;AACrD,oBAAgB,mBAAmB,oBAAoB,QAAQ,UAAU;AAAA,EAC3E;AACA,QAAM,gBAAY,8CAAoB,QAAQ,QAAQ,KAAK,YAAY,gBAAe,aAAQ,aAAR,YAAoB,IAAI;AAC9G,QAAM,OAAO,MAAM,QAAQ,OAAO,YAAY,IAAI,OAAO,aAAa,SAAS;AAC/E,UAAQ,IAAI;AAAA,IACV,2BAA2B,OAAO,GAAG,IAAI,OAAO,QAAQ,KAAK,IAAI,qBAAgB,UAAU,MAAM;AAAA,EACnG;AACA,QAAM,KAAK,YAAY;AACrB,UAAM,GAAG,iBAAiB,MAAM;AAChC,UAAM,GAAG,gBAAgB,MAAM;AAC/B,UAAM,GAAG,kBAAkB,QAAQ,SAAS;AAC5C,UAAM,GAAG,yBAAyB,MAAM;AACxC,UAAM,GAAG,iBAAiB,YAAQ,sCAAc,OAAO,GAAG,CAAC;AAAA,EAC7D,GAAG,EAAE,MAAM,OAAK;AACd,YAAQ,IAAI,MAAM,+BAA+B,OAAO,IAAI,SAAK,yBAAW,CAAC,CAAC,EAAE;AAAA,EAClF,CAAC;AACD,qBAAmB,SAAS,CAAC;AAC7B,kBAAgB,sBAAsB,OAAO;AAC7C,MAAI,QAAQ,aAAa;AACvB,kBAAQ,qBAAR,iCAA6B,MAAM,MAAM;AAAA,EAC3C;AACF;AAWO,SAAS,oBACd,SACA,OACA,YACM;AAGN,mBAAiB,SAAS,OAAO,UAAU;AAC7C;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/build/lib/i18n-states.js
CHANGED
|
@@ -21,6 +21,7 @@ __export(i18n_states_exports, {
|
|
|
21
21
|
STATE_DESCS: () => STATE_DESCS,
|
|
22
22
|
STATE_LABELS: () => STATE_LABELS,
|
|
23
23
|
STATE_NAMES: () => STATE_NAMES,
|
|
24
|
+
resolveLabel: () => resolveLabel,
|
|
24
25
|
tDesc: () => tDesc,
|
|
25
26
|
tLabel: () => tLabel,
|
|
26
27
|
tName: () => tName
|
|
@@ -710,11 +711,21 @@ const STATE_LABELS = {
|
|
|
710
711
|
function tLabel(key) {
|
|
711
712
|
return STATE_LABELS[key];
|
|
712
713
|
}
|
|
714
|
+
function resolveLabel(key, lang) {
|
|
715
|
+
var _a, _b;
|
|
716
|
+
const obj = STATE_LABELS[key];
|
|
717
|
+
if (typeof obj === "string") {
|
|
718
|
+
return obj;
|
|
719
|
+
}
|
|
720
|
+
const dict = obj;
|
|
721
|
+
return (_b = (_a = dict[lang]) != null ? _a : dict.en) != null ? _b : key;
|
|
722
|
+
}
|
|
713
723
|
// Annotate the CommonJS export names for ESM import in node:
|
|
714
724
|
0 && (module.exports = {
|
|
715
725
|
STATE_DESCS,
|
|
716
726
|
STATE_LABELS,
|
|
717
727
|
STATE_NAMES,
|
|
728
|
+
resolveLabel,
|
|
718
729
|
tDesc,
|
|
719
730
|
tLabel,
|
|
720
731
|
tName
|