iobroker.govee-smart 2.13.2 → 2.13.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/build/lib/state-manager.js +1 -2
- package/build/lib/state-manager.js.map +2 -2
- package/io-package.json +14 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -91,6 +91,10 @@ This adapter's MQTT authentication and BLE-over-LAN (ptReal) protocol implementa
|
|
|
91
91
|
Placeholder for the next version (at the beginning of the line):
|
|
92
92
|
### **WORK IN PROGRESS**
|
|
93
93
|
-->
|
|
94
|
+
### 2.13.3 (2026-05-23)
|
|
95
|
+
|
|
96
|
+
- Replaced deprecated setObject with extendObject (repochecker S5054)
|
|
97
|
+
|
|
94
98
|
### 2.13.2 (2026-05-23)
|
|
95
99
|
|
|
96
100
|
- User-modified state names are no longer overwritten on adapter restart
|
|
@@ -111,10 +115,6 @@ This adapter's MQTT authentication and BLE-over-LAN (ptReal) protocol implementa
|
|
|
111
115
|
|
|
112
116
|
- Verified against Node.js 24. Internal cleanup for stricter ioBroker repochecker compliance.
|
|
113
117
|
|
|
114
|
-
### 2.12.1 (2026-05-19)
|
|
115
|
-
|
|
116
|
-
- Code quality enforced with standard formatting.
|
|
117
|
-
|
|
118
118
|
Older entries are in [CHANGELOG_OLD.md](CHANGELOG_OLD.md).
|
|
119
119
|
|
|
120
120
|
## Support
|
|
@@ -156,8 +156,7 @@ class StateManager {
|
|
|
156
156
|
if (!buggy) {
|
|
157
157
|
return;
|
|
158
158
|
}
|
|
159
|
-
|
|
160
|
-
await this.adapter.setObject(id, existing).catch(() => void 0);
|
|
159
|
+
await this.adapter.extendObjectAsync(id, { common: { states: fresh } }).catch(() => void 0);
|
|
161
160
|
}
|
|
162
161
|
/**
|
|
163
162
|
* @param id Voller State-Pfad (`devices.X.info.Y`)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/state-manager.ts"],
|
|
4
|
-
"sourcesContent": ["import type * as utils from \"@iobroker/adapter-core\";\nimport { buildLanStateDefs, LAN_STATE_IDS, type StateDefinition } from \"./capability-mapper\";\nimport { GROUP_ICON, iconForGoveeType, shortenGoveeType } from \"./device-icons\";\nimport { resolveSegmentCount } from \"./device-manager\";\nimport { GOVEE_DEVICE_TYPE } from \"./govee-constants\";\nimport type { I18nKey } from \"./i18n\";\nimport { tDesc, tName } from \"./i18n\";\nimport { normalizeDeviceId, type DeviceState, type GoveeDevice } from \"./types\";\n\n/**\n * Sanitize a string for ioBroker object ID\n *\n * @param str Input string to sanitize\n */\nfunction sanitize(str: string): string {\n return str.replace(/[^a-zA-Z0-9_-]/g, \"_\").toLowerCase();\n}\n\n/**\n * Channels whose state-set is fully described by capability-driven stateDefs.\n * Only these get the stale-state cleanup pass \u2014 `info` is intentionally absent\n * because it mixes capability-driven states (diagnostics_export/result) with\n * adapter-managed ones (online, model, serial, ip, members) that come from\n * ensureState instead of stateDefs. Cleaning `info` by stateDef-set would\n * delete the adapter-managed ones.\n */\nconst MANAGED_CHANNELS = [\"control\", \"scenes\", \"music\", \"snapshots\", \"sensor\", \"events\"];\n/**\n * Display names used when the channel object is (re-)created. `info` is\n * listed here even though it's not in MANAGED_CHANNELS \u2014 capability-mapper\n * emits states with `channel: \"info\"`, and without this entry the create\n * path would overwrite the original \"Device Information\" name with the\n * literal \"info\".\n */\nconst CHANNEL_NAMES: Record<string, string> = {\n control: \"Controls\",\n scenes: \"Scenes\",\n music: \"Music\",\n snapshots: \"Snapshots\",\n sensor: \"Sensor Data\",\n events: \"Events\",\n info: \"Device Information\",\n diag: \"Diagnostics\",\n};\n\n/**\n * Synthetic capabilities written by the App-API poll and OpenAPI-MQTT\n * handler arrive as `(stateId, value)` pairs without a channel hint \u2014\n * `mapCloudStateValue` returns only what the Govee response carries.\n *\n * For lights the LAN/Cloud-state pipeline pre-populates `stateChannelMap`\n * via `createDeviceStates`, so `resolveStatePath` finds the channel.\n * For thermometer/appliance state IDs that are *only* delivered via the\n * App-API path (battery, temperature, humidity, CO\u2082, lackWater, \u2026) the map\n * is empty and the lookup would default to \"control\" \u2014 visibly wrong:\n * `info: control.battery has no existing object`.\n *\n * This routing table assigns those IDs to their semantic channel without\n * needing a separate `createDeviceStates` pass for sensor-only devices.\n * Keep IDs lowercase; resolveStatePath calls this on the raw stateId.\n */\n// Beide Lookup-Sets enthalten zwei Schreibweisen pro State-ID:\n// - \u201Eraw\"-Form (z.B. `temperature`) f\u00FCr instances die direkt so hei\u00DFen\n// - sanitizeId-Output (z.B. `sensor_temperature`) f\u00FCr camelCase-instances\n// die durch sanitizeId zu snake_case konvertiert wurden\n// `sanitizeId` in capability-mapper konvertiert camelCase \u2192 snake_case, also\n// werden \u201EsensorTemperature\" zu \u201Esensor_temperature\" und \u201ElackWaterEvent\"\n// zu \u201Elack_water_event\". Ohne diese Aliase fielen sanitize-Varianten auf den\n// safe-default \u201Econtrol\" zur\u00FCck und die States w\u00E4ren nicht erreichbar.\nconst SENSOR_STATE_IDS = new Set([\n // raw forms\n \"temperature\",\n \"humidity\",\n \"battery\",\n \"co2\",\n \"carbondioxide\",\n \"online\",\n // sanitizeId(instance) forms\n \"sensor_temperature\",\n \"sensor_humidity\",\n \"sensor_battery\",\n]);\nconst EVENT_STATE_IDS = new Set([\n // raw forms (no underscore separator)\n \"lackwater\",\n \"lackwaterevent\",\n \"icefull\",\n \"icefullevent\",\n \"bodyappeared\",\n \"dirtdetected\",\n // sanitizeId(instance) forms (camelCase \u2192 snake_case)\n \"lack_water\",\n \"lack_water_event\",\n \"ice_full\",\n \"ice_full_event\",\n \"body_appeared\",\n \"dirt_detected\",\n]);\n\n/**\n * Best-effort channel routing for state IDs that don't have a\n * stateChannelMap entry yet (e.g. App-API synthetic caps before the device\n * has gone through createDeviceStates). Empty input falls back to the safe\n * default \"control\".\n *\n * @param stateId The raw state ID (e.g. \"battery\", \"lackWater\")\n */\nfunction inferChannelFromStateId(stateId: string): string {\n const normalised = stateId.toLowerCase();\n if (SENSOR_STATE_IDS.has(normalised)) {\n return \"sensor\";\n }\n if (EVENT_STATE_IDS.has(normalised)) {\n return \"events\";\n }\n return \"control\";\n}\n\n/** Per-stateId metadata for synthetic states (App-API/OpenAPI-MQTT pipe). */\ninterface SyntheticStateMeta {\n type: \"boolean\" | \"number\";\n role: string;\n unit?: string;\n nameKey: I18nKey;\n}\nconst SYNTHETIC_STATE_META: Record<string, SyntheticStateMeta> = {\n temperature: { type: \"number\", role: \"value.temperature\", unit: \"\u00B0C\", nameKey: \"temperature\" },\n humidity: { type: \"number\", role: \"value.humidity\", unit: \"%\", nameKey: \"humidity\" },\n battery: { type: \"number\", role: \"value.battery\", unit: \"%\", nameKey: \"battery\" },\n co2: { type: \"number\", role: \"value.co2\", unit: \"ppm\", nameKey: \"co2\" },\n carbondioxide: { type: \"number\", role: \"value.co2\", unit: \"ppm\", nameKey: \"co2\" },\n online: { type: \"boolean\", role: \"indicator.connected\", nameKey: \"online\" },\n lackwater: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"lackOfWater\" },\n lackwaterevent: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"lackOfWater\" },\n icefull: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"iceBucketFull\" },\n icefullevent: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"iceBucketFull\" },\n bodyappeared: { type: \"boolean\", role: \"sensor.motion\", nameKey: \"bodyDetected\" },\n dirtdetected: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"dirtDetected\" },\n sensor_temperature: { type: \"number\", role: \"value.temperature\", unit: \"\u00B0C\", nameKey: \"temperature\" },\n sensor_humidity: { type: \"number\", role: \"value.humidity\", unit: \"%\", nameKey: \"humidity\" },\n sensor_battery: { type: \"number\", role: \"value.battery\", unit: \"%\", nameKey: \"battery\" },\n lack_water: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"lackOfWater\" },\n lack_water_event: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"lackOfWater\" },\n ice_full: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"iceBucketFull\" },\n ice_full_event: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"iceBucketFull\" },\n body_appeared: { type: \"boolean\", role: \"sensor.motion\", nameKey: \"bodyDetected\" },\n dirt_detected: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"dirtDetected\" },\n};\n\n/** Manages ioBroker state creation and updates for Govee devices */\nexport class StateManager {\n private readonly adapter: utils.AdapterInstance;\n /** Maps deviceKey (sku_deviceId) \u2192 current object prefix */\n private readonly prefixMap = new Map<string, string>();\n /** Maps \"prefix.stateId\" \u2192 channel name (populated during createDeviceStates) */\n private readonly stateChannelMap = new Map<string, string>();\n /**\n * Cache of state IDs already created via {@link ensureState} \u2014 skips the\n * `extendObjectAsync` round-trip on the hot path. Refreshed on\n * {@link removeDevice}/{@link forgetPrefix} so a re-pair doesn't reuse stale\n * cache entries.\n */\n private readonly ensuredStates = new Set<string>();\n\n /** @param adapter The ioBroker adapter instance */\n constructor(adapter: utils.AdapterInstance) {\n this.adapter = adapter;\n }\n\n /**\n * Idempotent state-delete: pr\u00FCft erst ob das Object existiert. Wenn nicht,\n * no-op (verhindert \u201Ehas no existing object\"-WARN den `delStateAsync`\n * sonst intern triggert wenn das Object weg ist).\n *\n * Pattern: Caller will ein State l\u00F6schen (z.B. weil der Zustand \u201Ecleaned\"\n * geworden ist), aber wei\u00DF nicht ob das Object jemals da war. delObject\n * + delState ist nur dann sicher wenn das Object EXISTIERT.\n *\n * Force-replace `common.states` on a persisted state object if any existing\n * value is non-string (= translation object from older releases).\n *\n * `extendObjectAsync` deep-merges and CANNOT replace an object-value with a\n * string. Only a full `setObjectAsync` replaces. Same fix-pattern as\n * hassemu v1.27.2 (URL-dropdown) and v1.28.4 (mode-dropdown). Admin\n * renders states-values as React children \u2014 a translation object triggers\n * React Error #31 \u2192 fatal \"Error in GUI\" on dropdown open (write:true) or\n * any render path (write:false like diag.tier).\n *\n * @param id Full state path.\n * @param fresh Plain-string `common.states` map to write.\n */\n private async repairCommonStatesIfBuggy(id: string, fresh: Record<string, string>): Promise<void> {\n const existing = await this.adapter.getObjectAsync(id).catch(() => null);\n if (!existing) {\n return;\n }\n const states = existing.common?.states;\n if (!states || typeof states !== \"object\") {\n return;\n }\n const buggy = Object.values(states as Record<string, unknown>).some(v => typeof v !== \"string\");\n if (!buggy) {\n return;\n }\n existing.common = { ...existing.common, states: fresh } as ioBroker.StateCommon;\n // setObject is promise-correct since js-controller 7.0.4; setObjectAsync deprecated.\n await this.adapter.setObject(id, existing).catch(() => undefined);\n }\n\n /**\n * @param id Voller State-Pfad (`devices.X.info.Y`)\n */\n private async safeDeleteState(id: string): Promise<void> {\n const obj = await this.adapter.getObjectAsync(id).catch(() => null);\n if (!obj) {\n return;\n }\n await this.adapter.delStateAsync(id).catch(() => undefined);\n await this.adapter.delObjectAsync(id).catch(() => undefined);\n }\n\n /**\n * Push the device's trust tier (verified/reported/seed/unknown) into\n * the user-visible `diag.tier` state. Called after every device-state\n * refresh so the value tracks any registry change between adapter\n * restarts (e.g. seed \u2192 verified once a tester confirms). No-op for\n * groups (BaseGroup has no per-device tier).\n *\n * @param device Govee device\n * @param tier Canonical tier label\n */\n async updateDeviceTier(device: GoveeDevice, tier: string): Promise<void> {\n if (device.sku === \"BaseGroup\") {\n return;\n }\n const prefix = this.devicePrefix(device);\n await this.adapter.setStateAsync(`${prefix}.diag.tier`, { val: tier, ack: true }).catch(() => undefined);\n }\n\n /**\n * Migrate v2.1.0 layout (`info.diagnostics_*`) to v2.1.1 layout\n * (`diag.*`). Deletes the three old objects + states; the new ones get\n * created by the regular `createDeviceStates` pass. Idempotent \u2014 calling\n * twice is a no-op once the old objects are gone.\n *\n * @param device Govee device\n */\n async migrateLegacyDiagnostics(device: GoveeDevice): Promise<void> {\n if (device.sku === \"BaseGroup\") {\n return;\n }\n const prefix = this.devicePrefix(device);\n for (const stale of [\"diagnostics_export\", \"diagnostics_result\", \"diagnostics_tier\"]) {\n await this.safeDeleteState(`${prefix}.info.${stale}`);\n this.stateChannelMap.delete(`${prefix}.${stale}`);\n }\n }\n\n /**\n * Resolve full state path for a given device prefix and state ID.\n * Routes the state to the correct channel (control, scenes, music, snapshots).\n *\n * @param prefix Device object ID prefix\n * @param stateId State ID suffix\n */\n resolveStatePath(prefix: string, stateId: string): string {\n const channel = this.stateChannelMap.get(`${prefix}.${stateId}`) ?? inferChannelFromStateId(stateId);\n return `${prefix}.${channel}.${stateId}`;\n }\n\n /**\n * Lazily create the channel + state object for synthetic state IDs the\n * App-API poll and OpenAPI-MQTT pipeline write. Cloud-capability defs\n * for sensor SKUs (e.g. H5179) are often empty in OpenAPI v2, so the\n * usual `createDeviceStates` pass would not declare battery / temperature\n * / events.* \u2014 without this helper the first write logs\n * `info: <id> has no existing object`.\n *\n * Idempotent: skips when the meta table doesn't know the stateId, and\n * `setObjectNotExistsAsync` is itself a no-op for existing objects.\n *\n * @param prefix Device prefix (e.g. \"devices.h5179_aabb\")\n * @param stateId State ID without channel (e.g. \"battery\")\n */\n async ensureSyntheticStateObject(prefix: string, stateId: string): Promise<void> {\n const meta = SYNTHETIC_STATE_META[stateId.toLowerCase()];\n if (!meta) {\n return;\n }\n const channel = inferChannelFromStateId(stateId);\n // Channel object first \u2014 sensors land in the new sensor/ subtree, events\n // in events/. Without an extendObject the channel parent stays missing\n // and Admin shows the state directly under the device root.\n await this.adapter\n .extendObjectAsync(\n `${prefix}.${channel}`,\n {\n type: \"channel\",\n common: { name: CHANNEL_NAMES[channel] ?? channel },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n )\n .catch(() => undefined);\n await this.adapter\n .extendObjectAsync(\n `${prefix}.${channel}.${stateId}`,\n {\n type: \"state\",\n common: {\n name: tName(meta.nameKey),\n type: meta.type,\n role: meta.role,\n read: true,\n write: false,\n ...(meta.unit !== undefined ? { unit: meta.unit } : {}),\n def: meta.type === \"boolean\" ? false : 0,\n },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n )\n .catch(() => undefined);\n this.stateChannelMap.set(`${prefix}.${stateId}`, channel);\n }\n\n /**\n * Phase 1 \u2014 Info-States. Always-existing device metadata: info.name,\n * info.online (per-device for lights, global for groups), info.model,\n * info.serial, info.ip, info.type. For groups: info.members + cleanup\n * of legacy device-level info states.\n *\n * Idempotent. Called from every phase callback (LAN-Phase + Cloud-Phase\n * + Group-Phase) \u2014 extendObjectAsync de-duplicates so the cost is small.\n *\n * Never deletes states from MANAGED_CHANNELS. The info channel is not in\n * MANAGED_CHANNELS, so cleanup never touches its content.\n *\n * @param device Govee device\n */\n async createInfoStates(device: GoveeDevice): Promise<void> {\n const key = this.deviceKey(device);\n const newPrefix = this.devicePrefix(device);\n const oldPrefix = this.prefixMap.get(key);\n\n // Migrate if prefix changed (e.g., old naming scheme)\n if (oldPrefix && oldPrefix !== newPrefix) {\n this.adapter.log.debug(`Migrating device ${device.sku}: ${oldPrefix} \u2192 ${newPrefix}`);\n await this.adapter.delObjectAsync(oldPrefix, { recursive: true });\n // Drop stale channel-map entries under the old prefix so they don't\n // shadow resolveStatePath lookups after the rename.\n const oldChannelKey = `${oldPrefix}.`;\n for (const mapKey of this.stateChannelMap.keys()) {\n if (mapKey.startsWith(oldChannelKey)) {\n this.stateChannelMap.delete(mapKey);\n }\n }\n }\n this.prefixMap.set(key, newPrefix);\n\n const prefix = newPrefix;\n const isGroup = device.sku === \"BaseGroup\";\n\n // Device object with online status indicator + type-aware icon.\n // Groups use the general groups.info.online state instead of per-group online.\n const onlineId = isGroup\n ? `${this.adapter.namespace}.groups.info.online`\n : `${this.adapter.namespace}.${prefix}.info.online`;\n const icon = isGroup ? GROUP_ICON : iconForGoveeType(device.type);\n await this.adapter.extendObjectAsync(\n prefix,\n {\n type: \"device\",\n common: {\n name: device.name,\n icon,\n statusStates: { onlineId },\n },\n native: {\n sku: device.sku,\n deviceId: device.deviceId,\n },\n },\n { preserve: { common: [\"name\"] } },\n );\n\n // Info channel \u2014 groups only get name (no individual online)\n await this.adapter.extendObjectAsync(\n `${prefix}.info`,\n {\n type: \"channel\",\n common: { name: tName(\"deviceInformation\") },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n\n await this.ensureState(`${prefix}.info.name`, \"Name\", \"string\", \"text\", false);\n await this.adapter.setStateAsync(`${prefix}.info.name`, {\n val: device.name,\n ack: true,\n });\n\n if (!isGroup) {\n await this.ensureState(\n `${prefix}.info.online`,\n \"Online\",\n \"boolean\",\n \"indicator.reachable\",\n false,\n undefined,\n false,\n );\n // info.online is written via syncInfoOnline (resolver-based, no\n // ts-rewrite-spam). The initial sync happens right after this method\n // returns \u2014 see syncInfoOnline. Direct write here was the source of\n // periodic false\u2192true bounces (captured 2026-05-13).\n await this.ensureState(`${prefix}.info.model`, \"Model\", \"string\", \"text\", false, undefined, \"\");\n await this.ensureState(`${prefix}.info.serial`, \"Serial Number\", \"string\", \"text\", false, undefined, \"\");\n await this.ensureState(`${prefix}.info.ip`, \"IP Address\", \"string\", \"info.ip\", false, undefined, \"\");\n // Device-type marker \u2014 short label like \"light\", \"thermometer\",\n // \"heater\" (Govee API type without the \"devices.types.\" prefix).\n // Lets scripts filter `*.info.type === \"light\"` without parsing.\n await this.ensureState(`${prefix}.info.type`, \"Device Type\", \"string\", \"text\", false, undefined, \"\");\n await this.adapter.setStateAsync(`${prefix}.info.model`, {\n val: device.sku,\n ack: true,\n });\n await this.adapter.setStateAsync(`${prefix}.info.serial`, {\n val: device.deviceId,\n ack: true,\n });\n await this.adapter.setStateAsync(`${prefix}.info.ip`, {\n val: device.lanIp ?? \"\",\n ack: true,\n });\n await this.adapter.setStateAsync(`${prefix}.info.type`, {\n val: shortenGoveeType(device.type),\n ack: true,\n });\n // Initial info.online sync \u2014 see syncInfoOnline for the resolver.\n // Subsequent updates come from the periodic sync timer in main.ts\n // and from direct calls in onDeviceStateUpdate when state.online\n // arrives via the existing event paths.\n await this.syncInfoOnline(device);\n } else {\n // Group members: comma-separated device prefix IDs\n const memberIds = (device.groupMembers ?? [])\n .map(m => {\n const shortId = normalizeDeviceId(m.deviceId).slice(-4);\n return sanitize(`${m.sku}_${shortId}`);\n })\n .join(\", \");\n await this.ensureState(`${prefix}.info.members`, \"Members\", \"string\", \"text\", false);\n await this.adapter.setStateAsync(`${prefix}.info.members`, {\n val: memberIds,\n ack: true,\n });\n\n // Legacy cleanup \u2014 groups never carry device-level info states or\n // diagnostics, but older installs had them. Drop any leftovers so the\n // tree reflects the current layout.\n for (const staleId of [\n \"online\",\n \"model\",\n \"serial\",\n \"ip\",\n \"diagnostics_export\",\n \"diagnostics_result\",\n \"diagnostics_tier\",\n ]) {\n await this.safeDeleteState(`${prefix}.info.${staleId}`);\n }\n // Groups never had a `diag` channel \u2014 drop any leftover from migrated installs.\n await this.adapter.delObjectAsync(`${prefix}.diag`, { recursive: true }).catch(() => {});\n }\n }\n\n /**\n * Phase 2 \u2014 LAN-States. Power, brightness, colorRgb, colorTemperature\n * (the LAN-default set defined by getDefaultLanStates). Pure additive:\n * never deletes from MANAGED_CHANNELS, no cleanup at end. Devices without\n * lanIp get no states (sensors/appliances/groups skip silently).\n *\n * @param device Govee device\n */\n async createLanStates(device: GoveeDevice): Promise<void> {\n const stateDefs = buildLanStateDefs(device, this.adapter.log);\n if (stateDefs.length === 0) {\n this.adapter.log.debug(\n `buildLanStateDefs for ${device.sku} ${device.deviceId}: 0 states (no LAN IP / not a light) \u2014 LAN phase skipped`,\n );\n return;\n }\n this.adapter.log.debug(\n `buildLanStateDefs for ${device.sku} ${device.deviceId}: ${stateDefs.length} state(s) \u2192 writing to LAN channel`,\n );\n const prefix = this.devicePrefix(device);\n await this.writeStateDefsToChannels(prefix, stateDefs, \"LAN\");\n }\n\n /**\n * Phase 3 \u2014 Cloud-States. Capability-derived states (scenes, music,\n * snapshots, sensor, events, segments, cloud-only control toggles) plus\n * synthetic local states (diagnostics, refresh_cloud, snapshot_local/...).\n * Runs cleanupCloudOwnedStates at the end to remove states no longer\n * present in stateDefs \u2014 but LAN-default ids in the control channel are\n * preserved via the LAN_STATE_IDS skip.\n *\n * @param device Govee device\n * @param stateDefs Cloud-owned state definitions from buildCloudStateDefs\n */\n async createCloudStates(device: GoveeDevice, stateDefs: StateDefinition[]): Promise<void> {\n const prefix = this.devicePrefix(device);\n\n // Drop _segment_ marker entries \u2014 segments have their own dedicated\n // createSegmentStates pass (per-segment color/brightness states).\n const nonSegmentDefs = stateDefs.filter(d => !d.id.startsWith(\"_segment_\"));\n await this.writeStateDefsToChannels(prefix, nonSegmentDefs, `Cloud ${device.sku}`);\n\n // Remove states no longer present in this Cloud-phase build. LAN_STATE_IDS\n // protects the LAN-default ids in the control channel \u2014 the LAN phase\n // owns those.\n await this.cleanupCloudOwnedStates(prefix, nonSegmentDefs);\n\n // Segment channel if device has segment caps\n if (stateDefs.some(d => d.id.startsWith(\"_segment_\"))) {\n await this.createSegmentStates(device);\n }\n }\n\n /**\n * Shared inner loop \u2014 group stateDefs by channel, create the channel\n * object once, then create each state. Called from createLanStates and\n * createCloudStates. Idempotent (extendObjectAsync).\n *\n * @param prefix Device prefix (e.g. \"devices.h6172_abcd\")\n * @param stateDefs State definitions to write\n * @param logTag Short tag for the per-phase debug log line\n */\n private async writeStateDefsToChannels(prefix: string, stateDefs: StateDefinition[], logTag: string): Promise<void> {\n const channelGroups = new Map<string, StateDefinition[]>();\n for (const def of stateDefs) {\n const channel = def.channel ?? \"control\";\n this.stateChannelMap.set(`${prefix}.${def.id}`, channel);\n if (!channelGroups.has(channel)) {\n channelGroups.set(channel, []);\n }\n channelGroups.get(channel)!.push(def);\n }\n\n this.adapter.log.debug(\n `createStates [${logTag}] ${prefix}: ${stateDefs.length} states in ${channelGroups.size} channel(s)`,\n );\n\n for (const [channel, defs] of channelGroups) {\n await this.adapter.extendObjectAsync(\n `${prefix}.${channel}`,\n {\n type: \"channel\",\n common: { name: CHANNEL_NAMES[channel] ?? channel },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n\n for (const def of defs) {\n const common: Partial<ioBroker.StateCommon> = {\n name: def.name as ioBroker.StringOrTranslated,\n type: def.type,\n role: def.role,\n read: true,\n write: def.write,\n };\n\n if (def.unit) {\n common.unit = def.unit;\n }\n if (def.min !== undefined) {\n common.min = def.min;\n }\n if (def.max !== undefined) {\n common.max = def.max;\n }\n if (def.states) {\n common.states = def.states;\n }\n if (def.def !== undefined) {\n common.def = def.def;\n }\n if (def.desc) {\n common.desc = def.desc as ioBroker.StringOrTranslated;\n }\n\n await this.adapter.extendObjectAsync(\n `${prefix}.${channel}.${def.id}`,\n {\n type: \"state\",\n common: common,\n native: {\n capabilityType: def.capabilityType,\n capabilityInstance: def.capabilityInstance,\n },\n },\n { preserve: { common: [\"name\"] } },\n );\n\n // Existing diag.tier datapoints from v2.6.0+ may carry translation-object\n // VALUES in common.states (the old buildCloudStateDefs wrote tLabel(...)\n // directly). extendObjectAsync deep-merges and cannot replace an\n // object-value with a string. Force-replace via setObjectAsync when\n // any persisted state value is non-string. Pattern proven in hassemu\n // v1.27.2 / v1.28.4. React Error #31 would otherwise fatal-crash Admin\n // on dropdown open (write:true states) or any view that renders the\n // value (write:false states like diag.tier).\n if (def.states) {\n await this.repairCommonStatesIfBuggy(`${prefix}.${channel}.${def.id}`, def.states);\n }\n\n // Initialize or validate state value\n if (def.def !== undefined) {\n const current = await this.adapter.getStateAsync(`${prefix}.${channel}.${def.id}`);\n if (!current || current.val === null || current.val === undefined) {\n // Set default value for new states\n await this.adapter.setStateAsync(`${prefix}.${channel}.${def.id}`, {\n val: def.def,\n ack: true,\n });\n } else if (def.states && !(String(current.val) in def.states)) {\n // Reset dropdown to default if current value is no longer valid\n this.adapter.log.debug(\n `Resetting stale dropdown: ${prefix}.${channel}.${def.id} = \"${String(current.val)}\" \u2192 \"${String(def.def)}\"`,\n );\n await this.adapter.setStateAsync(`${prefix}.${channel}.${def.id}`, {\n val: def.def,\n ack: true,\n });\n }\n }\n }\n }\n }\n\n /**\n * Create segment channel with per-segment color + brightness states.\n *\n * @param device Govee device\n */\n async createSegmentStates(device: GoveeDevice): Promise<void> {\n const prefix = this.devicePrefix(device);\n\n await this.adapter.extendObjectAsync(\n `${prefix}.segments`,\n {\n type: \"channel\",\n common: { name: tName(\"ledSegments\") },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n\n // Resolve the authoritative count: cache/MQTT-learned wins over Cloud\n // capabilities. A manual list can only grow the count (never shrink it)\n // so users editing manual_list can reveal hidden indices without losing\n // the already-learned total.\n const resolved = resolveSegmentCount(device);\n const manualMax =\n Array.isArray(device.manualSegments) && device.manualSegments.length > 0\n ? Math.max(...device.manualSegments) + 1\n : 0;\n const segmentCount = Math.max(resolved, manualMax);\n device.segmentCount = segmentCount;\n\n // Effective segment list \u2014 honor manual override if active (cut-strip support)\n const validIndices =\n device.manualMode && Array.isArray(device.manualSegments) && device.manualSegments.length > 0\n ? device.manualSegments.slice().sort((a, b) => a - b)\n : Array.from({ length: segmentCount }, (_, i) => i);\n const reportedCount = validIndices.length;\n\n await this.ensureState(`${prefix}.segments.count`, \"Segment Count\", \"number\", \"value\", false);\n await this.adapter.setStateAsync(`${prefix}.segments.count`, {\n val: reportedCount,\n ack: true,\n });\n\n // Manual-mode toggle and list \u2014 user-writable for cut-strip overrides\n await this.adapter.extendObjectAsync(\n `${prefix}.segments.manual_mode`,\n {\n type: \"state\",\n common: {\n name: tName(\"manualSegmentsActive\"),\n type: \"boolean\",\n role: \"switch\",\n read: true,\n write: true,\n def: false,\n desc: tDesc(\"manualSegmentsDesc\"),\n },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n await this.adapter.extendObjectAsync(\n `${prefix}.segments.manual_list`,\n {\n type: \"state\",\n common: {\n name: tName(\"manualSegmentList\"),\n type: \"string\",\n role: \"text\",\n read: true,\n write: true,\n def: \"\",\n desc: tDesc(\"manualListDesc\"),\n },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n\n // Sync manual_mode / manual_list states back from the runtime device\n // (restored from cache on startup, or updated by the wizard). Using\n // ack=true keeps this out of the user-change handler path.\n const manualModeVal = device.manualMode === true;\n const manualListVal =\n device.manualMode && Array.isArray(device.manualSegments) && device.manualSegments.length > 0\n ? device.manualSegments.join(\",\")\n : \"\";\n await this.adapter.setStateAsync(`${prefix}.segments.manual_mode`, {\n val: manualModeVal,\n ack: true,\n });\n await this.adapter.setStateAsync(`${prefix}.segments.manual_list`, {\n val: manualListVal,\n ack: true,\n });\n\n for (const i of validIndices) {\n await this.adapter.extendObjectAsync(\n `${prefix}.segments.${i}`,\n {\n type: \"channel\",\n common: { name: `Segment ${i}` },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n\n await this.adapter.extendObjectAsync(\n `${prefix}.segments.${i}.color`,\n {\n type: \"state\",\n common: {\n name: tName(\"color\"),\n type: \"string\",\n role: \"level.color.rgb\",\n read: true,\n write: true,\n },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n\n await this.adapter.extendObjectAsync(\n `${prefix}.segments.${i}.brightness`,\n {\n type: \"state\",\n common: {\n name: tName(\"brightness\"),\n type: \"number\",\n role: \"level.brightness\",\n read: true,\n write: true,\n min: 0,\n max: 100,\n unit: \"%\",\n },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n }\n\n // Comfort command state for batch segment control\n await this.adapter.extendObjectAsync(\n `${prefix}.segments.command`,\n {\n type: \"state\",\n common: {\n name: tName(\"batchSegmentCommand\"),\n type: \"string\",\n role: \"text\",\n read: false,\n write: true,\n desc: tDesc(\"batchCommandDesc\"),\n },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n\n // Remove segment channels that aren't in the valid list (supports gaps for manual mode)\n await this.cleanupExcessSegments(prefix, validIndices);\n }\n\n /**\n * Remove segment sub-channels that are not in the valid-indices list.\n * Supports gaps (e.g. manual list \"0-8,10-14\" \u2192 segment 9 channel gets removed).\n *\n * @param prefix Device prefix\n * @param validIndices Valid segment indices (all others will be deleted)\n */\n private async cleanupExcessSegments(prefix: string, validIndices: number[]): Promise<void> {\n const valid = new Set(validIndices);\n const segPrefix = `${this.adapter.namespace}.${prefix}.segments.`;\n const existing = await this.adapter.getObjectViewAsync(\"system\", \"channel\", {\n startkey: segPrefix,\n endkey: `${segPrefix}\\u9999`,\n });\n\n if (!existing?.rows) {\n return;\n }\n\n for (const row of existing.rows) {\n const localId = row.id.replace(`${this.adapter.namespace}.`, \"\");\n const segPart = localId.replace(`${prefix}.segments.`, \"\");\n const segIdx = parseInt(segPart, 10);\n if (!isNaN(segIdx) && !valid.has(segIdx)) {\n this.adapter.log.debug(`Removing excess segment: ${localId}`);\n // Drop orphan state values too \u2014 `delObjectAsync(recursive)` removes\n // the object tree but leaves the state-table values for color and\n // brightness behind. Without these explicit `delStateAsync` calls,\n // historical values would resurrect into a re-created segment after\n // a length change.\n await this.adapter.delStateAsync(`${localId}.color`).catch(() => undefined);\n await this.adapter.delStateAsync(`${localId}.brightness`).catch(() => undefined);\n await this.adapter.delObjectAsync(localId, { recursive: true });\n }\n }\n }\n\n /**\n * Update device state from any source (LAN, MQTT, Cloud).\n *\n * Writes are fire-and-forget and run in parallel \u2014 they're independent,\n * and the \"does this state exist?\" check that used to guard each write\n * was an extra object-read on the hot path (one MQTT push = one update\n * call). createDeviceStates has already run before any update lands,\n * so the states are guaranteed to exist; if one disappears (manual\n * deletion), the setStateAsync will reject and we swallow it.\n *\n * @param device Govee device\n * @param state Partial state update\n */\n async updateDeviceState(device: GoveeDevice, state: Partial<DeviceState>): Promise<void> {\n const prefix = this.devicePrefix(device);\n const writes: Promise<unknown>[] = [];\n\n const set = (id: string, val: ioBroker.StateValue): void => {\n writes.push(this.adapter.setStateAsync(id, { val, ack: true }).catch(() => undefined));\n };\n\n // info.online for Lights is owned by syncInfoOnline \u2014 direct writes here\n // would re-introduce the ts-rewrite-spam (every 2 min same value) and\n // the false-positive `true` writes from Cloud/MQTT paths. For Sensors/\n // Appliances the existing flow stays (applyOnlineCap \u2192 onDeviceUpdate \u2192\n // here \u2192 info.online).\n if (state.online !== undefined && device.type !== GOVEE_DEVICE_TYPE.LIGHT) {\n set(`${prefix}.info.online`, state.online);\n }\n if (state.power !== undefined) {\n set(`${prefix}.control.power`, state.power);\n }\n if (state.brightness !== undefined) {\n set(`${prefix}.control.brightness`, state.brightness);\n }\n if (state.colorRgb !== undefined) {\n set(`${prefix}.control.colorRgb`, state.colorRgb);\n }\n if (state.colorTemperature !== undefined) {\n set(`${prefix}.control.colorTemperature`, state.colorTemperature);\n }\n if (state.scene !== undefined) {\n set(`${prefix}.control.scene`, state.scene);\n }\n\n await Promise.all(writes);\n }\n\n /**\n * Create the general groups.info.online state (reflects Cloud connection).\n *\n * @param online Initial online value\n */\n async createGroupsOnlineState(online: boolean): Promise<void> {\n await this.adapter.extendObjectAsync(\n \"groups\",\n {\n type: \"folder\",\n common: { name: tName(\"groups\") },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n await this.adapter.extendObjectAsync(\n \"groups.info\",\n {\n type: \"channel\",\n common: { name: tName(\"groupsStatus\") },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n await this.ensureState(\"groups.info.online\", \"Cloud Online\", \"boolean\", \"indicator.reachable\", false);\n await this.adapter.setStateAsync(\"groups.info.online\", {\n val: online,\n ack: true,\n });\n }\n\n /**\n * Update the general groups online state.\n *\n * @param online Cloud connection status\n */\n async updateGroupsOnline(online: boolean): Promise<void> {\n await this.adapter.setStateAsync(\"groups.info.online\", { val: online, ack: true }).catch(() => undefined);\n }\n\n /**\n * Update info.membersUnreachable for a group.\n *\n * Pflegt den state IMMER (existing) und schreibt eine comma-separated\n * Liste der unreachable members oder einen leeren String wenn alle\n * online sind. Vorher haben wir bei \u201Ealle reachable\" das Object\n * gel\u00F6scht \u2014 das produzierte aber js-controller-WARN \u201EState\n * 'X.membersUnreachable' has no existing object\" alle ~2 Minuten,\n * weil parallele updateGroupReachability-Aufrufe (LAN+MQTT-Status-\n * Updates feuern fast gleichzeitig) eine race condition zwischen\n * setStateAsync (Object existiert) und safeDeleteState (Object weg)\n * triggern k\u00F6nnen. State immer existent zu halten umgeht das komplett.\n *\n * @param group BaseGroup device\n * @param memberDevices Resolved member devices\n */\n async updateGroupMembersUnreachable(group: GoveeDevice, memberDevices: GoveeDevice[]): Promise<void> {\n const prefix = this.devicePrefix(group);\n const stateId = `${prefix}.info.membersUnreachable`;\n\n const unreachable = memberDevices\n .filter(m => !m.state.online)\n .map(m => {\n const shortId = normalizeDeviceId(m.deviceId).slice(-4);\n return sanitize(`${m.sku}_${shortId}`);\n });\n\n await this.ensureState(stateId, \"Unreachable Members\", \"string\", \"text\", false);\n await this.adapter.setStateAsync(stateId, {\n val: unreachable.join(\", \"),\n ack: true,\n });\n }\n\n /**\n * Cleanup stale devices that no longer exist.\n *\n * Returns the prefixes of removed devices so callers (DeviceManager,\n * adapter-level maps) can drop their own entries for the same devices\n * and prevent unbounded map growth across the adapter's lifetime.\n *\n * @param currentDevices Current device list\n * @returns Prefixes of removed devices (e.g. \"devices.h61be_1d6f\")\n */\n async cleanupDevices(currentDevices: GoveeDevice[]): Promise<string[]> {\n const currentPrefixes = new Set(currentDevices.map(d => this.devicePrefix(d)));\n const removed: string[] = [];\n\n // Cleanup both devices/ and groups/ folders\n for (const folder of [\"devices\", \"groups\"]) {\n // getObjectViewAsync can throw on transient js-controller hiccups \\u2014\n // wrapping it lets cleanupDevices proceed with the other folder\n // instead of bailing out of the whole cleanup pass.\n let existingObjects;\n try {\n existingObjects = await this.adapter.getObjectViewAsync(\"system\", \"device\", {\n startkey: `${this.adapter.namespace}.${folder}.`,\n endkey: `${this.adapter.namespace}.${folder}.\\u9999`,\n });\n } catch (e) {\n this.adapter.log.debug(\n `cleanupDevices: getObjectViewAsync failed for ${folder}: ${e instanceof Error ? e.message : String(e)}`,\n );\n continue;\n }\n\n if (!existingObjects?.rows) {\n continue;\n }\n\n for (const row of existingObjects.rows) {\n const localId = row.id.replace(`${this.adapter.namespace}.`, \"\");\n if (!currentPrefixes.has(localId)) {\n this.adapter.log.debug(`Removing stale device: ${localId}`);\n // Recursive delObject removes the object tree but can leave\n // orphan state values in the state-table \u2014 clean those too so\n // historical values don't survive a device removal.\n const stateRows = await this.adapter\n .getObjectViewAsync(\"system\", \"state\", {\n startkey: `${row.id}.`,\n endkey: `${row.id}.\u9999`,\n })\n .catch(() => undefined);\n if (stateRows?.rows) {\n for (const stateRow of stateRows.rows) {\n const stateLocalId = stateRow.id.replace(`${this.adapter.namespace}.`, \"\");\n await this.adapter.delStateAsync(stateLocalId).catch(() => undefined);\n }\n }\n await this.adapter.delObjectAsync(localId, { recursive: true });\n this.forgetPrefix(localId);\n removed.push(localId);\n }\n }\n }\n return removed;\n }\n\n /**\n * Phase 3 cleanup \u2014 remove Cloud-owned states that are no longer in the\n * current Cloud-phase stateDefs. Respects LAN_STATE_IDS so the LAN phase's\n * states in the control channel never get touched.\n *\n * The Cloud-owned channels (scenes, music, snapshots, sensor, events) are\n * 100% Cloud territory \u2014 anything not in cloudStateDefs there is stale.\n * The control channel is mixed: LAN-default ids (power, brightness, \u2026)\n * belong to the LAN phase and are skipped via the LAN_STATE_IDS constant.\n *\n * Public for the v2.8.0 migration shot in main.ts.onReady \u2014 pure-LAN\n * devices need a one-time cleanupCloudOwnedStates(prefix, []) to wipe\n * scene/music/snapshot leftovers from prior versions.\n *\n * @param prefix Device prefix\n * @param cloudStateDefs Current Cloud-phase state definitions (non-segment)\n */\n async cleanupCloudOwnedStates(prefix: string, cloudStateDefs: StateDefinition[]): Promise<void> {\n // Build expected state set per channel\n const expectedByChannel = new Map<string, Set<string>>();\n for (const def of cloudStateDefs) {\n const channel = def.channel ?? \"control\";\n if (!expectedByChannel.has(channel)) {\n expectedByChannel.set(channel, new Set());\n }\n expectedByChannel.get(channel)!.add(def.id);\n }\n\n const devicePrefix = `${this.adapter.namespace}.${prefix}.`;\n const existing = await this.adapter.getObjectViewAsync(\"system\", \"state\", {\n startkey: devicePrefix,\n endkey: `${devicePrefix}\\u9999`,\n });\n if (!existing?.rows) {\n return;\n }\n\n const totalsPerChannel = new Map<string, { seen: number; deleted: number }>();\n for (const row of existing.rows) {\n const rest = row.id.replace(devicePrefix, \"\");\n const dotIdx = rest.indexOf(\".\");\n if (dotIdx < 0) {\n continue;\n }\n const channel = rest.slice(0, dotIdx);\n const stateId = rest.slice(dotIdx + 1);\n if (!MANAGED_CHANNELS.includes(channel)) {\n continue;\n }\n // In the control channel, LAN-default ids belong to the LAN phase \u2014\n // Cloud cleanup must not touch them. Other MANAGED_CHANNELS are\n // wholly Cloud territory.\n if (channel === \"control\" && LAN_STATE_IDS.has(stateId)) {\n continue;\n }\n const totals = totalsPerChannel.get(channel) ?? { seen: 0, deleted: 0 };\n totals.seen++;\n const validIds = expectedByChannel.get(channel) ?? new Set<string>();\n if (!validIds.has(stateId)) {\n const localId = row.id.replace(`${this.adapter.namespace}.`, \"\");\n this.adapter.log.debug(`Removing stale state: ${localId}`);\n await this.adapter.delObjectAsync(localId);\n await this.adapter.delStateAsync(localId).catch(() => {});\n totals.deleted++;\n }\n totalsPerChannel.set(channel, totals);\n }\n\n // Remove empty channel objects \u2014 no surviving states for this channel\n for (const [channel, totals] of totalsPerChannel) {\n if (totals.deleted > 0 && totals.deleted === totals.seen) {\n this.adapter.log.debug(`Removing empty channel: ${prefix}.${channel}`);\n await this.adapter.delObjectAsync(`${prefix}.${channel}`).catch(() => undefined);\n }\n }\n }\n\n /**\n * Get device object ID prefix \u2014 stable SKU + short device ID.\n * Groups (BaseGroup) go under groups/, devices under devices/.\n * Human-readable name is in common.name, not in the object ID.\n *\n * @param device Govee device\n */\n devicePrefix(device: GoveeDevice): string {\n const shortId = normalizeDeviceId(device.deviceId).slice(-4);\n const folder = device.sku === \"BaseGroup\" ? \"groups\" : \"devices\";\n return `${folder}.${sanitize(`${device.sku}_${shortId}`)}`;\n }\n\n /**\n * Drop prefix + stateChannel entries for a device that was removed.\n * Prevents the maps from growing indefinitely across adapter lifetime.\n *\n * @param prefix Device prefix that was removed\n */\n private forgetPrefix(prefix: string): void {\n for (const key of this.prefixMap.keys()) {\n if (this.prefixMap.get(key) === prefix) {\n this.prefixMap.delete(key);\n }\n }\n const stalePrefix = `${prefix}.`;\n for (const key of this.stateChannelMap.keys()) {\n if (key.startsWith(stalePrefix)) {\n this.stateChannelMap.delete(key);\n }\n }\n // Drop ensureState cache for this device too \u2014 a re-pair must run the\n // full extendObjectAsync path again so the new device's name/type get\n // applied (cache hit would skip the round-trip and keep stale common.*).\n const stalePrefixFull = `${this.adapter.namespace}.${prefix}.`;\n for (const id of this.ensuredStates) {\n if (id === `${this.adapter.namespace}.${prefix}` || id.startsWith(stalePrefixFull)) {\n this.ensuredStates.delete(id);\n }\n }\n }\n\n /**\n * Unique key for internal tracking (not used as object ID).\n *\n * @param device Govee device\n */\n private deviceKey(device: GoveeDevice): string {\n // Use normalizeDeviceId which is defensive against non-string input \u2014\n // cached data on disk could theoretically be tampered with.\n return `${device.sku}_${normalizeDeviceId(device.deviceId)}`;\n }\n\n /**\n * Create a state if it doesn't exist. Cached after the first successful\n * `extendObjectAsync` so hot-path callers (e.g. `updateGroupMembersUnreachable`\n * fires per status update) skip the Redis round-trip.\n *\n * @param id State object ID\n * @param name Display name\n * @param type Value type\n * @param role ioBroker role\n * @param write Whether state is writable\n * @param unit Optional unit of measurement\n * @param def Optional default value \u2014 set so the state has a sensible\n * initial value before the first writeback (avoids `null`\n * display in admin between create and first setState).\n */\n private async ensureState(\n id: string,\n name: string,\n type: ioBroker.CommonType,\n role: string,\n write: boolean,\n unit?: string,\n def?: ioBroker.StateValue,\n ): Promise<void> {\n if (this.ensuredStates.has(id)) {\n return;\n }\n const common: Partial<ioBroker.StateCommon> = {\n name,\n type,\n role,\n read: true,\n write,\n };\n if (unit) {\n common.unit = unit;\n }\n if (def !== undefined) {\n common.def = def;\n }\n await this.adapter.extendObjectAsync(\n id,\n {\n type: \"state\",\n common: common,\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n this.ensuredStates.add(id);\n }\n\n /**\n * Resolver-based info.online sync.\n *\n * For LED Lights (`type === \"devices.types.light\"`) the truth-source is\n * exclusively `device.lastLanReplyAt` \u2014 set when the device replies to a\n * LAN-Discovery multicast or LAN-Unicast devStatus. The 90 s freshness\n * window tolerates 3 missed 30 s scans against UDP packet loss but still\n * flips offline reasonably fast on a real outage.\n *\n * For Sensors/Appliances (no LAN protocol) the existing flow is unchanged:\n * `device.state.online` is set by `applyOnlineCap` from App-API / OpenAPI-\n * MQTT and read straight through here.\n *\n * Writes `info.online` only when the resolved value differs from the\n * current state \u2014 kills the 2-min ts-rewrite-spam captured 2026-05-13.\n *\n * For Lights: when the resolved online value changes, the internal\n * `device.state.online` is also updated so downstream consumers\n * (`updateGroupReachability`, `handleLanDiscovery` wasOffline check)\n * stay in sync. Returns `true` in that case so the caller can fire\n * the group-fanout reachability refresh.\n *\n * Skips BaseGroup devices \u2014 groups have a global `groups.info.online`\n * managed elsewhere.\n *\n * @param device Govee device to sync\n * @returns true if a Light's resolved online state changed (caller should\n * refresh group-reachability), false otherwise\n */\n async syncInfoOnline(device: GoveeDevice): Promise<boolean> {\n if (device.sku === \"BaseGroup\") {\n return false;\n }\n const prefix = this.devicePrefix(device);\n const stateId = `${prefix}.info.online`;\n\n let desiredOnline: boolean;\n if (device.type === GOVEE_DEVICE_TYPE.LIGHT) {\n desiredOnline = !!(device.lastLanReplyAt && Date.now() - device.lastLanReplyAt < 90_000);\n } else {\n desiredOnline = device.state.online === true;\n }\n\n const current = await this.adapter.getStateAsync(stateId).catch(() => null);\n if (!current || current.val !== desiredOnline) {\n await this.adapter.setStateAsync(stateId, { val: desiredOnline, ack: true }).catch(() => undefined);\n }\n\n let lightOnlineChanged = false;\n if (device.type === GOVEE_DEVICE_TYPE.LIGHT && device.state.online !== desiredOnline) {\n device.state.online = desiredOnline;\n lightOnlineChanged = true;\n }\n\n return lightOnlineChanged;\n }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import type * as utils from \"@iobroker/adapter-core\";\nimport { buildLanStateDefs, LAN_STATE_IDS, type StateDefinition } from \"./capability-mapper\";\nimport { GROUP_ICON, iconForGoveeType, shortenGoveeType } from \"./device-icons\";\nimport { resolveSegmentCount } from \"./device-manager\";\nimport { GOVEE_DEVICE_TYPE } from \"./govee-constants\";\nimport type { I18nKey } from \"./i18n\";\nimport { tDesc, tName } from \"./i18n\";\nimport { normalizeDeviceId, type DeviceState, type GoveeDevice } from \"./types\";\n\n/**\n * Sanitize a string for ioBroker object ID\n *\n * @param str Input string to sanitize\n */\nfunction sanitize(str: string): string {\n return str.replace(/[^a-zA-Z0-9_-]/g, \"_\").toLowerCase();\n}\n\n/**\n * Channels whose state-set is fully described by capability-driven stateDefs.\n * Only these get the stale-state cleanup pass \u2014 `info` is intentionally absent\n * because it mixes capability-driven states (diagnostics_export/result) with\n * adapter-managed ones (online, model, serial, ip, members) that come from\n * ensureState instead of stateDefs. Cleaning `info` by stateDef-set would\n * delete the adapter-managed ones.\n */\nconst MANAGED_CHANNELS = [\"control\", \"scenes\", \"music\", \"snapshots\", \"sensor\", \"events\"];\n/**\n * Display names used when the channel object is (re-)created. `info` is\n * listed here even though it's not in MANAGED_CHANNELS \u2014 capability-mapper\n * emits states with `channel: \"info\"`, and without this entry the create\n * path would overwrite the original \"Device Information\" name with the\n * literal \"info\".\n */\nconst CHANNEL_NAMES: Record<string, string> = {\n control: \"Controls\",\n scenes: \"Scenes\",\n music: \"Music\",\n snapshots: \"Snapshots\",\n sensor: \"Sensor Data\",\n events: \"Events\",\n info: \"Device Information\",\n diag: \"Diagnostics\",\n};\n\n/**\n * Synthetic capabilities written by the App-API poll and OpenAPI-MQTT\n * handler arrive as `(stateId, value)` pairs without a channel hint \u2014\n * `mapCloudStateValue` returns only what the Govee response carries.\n *\n * For lights the LAN/Cloud-state pipeline pre-populates `stateChannelMap`\n * via `createDeviceStates`, so `resolveStatePath` finds the channel.\n * For thermometer/appliance state IDs that are *only* delivered via the\n * App-API path (battery, temperature, humidity, CO\u2082, lackWater, \u2026) the map\n * is empty and the lookup would default to \"control\" \u2014 visibly wrong:\n * `info: control.battery has no existing object`.\n *\n * This routing table assigns those IDs to their semantic channel without\n * needing a separate `createDeviceStates` pass for sensor-only devices.\n * Keep IDs lowercase; resolveStatePath calls this on the raw stateId.\n */\n// Beide Lookup-Sets enthalten zwei Schreibweisen pro State-ID:\n// - \u201Eraw\"-Form (z.B. `temperature`) f\u00FCr instances die direkt so hei\u00DFen\n// - sanitizeId-Output (z.B. `sensor_temperature`) f\u00FCr camelCase-instances\n// die durch sanitizeId zu snake_case konvertiert wurden\n// `sanitizeId` in capability-mapper konvertiert camelCase \u2192 snake_case, also\n// werden \u201EsensorTemperature\" zu \u201Esensor_temperature\" und \u201ElackWaterEvent\"\n// zu \u201Elack_water_event\". Ohne diese Aliase fielen sanitize-Varianten auf den\n// safe-default \u201Econtrol\" zur\u00FCck und die States w\u00E4ren nicht erreichbar.\nconst SENSOR_STATE_IDS = new Set([\n // raw forms\n \"temperature\",\n \"humidity\",\n \"battery\",\n \"co2\",\n \"carbondioxide\",\n \"online\",\n // sanitizeId(instance) forms\n \"sensor_temperature\",\n \"sensor_humidity\",\n \"sensor_battery\",\n]);\nconst EVENT_STATE_IDS = new Set([\n // raw forms (no underscore separator)\n \"lackwater\",\n \"lackwaterevent\",\n \"icefull\",\n \"icefullevent\",\n \"bodyappeared\",\n \"dirtdetected\",\n // sanitizeId(instance) forms (camelCase \u2192 snake_case)\n \"lack_water\",\n \"lack_water_event\",\n \"ice_full\",\n \"ice_full_event\",\n \"body_appeared\",\n \"dirt_detected\",\n]);\n\n/**\n * Best-effort channel routing for state IDs that don't have a\n * stateChannelMap entry yet (e.g. App-API synthetic caps before the device\n * has gone through createDeviceStates). Empty input falls back to the safe\n * default \"control\".\n *\n * @param stateId The raw state ID (e.g. \"battery\", \"lackWater\")\n */\nfunction inferChannelFromStateId(stateId: string): string {\n const normalised = stateId.toLowerCase();\n if (SENSOR_STATE_IDS.has(normalised)) {\n return \"sensor\";\n }\n if (EVENT_STATE_IDS.has(normalised)) {\n return \"events\";\n }\n return \"control\";\n}\n\n/** Per-stateId metadata for synthetic states (App-API/OpenAPI-MQTT pipe). */\ninterface SyntheticStateMeta {\n type: \"boolean\" | \"number\";\n role: string;\n unit?: string;\n nameKey: I18nKey;\n}\nconst SYNTHETIC_STATE_META: Record<string, SyntheticStateMeta> = {\n temperature: { type: \"number\", role: \"value.temperature\", unit: \"\u00B0C\", nameKey: \"temperature\" },\n humidity: { type: \"number\", role: \"value.humidity\", unit: \"%\", nameKey: \"humidity\" },\n battery: { type: \"number\", role: \"value.battery\", unit: \"%\", nameKey: \"battery\" },\n co2: { type: \"number\", role: \"value.co2\", unit: \"ppm\", nameKey: \"co2\" },\n carbondioxide: { type: \"number\", role: \"value.co2\", unit: \"ppm\", nameKey: \"co2\" },\n online: { type: \"boolean\", role: \"indicator.connected\", nameKey: \"online\" },\n lackwater: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"lackOfWater\" },\n lackwaterevent: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"lackOfWater\" },\n icefull: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"iceBucketFull\" },\n icefullevent: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"iceBucketFull\" },\n bodyappeared: { type: \"boolean\", role: \"sensor.motion\", nameKey: \"bodyDetected\" },\n dirtdetected: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"dirtDetected\" },\n sensor_temperature: { type: \"number\", role: \"value.temperature\", unit: \"\u00B0C\", nameKey: \"temperature\" },\n sensor_humidity: { type: \"number\", role: \"value.humidity\", unit: \"%\", nameKey: \"humidity\" },\n sensor_battery: { type: \"number\", role: \"value.battery\", unit: \"%\", nameKey: \"battery\" },\n lack_water: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"lackOfWater\" },\n lack_water_event: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"lackOfWater\" },\n ice_full: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"iceBucketFull\" },\n ice_full_event: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"iceBucketFull\" },\n body_appeared: { type: \"boolean\", role: \"sensor.motion\", nameKey: \"bodyDetected\" },\n dirt_detected: { type: \"boolean\", role: \"indicator.maintenance\", nameKey: \"dirtDetected\" },\n};\n\n/** Manages ioBroker state creation and updates for Govee devices */\nexport class StateManager {\n private readonly adapter: utils.AdapterInstance;\n /** Maps deviceKey (sku_deviceId) \u2192 current object prefix */\n private readonly prefixMap = new Map<string, string>();\n /** Maps \"prefix.stateId\" \u2192 channel name (populated during createDeviceStates) */\n private readonly stateChannelMap = new Map<string, string>();\n /**\n * Cache of state IDs already created via {@link ensureState} \u2014 skips the\n * `extendObjectAsync` round-trip on the hot path. Refreshed on\n * {@link removeDevice}/{@link forgetPrefix} so a re-pair doesn't reuse stale\n * cache entries.\n */\n private readonly ensuredStates = new Set<string>();\n\n /** @param adapter The ioBroker adapter instance */\n constructor(adapter: utils.AdapterInstance) {\n this.adapter = adapter;\n }\n\n /**\n * Idempotent state-delete: pr\u00FCft erst ob das Object existiert. Wenn nicht,\n * no-op (verhindert \u201Ehas no existing object\"-WARN den `delStateAsync`\n * sonst intern triggert wenn das Object weg ist).\n *\n * Pattern: Caller will ein State l\u00F6schen (z.B. weil der Zustand \u201Ecleaned\"\n * geworden ist), aber wei\u00DF nicht ob das Object jemals da war. delObject\n * + delState ist nur dann sicher wenn das Object EXISTIERT.\n *\n * Force-replace `common.states` on a persisted state object if any existing\n * value is non-string (= translation object from older releases).\n *\n * `extendObjectAsync` deep-merges and CANNOT replace an object-value with a\n * string. Only a full `setObjectAsync` replaces. Same fix-pattern as\n * hassemu v1.27.2 (URL-dropdown) and v1.28.4 (mode-dropdown). Admin\n * renders states-values as React children \u2014 a translation object triggers\n * React Error #31 \u2192 fatal \"Error in GUI\" on dropdown open (write:true) or\n * any render path (write:false like diag.tier).\n *\n * @param id Full state path.\n * @param fresh Plain-string `common.states` map to write.\n */\n private async repairCommonStatesIfBuggy(id: string, fresh: Record<string, string>): Promise<void> {\n const existing = await this.adapter.getObjectAsync(id).catch(() => null);\n if (!existing) {\n return;\n }\n const states = existing.common?.states;\n if (!states || typeof states !== \"object\") {\n return;\n }\n const buggy = Object.values(states as Record<string, unknown>).some(v => typeof v !== \"string\");\n if (!buggy) {\n return;\n }\n await this.adapter.extendObjectAsync(id, { common: { states: fresh } }).catch(() => undefined);\n }\n\n /**\n * @param id Voller State-Pfad (`devices.X.info.Y`)\n */\n private async safeDeleteState(id: string): Promise<void> {\n const obj = await this.adapter.getObjectAsync(id).catch(() => null);\n if (!obj) {\n return;\n }\n await this.adapter.delStateAsync(id).catch(() => undefined);\n await this.adapter.delObjectAsync(id).catch(() => undefined);\n }\n\n /**\n * Push the device's trust tier (verified/reported/seed/unknown) into\n * the user-visible `diag.tier` state. Called after every device-state\n * refresh so the value tracks any registry change between adapter\n * restarts (e.g. seed \u2192 verified once a tester confirms). No-op for\n * groups (BaseGroup has no per-device tier).\n *\n * @param device Govee device\n * @param tier Canonical tier label\n */\n async updateDeviceTier(device: GoveeDevice, tier: string): Promise<void> {\n if (device.sku === \"BaseGroup\") {\n return;\n }\n const prefix = this.devicePrefix(device);\n await this.adapter.setStateAsync(`${prefix}.diag.tier`, { val: tier, ack: true }).catch(() => undefined);\n }\n\n /**\n * Migrate v2.1.0 layout (`info.diagnostics_*`) to v2.1.1 layout\n * (`diag.*`). Deletes the three old objects + states; the new ones get\n * created by the regular `createDeviceStates` pass. Idempotent \u2014 calling\n * twice is a no-op once the old objects are gone.\n *\n * @param device Govee device\n */\n async migrateLegacyDiagnostics(device: GoveeDevice): Promise<void> {\n if (device.sku === \"BaseGroup\") {\n return;\n }\n const prefix = this.devicePrefix(device);\n for (const stale of [\"diagnostics_export\", \"diagnostics_result\", \"diagnostics_tier\"]) {\n await this.safeDeleteState(`${prefix}.info.${stale}`);\n this.stateChannelMap.delete(`${prefix}.${stale}`);\n }\n }\n\n /**\n * Resolve full state path for a given device prefix and state ID.\n * Routes the state to the correct channel (control, scenes, music, snapshots).\n *\n * @param prefix Device object ID prefix\n * @param stateId State ID suffix\n */\n resolveStatePath(prefix: string, stateId: string): string {\n const channel = this.stateChannelMap.get(`${prefix}.${stateId}`) ?? inferChannelFromStateId(stateId);\n return `${prefix}.${channel}.${stateId}`;\n }\n\n /**\n * Lazily create the channel + state object for synthetic state IDs the\n * App-API poll and OpenAPI-MQTT pipeline write. Cloud-capability defs\n * for sensor SKUs (e.g. H5179) are often empty in OpenAPI v2, so the\n * usual `createDeviceStates` pass would not declare battery / temperature\n * / events.* \u2014 without this helper the first write logs\n * `info: <id> has no existing object`.\n *\n * Idempotent: skips when the meta table doesn't know the stateId, and\n * `setObjectNotExistsAsync` is itself a no-op for existing objects.\n *\n * @param prefix Device prefix (e.g. \"devices.h5179_aabb\")\n * @param stateId State ID without channel (e.g. \"battery\")\n */\n async ensureSyntheticStateObject(prefix: string, stateId: string): Promise<void> {\n const meta = SYNTHETIC_STATE_META[stateId.toLowerCase()];\n if (!meta) {\n return;\n }\n const channel = inferChannelFromStateId(stateId);\n // Channel object first \u2014 sensors land in the new sensor/ subtree, events\n // in events/. Without an extendObject the channel parent stays missing\n // and Admin shows the state directly under the device root.\n await this.adapter\n .extendObjectAsync(\n `${prefix}.${channel}`,\n {\n type: \"channel\",\n common: { name: CHANNEL_NAMES[channel] ?? channel },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n )\n .catch(() => undefined);\n await this.adapter\n .extendObjectAsync(\n `${prefix}.${channel}.${stateId}`,\n {\n type: \"state\",\n common: {\n name: tName(meta.nameKey),\n type: meta.type,\n role: meta.role,\n read: true,\n write: false,\n ...(meta.unit !== undefined ? { unit: meta.unit } : {}),\n def: meta.type === \"boolean\" ? false : 0,\n },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n )\n .catch(() => undefined);\n this.stateChannelMap.set(`${prefix}.${stateId}`, channel);\n }\n\n /**\n * Phase 1 \u2014 Info-States. Always-existing device metadata: info.name,\n * info.online (per-device for lights, global for groups), info.model,\n * info.serial, info.ip, info.type. For groups: info.members + cleanup\n * of legacy device-level info states.\n *\n * Idempotent. Called from every phase callback (LAN-Phase + Cloud-Phase\n * + Group-Phase) \u2014 extendObjectAsync de-duplicates so the cost is small.\n *\n * Never deletes states from MANAGED_CHANNELS. The info channel is not in\n * MANAGED_CHANNELS, so cleanup never touches its content.\n *\n * @param device Govee device\n */\n async createInfoStates(device: GoveeDevice): Promise<void> {\n const key = this.deviceKey(device);\n const newPrefix = this.devicePrefix(device);\n const oldPrefix = this.prefixMap.get(key);\n\n // Migrate if prefix changed (e.g., old naming scheme)\n if (oldPrefix && oldPrefix !== newPrefix) {\n this.adapter.log.debug(`Migrating device ${device.sku}: ${oldPrefix} \u2192 ${newPrefix}`);\n await this.adapter.delObjectAsync(oldPrefix, { recursive: true });\n // Drop stale channel-map entries under the old prefix so they don't\n // shadow resolveStatePath lookups after the rename.\n const oldChannelKey = `${oldPrefix}.`;\n for (const mapKey of this.stateChannelMap.keys()) {\n if (mapKey.startsWith(oldChannelKey)) {\n this.stateChannelMap.delete(mapKey);\n }\n }\n }\n this.prefixMap.set(key, newPrefix);\n\n const prefix = newPrefix;\n const isGroup = device.sku === \"BaseGroup\";\n\n // Device object with online status indicator + type-aware icon.\n // Groups use the general groups.info.online state instead of per-group online.\n const onlineId = isGroup\n ? `${this.adapter.namespace}.groups.info.online`\n : `${this.adapter.namespace}.${prefix}.info.online`;\n const icon = isGroup ? GROUP_ICON : iconForGoveeType(device.type);\n await this.adapter.extendObjectAsync(\n prefix,\n {\n type: \"device\",\n common: {\n name: device.name,\n icon,\n statusStates: { onlineId },\n },\n native: {\n sku: device.sku,\n deviceId: device.deviceId,\n },\n },\n { preserve: { common: [\"name\"] } },\n );\n\n // Info channel \u2014 groups only get name (no individual online)\n await this.adapter.extendObjectAsync(\n `${prefix}.info`,\n {\n type: \"channel\",\n common: { name: tName(\"deviceInformation\") },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n\n await this.ensureState(`${prefix}.info.name`, \"Name\", \"string\", \"text\", false);\n await this.adapter.setStateAsync(`${prefix}.info.name`, {\n val: device.name,\n ack: true,\n });\n\n if (!isGroup) {\n await this.ensureState(\n `${prefix}.info.online`,\n \"Online\",\n \"boolean\",\n \"indicator.reachable\",\n false,\n undefined,\n false,\n );\n // info.online is written via syncInfoOnline (resolver-based, no\n // ts-rewrite-spam). The initial sync happens right after this method\n // returns \u2014 see syncInfoOnline. Direct write here was the source of\n // periodic false\u2192true bounces (captured 2026-05-13).\n await this.ensureState(`${prefix}.info.model`, \"Model\", \"string\", \"text\", false, undefined, \"\");\n await this.ensureState(`${prefix}.info.serial`, \"Serial Number\", \"string\", \"text\", false, undefined, \"\");\n await this.ensureState(`${prefix}.info.ip`, \"IP Address\", \"string\", \"info.ip\", false, undefined, \"\");\n // Device-type marker \u2014 short label like \"light\", \"thermometer\",\n // \"heater\" (Govee API type without the \"devices.types.\" prefix).\n // Lets scripts filter `*.info.type === \"light\"` without parsing.\n await this.ensureState(`${prefix}.info.type`, \"Device Type\", \"string\", \"text\", false, undefined, \"\");\n await this.adapter.setStateAsync(`${prefix}.info.model`, {\n val: device.sku,\n ack: true,\n });\n await this.adapter.setStateAsync(`${prefix}.info.serial`, {\n val: device.deviceId,\n ack: true,\n });\n await this.adapter.setStateAsync(`${prefix}.info.ip`, {\n val: device.lanIp ?? \"\",\n ack: true,\n });\n await this.adapter.setStateAsync(`${prefix}.info.type`, {\n val: shortenGoveeType(device.type),\n ack: true,\n });\n // Initial info.online sync \u2014 see syncInfoOnline for the resolver.\n // Subsequent updates come from the periodic sync timer in main.ts\n // and from direct calls in onDeviceStateUpdate when state.online\n // arrives via the existing event paths.\n await this.syncInfoOnline(device);\n } else {\n // Group members: comma-separated device prefix IDs\n const memberIds = (device.groupMembers ?? [])\n .map(m => {\n const shortId = normalizeDeviceId(m.deviceId).slice(-4);\n return sanitize(`${m.sku}_${shortId}`);\n })\n .join(\", \");\n await this.ensureState(`${prefix}.info.members`, \"Members\", \"string\", \"text\", false);\n await this.adapter.setStateAsync(`${prefix}.info.members`, {\n val: memberIds,\n ack: true,\n });\n\n // Legacy cleanup \u2014 groups never carry device-level info states or\n // diagnostics, but older installs had them. Drop any leftovers so the\n // tree reflects the current layout.\n for (const staleId of [\n \"online\",\n \"model\",\n \"serial\",\n \"ip\",\n \"diagnostics_export\",\n \"diagnostics_result\",\n \"diagnostics_tier\",\n ]) {\n await this.safeDeleteState(`${prefix}.info.${staleId}`);\n }\n // Groups never had a `diag` channel \u2014 drop any leftover from migrated installs.\n await this.adapter.delObjectAsync(`${prefix}.diag`, { recursive: true }).catch(() => {});\n }\n }\n\n /**\n * Phase 2 \u2014 LAN-States. Power, brightness, colorRgb, colorTemperature\n * (the LAN-default set defined by getDefaultLanStates). Pure additive:\n * never deletes from MANAGED_CHANNELS, no cleanup at end. Devices without\n * lanIp get no states (sensors/appliances/groups skip silently).\n *\n * @param device Govee device\n */\n async createLanStates(device: GoveeDevice): Promise<void> {\n const stateDefs = buildLanStateDefs(device, this.adapter.log);\n if (stateDefs.length === 0) {\n this.adapter.log.debug(\n `buildLanStateDefs for ${device.sku} ${device.deviceId}: 0 states (no LAN IP / not a light) \u2014 LAN phase skipped`,\n );\n return;\n }\n this.adapter.log.debug(\n `buildLanStateDefs for ${device.sku} ${device.deviceId}: ${stateDefs.length} state(s) \u2192 writing to LAN channel`,\n );\n const prefix = this.devicePrefix(device);\n await this.writeStateDefsToChannels(prefix, stateDefs, \"LAN\");\n }\n\n /**\n * Phase 3 \u2014 Cloud-States. Capability-derived states (scenes, music,\n * snapshots, sensor, events, segments, cloud-only control toggles) plus\n * synthetic local states (diagnostics, refresh_cloud, snapshot_local/...).\n * Runs cleanupCloudOwnedStates at the end to remove states no longer\n * present in stateDefs \u2014 but LAN-default ids in the control channel are\n * preserved via the LAN_STATE_IDS skip.\n *\n * @param device Govee device\n * @param stateDefs Cloud-owned state definitions from buildCloudStateDefs\n */\n async createCloudStates(device: GoveeDevice, stateDefs: StateDefinition[]): Promise<void> {\n const prefix = this.devicePrefix(device);\n\n // Drop _segment_ marker entries \u2014 segments have their own dedicated\n // createSegmentStates pass (per-segment color/brightness states).\n const nonSegmentDefs = stateDefs.filter(d => !d.id.startsWith(\"_segment_\"));\n await this.writeStateDefsToChannels(prefix, nonSegmentDefs, `Cloud ${device.sku}`);\n\n // Remove states no longer present in this Cloud-phase build. LAN_STATE_IDS\n // protects the LAN-default ids in the control channel \u2014 the LAN phase\n // owns those.\n await this.cleanupCloudOwnedStates(prefix, nonSegmentDefs);\n\n // Segment channel if device has segment caps\n if (stateDefs.some(d => d.id.startsWith(\"_segment_\"))) {\n await this.createSegmentStates(device);\n }\n }\n\n /**\n * Shared inner loop \u2014 group stateDefs by channel, create the channel\n * object once, then create each state. Called from createLanStates and\n * createCloudStates. Idempotent (extendObjectAsync).\n *\n * @param prefix Device prefix (e.g. \"devices.h6172_abcd\")\n * @param stateDefs State definitions to write\n * @param logTag Short tag for the per-phase debug log line\n */\n private async writeStateDefsToChannels(prefix: string, stateDefs: StateDefinition[], logTag: string): Promise<void> {\n const channelGroups = new Map<string, StateDefinition[]>();\n for (const def of stateDefs) {\n const channel = def.channel ?? \"control\";\n this.stateChannelMap.set(`${prefix}.${def.id}`, channel);\n if (!channelGroups.has(channel)) {\n channelGroups.set(channel, []);\n }\n channelGroups.get(channel)!.push(def);\n }\n\n this.adapter.log.debug(\n `createStates [${logTag}] ${prefix}: ${stateDefs.length} states in ${channelGroups.size} channel(s)`,\n );\n\n for (const [channel, defs] of channelGroups) {\n await this.adapter.extendObjectAsync(\n `${prefix}.${channel}`,\n {\n type: \"channel\",\n common: { name: CHANNEL_NAMES[channel] ?? channel },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n\n for (const def of defs) {\n const common: Partial<ioBroker.StateCommon> = {\n name: def.name as ioBroker.StringOrTranslated,\n type: def.type,\n role: def.role,\n read: true,\n write: def.write,\n };\n\n if (def.unit) {\n common.unit = def.unit;\n }\n if (def.min !== undefined) {\n common.min = def.min;\n }\n if (def.max !== undefined) {\n common.max = def.max;\n }\n if (def.states) {\n common.states = def.states;\n }\n if (def.def !== undefined) {\n common.def = def.def;\n }\n if (def.desc) {\n common.desc = def.desc as ioBroker.StringOrTranslated;\n }\n\n await this.adapter.extendObjectAsync(\n `${prefix}.${channel}.${def.id}`,\n {\n type: \"state\",\n common: common,\n native: {\n capabilityType: def.capabilityType,\n capabilityInstance: def.capabilityInstance,\n },\n },\n { preserve: { common: [\"name\"] } },\n );\n\n // Existing diag.tier datapoints from v2.6.0+ may carry translation-object\n // VALUES in common.states (the old buildCloudStateDefs wrote tLabel(...)\n // directly). extendObjectAsync deep-merges and cannot replace an\n // object-value with a string. Force-replace via setObjectAsync when\n // any persisted state value is non-string. Pattern proven in hassemu\n // v1.27.2 / v1.28.4. React Error #31 would otherwise fatal-crash Admin\n // on dropdown open (write:true states) or any view that renders the\n // value (write:false states like diag.tier).\n if (def.states) {\n await this.repairCommonStatesIfBuggy(`${prefix}.${channel}.${def.id}`, def.states);\n }\n\n // Initialize or validate state value\n if (def.def !== undefined) {\n const current = await this.adapter.getStateAsync(`${prefix}.${channel}.${def.id}`);\n if (!current || current.val === null || current.val === undefined) {\n // Set default value for new states\n await this.adapter.setStateAsync(`${prefix}.${channel}.${def.id}`, {\n val: def.def,\n ack: true,\n });\n } else if (def.states && !(String(current.val) in def.states)) {\n // Reset dropdown to default if current value is no longer valid\n this.adapter.log.debug(\n `Resetting stale dropdown: ${prefix}.${channel}.${def.id} = \"${String(current.val)}\" \u2192 \"${String(def.def)}\"`,\n );\n await this.adapter.setStateAsync(`${prefix}.${channel}.${def.id}`, {\n val: def.def,\n ack: true,\n });\n }\n }\n }\n }\n }\n\n /**\n * Create segment channel with per-segment color + brightness states.\n *\n * @param device Govee device\n */\n async createSegmentStates(device: GoveeDevice): Promise<void> {\n const prefix = this.devicePrefix(device);\n\n await this.adapter.extendObjectAsync(\n `${prefix}.segments`,\n {\n type: \"channel\",\n common: { name: tName(\"ledSegments\") },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n\n // Resolve the authoritative count: cache/MQTT-learned wins over Cloud\n // capabilities. A manual list can only grow the count (never shrink it)\n // so users editing manual_list can reveal hidden indices without losing\n // the already-learned total.\n const resolved = resolveSegmentCount(device);\n const manualMax =\n Array.isArray(device.manualSegments) && device.manualSegments.length > 0\n ? Math.max(...device.manualSegments) + 1\n : 0;\n const segmentCount = Math.max(resolved, manualMax);\n device.segmentCount = segmentCount;\n\n // Effective segment list \u2014 honor manual override if active (cut-strip support)\n const validIndices =\n device.manualMode && Array.isArray(device.manualSegments) && device.manualSegments.length > 0\n ? device.manualSegments.slice().sort((a, b) => a - b)\n : Array.from({ length: segmentCount }, (_, i) => i);\n const reportedCount = validIndices.length;\n\n await this.ensureState(`${prefix}.segments.count`, \"Segment Count\", \"number\", \"value\", false);\n await this.adapter.setStateAsync(`${prefix}.segments.count`, {\n val: reportedCount,\n ack: true,\n });\n\n // Manual-mode toggle and list \u2014 user-writable for cut-strip overrides\n await this.adapter.extendObjectAsync(\n `${prefix}.segments.manual_mode`,\n {\n type: \"state\",\n common: {\n name: tName(\"manualSegmentsActive\"),\n type: \"boolean\",\n role: \"switch\",\n read: true,\n write: true,\n def: false,\n desc: tDesc(\"manualSegmentsDesc\"),\n },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n await this.adapter.extendObjectAsync(\n `${prefix}.segments.manual_list`,\n {\n type: \"state\",\n common: {\n name: tName(\"manualSegmentList\"),\n type: \"string\",\n role: \"text\",\n read: true,\n write: true,\n def: \"\",\n desc: tDesc(\"manualListDesc\"),\n },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n\n // Sync manual_mode / manual_list states back from the runtime device\n // (restored from cache on startup, or updated by the wizard). Using\n // ack=true keeps this out of the user-change handler path.\n const manualModeVal = device.manualMode === true;\n const manualListVal =\n device.manualMode && Array.isArray(device.manualSegments) && device.manualSegments.length > 0\n ? device.manualSegments.join(\",\")\n : \"\";\n await this.adapter.setStateAsync(`${prefix}.segments.manual_mode`, {\n val: manualModeVal,\n ack: true,\n });\n await this.adapter.setStateAsync(`${prefix}.segments.manual_list`, {\n val: manualListVal,\n ack: true,\n });\n\n for (const i of validIndices) {\n await this.adapter.extendObjectAsync(\n `${prefix}.segments.${i}`,\n {\n type: \"channel\",\n common: { name: `Segment ${i}` },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n\n await this.adapter.extendObjectAsync(\n `${prefix}.segments.${i}.color`,\n {\n type: \"state\",\n common: {\n name: tName(\"color\"),\n type: \"string\",\n role: \"level.color.rgb\",\n read: true,\n write: true,\n },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n\n await this.adapter.extendObjectAsync(\n `${prefix}.segments.${i}.brightness`,\n {\n type: \"state\",\n common: {\n name: tName(\"brightness\"),\n type: \"number\",\n role: \"level.brightness\",\n read: true,\n write: true,\n min: 0,\n max: 100,\n unit: \"%\",\n },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n }\n\n // Comfort command state for batch segment control\n await this.adapter.extendObjectAsync(\n `${prefix}.segments.command`,\n {\n type: \"state\",\n common: {\n name: tName(\"batchSegmentCommand\"),\n type: \"string\",\n role: \"text\",\n read: false,\n write: true,\n desc: tDesc(\"batchCommandDesc\"),\n },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n\n // Remove segment channels that aren't in the valid list (supports gaps for manual mode)\n await this.cleanupExcessSegments(prefix, validIndices);\n }\n\n /**\n * Remove segment sub-channels that are not in the valid-indices list.\n * Supports gaps (e.g. manual list \"0-8,10-14\" \u2192 segment 9 channel gets removed).\n *\n * @param prefix Device prefix\n * @param validIndices Valid segment indices (all others will be deleted)\n */\n private async cleanupExcessSegments(prefix: string, validIndices: number[]): Promise<void> {\n const valid = new Set(validIndices);\n const segPrefix = `${this.adapter.namespace}.${prefix}.segments.`;\n const existing = await this.adapter.getObjectViewAsync(\"system\", \"channel\", {\n startkey: segPrefix,\n endkey: `${segPrefix}\\u9999`,\n });\n\n if (!existing?.rows) {\n return;\n }\n\n for (const row of existing.rows) {\n const localId = row.id.replace(`${this.adapter.namespace}.`, \"\");\n const segPart = localId.replace(`${prefix}.segments.`, \"\");\n const segIdx = parseInt(segPart, 10);\n if (!isNaN(segIdx) && !valid.has(segIdx)) {\n this.adapter.log.debug(`Removing excess segment: ${localId}`);\n // Drop orphan state values too \u2014 `delObjectAsync(recursive)` removes\n // the object tree but leaves the state-table values for color and\n // brightness behind. Without these explicit `delStateAsync` calls,\n // historical values would resurrect into a re-created segment after\n // a length change.\n await this.adapter.delStateAsync(`${localId}.color`).catch(() => undefined);\n await this.adapter.delStateAsync(`${localId}.brightness`).catch(() => undefined);\n await this.adapter.delObjectAsync(localId, { recursive: true });\n }\n }\n }\n\n /**\n * Update device state from any source (LAN, MQTT, Cloud).\n *\n * Writes are fire-and-forget and run in parallel \u2014 they're independent,\n * and the \"does this state exist?\" check that used to guard each write\n * was an extra object-read on the hot path (one MQTT push = one update\n * call). createDeviceStates has already run before any update lands,\n * so the states are guaranteed to exist; if one disappears (manual\n * deletion), the setStateAsync will reject and we swallow it.\n *\n * @param device Govee device\n * @param state Partial state update\n */\n async updateDeviceState(device: GoveeDevice, state: Partial<DeviceState>): Promise<void> {\n const prefix = this.devicePrefix(device);\n const writes: Promise<unknown>[] = [];\n\n const set = (id: string, val: ioBroker.StateValue): void => {\n writes.push(this.adapter.setStateAsync(id, { val, ack: true }).catch(() => undefined));\n };\n\n // info.online for Lights is owned by syncInfoOnline \u2014 direct writes here\n // would re-introduce the ts-rewrite-spam (every 2 min same value) and\n // the false-positive `true` writes from Cloud/MQTT paths. For Sensors/\n // Appliances the existing flow stays (applyOnlineCap \u2192 onDeviceUpdate \u2192\n // here \u2192 info.online).\n if (state.online !== undefined && device.type !== GOVEE_DEVICE_TYPE.LIGHT) {\n set(`${prefix}.info.online`, state.online);\n }\n if (state.power !== undefined) {\n set(`${prefix}.control.power`, state.power);\n }\n if (state.brightness !== undefined) {\n set(`${prefix}.control.brightness`, state.brightness);\n }\n if (state.colorRgb !== undefined) {\n set(`${prefix}.control.colorRgb`, state.colorRgb);\n }\n if (state.colorTemperature !== undefined) {\n set(`${prefix}.control.colorTemperature`, state.colorTemperature);\n }\n if (state.scene !== undefined) {\n set(`${prefix}.control.scene`, state.scene);\n }\n\n await Promise.all(writes);\n }\n\n /**\n * Create the general groups.info.online state (reflects Cloud connection).\n *\n * @param online Initial online value\n */\n async createGroupsOnlineState(online: boolean): Promise<void> {\n await this.adapter.extendObjectAsync(\n \"groups\",\n {\n type: \"folder\",\n common: { name: tName(\"groups\") },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n await this.adapter.extendObjectAsync(\n \"groups.info\",\n {\n type: \"channel\",\n common: { name: tName(\"groupsStatus\") },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n await this.ensureState(\"groups.info.online\", \"Cloud Online\", \"boolean\", \"indicator.reachable\", false);\n await this.adapter.setStateAsync(\"groups.info.online\", {\n val: online,\n ack: true,\n });\n }\n\n /**\n * Update the general groups online state.\n *\n * @param online Cloud connection status\n */\n async updateGroupsOnline(online: boolean): Promise<void> {\n await this.adapter.setStateAsync(\"groups.info.online\", { val: online, ack: true }).catch(() => undefined);\n }\n\n /**\n * Update info.membersUnreachable for a group.\n *\n * Pflegt den state IMMER (existing) und schreibt eine comma-separated\n * Liste der unreachable members oder einen leeren String wenn alle\n * online sind. Vorher haben wir bei \u201Ealle reachable\" das Object\n * gel\u00F6scht \u2014 das produzierte aber js-controller-WARN \u201EState\n * 'X.membersUnreachable' has no existing object\" alle ~2 Minuten,\n * weil parallele updateGroupReachability-Aufrufe (LAN+MQTT-Status-\n * Updates feuern fast gleichzeitig) eine race condition zwischen\n * setStateAsync (Object existiert) und safeDeleteState (Object weg)\n * triggern k\u00F6nnen. State immer existent zu halten umgeht das komplett.\n *\n * @param group BaseGroup device\n * @param memberDevices Resolved member devices\n */\n async updateGroupMembersUnreachable(group: GoveeDevice, memberDevices: GoveeDevice[]): Promise<void> {\n const prefix = this.devicePrefix(group);\n const stateId = `${prefix}.info.membersUnreachable`;\n\n const unreachable = memberDevices\n .filter(m => !m.state.online)\n .map(m => {\n const shortId = normalizeDeviceId(m.deviceId).slice(-4);\n return sanitize(`${m.sku}_${shortId}`);\n });\n\n await this.ensureState(stateId, \"Unreachable Members\", \"string\", \"text\", false);\n await this.adapter.setStateAsync(stateId, {\n val: unreachable.join(\", \"),\n ack: true,\n });\n }\n\n /**\n * Cleanup stale devices that no longer exist.\n *\n * Returns the prefixes of removed devices so callers (DeviceManager,\n * adapter-level maps) can drop their own entries for the same devices\n * and prevent unbounded map growth across the adapter's lifetime.\n *\n * @param currentDevices Current device list\n * @returns Prefixes of removed devices (e.g. \"devices.h61be_1d6f\")\n */\n async cleanupDevices(currentDevices: GoveeDevice[]): Promise<string[]> {\n const currentPrefixes = new Set(currentDevices.map(d => this.devicePrefix(d)));\n const removed: string[] = [];\n\n // Cleanup both devices/ and groups/ folders\n for (const folder of [\"devices\", \"groups\"]) {\n // getObjectViewAsync can throw on transient js-controller hiccups \\u2014\n // wrapping it lets cleanupDevices proceed with the other folder\n // instead of bailing out of the whole cleanup pass.\n let existingObjects;\n try {\n existingObjects = await this.adapter.getObjectViewAsync(\"system\", \"device\", {\n startkey: `${this.adapter.namespace}.${folder}.`,\n endkey: `${this.adapter.namespace}.${folder}.\\u9999`,\n });\n } catch (e) {\n this.adapter.log.debug(\n `cleanupDevices: getObjectViewAsync failed for ${folder}: ${e instanceof Error ? e.message : String(e)}`,\n );\n continue;\n }\n\n if (!existingObjects?.rows) {\n continue;\n }\n\n for (const row of existingObjects.rows) {\n const localId = row.id.replace(`${this.adapter.namespace}.`, \"\");\n if (!currentPrefixes.has(localId)) {\n this.adapter.log.debug(`Removing stale device: ${localId}`);\n // Recursive delObject removes the object tree but can leave\n // orphan state values in the state-table \u2014 clean those too so\n // historical values don't survive a device removal.\n const stateRows = await this.adapter\n .getObjectViewAsync(\"system\", \"state\", {\n startkey: `${row.id}.`,\n endkey: `${row.id}.\u9999`,\n })\n .catch(() => undefined);\n if (stateRows?.rows) {\n for (const stateRow of stateRows.rows) {\n const stateLocalId = stateRow.id.replace(`${this.adapter.namespace}.`, \"\");\n await this.adapter.delStateAsync(stateLocalId).catch(() => undefined);\n }\n }\n await this.adapter.delObjectAsync(localId, { recursive: true });\n this.forgetPrefix(localId);\n removed.push(localId);\n }\n }\n }\n return removed;\n }\n\n /**\n * Phase 3 cleanup \u2014 remove Cloud-owned states that are no longer in the\n * current Cloud-phase stateDefs. Respects LAN_STATE_IDS so the LAN phase's\n * states in the control channel never get touched.\n *\n * The Cloud-owned channels (scenes, music, snapshots, sensor, events) are\n * 100% Cloud territory \u2014 anything not in cloudStateDefs there is stale.\n * The control channel is mixed: LAN-default ids (power, brightness, \u2026)\n * belong to the LAN phase and are skipped via the LAN_STATE_IDS constant.\n *\n * Public for the v2.8.0 migration shot in main.ts.onReady \u2014 pure-LAN\n * devices need a one-time cleanupCloudOwnedStates(prefix, []) to wipe\n * scene/music/snapshot leftovers from prior versions.\n *\n * @param prefix Device prefix\n * @param cloudStateDefs Current Cloud-phase state definitions (non-segment)\n */\n async cleanupCloudOwnedStates(prefix: string, cloudStateDefs: StateDefinition[]): Promise<void> {\n // Build expected state set per channel\n const expectedByChannel = new Map<string, Set<string>>();\n for (const def of cloudStateDefs) {\n const channel = def.channel ?? \"control\";\n if (!expectedByChannel.has(channel)) {\n expectedByChannel.set(channel, new Set());\n }\n expectedByChannel.get(channel)!.add(def.id);\n }\n\n const devicePrefix = `${this.adapter.namespace}.${prefix}.`;\n const existing = await this.adapter.getObjectViewAsync(\"system\", \"state\", {\n startkey: devicePrefix,\n endkey: `${devicePrefix}\\u9999`,\n });\n if (!existing?.rows) {\n return;\n }\n\n const totalsPerChannel = new Map<string, { seen: number; deleted: number }>();\n for (const row of existing.rows) {\n const rest = row.id.replace(devicePrefix, \"\");\n const dotIdx = rest.indexOf(\".\");\n if (dotIdx < 0) {\n continue;\n }\n const channel = rest.slice(0, dotIdx);\n const stateId = rest.slice(dotIdx + 1);\n if (!MANAGED_CHANNELS.includes(channel)) {\n continue;\n }\n // In the control channel, LAN-default ids belong to the LAN phase \u2014\n // Cloud cleanup must not touch them. Other MANAGED_CHANNELS are\n // wholly Cloud territory.\n if (channel === \"control\" && LAN_STATE_IDS.has(stateId)) {\n continue;\n }\n const totals = totalsPerChannel.get(channel) ?? { seen: 0, deleted: 0 };\n totals.seen++;\n const validIds = expectedByChannel.get(channel) ?? new Set<string>();\n if (!validIds.has(stateId)) {\n const localId = row.id.replace(`${this.adapter.namespace}.`, \"\");\n this.adapter.log.debug(`Removing stale state: ${localId}`);\n await this.adapter.delObjectAsync(localId);\n await this.adapter.delStateAsync(localId).catch(() => {});\n totals.deleted++;\n }\n totalsPerChannel.set(channel, totals);\n }\n\n // Remove empty channel objects \u2014 no surviving states for this channel\n for (const [channel, totals] of totalsPerChannel) {\n if (totals.deleted > 0 && totals.deleted === totals.seen) {\n this.adapter.log.debug(`Removing empty channel: ${prefix}.${channel}`);\n await this.adapter.delObjectAsync(`${prefix}.${channel}`).catch(() => undefined);\n }\n }\n }\n\n /**\n * Get device object ID prefix \u2014 stable SKU + short device ID.\n * Groups (BaseGroup) go under groups/, devices under devices/.\n * Human-readable name is in common.name, not in the object ID.\n *\n * @param device Govee device\n */\n devicePrefix(device: GoveeDevice): string {\n const shortId = normalizeDeviceId(device.deviceId).slice(-4);\n const folder = device.sku === \"BaseGroup\" ? \"groups\" : \"devices\";\n return `${folder}.${sanitize(`${device.sku}_${shortId}`)}`;\n }\n\n /**\n * Drop prefix + stateChannel entries for a device that was removed.\n * Prevents the maps from growing indefinitely across adapter lifetime.\n *\n * @param prefix Device prefix that was removed\n */\n private forgetPrefix(prefix: string): void {\n for (const key of this.prefixMap.keys()) {\n if (this.prefixMap.get(key) === prefix) {\n this.prefixMap.delete(key);\n }\n }\n const stalePrefix = `${prefix}.`;\n for (const key of this.stateChannelMap.keys()) {\n if (key.startsWith(stalePrefix)) {\n this.stateChannelMap.delete(key);\n }\n }\n // Drop ensureState cache for this device too \u2014 a re-pair must run the\n // full extendObjectAsync path again so the new device's name/type get\n // applied (cache hit would skip the round-trip and keep stale common.*).\n const stalePrefixFull = `${this.adapter.namespace}.${prefix}.`;\n for (const id of this.ensuredStates) {\n if (id === `${this.adapter.namespace}.${prefix}` || id.startsWith(stalePrefixFull)) {\n this.ensuredStates.delete(id);\n }\n }\n }\n\n /**\n * Unique key for internal tracking (not used as object ID).\n *\n * @param device Govee device\n */\n private deviceKey(device: GoveeDevice): string {\n // Use normalizeDeviceId which is defensive against non-string input \u2014\n // cached data on disk could theoretically be tampered with.\n return `${device.sku}_${normalizeDeviceId(device.deviceId)}`;\n }\n\n /**\n * Create a state if it doesn't exist. Cached after the first successful\n * `extendObjectAsync` so hot-path callers (e.g. `updateGroupMembersUnreachable`\n * fires per status update) skip the Redis round-trip.\n *\n * @param id State object ID\n * @param name Display name\n * @param type Value type\n * @param role ioBroker role\n * @param write Whether state is writable\n * @param unit Optional unit of measurement\n * @param def Optional default value \u2014 set so the state has a sensible\n * initial value before the first writeback (avoids `null`\n * display in admin between create and first setState).\n */\n private async ensureState(\n id: string,\n name: string,\n type: ioBroker.CommonType,\n role: string,\n write: boolean,\n unit?: string,\n def?: ioBroker.StateValue,\n ): Promise<void> {\n if (this.ensuredStates.has(id)) {\n return;\n }\n const common: Partial<ioBroker.StateCommon> = {\n name,\n type,\n role,\n read: true,\n write,\n };\n if (unit) {\n common.unit = unit;\n }\n if (def !== undefined) {\n common.def = def;\n }\n await this.adapter.extendObjectAsync(\n id,\n {\n type: \"state\",\n common: common,\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n this.ensuredStates.add(id);\n }\n\n /**\n * Resolver-based info.online sync.\n *\n * For LED Lights (`type === \"devices.types.light\"`) the truth-source is\n * exclusively `device.lastLanReplyAt` \u2014 set when the device replies to a\n * LAN-Discovery multicast or LAN-Unicast devStatus. The 90 s freshness\n * window tolerates 3 missed 30 s scans against UDP packet loss but still\n * flips offline reasonably fast on a real outage.\n *\n * For Sensors/Appliances (no LAN protocol) the existing flow is unchanged:\n * `device.state.online` is set by `applyOnlineCap` from App-API / OpenAPI-\n * MQTT and read straight through here.\n *\n * Writes `info.online` only when the resolved value differs from the\n * current state \u2014 kills the 2-min ts-rewrite-spam captured 2026-05-13.\n *\n * For Lights: when the resolved online value changes, the internal\n * `device.state.online` is also updated so downstream consumers\n * (`updateGroupReachability`, `handleLanDiscovery` wasOffline check)\n * stay in sync. Returns `true` in that case so the caller can fire\n * the group-fanout reachability refresh.\n *\n * Skips BaseGroup devices \u2014 groups have a global `groups.info.online`\n * managed elsewhere.\n *\n * @param device Govee device to sync\n * @returns true if a Light's resolved online state changed (caller should\n * refresh group-reachability), false otherwise\n */\n async syncInfoOnline(device: GoveeDevice): Promise<boolean> {\n if (device.sku === \"BaseGroup\") {\n return false;\n }\n const prefix = this.devicePrefix(device);\n const stateId = `${prefix}.info.online`;\n\n let desiredOnline: boolean;\n if (device.type === GOVEE_DEVICE_TYPE.LIGHT) {\n desiredOnline = !!(device.lastLanReplyAt && Date.now() - device.lastLanReplyAt < 90_000);\n } else {\n desiredOnline = device.state.online === true;\n }\n\n const current = await this.adapter.getStateAsync(stateId).catch(() => null);\n if (!current || current.val !== desiredOnline) {\n await this.adapter.setStateAsync(stateId, { val: desiredOnline, ack: true }).catch(() => undefined);\n }\n\n let lightOnlineChanged = false;\n if (device.type === GOVEE_DEVICE_TYPE.LIGHT && device.state.online !== desiredOnline) {\n device.state.online = desiredOnline;\n lightOnlineChanged = true;\n }\n\n return lightOnlineChanged;\n }\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,+BAAuE;AACvE,0BAA+D;AAC/D,4BAAoC;AACpC,6BAAkC;AAElC,kBAA6B;AAC7B,mBAAsE;AAOtE,SAAS,SAAS,KAAqB;AACrC,SAAO,IAAI,QAAQ,mBAAmB,GAAG,EAAE,YAAY;AACzD;AAUA,MAAM,mBAAmB,CAAC,WAAW,UAAU,SAAS,aAAa,UAAU,QAAQ;AAQvF,MAAM,gBAAwC;AAAA,EAC5C,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,MAAM;AACR;AA0BA,MAAM,mBAAmB,oBAAI,IAAI;AAAA;AAAA,EAE/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AACD,MAAM,kBAAkB,oBAAI,IAAI;AAAA;AAAA,EAE9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAUD,SAAS,wBAAwB,SAAyB;AACxD,QAAM,aAAa,QAAQ,YAAY;AACvC,MAAI,iBAAiB,IAAI,UAAU,GAAG;AACpC,WAAO;AAAA,EACT;AACA,MAAI,gBAAgB,IAAI,UAAU,GAAG;AACnC,WAAO;AAAA,EACT;AACA,SAAO;AACT;AASA,MAAM,uBAA2D;AAAA,EAC/D,aAAa,EAAE,MAAM,UAAU,MAAM,qBAAqB,MAAM,SAAM,SAAS,cAAc;AAAA,EAC7F,UAAU,EAAE,MAAM,UAAU,MAAM,kBAAkB,MAAM,KAAK,SAAS,WAAW;AAAA,EACnF,SAAS,EAAE,MAAM,UAAU,MAAM,iBAAiB,MAAM,KAAK,SAAS,UAAU;AAAA,EAChF,KAAK,EAAE,MAAM,UAAU,MAAM,aAAa,MAAM,OAAO,SAAS,MAAM;AAAA,EACtE,eAAe,EAAE,MAAM,UAAU,MAAM,aAAa,MAAM,OAAO,SAAS,MAAM;AAAA,EAChF,QAAQ,EAAE,MAAM,WAAW,MAAM,uBAAuB,SAAS,SAAS;AAAA,EAC1E,WAAW,EAAE,MAAM,WAAW,MAAM,yBAAyB,SAAS,cAAc;AAAA,EACpF,gBAAgB,EAAE,MAAM,WAAW,MAAM,yBAAyB,SAAS,cAAc;AAAA,EACzF,SAAS,EAAE,MAAM,WAAW,MAAM,yBAAyB,SAAS,gBAAgB;AAAA,EACpF,cAAc,EAAE,MAAM,WAAW,MAAM,yBAAyB,SAAS,gBAAgB;AAAA,EACzF,cAAc,EAAE,MAAM,WAAW,MAAM,iBAAiB,SAAS,eAAe;AAAA,EAChF,cAAc,EAAE,MAAM,WAAW,MAAM,yBAAyB,SAAS,eAAe;AAAA,EACxF,oBAAoB,EAAE,MAAM,UAAU,MAAM,qBAAqB,MAAM,SAAM,SAAS,cAAc;AAAA,EACpG,iBAAiB,EAAE,MAAM,UAAU,MAAM,kBAAkB,MAAM,KAAK,SAAS,WAAW;AAAA,EAC1F,gBAAgB,EAAE,MAAM,UAAU,MAAM,iBAAiB,MAAM,KAAK,SAAS,UAAU;AAAA,EACvF,YAAY,EAAE,MAAM,WAAW,MAAM,yBAAyB,SAAS,cAAc;AAAA,EACrF,kBAAkB,EAAE,MAAM,WAAW,MAAM,yBAAyB,SAAS,cAAc;AAAA,EAC3F,UAAU,EAAE,MAAM,WAAW,MAAM,yBAAyB,SAAS,gBAAgB;AAAA,EACrF,gBAAgB,EAAE,MAAM,WAAW,MAAM,yBAAyB,SAAS,gBAAgB;AAAA,EAC3F,eAAe,EAAE,MAAM,WAAW,MAAM,iBAAiB,SAAS,eAAe;AAAA,EACjF,eAAe,EAAE,MAAM,WAAW,MAAM,yBAAyB,SAAS,eAAe;AAC3F;AAGO,MAAM,aAAa;AAAA,EACP;AAAA;AAAA,EAEA,YAAY,oBAAI,IAAoB;AAAA;AAAA,EAEpC,kBAAkB,oBAAI,IAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAO1C,gBAAgB,oBAAI,IAAY;AAAA;AAAA,EAGjD,YAAY,SAAgC;AAC1C,SAAK,UAAU;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAwBA,MAAc,0BAA0B,IAAY,OAA8C;AA/LpG;AAgMI,UAAM,WAAW,MAAM,KAAK,QAAQ,eAAe,EAAE,EAAE,MAAM,MAAM,IAAI;AACvE,QAAI,CAAC,UAAU;AACb;AAAA,IACF;AACA,UAAM,UAAS,cAAS,WAAT,mBAAiB;AAChC,QAAI,CAAC,UAAU,OAAO,WAAW,UAAU;AACzC;AAAA,IACF;AACA,UAAM,QAAQ,OAAO,OAAO,MAAiC,EAAE,KAAK,OAAK,OAAO,MAAM,QAAQ;AAC9F,QAAI,CAAC,OAAO;AACV;AAAA,IACF;AACA,UAAM,KAAK,QAAQ,kBAAkB,IAAI,EAAE,QAAQ,EAAE,QAAQ,MAAM,EAAE,CAAC,EAAE,MAAM,MAAM,MAAS;AAAA,EAC/F;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,gBAAgB,IAA2B;AACvD,UAAM,MAAM,MAAM,KAAK,QAAQ,eAAe,EAAE,EAAE,MAAM,MAAM,IAAI;AAClE,QAAI,CAAC,KAAK;AACR;AAAA,IACF;AACA,UAAM,KAAK,QAAQ,cAAc,EAAE,EAAE,MAAM,MAAM,MAAS;AAC1D,UAAM,KAAK,QAAQ,eAAe,EAAE,EAAE,MAAM,MAAM,MAAS;AAAA,EAC7D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,iBAAiB,QAAqB,MAA6B;AACvE,QAAI,OAAO,QAAQ,aAAa;AAC9B;AAAA,IACF;AACA,UAAM,SAAS,KAAK,aAAa,MAAM;AACvC,UAAM,KAAK,QAAQ,cAAc,GAAG,MAAM,cAAc,EAAE,KAAK,MAAM,KAAK,KAAK,CAAC,EAAE,MAAM,MAAM,MAAS;AAAA,EACzG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,yBAAyB,QAAoC;AACjE,QAAI,OAAO,QAAQ,aAAa;AAC9B;AAAA,IACF;AACA,UAAM,SAAS,KAAK,aAAa,MAAM;AACvC,eAAW,SAAS,CAAC,sBAAsB,sBAAsB,kBAAkB,GAAG;AACpF,YAAM,KAAK,gBAAgB,GAAG,MAAM,SAAS,KAAK,EAAE;AACpD,WAAK,gBAAgB,OAAO,GAAG,MAAM,IAAI,KAAK,EAAE;AAAA,IAClD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,iBAAiB,QAAgB,SAAyB;AAvQ5D;AAwQI,UAAM,WAAU,UAAK,gBAAgB,IAAI,GAAG,MAAM,IAAI,OAAO,EAAE,MAA/C,YAAoD,wBAAwB,OAAO;AACnG,WAAO,GAAG,MAAM,IAAI,OAAO,IAAI,OAAO;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,MAAM,2BAA2B,QAAgB,SAAgC;AA1RnF;AA2RI,UAAM,OAAO,qBAAqB,QAAQ,YAAY,CAAC;AACvD,QAAI,CAAC,MAAM;AACT;AAAA,IACF;AACA,UAAM,UAAU,wBAAwB,OAAO;AAI/C,UAAM,KAAK,QACR;AAAA,MACC,GAAG,MAAM,IAAI,OAAO;AAAA,MACpB;AAAA,QACE,MAAM;AAAA,QACN,QAAQ,EAAE,OAAM,mBAAc,OAAO,MAArB,YAA0B,QAAQ;AAAA,QAClD,QAAQ,CAAC;AAAA,MACX;AAAA,MACA,EAAE,UAAU,EAAE,QAAQ,CAAC,MAAM,EAAE,EAAE;AAAA,IACnC,EACC,MAAM,MAAM,MAAS;AACxB,UAAM,KAAK,QACR;AAAA,MACC,GAAG,MAAM,IAAI,OAAO,IAAI,OAAO;AAAA,MAC/B;AAAA,QACE,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,UAAM,mBAAM,KAAK,OAAO;AAAA,UACxB,MAAM,KAAK;AAAA,UACX,MAAM,KAAK;AAAA,UACX,MAAM;AAAA,UACN,OAAO;AAAA,UACP,GAAI,KAAK,SAAS,SAAY,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;AAAA,UACrD,KAAK,KAAK,SAAS,YAAY,QAAQ;AAAA,QACzC;AAAA,QACA,QAAQ,CAAC;AAAA,MACX;AAAA,MACA,EAAE,UAAU,EAAE,QAAQ,CAAC,MAAM,EAAE,EAAE;AAAA,IACnC,EACC,MAAM,MAAM,MAAS;AACxB,SAAK,gBAAgB,IAAI,GAAG,MAAM,IAAI,OAAO,IAAI,OAAO;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,MAAM,iBAAiB,QAAoC;AAlV7D;AAmVI,UAAM,MAAM,KAAK,UAAU,MAAM;AACjC,UAAM,YAAY,KAAK,aAAa,MAAM;AAC1C,UAAM,YAAY,KAAK,UAAU,IAAI,GAAG;AAGxC,QAAI,aAAa,cAAc,WAAW;AACxC,WAAK,QAAQ,IAAI,MAAM,oBAAoB,OAAO,GAAG,KAAK,SAAS,WAAM,SAAS,EAAE;AACpF,YAAM,KAAK,QAAQ,eAAe,WAAW,EAAE,WAAW,KAAK,CAAC;AAGhE,YAAM,gBAAgB,GAAG,SAAS;AAClC,iBAAW,UAAU,KAAK,gBAAgB,KAAK,GAAG;AAChD,YAAI,OAAO,WAAW,aAAa,GAAG;AACpC,eAAK,gBAAgB,OAAO,MAAM;AAAA,QACpC;AAAA,MACF;AAAA,IACF;AACA,SAAK,UAAU,IAAI,KAAK,SAAS;AAEjC,UAAM,SAAS;AACf,UAAM,UAAU,OAAO,QAAQ;AAI/B,UAAM,WAAW,UACb,GAAG,KAAK,QAAQ,SAAS,wBACzB,GAAG,KAAK,QAAQ,SAAS,IAAI,MAAM;AACvC,UAAM,OAAO,UAAU,qCAAa,sCAAiB,OAAO,IAAI;AAChE,UAAM,KAAK,QAAQ;AAAA,MACjB;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM,OAAO;AAAA,UACb;AAAA,UACA,cAAc,EAAE,SAAS;AAAA,QAC3B;AAAA,QACA,QAAQ;AAAA,UACN,KAAK,OAAO;AAAA,UACZ,UAAU,OAAO;AAAA,QACnB;AAAA,MACF;AAAA,MACA,EAAE,UAAU,EAAE,QAAQ,CAAC,MAAM,EAAE,EAAE;AAAA,IACnC;AAGA,UAAM,KAAK,QAAQ;AAAA,MACjB,GAAG,MAAM;AAAA,MACT;AAAA,QACE,MAAM;AAAA,QACN,QAAQ,EAAE,UAAM,mBAAM,mBAAmB,EAAE;AAAA,QAC3C,QAAQ,CAAC;AAAA,MACX;AAAA,MACA,EAAE,UAAU,EAAE,QAAQ,CAAC,MAAM,EAAE,EAAE;AAAA,IACnC;AAEA,UAAM,KAAK,YAAY,GAAG,MAAM,cAAc,QAAQ,UAAU,QAAQ,KAAK;AAC7E,UAAM,KAAK,QAAQ,cAAc,GAAG,MAAM,cAAc;AAAA,MACtD,KAAK,OAAO;AAAA,MACZ,KAAK;AAAA,IACP,CAAC;AAED,QAAI,CAAC,SAAS;AACZ,YAAM,KAAK;AAAA,QACT,GAAG,MAAM;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAKA,YAAM,KAAK,YAAY,GAAG,MAAM,eAAe,SAAS,UAAU,QAAQ,OAAO,QAAW,EAAE;AAC9F,YAAM,KAAK,YAAY,GAAG,MAAM,gBAAgB,iBAAiB,UAAU,QAAQ,OAAO,QAAW,EAAE;AACvG,YAAM,KAAK,YAAY,GAAG,MAAM,YAAY,cAAc,UAAU,WAAW,OAAO,QAAW,EAAE;AAInG,YAAM,KAAK,YAAY,GAAG,MAAM,cAAc,eAAe,UAAU,QAAQ,OAAO,QAAW,EAAE;AACnG,YAAM,KAAK,QAAQ,cAAc,GAAG,MAAM,eAAe;AAAA,QACvD,KAAK,OAAO;AAAA,QACZ,KAAK;AAAA,MACP,CAAC;AACD,YAAM,KAAK,QAAQ,cAAc,GAAG,MAAM,gBAAgB;AAAA,QACxD,KAAK,OAAO;AAAA,QACZ,KAAK;AAAA,MACP,CAAC;AACD,YAAM,KAAK,QAAQ,cAAc,GAAG,MAAM,YAAY;AAAA,QACpD,MAAK,YAAO,UAAP,YAAgB;AAAA,QACrB,KAAK;AAAA,MACP,CAAC;AACD,YAAM,KAAK,QAAQ,cAAc,GAAG,MAAM,cAAc;AAAA,QACtD,SAAK,sCAAiB,OAAO,IAAI;AAAA,QACjC,KAAK;AAAA,MACP,CAAC;AAKD,YAAM,KAAK,eAAe,MAAM;AAAA,IAClC,OAAO;AAEL,YAAM,cAAa,YAAO,iBAAP,YAAuB,CAAC,GACxC,IAAI,OAAK;AACR,cAAM,cAAU,gCAAkB,EAAE,QAAQ,EAAE,MAAM,EAAE;AACtD,eAAO,SAAS,GAAG,EAAE,GAAG,IAAI,OAAO,EAAE;AAAA,MACvC,CAAC,EACA,KAAK,IAAI;AACZ,YAAM,KAAK,YAAY,GAAG,MAAM,iBAAiB,WAAW,UAAU,QAAQ,KAAK;AACnF,YAAM,KAAK,QAAQ,cAAc,GAAG,MAAM,iBAAiB;AAAA,QACzD,KAAK;AAAA,QACL,KAAK;AAAA,MACP,CAAC;AAKD,iBAAW,WAAW;AAAA,QACpB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,GAAG;AACD,cAAM,KAAK,gBAAgB,GAAG,MAAM,SAAS,OAAO,EAAE;AAAA,MACxD;AAEA,YAAM,KAAK,QAAQ,eAAe,GAAG,MAAM,SAAS,EAAE,WAAW,KAAK,CAAC,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IACzF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,gBAAgB,QAAoC;AACxD,UAAM,gBAAY,4CAAkB,QAAQ,KAAK,QAAQ,GAAG;AAC5D,QAAI,UAAU,WAAW,GAAG;AAC1B,WAAK,QAAQ,IAAI;AAAA,QACf,yBAAyB,OAAO,GAAG,IAAI,OAAO,QAAQ;AAAA,MACxD;AACA;AAAA,IACF;AACA,SAAK,QAAQ,IAAI;AAAA,MACf,yBAAyB,OAAO,GAAG,IAAI,OAAO,QAAQ,KAAK,UAAU,MAAM;AAAA,IAC7E;AACA,UAAM,SAAS,KAAK,aAAa,MAAM;AACvC,UAAM,KAAK,yBAAyB,QAAQ,WAAW,KAAK;AAAA,EAC9D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAM,kBAAkB,QAAqB,WAA6C;AACxF,UAAM,SAAS,KAAK,aAAa,MAAM;AAIvC,UAAM,iBAAiB,UAAU,OAAO,OAAK,CAAC,EAAE,GAAG,WAAW,WAAW,CAAC;AAC1E,UAAM,KAAK,yBAAyB,QAAQ,gBAAgB,SAAS,OAAO,GAAG,EAAE;AAKjF,UAAM,KAAK,wBAAwB,QAAQ,cAAc;AAGzD,QAAI,UAAU,KAAK,OAAK,EAAE,GAAG,WAAW,WAAW,CAAC,GAAG;AACrD,YAAM,KAAK,oBAAoB,MAAM;AAAA,IACvC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAc,yBAAyB,QAAgB,WAA8B,QAA+B;AA1hBtH;AA2hBI,UAAM,gBAAgB,oBAAI,IAA+B;AACzD,eAAW,OAAO,WAAW;AAC3B,YAAM,WAAU,SAAI,YAAJ,YAAe;AAC/B,WAAK,gBAAgB,IAAI,GAAG,MAAM,IAAI,IAAI,EAAE,IAAI,OAAO;AACvD,UAAI,CAAC,cAAc,IAAI,OAAO,GAAG;AAC/B,sBAAc,IAAI,SAAS,CAAC,CAAC;AAAA,MAC/B;AACA,oBAAc,IAAI,OAAO,EAAG,KAAK,GAAG;AAAA,IACtC;AAEA,SAAK,QAAQ,IAAI;AAAA,MACf,iBAAiB,MAAM,KAAK,MAAM,KAAK,UAAU,MAAM,cAAc,cAAc,IAAI;AAAA,IACzF;AAEA,eAAW,CAAC,SAAS,IAAI,KAAK,eAAe;AAC3C,YAAM,KAAK,QAAQ;AAAA,QACjB,GAAG,MAAM,IAAI,OAAO;AAAA,QACpB;AAAA,UACE,MAAM;AAAA,UACN,QAAQ,EAAE,OAAM,mBAAc,OAAO,MAArB,YAA0B,QAAQ;AAAA,UAClD,QAAQ,CAAC;AAAA,QACX;AAAA,QACA,EAAE,UAAU,EAAE,QAAQ,CAAC,MAAM,EAAE,EAAE;AAAA,MACnC;AAEA,iBAAW,OAAO,MAAM;AACtB,cAAM,SAAwC;AAAA,UAC5C,MAAM,IAAI;AAAA,UACV,MAAM,IAAI;AAAA,UACV,MAAM,IAAI;AAAA,UACV,MAAM;AAAA,UACN,OAAO,IAAI;AAAA,QACb;AAEA,YAAI,IAAI,MAAM;AACZ,iBAAO,OAAO,IAAI;AAAA,QACpB;AACA,YAAI,IAAI,QAAQ,QAAW;AACzB,iBAAO,MAAM,IAAI;AAAA,QACnB;AACA,YAAI,IAAI,QAAQ,QAAW;AACzB,iBAAO,MAAM,IAAI;AAAA,QACnB;AACA,YAAI,IAAI,QAAQ;AACd,iBAAO,SAAS,IAAI;AAAA,QACtB;AACA,YAAI,IAAI,QAAQ,QAAW;AACzB,iBAAO,MAAM,IAAI;AAAA,QACnB;AACA,YAAI,IAAI,MAAM;AACZ,iBAAO,OAAO,IAAI;AAAA,QACpB;AAEA,cAAM,KAAK,QAAQ;AAAA,UACjB,GAAG,MAAM,IAAI,OAAO,IAAI,IAAI,EAAE;AAAA,UAC9B;AAAA,YACE,MAAM;AAAA,YACN;AAAA,YACA,QAAQ;AAAA,cACN,gBAAgB,IAAI;AAAA,cACpB,oBAAoB,IAAI;AAAA,YAC1B;AAAA,UACF;AAAA,UACA,EAAE,UAAU,EAAE,QAAQ,CAAC,MAAM,EAAE,EAAE;AAAA,QACnC;AAUA,YAAI,IAAI,QAAQ;AACd,gBAAM,KAAK,0BAA0B,GAAG,MAAM,IAAI,OAAO,IAAI,IAAI,EAAE,IAAI,IAAI,MAAM;AAAA,QACnF;AAGA,YAAI,IAAI,QAAQ,QAAW;AACzB,gBAAM,UAAU,MAAM,KAAK,QAAQ,cAAc,GAAG,MAAM,IAAI,OAAO,IAAI,IAAI,EAAE,EAAE;AACjF,cAAI,CAAC,WAAW,QAAQ,QAAQ,QAAQ,QAAQ,QAAQ,QAAW;AAEjE,kBAAM,KAAK,QAAQ,cAAc,GAAG,MAAM,IAAI,OAAO,IAAI,IAAI,EAAE,IAAI;AAAA,cACjE,KAAK,IAAI;AAAA,cACT,KAAK;AAAA,YACP,CAAC;AAAA,UACH,WAAW,IAAI,UAAU,EAAE,OAAO,QAAQ,GAAG,KAAK,IAAI,SAAS;AAE7D,iBAAK,QAAQ,IAAI;AAAA,cACf,6BAA6B,MAAM,IAAI,OAAO,IAAI,IAAI,EAAE,OAAO,OAAO,QAAQ,GAAG,CAAC,aAAQ,OAAO,IAAI,GAAG,CAAC;AAAA,YAC3G;AACA,kBAAM,KAAK,QAAQ,cAAc,GAAG,MAAM,IAAI,OAAO,IAAI,IAAI,EAAE,IAAI;AAAA,cACjE,KAAK,IAAI;AAAA,cACT,KAAK;AAAA,YACP,CAAC;AAAA,UACH;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,oBAAoB,QAAoC;AAC5D,UAAM,SAAS,KAAK,aAAa,MAAM;AAEvC,UAAM,KAAK,QAAQ;AAAA,MACjB,GAAG,MAAM;AAAA,MACT;AAAA,QACE,MAAM;AAAA,QACN,QAAQ,EAAE,UAAM,mBAAM,aAAa,EAAE;AAAA,QACrC,QAAQ,CAAC;AAAA,MACX;AAAA,MACA,EAAE,UAAU,EAAE,QAAQ,CAAC,MAAM,EAAE,EAAE;AAAA,IACnC;AAMA,UAAM,eAAW,2CAAoB,MAAM;AAC3C,UAAM,YACJ,MAAM,QAAQ,OAAO,cAAc,KAAK,OAAO,eAAe,SAAS,IACnE,KAAK,IAAI,GAAG,OAAO,cAAc,IAAI,IACrC;AACN,UAAM,eAAe,KAAK,IAAI,UAAU,SAAS;AACjD,WAAO,eAAe;AAGtB,UAAM,eACJ,OAAO,cAAc,MAAM,QAAQ,OAAO,cAAc,KAAK,OAAO,eAAe,SAAS,IACxF,OAAO,eAAe,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC,IAClD,MAAM,KAAK,EAAE,QAAQ,aAAa,GAAG,CAAC,GAAG,MAAM,CAAC;AACtD,UAAM,gBAAgB,aAAa;AAEnC,UAAM,KAAK,YAAY,GAAG,MAAM,mBAAmB,iBAAiB,UAAU,SAAS,KAAK;AAC5F,UAAM,KAAK,QAAQ,cAAc,GAAG,MAAM,mBAAmB;AAAA,MAC3D,KAAK;AAAA,MACL,KAAK;AAAA,IACP,CAAC;AAGD,UAAM,KAAK,QAAQ;AAAA,MACjB,GAAG,MAAM;AAAA,MACT;AAAA,QACE,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,UAAM,mBAAM,sBAAsB;AAAA,UAClC,MAAM;AAAA,UACN,MAAM;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,UACP,KAAK;AAAA,UACL,UAAM,mBAAM,oBAAoB;AAAA,QAClC;AAAA,QACA,QAAQ,CAAC;AAAA,MACX;AAAA,MACA,EAAE,UAAU,EAAE,QAAQ,CAAC,MAAM,EAAE,EAAE;AAAA,IACnC;AACA,UAAM,KAAK,QAAQ;AAAA,MACjB,GAAG,MAAM;AAAA,MACT;AAAA,QACE,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,UAAM,mBAAM,mBAAmB;AAAA,UAC/B,MAAM;AAAA,UACN,MAAM;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,UACP,KAAK;AAAA,UACL,UAAM,mBAAM,gBAAgB;AAAA,QAC9B;AAAA,QACA,QAAQ,CAAC;AAAA,MACX;AAAA,MACA,EAAE,UAAU,EAAE,QAAQ,CAAC,MAAM,EAAE,EAAE;AAAA,IACnC;AAKA,UAAM,gBAAgB,OAAO,eAAe;AAC5C,UAAM,gBACJ,OAAO,cAAc,MAAM,QAAQ,OAAO,cAAc,KAAK,OAAO,eAAe,SAAS,IACxF,OAAO,eAAe,KAAK,GAAG,IAC9B;AACN,UAAM,KAAK,QAAQ,cAAc,GAAG,MAAM,yBAAyB;AAAA,MACjE,KAAK;AAAA,MACL,KAAK;AAAA,IACP,CAAC;AACD,UAAM,KAAK,QAAQ,cAAc,GAAG,MAAM,yBAAyB;AAAA,MACjE,KAAK;AAAA,MACL,KAAK;AAAA,IACP,CAAC;AAED,eAAW,KAAK,cAAc;AAC5B,YAAM,KAAK,QAAQ;AAAA,QACjB,GAAG,MAAM,aAAa,CAAC;AAAA,QACvB;AAAA,UACE,MAAM;AAAA,UACN,QAAQ,EAAE,MAAM,WAAW,CAAC,GAAG;AAAA,UAC/B,QAAQ,CAAC;AAAA,QACX;AAAA,QACA,EAAE,UAAU,EAAE,QAAQ,CAAC,MAAM,EAAE,EAAE;AAAA,MACnC;AAEA,YAAM,KAAK,QAAQ;AAAA,QACjB,GAAG,MAAM,aAAa,CAAC;AAAA,QACvB;AAAA,UACE,MAAM;AAAA,UACN,QAAQ;AAAA,YACN,UAAM,mBAAM,OAAO;AAAA,YACnB,MAAM;AAAA,YACN,MAAM;AAAA,YACN,MAAM;AAAA,YACN,OAAO;AAAA,UACT;AAAA,UACA,QAAQ,CAAC;AAAA,QACX;AAAA,QACA,EAAE,UAAU,EAAE,QAAQ,CAAC,MAAM,EAAE,EAAE;AAAA,MACnC;AAEA,YAAM,KAAK,QAAQ;AAAA,QACjB,GAAG,MAAM,aAAa,CAAC;AAAA,QACvB;AAAA,UACE,MAAM;AAAA,UACN,QAAQ;AAAA,YACN,UAAM,mBAAM,YAAY;AAAA,YACxB,MAAM;AAAA,YACN,MAAM;AAAA,YACN,MAAM;AAAA,YACN,OAAO;AAAA,YACP,KAAK;AAAA,YACL,KAAK;AAAA,YACL,MAAM;AAAA,UACR;AAAA,UACA,QAAQ,CAAC;AAAA,QACX;AAAA,QACA,EAAE,UAAU,EAAE,QAAQ,CAAC,MAAM,EAAE,EAAE;AAAA,MACnC;AAAA,IACF;AAGA,UAAM,KAAK,QAAQ;AAAA,MACjB,GAAG,MAAM;AAAA,MACT;AAAA,QACE,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,UAAM,mBAAM,qBAAqB;AAAA,UACjC,MAAM;AAAA,UACN,MAAM;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,UACP,UAAM,mBAAM,kBAAkB;AAAA,QAChC;AAAA,QACA,QAAQ,CAAC;AAAA,MACX;AAAA,MACA,EAAE,UAAU,EAAE,QAAQ,CAAC,MAAM,EAAE,EAAE;AAAA,IACnC;AAGA,UAAM,KAAK,sBAAsB,QAAQ,YAAY;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAc,sBAAsB,QAAgB,cAAuC;AACzF,UAAM,QAAQ,IAAI,IAAI,YAAY;AAClC,UAAM,YAAY,GAAG,KAAK,QAAQ,SAAS,IAAI,MAAM;AACrD,UAAM,WAAW,MAAM,KAAK,QAAQ,mBAAmB,UAAU,WAAW;AAAA,MAC1E,UAAU;AAAA,MACV,QAAQ,GAAG,SAAS;AAAA,IACtB,CAAC;AAED,QAAI,EAAC,qCAAU,OAAM;AACnB;AAAA,IACF;AAEA,eAAW,OAAO,SAAS,MAAM;AAC/B,YAAM,UAAU,IAAI,GAAG,QAAQ,GAAG,KAAK,QAAQ,SAAS,KAAK,EAAE;AAC/D,YAAM,UAAU,QAAQ,QAAQ,GAAG,MAAM,cAAc,EAAE;AACzD,YAAM,SAAS,SAAS,SAAS,EAAE;AACnC,UAAI,CAAC,MAAM,MAAM,KAAK,CAAC,MAAM,IAAI,MAAM,GAAG;AACxC,aAAK,QAAQ,IAAI,MAAM,4BAA4B,OAAO,EAAE;AAM5D,cAAM,KAAK,QAAQ,cAAc,GAAG,OAAO,QAAQ,EAAE,MAAM,MAAM,MAAS;AAC1E,cAAM,KAAK,QAAQ,cAAc,GAAG,OAAO,aAAa,EAAE,MAAM,MAAM,MAAS;AAC/E,cAAM,KAAK,QAAQ,eAAe,SAAS,EAAE,WAAW,KAAK,CAAC;AAAA,MAChE;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,MAAM,kBAAkB,QAAqB,OAA4C;AACvF,UAAM,SAAS,KAAK,aAAa,MAAM;AACvC,UAAM,SAA6B,CAAC;AAEpC,UAAM,MAAM,CAAC,IAAY,QAAmC;AAC1D,aAAO,KAAK,KAAK,QAAQ,cAAc,IAAI,EAAE,KAAK,KAAK,KAAK,CAAC,EAAE,MAAM,MAAM,MAAS,CAAC;AAAA,IACvF;AAOA,QAAI,MAAM,WAAW,UAAa,OAAO,SAAS,yCAAkB,OAAO;AACzE,UAAI,GAAG,MAAM,gBAAgB,MAAM,MAAM;AAAA,IAC3C;AACA,QAAI,MAAM,UAAU,QAAW;AAC7B,UAAI,GAAG,MAAM,kBAAkB,MAAM,KAAK;AAAA,IAC5C;AACA,QAAI,MAAM,eAAe,QAAW;AAClC,UAAI,GAAG,MAAM,uBAAuB,MAAM,UAAU;AAAA,IACtD;AACA,QAAI,MAAM,aAAa,QAAW;AAChC,UAAI,GAAG,MAAM,qBAAqB,MAAM,QAAQ;AAAA,IAClD;AACA,QAAI,MAAM,qBAAqB,QAAW;AACxC,UAAI,GAAG,MAAM,6BAA6B,MAAM,gBAAgB;AAAA,IAClE;AACA,QAAI,MAAM,UAAU,QAAW;AAC7B,UAAI,GAAG,MAAM,kBAAkB,MAAM,KAAK;AAAA,IAC5C;AAEA,UAAM,QAAQ,IAAI,MAAM;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,wBAAwB,QAAgC;AAC5D,UAAM,KAAK,QAAQ;AAAA,MACjB;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,QAAQ,EAAE,UAAM,mBAAM,QAAQ,EAAE;AAAA,QAChC,QAAQ,CAAC;AAAA,MACX;AAAA,MACA,EAAE,UAAU,EAAE,QAAQ,CAAC,MAAM,EAAE,EAAE;AAAA,IACnC;AACA,UAAM,KAAK,QAAQ;AAAA,MACjB;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,QAAQ,EAAE,UAAM,mBAAM,cAAc,EAAE;AAAA,QACtC,QAAQ,CAAC;AAAA,MACX;AAAA,MACA,EAAE,UAAU,EAAE,QAAQ,CAAC,MAAM,EAAE,EAAE;AAAA,IACnC;AACA,UAAM,KAAK,YAAY,sBAAsB,gBAAgB,WAAW,uBAAuB,KAAK;AACpG,UAAM,KAAK,QAAQ,cAAc,sBAAsB;AAAA,MACrD,KAAK;AAAA,MACL,KAAK;AAAA,IACP,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,mBAAmB,QAAgC;AACvD,UAAM,KAAK,QAAQ,cAAc,sBAAsB,EAAE,KAAK,QAAQ,KAAK,KAAK,CAAC,EAAE,MAAM,MAAM,MAAS;AAAA,EAC1G;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBA,MAAM,8BAA8B,OAAoB,eAA6C;AACnG,UAAM,SAAS,KAAK,aAAa,KAAK;AACtC,UAAM,UAAU,GAAG,MAAM;AAEzB,UAAM,cAAc,cACjB,OAAO,OAAK,CAAC,EAAE,MAAM,MAAM,EAC3B,IAAI,OAAK;AACR,YAAM,cAAU,gCAAkB,EAAE,QAAQ,EAAE,MAAM,EAAE;AACtD,aAAO,SAAS,GAAG,EAAE,GAAG,IAAI,OAAO,EAAE;AAAA,IACvC,CAAC;AAEH,UAAM,KAAK,YAAY,SAAS,uBAAuB,UAAU,QAAQ,KAAK;AAC9E,UAAM,KAAK,QAAQ,cAAc,SAAS;AAAA,MACxC,KAAK,YAAY,KAAK,IAAI;AAAA,MAC1B,KAAK;AAAA,IACP,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,eAAe,gBAAkD;AACrE,UAAM,kBAAkB,IAAI,IAAI,eAAe,IAAI,OAAK,KAAK,aAAa,CAAC,CAAC,CAAC;AAC7E,UAAM,UAAoB,CAAC;AAG3B,eAAW,UAAU,CAAC,WAAW,QAAQ,GAAG;AAI1C,UAAI;AACJ,UAAI;AACF,0BAAkB,MAAM,KAAK,QAAQ,mBAAmB,UAAU,UAAU;AAAA,UAC1E,UAAU,GAAG,KAAK,QAAQ,SAAS,IAAI,MAAM;AAAA,UAC7C,QAAQ,GAAG,KAAK,QAAQ,SAAS,IAAI,MAAM;AAAA,QAC7C,CAAC;AAAA,MACH,SAAS,GAAG;AACV,aAAK,QAAQ,IAAI;AAAA,UACf,iDAAiD,MAAM,KAAK,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;AAAA,QACxG;AACA;AAAA,MACF;AAEA,UAAI,EAAC,mDAAiB,OAAM;AAC1B;AAAA,MACF;AAEA,iBAAW,OAAO,gBAAgB,MAAM;AACtC,cAAM,UAAU,IAAI,GAAG,QAAQ,GAAG,KAAK,QAAQ,SAAS,KAAK,EAAE;AAC/D,YAAI,CAAC,gBAAgB,IAAI,OAAO,GAAG;AACjC,eAAK,QAAQ,IAAI,MAAM,0BAA0B,OAAO,EAAE;AAI1D,gBAAM,YAAY,MAAM,KAAK,QAC1B,mBAAmB,UAAU,SAAS;AAAA,YACrC,UAAU,GAAG,IAAI,EAAE;AAAA,YACnB,QAAQ,GAAG,IAAI,EAAE;AAAA,UACnB,CAAC,EACA,MAAM,MAAM,MAAS;AACxB,cAAI,uCAAW,MAAM;AACnB,uBAAW,YAAY,UAAU,MAAM;AACrC,oBAAM,eAAe,SAAS,GAAG,QAAQ,GAAG,KAAK,QAAQ,SAAS,KAAK,EAAE;AACzE,oBAAM,KAAK,QAAQ,cAAc,YAAY,EAAE,MAAM,MAAM,MAAS;AAAA,YACtE;AAAA,UACF;AACA,gBAAM,KAAK,QAAQ,eAAe,SAAS,EAAE,WAAW,KAAK,CAAC;AAC9D,eAAK,aAAa,OAAO;AACzB,kBAAQ,KAAK,OAAO;AAAA,QACtB;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmBA,MAAM,wBAAwB,QAAgB,gBAAkD;AAthClG;AAwhCI,UAAM,oBAAoB,oBAAI,IAAyB;AACvD,eAAW,OAAO,gBAAgB;AAChC,YAAM,WAAU,SAAI,YAAJ,YAAe;AAC/B,UAAI,CAAC,kBAAkB,IAAI,OAAO,GAAG;AACnC,0BAAkB,IAAI,SAAS,oBAAI,IAAI,CAAC;AAAA,MAC1C;AACA,wBAAkB,IAAI,OAAO,EAAG,IAAI,IAAI,EAAE;AAAA,IAC5C;AAEA,UAAM,eAAe,GAAG,KAAK,QAAQ,SAAS,IAAI,MAAM;AACxD,UAAM,WAAW,MAAM,KAAK,QAAQ,mBAAmB,UAAU,SAAS;AAAA,MACxE,UAAU;AAAA,MACV,QAAQ,GAAG,YAAY;AAAA,IACzB,CAAC;AACD,QAAI,EAAC,qCAAU,OAAM;AACnB;AAAA,IACF;AAEA,UAAM,mBAAmB,oBAAI,IAA+C;AAC5E,eAAW,OAAO,SAAS,MAAM;AAC/B,YAAM,OAAO,IAAI,GAAG,QAAQ,cAAc,EAAE;AAC5C,YAAM,SAAS,KAAK,QAAQ,GAAG;AAC/B,UAAI,SAAS,GAAG;AACd;AAAA,MACF;AACA,YAAM,UAAU,KAAK,MAAM,GAAG,MAAM;AACpC,YAAM,UAAU,KAAK,MAAM,SAAS,CAAC;AACrC,UAAI,CAAC,iBAAiB,SAAS,OAAO,GAAG;AACvC;AAAA,MACF;AAIA,UAAI,YAAY,aAAa,uCAAc,IAAI,OAAO,GAAG;AACvD;AAAA,MACF;AACA,YAAM,UAAS,sBAAiB,IAAI,OAAO,MAA5B,YAAiC,EAAE,MAAM,GAAG,SAAS,EAAE;AACtE,aAAO;AACP,YAAM,YAAW,uBAAkB,IAAI,OAAO,MAA7B,YAAkC,oBAAI,IAAY;AACnE,UAAI,CAAC,SAAS,IAAI,OAAO,GAAG;AAC1B,cAAM,UAAU,IAAI,GAAG,QAAQ,GAAG,KAAK,QAAQ,SAAS,KAAK,EAAE;AAC/D,aAAK,QAAQ,IAAI,MAAM,yBAAyB,OAAO,EAAE;AACzD,cAAM,KAAK,QAAQ,eAAe,OAAO;AACzC,cAAM,KAAK,QAAQ,cAAc,OAAO,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AACxD,eAAO;AAAA,MACT;AACA,uBAAiB,IAAI,SAAS,MAAM;AAAA,IACtC;AAGA,eAAW,CAAC,SAAS,MAAM,KAAK,kBAAkB;AAChD,UAAI,OAAO,UAAU,KAAK,OAAO,YAAY,OAAO,MAAM;AACxD,aAAK,QAAQ,IAAI,MAAM,2BAA2B,MAAM,IAAI,OAAO,EAAE;AACrE,cAAM,KAAK,QAAQ,eAAe,GAAG,MAAM,IAAI,OAAO,EAAE,EAAE,MAAM,MAAM,MAAS;AAAA,MACjF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,aAAa,QAA6B;AACxC,UAAM,cAAU,gCAAkB,OAAO,QAAQ,EAAE,MAAM,EAAE;AAC3D,UAAM,SAAS,OAAO,QAAQ,cAAc,WAAW;AACvD,WAAO,GAAG,MAAM,IAAI,SAAS,GAAG,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,aAAa,QAAsB;AACzC,eAAW,OAAO,KAAK,UAAU,KAAK,GAAG;AACvC,UAAI,KAAK,UAAU,IAAI,GAAG,MAAM,QAAQ;AACtC,aAAK,UAAU,OAAO,GAAG;AAAA,MAC3B;AAAA,IACF;AACA,UAAM,cAAc,GAAG,MAAM;AAC7B,eAAW,OAAO,KAAK,gBAAgB,KAAK,GAAG;AAC7C,UAAI,IAAI,WAAW,WAAW,GAAG;AAC/B,aAAK,gBAAgB,OAAO,GAAG;AAAA,MACjC;AAAA,IACF;AAIA,UAAM,kBAAkB,GAAG,KAAK,QAAQ,SAAS,IAAI,MAAM;AAC3D,eAAW,MAAM,KAAK,eAAe;AACnC,UAAI,OAAO,GAAG,KAAK,QAAQ,SAAS,IAAI,MAAM,MAAM,GAAG,WAAW,eAAe,GAAG;AAClF,aAAK,cAAc,OAAO,EAAE;AAAA,MAC9B;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,UAAU,QAA6B;AAG7C,WAAO,GAAG,OAAO,GAAG,QAAI,gCAAkB,OAAO,QAAQ,CAAC;AAAA,EAC5D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,MAAc,YACZ,IACA,MACA,MACA,MACA,OACA,MACA,KACe;AACf,QAAI,KAAK,cAAc,IAAI,EAAE,GAAG;AAC9B;AAAA,IACF;AACA,UAAM,SAAwC;AAAA,MAC5C;AAAA,MACA;AAAA,MACA;AAAA,MACA,MAAM;AAAA,MACN;AAAA,IACF;AACA,QAAI,MAAM;AACR,aAAO,OAAO;AAAA,IAChB;AACA,QAAI,QAAQ,QAAW;AACrB,aAAO,MAAM;AAAA,IACf;AACA,UAAM,KAAK,QAAQ;AAAA,MACjB;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN;AAAA,QACA,QAAQ,CAAC;AAAA,MACX;AAAA,MACA,EAAE,UAAU,EAAE,QAAQ,CAAC,MAAM,EAAE,EAAE;AAAA,IACnC;AACA,SAAK,cAAc,IAAI,EAAE;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA+BA,MAAM,eAAe,QAAuC;AAC1D,QAAI,OAAO,QAAQ,aAAa;AAC9B,aAAO;AAAA,IACT;AACA,UAAM,SAAS,KAAK,aAAa,MAAM;AACvC,UAAM,UAAU,GAAG,MAAM;AAEzB,QAAI;AACJ,QAAI,OAAO,SAAS,yCAAkB,OAAO;AAC3C,sBAAgB,CAAC,EAAE,OAAO,kBAAkB,KAAK,IAAI,IAAI,OAAO,iBAAiB;AAAA,IACnF,OAAO;AACL,sBAAgB,OAAO,MAAM,WAAW;AAAA,IAC1C;AAEA,UAAM,UAAU,MAAM,KAAK,QAAQ,cAAc,OAAO,EAAE,MAAM,MAAM,IAAI;AAC1E,QAAI,CAAC,WAAW,QAAQ,QAAQ,eAAe;AAC7C,YAAM,KAAK,QAAQ,cAAc,SAAS,EAAE,KAAK,eAAe,KAAK,KAAK,CAAC,EAAE,MAAM,MAAM,MAAS;AAAA,IACpG;AAEA,QAAI,qBAAqB;AACzB,QAAI,OAAO,SAAS,yCAAkB,SAAS,OAAO,MAAM,WAAW,eAAe;AACpF,aAAO,MAAM,SAAS;AACtB,2BAAqB;AAAA,IACvB;AAEA,WAAO;AAAA,EACT;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "govee-smart",
|
|
4
|
-
"version": "2.13.
|
|
4
|
+
"version": "2.13.3",
|
|
5
5
|
"news": {
|
|
6
|
+
"2.13.3": {
|
|
7
|
+
"en": "Replaced deprecated setObject with extendObject",
|
|
8
|
+
"de": "Veraltetes setObject durch extendObject ersetzt",
|
|
9
|
+
"ru": "Заменён устаревший setObject на extendObject",
|
|
10
|
+
"pt": "Substituído setObject obsoleto por extendObject",
|
|
11
|
+
"nl": "Verouderd setObject vervangen door extendObject",
|
|
12
|
+
"fr": "Remplacement de setObject obsolète par extendObject",
|
|
13
|
+
"it": "Sostituito setObject deprecato con extendObject",
|
|
14
|
+
"es": "Reemplazado setObject obsoleto por extendObject",
|
|
15
|
+
"pl": "Zamieniono przestarzałe setObject na extendObject",
|
|
16
|
+
"uk": "Замінено застарілий setObject на extendObject",
|
|
17
|
+
"zh-cn": "将已弃用的 setObject 替换为 extendObject"
|
|
18
|
+
},
|
|
6
19
|
"2.13.2": {
|
|
7
20
|
"en": "Preserve user-modified state names; state translations use standard ioBroker i18n",
|
|
8
21
|
"de": "Vom Benutzer geänderte Datenpunktnamen bleiben erhalten; Übersetzungen nutzen Standard-ioBroker-i18n",
|
|
@@ -80,19 +93,6 @@
|
|
|
80
93
|
"pl": "Usunięto nieużywany punkt info.legacyMqttCleaned. Nazwy wszystkich punktów info wyświetlane w 11 językach zamiast tylko po angielsku.",
|
|
81
94
|
"uk": "Видалено невикористану точку info.legacyMqttCleaned. Назви всіх info-точок тепер відображаються 11 мовами замість лише англійської.",
|
|
82
95
|
"zh-cn": "移除了未使用的 info.legacyMqttCleaned 数据点。所有 info 数据点名称现在以 11 种语言显示,不再仅限英语。"
|
|
83
|
-
},
|
|
84
|
-
"2.11.1": {
|
|
85
|
-
"en": "Internal cleanup. No user-facing changes.",
|
|
86
|
-
"de": "Interne Bereinigung. Keine sichtbaren Änderungen.",
|
|
87
|
-
"ru": "Внутренняя оптимизация. Без видимых изменений.",
|
|
88
|
-
"pt": "Limpeza interna. Sem alterações visíveis.",
|
|
89
|
-
"nl": "Interne opruiming. Geen zichtbare wijzigingen.",
|
|
90
|
-
"fr": "Nettoyage interne. Aucun changement visible.",
|
|
91
|
-
"it": "Pulizia interna. Nessuna modifica visibile.",
|
|
92
|
-
"es": "Limpieza interna. Sin cambios visibles.",
|
|
93
|
-
"pl": "Wewnętrzne porządki. Brak widocznych zmian.",
|
|
94
|
-
"uk": "Внутрішнє прибирання. Без видимих змін.",
|
|
95
|
-
"zh-cn": "内部清理。无用户可见更改。"
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
98
|
"titleLang": {
|