iobroker.govee-smart 2.3.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -14
- package/build/lib/snapshot-handler.js +132 -0
- package/build/lib/snapshot-handler.js.map +7 -0
- package/build/main.js +24 -105
- package/build/main.js.map +2 -2
- package/io-package.json +27 -27
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -124,6 +124,14 @@ This adapter's MQTT authentication and BLE-over-LAN (ptReal) protocol implementa
|
|
|
124
124
|
---
|
|
125
125
|
|
|
126
126
|
## Changelog
|
|
127
|
+
### 2.4.0 (2026-05-04)
|
|
128
|
+
|
|
129
|
+
- Lokaler Snapshot-Manager (Save/Restore/Delete) ist jetzt eine eigene Klasse mit Host-Interface — `main.ts` ist kleiner und der Snapshot-Pfad ist isoliert testbar. Verhalten identisch.
|
|
130
|
+
|
|
131
|
+
### 2.3.1 (2026-05-04)
|
|
132
|
+
|
|
133
|
+
- Smoke-Tests für GoveeCloudClient + GoveeMqttClient — Initial-State-Checks für getFailureReason, token, connected, plus Setter-Smoke-Tests (637 → 637+9 Tests). Volle Pfade über https/mqtt-Mocks kommen separat.
|
|
134
|
+
|
|
127
135
|
### 2.3.0 (2026-05-04)
|
|
128
136
|
|
|
129
137
|
- App-Version-Drift-Monitor: täglicher iTunes-Lookup vergleicht die im Adapter hinterlegte Govee-App-Version mit der aktuellen iOS-Version. Bei Drift > 2 Minor wird gewarnt — Govees undokumentierte Endpoints rejecten gelegentlich zu alte Clients.
|
|
@@ -141,20 +149,6 @@ This adapter's MQTT authentication and BLE-over-LAN (ptReal) protocol implementa
|
|
|
141
149
|
|
|
142
150
|
- Online status correct again after adapter restart — lights flip to online with the first LAN scan, sensors with the first cloud poll (5 s after start instead of 2 minutes).
|
|
143
151
|
|
|
144
|
-
### 2.1.3 (2026-05-03)
|
|
145
|
-
|
|
146
|
-
- Critical fix: no more restart-loop after entering the verification code. The cached login is now stored in a state, not in the adapter config — saving the config doesn't trigger a restart anymore.
|
|
147
|
-
- Saving email + password in the adapter config works again. The previous loop made it look like only the "Test login" button worked.
|
|
148
|
-
- Honest startup messages: when MQTT really doesn't connect within the first minute, the log says why ("login rejected", "verification needed", etc.) instead of "still pending".
|
|
149
|
-
- Verification warning shortened. The full step-by-step instructions live in the Wiki, the log only states the action.
|
|
150
|
-
- "MQTT connected to AWS IoT" → "MQTT connected". "OpenAPI MQTT" → "Cloud-events" in user-facing logs.
|
|
151
|
-
|
|
152
|
-
### 2.1.2 (2026-05-02)
|
|
153
|
-
|
|
154
|
-
- The verification message no longer claims your account has 2FA when it doesn't. Govee asks for a one-time check the first time it sees this client — same dialog, but the wording matches reality now.
|
|
155
|
-
- Adapters upgrading from v2.1.0 had stored MQTT credentials as plain text by mistake. The corrupted leftover bytes are now cleared on first start, so the verification flow only runs once.
|
|
156
|
-
- New device added to the catalogue: H61D5 (LED Strip).
|
|
157
|
-
|
|
158
152
|
Older entries are in [CHANGELOG_OLD.md](CHANGELOG_OLD.md).
|
|
159
153
|
|
|
160
154
|
## Support
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var snapshot_handler_exports = {};
|
|
20
|
+
__export(snapshot_handler_exports, {
|
|
21
|
+
SnapshotHandler: () => SnapshotHandler
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(snapshot_handler_exports);
|
|
24
|
+
class SnapshotHandler {
|
|
25
|
+
/**
|
|
26
|
+
* @param host Adapter dependencies via Host-Interface (testbar via Mocks)
|
|
27
|
+
*/
|
|
28
|
+
constructor(host) {
|
|
29
|
+
this.host = host;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Save current device state as a local snapshot.
|
|
33
|
+
*
|
|
34
|
+
* @param device Target device
|
|
35
|
+
* @param name Snapshot name
|
|
36
|
+
*/
|
|
37
|
+
async save(device, name) {
|
|
38
|
+
var _a;
|
|
39
|
+
const prefix = this.host.devicePrefix(device);
|
|
40
|
+
const ns = this.host.namespace;
|
|
41
|
+
const [powerState, brightState, colorState, ctState] = await Promise.all([
|
|
42
|
+
this.host.getState(`${ns}.${prefix}.control.power`),
|
|
43
|
+
this.host.getState(`${ns}.${prefix}.control.brightness`),
|
|
44
|
+
this.host.getState(`${ns}.${prefix}.control.colorRgb`),
|
|
45
|
+
this.host.getState(`${ns}.${prefix}.control.colorTemperature`)
|
|
46
|
+
]);
|
|
47
|
+
let segments;
|
|
48
|
+
const segCount = (_a = device.segmentCount) != null ? _a : 0;
|
|
49
|
+
if (segCount > 0) {
|
|
50
|
+
const segReads = [];
|
|
51
|
+
for (let i = 0; i < segCount; i++) {
|
|
52
|
+
segReads.push(
|
|
53
|
+
Promise.all([
|
|
54
|
+
this.host.getState(`${ns}.${prefix}.segments.${i}.color`),
|
|
55
|
+
this.host.getState(`${ns}.${prefix}.segments.${i}.brightness`)
|
|
56
|
+
])
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
const segResults = await Promise.all(segReads);
|
|
60
|
+
segments = segResults.map(([segColor, segBright]) => ({
|
|
61
|
+
color: typeof (segColor == null ? void 0 : segColor.val) === "string" ? segColor.val : "#000000",
|
|
62
|
+
brightness: typeof (segBright == null ? void 0 : segBright.val) === "number" ? segBright.val : 100
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
65
|
+
const snapshot = {
|
|
66
|
+
name,
|
|
67
|
+
power: (powerState == null ? void 0 : powerState.val) === true,
|
|
68
|
+
brightness: typeof (brightState == null ? void 0 : brightState.val) === "number" ? brightState.val : 0,
|
|
69
|
+
colorRgb: typeof (colorState == null ? void 0 : colorState.val) === "string" ? colorState.val : "#000000",
|
|
70
|
+
colorTemperature: typeof (ctState == null ? void 0 : ctState.val) === "number" ? ctState.val : 0,
|
|
71
|
+
segments,
|
|
72
|
+
savedAt: Date.now()
|
|
73
|
+
};
|
|
74
|
+
this.host.store.saveSnapshot(device.sku, device.deviceId, snapshot);
|
|
75
|
+
this.host.log.info(`Local snapshot saved: "${name}" for ${device.name}`);
|
|
76
|
+
this.host.refreshDeviceStates(device);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Restore a local snapshot by index.
|
|
80
|
+
*
|
|
81
|
+
* @param device Target device
|
|
82
|
+
* @param val Dropdown index value
|
|
83
|
+
*/
|
|
84
|
+
async restore(device, val) {
|
|
85
|
+
const idx = parseInt(String(val), 10);
|
|
86
|
+
if (idx < 1) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const snaps = this.host.store.getSnapshots(device.sku, device.deviceId);
|
|
90
|
+
const snap = snaps[idx - 1];
|
|
91
|
+
if (!snap) {
|
|
92
|
+
this.host.log.warn(`Local snapshot index ${idx} not found for ${device.name}`);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
this.host.log.info(`Restoring local snapshot "${snap.name}" for ${device.name}`);
|
|
96
|
+
await this.host.sendCommand(device, "power", snap.power);
|
|
97
|
+
if (snap.power) {
|
|
98
|
+
await this.host.sendCommand(device, "brightness", snap.brightness);
|
|
99
|
+
if (snap.colorTemperature > 0) {
|
|
100
|
+
await this.host.sendCommand(device, "colorTemperature", snap.colorTemperature);
|
|
101
|
+
} else {
|
|
102
|
+
await this.host.sendCommand(device, "colorRgb", snap.colorRgb);
|
|
103
|
+
}
|
|
104
|
+
if (snap.segments && snap.segments.length > 0) {
|
|
105
|
+
for (let i = 0; i < snap.segments.length; i++) {
|
|
106
|
+
const seg = snap.segments[i];
|
|
107
|
+
await this.host.sendCommand(device, `segmentColor:${i}`, seg.color);
|
|
108
|
+
await this.host.sendCommand(device, `segmentBrightness:${i}`, seg.brightness);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Delete a local snapshot by name.
|
|
115
|
+
*
|
|
116
|
+
* @param device Target device
|
|
117
|
+
* @param name Snapshot name to delete
|
|
118
|
+
*/
|
|
119
|
+
delete(device, name) {
|
|
120
|
+
if (this.host.store.deleteSnapshot(device.sku, device.deviceId, name)) {
|
|
121
|
+
this.host.log.info(`Local snapshot deleted: "${name}" for ${device.name}`);
|
|
122
|
+
this.host.refreshDeviceStates(device);
|
|
123
|
+
} else {
|
|
124
|
+
this.host.log.warn(`Local snapshot "${name}" not found for ${device.name}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
129
|
+
0 && (module.exports = {
|
|
130
|
+
SnapshotHandler
|
|
131
|
+
});
|
|
132
|
+
//# sourceMappingURL=snapshot-handler.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/lib/snapshot-handler.ts"],
|
|
4
|
+
"sourcesContent": ["import type { LocalSnapshot, LocalSnapshotStore, SnapshotSegment } from \"./local-snapshots\";\nimport type { GoveeDevice } from \"./types\";\n\n/**\n * Host-Interface \u2014 die Adapter-Funktionen die der SnapshotHandler braucht\n * ohne von der Adapter-Klasse direkt zu h\u00E4ngen.\n *\n * Pattern analog `WizardHost` in segment-wizard.ts. Vorteil: testbar mit\n * Mocks, dependencies-flow ist explizit.\n */\nexport interface SnapshotHandlerHost {\n /** Adapter logger. */\n log: ioBroker.Logger;\n /** Local snapshot persistence (file-based JSON store). */\n store: LocalSnapshotStore;\n /** Adapter-namespace prefix (z.B. \"govee-smart.0\"). */\n namespace: string;\n /** Resolved object-prefix f\u00FCr ein Ger\u00E4t (z.B. \"devices.h61be_525f\"). */\n devicePrefix: (device: GoveeDevice) => string;\n /** State-read (volles ID `<namespace>.<prefix>.<channel>.<state>`). */\n getState: (id: string) => Promise<ioBroker.State | null | undefined>;\n /** Send-command via LAN\u2192Cloud-Routing (DeviceManager.sendCommand). */\n sendCommand: (device: GoveeDevice, command: string, value: unknown) => Promise<void>;\n /** Targeted state-tree refresh nach save/delete (snapshot_local Dropdown). */\n refreshDeviceStates: (device: GoveeDevice) => void;\n}\n\n/**\n * Lokaler Snapshot-Manager \u2014 kapselt save/restore/delete f\u00FCr die\n * snapshot_save / snapshot_local / snapshot_delete Dropdown-States.\n *\n * Vorher in `main.ts` als 3 private Methoden mit ~105 Zeilen. Hier in\n * eigenen Klasse mit Host-Interface \u2014 testbar isoliert, main.ts wird\n * kleiner, Maintainability gesteigert.\n */\nexport class SnapshotHandler {\n /**\n * @param host Adapter dependencies via Host-Interface (testbar via Mocks)\n */\n constructor(private readonly host: SnapshotHandlerHost) {}\n\n /**\n * Save current device state as a local snapshot.\n *\n * @param device Target device\n * @param name Snapshot name\n */\n async save(device: GoveeDevice, name: string): Promise<void> {\n const prefix = this.host.devicePrefix(device);\n const ns = this.host.namespace;\n\n // Read device-level state in parallel\n const [powerState, brightState, colorState, ctState] = await Promise.all([\n this.host.getState(`${ns}.${prefix}.control.power`),\n this.host.getState(`${ns}.${prefix}.control.brightness`),\n this.host.getState(`${ns}.${prefix}.control.colorRgb`),\n this.host.getState(`${ns}.${prefix}.control.colorTemperature`),\n ]);\n\n // Read per-segment states in parallel \u2014 sequenziell w\u00E4ren 20\u00D72 reads\n // ~80 ms; parallel = single round-trip.\n let segments: SnapshotSegment[] | undefined;\n const segCount = device.segmentCount ?? 0;\n if (segCount > 0) {\n const segReads: Promise<[ioBroker.State | null | undefined, ioBroker.State | null | undefined]>[] = [];\n for (let i = 0; i < segCount; i++) {\n segReads.push(\n Promise.all([\n this.host.getState(`${ns}.${prefix}.segments.${i}.color`),\n this.host.getState(`${ns}.${prefix}.segments.${i}.brightness`),\n ]),\n );\n }\n const segResults = await Promise.all(segReads);\n segments = segResults.map(([segColor, segBright]) => ({\n color: typeof segColor?.val === \"string\" ? segColor.val : \"#000000\",\n brightness: typeof segBright?.val === \"number\" ? segBright.val : 100,\n }));\n }\n\n const snapshot: LocalSnapshot = {\n name,\n power: powerState?.val === true,\n brightness: typeof brightState?.val === \"number\" ? brightState.val : 0,\n colorRgb: typeof colorState?.val === \"string\" ? colorState.val : \"#000000\",\n colorTemperature: typeof ctState?.val === \"number\" ? ctState.val : 0,\n segments,\n savedAt: Date.now(),\n };\n\n this.host.store.saveSnapshot(device.sku, device.deviceId, snapshot);\n this.host.log.info(`Local snapshot saved: \"${name}\" for ${device.name}`);\n // Targeted refresh \u2014 only this device's snapshot_local dropdown changed.\n this.host.refreshDeviceStates(device);\n }\n\n /**\n * Restore a local snapshot by index.\n *\n * @param device Target device\n * @param val Dropdown index value\n */\n async restore(device: GoveeDevice, val: ioBroker.StateValue): Promise<void> {\n const idx = parseInt(String(val), 10);\n if (idx < 1) {\n return;\n }\n const snaps = this.host.store.getSnapshots(device.sku, device.deviceId);\n const snap = snaps[idx - 1];\n if (!snap) {\n this.host.log.warn(`Local snapshot index ${idx} not found for ${device.name}`);\n return;\n }\n this.host.log.info(`Restoring local snapshot \"${snap.name}\" for ${device.name}`);\n\n // Send each state via LAN \u2192 Cloud routing\n await this.host.sendCommand(device, \"power\", snap.power);\n if (snap.power) {\n await this.host.sendCommand(device, \"brightness\", snap.brightness);\n if (snap.colorTemperature > 0) {\n await this.host.sendCommand(device, \"colorTemperature\", snap.colorTemperature);\n } else {\n await this.host.sendCommand(device, \"colorRgb\", snap.colorRgb);\n }\n // Restore per-segment states via ptReal\n if (snap.segments && snap.segments.length > 0) {\n for (let i = 0; i < snap.segments.length; i++) {\n const seg = snap.segments[i];\n await this.host.sendCommand(device, `segmentColor:${i}`, seg.color);\n await this.host.sendCommand(device, `segmentBrightness:${i}`, seg.brightness);\n }\n }\n }\n }\n\n /**\n * Delete a local snapshot by name.\n *\n * @param device Target device\n * @param name Snapshot name to delete\n */\n delete(device: GoveeDevice, name: string): void {\n if (this.host.store.deleteSnapshot(device.sku, device.deviceId, name)) {\n this.host.log.info(`Local snapshot deleted: \"${name}\" for ${device.name}`);\n // Targeted refresh \u2014 only this device's snapshot_local dropdown changed.\n this.host.refreshDeviceStates(device);\n } else {\n this.host.log.warn(`Local snapshot \"${name}\" not found for ${device.name}`);\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/build/main.js
CHANGED
|
@@ -31,6 +31,7 @@ var import_govee_lan_client = require("./lib/govee-lan-client");
|
|
|
31
31
|
var import_govee_mqtt_client = require("./lib/govee-mqtt-client");
|
|
32
32
|
var import_govee_openapi_mqtt_client = require("./lib/govee-openapi-mqtt-client");
|
|
33
33
|
var import_local_snapshots = require("./lib/local-snapshots");
|
|
34
|
+
var import_snapshot_handler = require("./lib/snapshot-handler");
|
|
34
35
|
var import_cloud_retry = require("./lib/cloud-retry");
|
|
35
36
|
var import_rate_limiter = require("./lib/rate-limiter");
|
|
36
37
|
var import_segment_wizard = require("./lib/segment-wizard");
|
|
@@ -101,6 +102,7 @@ class GoveeAdapter extends utils.Adapter {
|
|
|
101
102
|
// === Sub-Komponenten ===
|
|
102
103
|
skuCache = null;
|
|
103
104
|
localSnapshots = null;
|
|
105
|
+
snapshotHandler = null;
|
|
104
106
|
stateCreationQueue = [];
|
|
105
107
|
lanScanTimer;
|
|
106
108
|
cleanupTimer;
|
|
@@ -183,6 +185,7 @@ class GoveeAdapter extends utils.Adapter {
|
|
|
183
185
|
});
|
|
184
186
|
this.skuCache = new import_sku_cache.SkuCache(dataDir, this.log);
|
|
185
187
|
this.localSnapshots = new import_local_snapshots.LocalSnapshotStore(dataDir, this.log);
|
|
188
|
+
this.snapshotHandler = new import_snapshot_handler.SnapshotHandler(this.buildSnapshotHost());
|
|
186
189
|
this.deviceManager.setSkuCache(this.skuCache);
|
|
187
190
|
const apiClient = new import_govee_api_client.GoveeApiClient();
|
|
188
191
|
apiClient.setEmail(config.goveeEmail);
|
|
@@ -597,20 +600,20 @@ class GoveeAdapter extends utils.Adapter {
|
|
|
597
600
|
return;
|
|
598
601
|
}
|
|
599
602
|
if (stateSuffix === "snapshots.snapshot_save" && typeof val === "string" && val.trim()) {
|
|
600
|
-
await this.
|
|
603
|
+
await this.snapshotHandler.save(device, val.trim());
|
|
601
604
|
await this.setStateAsync(id, { val: "", ack: true });
|
|
602
605
|
return;
|
|
603
606
|
}
|
|
604
607
|
if (stateSuffix === "snapshots.snapshot_local") {
|
|
605
608
|
if (val !== "0" && val !== 0) {
|
|
606
|
-
await this.
|
|
609
|
+
await this.snapshotHandler.restore(device, val);
|
|
607
610
|
await this.resetRelatedDropdowns(prefix, "snapshotLocal");
|
|
608
611
|
}
|
|
609
612
|
await this.setStateAsync(id, { val, ack: true });
|
|
610
613
|
return;
|
|
611
614
|
}
|
|
612
615
|
if (stateSuffix === "snapshots.snapshot_delete" && typeof val === "string" && val.trim()) {
|
|
613
|
-
this.
|
|
616
|
+
this.snapshotHandler.delete(device, val.trim());
|
|
614
617
|
await this.setStateAsync(id, { val: "", ack: true });
|
|
615
618
|
return;
|
|
616
619
|
}
|
|
@@ -1649,110 +1652,26 @@ class GoveeAdapter extends utils.Adapter {
|
|
|
1649
1652
|
});
|
|
1650
1653
|
return response;
|
|
1651
1654
|
}
|
|
1652
|
-
/**
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
]);
|
|
1671
|
-
let segments;
|
|
1672
|
-
const segCount = (_a = device.segmentCount) != null ? _a : 0;
|
|
1673
|
-
if (segCount > 0) {
|
|
1674
|
-
const segReads = [];
|
|
1675
|
-
for (let i = 0; i < segCount; i++) {
|
|
1676
|
-
segReads.push(
|
|
1677
|
-
Promise.all([
|
|
1678
|
-
this.getStateAsync(`${ns}.${prefix}.segments.${i}.color`),
|
|
1679
|
-
this.getStateAsync(`${ns}.${prefix}.segments.${i}.brightness`)
|
|
1680
|
-
])
|
|
1681
|
-
);
|
|
1655
|
+
/** Construct host object for SnapshotHandler — adapter dependencies injected. */
|
|
1656
|
+
buildSnapshotHost() {
|
|
1657
|
+
return {
|
|
1658
|
+
log: this.log,
|
|
1659
|
+
store: this.localSnapshots,
|
|
1660
|
+
namespace: this.namespace,
|
|
1661
|
+
devicePrefix: (device) => {
|
|
1662
|
+
var _a, _b;
|
|
1663
|
+
return (_b = (_a = this.stateManager) == null ? void 0 : _a.devicePrefix(device)) != null ? _b : "";
|
|
1664
|
+
},
|
|
1665
|
+
getState: (id) => this.getStateAsync(id),
|
|
1666
|
+
sendCommand: async (device, command, value) => {
|
|
1667
|
+
var _a;
|
|
1668
|
+
await ((_a = this.deviceManager) == null ? void 0 : _a.sendCommand(device, command, value));
|
|
1669
|
+
},
|
|
1670
|
+
refreshDeviceStates: (device) => {
|
|
1671
|
+
var _a, _b;
|
|
1672
|
+
this.refreshDeviceStates(device, (_b = (_a = this.deviceManager) == null ? void 0 : _a.getDevices()) != null ? _b : []);
|
|
1682
1673
|
}
|
|
1683
|
-
const segResults = await Promise.all(segReads);
|
|
1684
|
-
segments = segResults.map(([segColor, segBright]) => ({
|
|
1685
|
-
color: typeof (segColor == null ? void 0 : segColor.val) === "string" ? segColor.val : "#000000",
|
|
1686
|
-
brightness: typeof (segBright == null ? void 0 : segBright.val) === "number" ? segBright.val : 100
|
|
1687
|
-
}));
|
|
1688
|
-
}
|
|
1689
|
-
const snapshot = {
|
|
1690
|
-
name,
|
|
1691
|
-
power: (powerState == null ? void 0 : powerState.val) === true,
|
|
1692
|
-
brightness: typeof (brightState == null ? void 0 : brightState.val) === "number" ? brightState.val : 0,
|
|
1693
|
-
colorRgb: typeof (colorState == null ? void 0 : colorState.val) === "string" ? colorState.val : "#000000",
|
|
1694
|
-
colorTemperature: typeof (ctState == null ? void 0 : ctState.val) === "number" ? ctState.val : 0,
|
|
1695
|
-
segments,
|
|
1696
|
-
savedAt: Date.now()
|
|
1697
1674
|
};
|
|
1698
|
-
this.localSnapshots.saveSnapshot(device.sku, device.deviceId, snapshot);
|
|
1699
|
-
this.log.info(`Local snapshot saved: "${name}" for ${device.name}`);
|
|
1700
|
-
this.refreshDeviceStates(device, this.deviceManager.getDevices());
|
|
1701
|
-
}
|
|
1702
|
-
/**
|
|
1703
|
-
* Restore a local snapshot by index.
|
|
1704
|
-
*
|
|
1705
|
-
* @param device Target device
|
|
1706
|
-
* @param val Dropdown index value
|
|
1707
|
-
*/
|
|
1708
|
-
async handleSnapshotRestore(device, val) {
|
|
1709
|
-
if (!this.localSnapshots || !this.deviceManager) {
|
|
1710
|
-
return;
|
|
1711
|
-
}
|
|
1712
|
-
const idx = parseInt(String(val), 10);
|
|
1713
|
-
if (idx < 1) {
|
|
1714
|
-
return;
|
|
1715
|
-
}
|
|
1716
|
-
const snaps = this.localSnapshots.getSnapshots(device.sku, device.deviceId);
|
|
1717
|
-
const snap = snaps[idx - 1];
|
|
1718
|
-
if (!snap) {
|
|
1719
|
-
this.log.warn(`Local snapshot index ${idx} not found for ${device.name}`);
|
|
1720
|
-
return;
|
|
1721
|
-
}
|
|
1722
|
-
this.log.info(`Restoring local snapshot "${snap.name}" for ${device.name}`);
|
|
1723
|
-
await this.deviceManager.sendCommand(device, "power", snap.power);
|
|
1724
|
-
if (snap.power) {
|
|
1725
|
-
await this.deviceManager.sendCommand(device, "brightness", snap.brightness);
|
|
1726
|
-
if (snap.colorTemperature > 0) {
|
|
1727
|
-
await this.deviceManager.sendCommand(device, "colorTemperature", snap.colorTemperature);
|
|
1728
|
-
} else {
|
|
1729
|
-
await this.deviceManager.sendCommand(device, "colorRgb", snap.colorRgb);
|
|
1730
|
-
}
|
|
1731
|
-
if (snap.segments && snap.segments.length > 0) {
|
|
1732
|
-
for (let i = 0; i < snap.segments.length; i++) {
|
|
1733
|
-
const seg = snap.segments[i];
|
|
1734
|
-
await this.deviceManager.sendCommand(device, `segmentColor:${i}`, seg.color);
|
|
1735
|
-
await this.deviceManager.sendCommand(device, `segmentBrightness:${i}`, seg.brightness);
|
|
1736
|
-
}
|
|
1737
|
-
}
|
|
1738
|
-
}
|
|
1739
|
-
}
|
|
1740
|
-
/**
|
|
1741
|
-
* Delete a local snapshot by name.
|
|
1742
|
-
*
|
|
1743
|
-
* @param device Target device
|
|
1744
|
-
* @param name Snapshot name to delete
|
|
1745
|
-
*/
|
|
1746
|
-
handleSnapshotDelete(device, name) {
|
|
1747
|
-
if (!this.localSnapshots) {
|
|
1748
|
-
return;
|
|
1749
|
-
}
|
|
1750
|
-
if (this.localSnapshots.deleteSnapshot(device.sku, device.deviceId, name)) {
|
|
1751
|
-
this.log.info(`Local snapshot deleted: "${name}" for ${device.name}`);
|
|
1752
|
-
this.refreshDeviceStates(device, this.deviceManager.getDevices());
|
|
1753
|
-
} else {
|
|
1754
|
-
this.log.warn(`Local snapshot "${name}" not found for ${device.name}`);
|
|
1755
|
-
}
|
|
1756
1675
|
}
|
|
1757
1676
|
/** Dropdowns whose value is a mode-selection — reset to "---" (0) when the mode stops. */
|
|
1758
1677
|
static MODE_DROPDOWNS = [
|
package/build/main.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/main.ts"],
|
|
4
|
-
"sourcesContent": ["import * as utils from \"@iobroker/adapter-core\";\nimport {\n buildDeviceStateDefs,\n getDefaultLanStates,\n mapCloudStateValue,\n planCloudCapabilityWrites,\n} from \"./lib/capability-mapper\";\nimport { getDeviceTier, initDeviceRegistry } from \"./lib/device-registry\";\nimport { DeviceManager, resolveSegmentCount, SEGMENT_HARD_MAX } from \"./lib/device-manager\";\nimport { GoveeApiClient } from \"./lib/govee-api-client\";\nimport { GoveeCloudClient } from \"./lib/govee-cloud-client\";\nimport { GoveeLanClient } from \"./lib/govee-lan-client\";\nimport { GoveeMqttClient } from \"./lib/govee-mqtt-client\";\nimport { GoveeOpenapiMqttClient } from \"./lib/govee-openapi-mqtt-client\";\nimport { LocalSnapshotStore, type LocalSnapshot, type SnapshotSegment } from \"./lib/local-snapshots\";\nimport { CloudRetryLoop, type CloudRetryHost } from \"./lib/cloud-retry\";\nimport { RateLimiter } from \"./lib/rate-limiter\";\nimport { SegmentWizard, wizardIdleText, type WizardHost, type WizardResult } from \"./lib/segment-wizard\";\nimport { SkuCache } from \"./lib/sku-cache\";\nimport { StateManager } from \"./lib/state-manager\";\nimport {\n hexToRgb,\n errMessage,\n parseSegmentList,\n resolveStatesValue,\n rgbIntToHex,\n rgbToHex,\n type AdapterConfig,\n type CloudLoadResult,\n type CloudStateCapability,\n type DeviceState,\n type GoveeDevice,\n type PersistedMqttCredentials,\n} from \"./lib/types\";\nimport { APP_API_INITIAL_DELAY_MS, APP_API_POLL_INTERVAL_MS, READY_TIMEOUT_MS } from \"./lib/timing-constants\";\nimport { GOVEE_APP_VERSION } from \"./lib/govee-constants\";\nimport { httpsRequest } from \"./lib/http-client\";\n\n/**\n * Rate limit defaults \u2014 full Cloud API budget (8/min, 9000/day). v2 no\n * longer halves this with govee-appliances because that adapter is\n * deprecated and won't run alongside govee-smart.\n */\nconst FULL_LIMITS = { perMinute: 8, perDay: 9000 };\n\n/**\n * Minimum gap between two `mqttAuth: requestCode` calls. Govee returns 200\n * for every request and queues another email \u2014 without a throttle, double-clicking\n * the button in admin would spam the user's inbox. 30 s is short enough that a\n * legitimate retry after a failed delivery still feels responsive.\n */\nconst VERIFICATION_REQUEST_THROTTLE_MS = 30_000;\n\n/**\n * State-suffix \u2192 command-name lookup for writable states. Segment indices\n * are dynamic and handled by regex in stateToCommand \u2014 everything else is\n * a straight string mapping.\n */\nconst STATE_TO_COMMAND: Readonly<Record<string, string>> = {\n \"control.power\": \"power\",\n \"control.brightness\": \"brightness\",\n \"control.colorRgb\": \"colorRgb\",\n \"control.colorTemperature\": \"colorTemperature\",\n \"control.scene\": \"scene\",\n \"control.gradient_toggle\": \"gradientToggle\",\n \"scenes.light_scene\": \"lightScene\",\n \"scenes.diy_scene\": \"diyScene\",\n \"scenes.scene_speed\": \"sceneSpeed\",\n \"music.music_mode\": \"music\",\n \"music.music_sensitivity\": \"music\",\n \"music.music_auto_color\": \"music\",\n \"snapshots.snapshot_cloud\": \"snapshot\",\n \"segments.command\": \"segmentBatch\",\n};\n\nclass GoveeAdapter extends utils.Adapter {\n private deviceManager: DeviceManager | null = null;\n private stateManager: StateManager | null = null;\n private lanClient: GoveeLanClient | null = null;\n private mqttClient: GoveeMqttClient | null = null;\n private openapiMqttClient: GoveeOpenapiMqttClient | null = null;\n private cloudClient: GoveeCloudClient | null = null;\n private rateLimiter: RateLimiter | null = null;\n /** Repeating timer for the App-API poll (sensor-state pull). */\n private appApiPollTimer: ioBroker.Interval | undefined;\n /**\n * One-shot timer for the FIRST app-api poll (5s nach start) \u2014 Handle\n * damit onUnload das wegr\u00E4umen kann bevor es ins Leere feuert.\n */\n private appApiInitialTimer: ioBroker.Timeout | undefined;\n /** One-shot timer for cloud-init 60s safety timeout \u2014 gleiches Pattern. */\n private cloudInitTimer: ioBroker.Timeout | undefined;\n /**\n * Letzter info.connection-Wert \u2014 Cache damit nicht jeder device-update\n * einen unn\u00F6tigen setStateAsync macht (H4).\n */\n private lastConnectionState: boolean | null = null;\n // === Lifecycle-Flags (Adapter-Boot-Sequenz) ===\n // checkAllReady() pr\u00FCft alle 5 Voraussetzungen gleichzeitig \u2014 sie laufen\n // parallel ab, kein lineares STATE_MACHINE-Pattern weil Channels\n // unabh\u00E4ngig connecten.\n /** LAN-Scan-Initial-Wait abgeschlossen (3s nach Start). */\n private lanScanDone = false;\n /** State-Tree-Erstellung f\u00FCr alle Cached/Cloud-Devices fertig. */\n private statesReady = false;\n /** Cloud-Init-Phase abgeschlossen (mit oder ohne Erfolg). */\n private cloudInitDone = false;\n /** True nach dem ersten erfolgreichen App-API-Poll (f\u00FCr Sensoren mit Werten). */\n private appApiInitialPollDone = false;\n /** Verhindert Mehrfach-Ready-Log innerhalb derselben Adapter-Session. */\n private readyLogged = false;\n /** Cloud war mindestens einmal connected \u2014 f\u00FCr \u201Erestored\"-Log nach Down. */\n private cloudWasConnected = false;\n /** T\u00E4gliches Interval f\u00FCr App-Version-Drift-Check gegen App-Store. */\n private appVersionCheckTimer: ioBroker.Interval | undefined;\n // === Sub-Komponenten ===\n private skuCache: SkuCache | null = null;\n private localSnapshots: LocalSnapshotStore | null = null;\n private stateCreationQueue: Promise<void>[] = [];\n private lanScanTimer: ioBroker.Timeout | undefined;\n private cleanupTimer: ioBroker.Timeout | undefined;\n private readyTimer: ioBroker.Timeout | undefined;\n private cloudRetry: CloudRetryLoop | null = null;\n private segmentWizard: SegmentWizard | null = null;\n private unhandledRejectionHandler: ((reason: unknown) => void) | null = null;\n private uncaughtExceptionHandler: ((err: Error) => void) | null = null;\n /** Per-device timestamp of the last diagnostics export \u2014 throttle gate */\n private diagnosticsLastRun = new Map<string, number>();\n /** Cached admin language from system.config \u2014 used for wizard UI text */\n private adminLanguage = \"en\";\n /** Last time `requestCode` was triggered via onMessage \u2014 guards against double-click email spam. */\n private lastVerificationRequestMs = 0;\n\n /** @param options Adapter options */\n public constructor(options: Partial<utils.AdapterOptions> = {}) {\n super({ ...options, name: \"govee-smart\" });\n // Per ioBroker rule: async handlers registered on events MUST .catch,\n // otherwise rejections become unhandled \u2192 SIGKILL code 6 \u2192 restart loop.\n this.on(\"ready\", () =>\n this.onReady().catch(e =>\n this.log.error(`onReady crashed: ${e instanceof Error ? (e.stack ?? e.message) : String(e)}`),\n ),\n );\n this.on(\"stateChange\", (id, state) =>\n this.onStateChange(id, state).catch(e => this.log.warn(`onStateChange crashed for ${id}: ${errMessage(e)}`)),\n );\n this.on(\"message\", obj => this.onMessage(obj));\n this.on(\"unload\", callback => this.onUnload(callback));\n // Last-line-of-defence against unhandled rejections / sync throws from\n // fire-and-forget paths. The per-handler .catch() wrappers cover the\n // direct entry points; this catches whatever slips past them so the\n // adapter logs the cause instead of triggering js-controller SIGKILL.\n this.unhandledRejectionHandler = (reason: unknown) => {\n this.log.error(\n `Unhandled rejection: ${reason instanceof Error ? (reason.stack ?? reason.message) : String(reason)}`,\n );\n };\n this.uncaughtExceptionHandler = (err: Error) => {\n this.log.error(`Uncaught exception: ${err.stack ?? err.message}`);\n };\n process.on(\"unhandledRejection\", this.unhandledRejectionHandler);\n process.on(\"uncaughtException\", this.uncaughtExceptionHandler);\n }\n\n /** Adapter started \u2014 initialize all channels */\n private async onReady(): Promise<void> {\n const config = this.config as unknown as AdapterConfig;\n\n // info channel + states are declared as instanceObjects in\n // io-package.json, so js-controller materialises them on install /\n // upgrade. We only initialise the runtime values here.\n await this.setStateAsync(\"info.connection\", { val: false, ack: true });\n await this.setStateAsync(\"info.mqttConnected\", { val: false, ack: true });\n await this.setStateAsync(\"info.cloudConnected\", { val: false, ack: true });\n await this.setStateAsync(\"info.openapiMqttConnected\", {\n val: false,\n ack: true,\n });\n await this.setStateAsync(\"info.refresh_cloud_data\", {\n val: false,\n ack: true,\n });\n // Load admin language from system.config so wizard prose matches the\n // user's Admin UI. Falls back to English on any lookup failure.\n try {\n const sysConf = await this.getForeignObjectAsync(\"system.config\");\n const lang = (sysConf?.common as { language?: string } | undefined)?.language;\n if (typeof lang === \"string\" && lang.length > 0) {\n this.adminLanguage = lang;\n }\n } catch {\n // Keep default \"en\"\n }\n await this.setStateAsync(\"info.wizardStatus\", {\n val: wizardIdleText(this.adminLanguage),\n ack: true,\n });\n\n this.stateManager = new StateManager(this);\n // General groups online state (reflects Cloud connection)\n await this.stateManager.createGroupsOnlineState(false);\n this.deviceManager = new DeviceManager(this.log, this);\n const dataDir = utils.getAbsoluteInstanceDataDir(this);\n\n // Load device registry from devices.json in the adapter package root.\n // Status filter: verified+reported active by default; seed-status entries\n // require the experimentalQuirks config toggle.\n initDeviceRegistry({\n experimental: config.experimentalQuirks === true,\n log: this.log,\n });\n this.skuCache = new SkuCache(dataDir, this.log);\n this.localSnapshots = new LocalSnapshotStore(dataDir, this.log);\n this.deviceManager.setSkuCache(this.skuCache);\n\n // API client for undocumented scene/music/DIY libraries (always available)\n const apiClient = new GoveeApiClient();\n apiClient.setEmail(config.goveeEmail);\n this.deviceManager.setApiClient(apiClient);\n\n this.deviceManager.setCallbacks(\n (device, state) => this.onDeviceStateUpdate(device, state),\n devices => this.onDeviceListChanged(devices),\n );\n\n // Update info.ip when LAN IP changes\n this.deviceManager.onLanIpChanged = (device, ip) => {\n const prefix = this.stateManager!.devicePrefix(device);\n this.setStateAsync(`${prefix}.info.ip`, { val: ip, ack: true }).catch(() => {});\n };\n\n // Sync individual segment states after batch command\n this.deviceManager.onSegmentBatchUpdate = (device, batch) => {\n const prefix = this.stateManager!.devicePrefix(device);\n for (const idx of batch.segments) {\n if (batch.color !== undefined) {\n const hex = rgbIntToHex(batch.color);\n this.setStateAsync(`${prefix}.segments.${idx}.color`, {\n val: hex,\n ack: true,\n }).catch(() => {});\n }\n if (batch.brightness !== undefined) {\n this.setStateAsync(`${prefix}.segments.${idx}.brightness`, {\n val: batch.brightness,\n ack: true,\n }).catch(() => {});\n }\n }\n };\n\n // Sync per-segment states from MQTT BLE status push (AA A5 packets)\n this.deviceManager.onMqttSegmentUpdate = (device, segments) => {\n const prefix = this.stateManager!.devicePrefix(device);\n for (const seg of segments) {\n this.setStateAsync(`${prefix}.segments.${seg.index}.color`, {\n val: rgbToHex(seg.r, seg.g, seg.b),\n ack: true,\n }).catch(() => {});\n this.setStateAsync(`${prefix}.segments.${seg.index}.brightness`, {\n val: seg.brightness,\n ack: true,\n }).catch(() => {});\n }\n };\n\n // When MQTT reveals more segments than the Cloud advertised, rebuild\n // the device's state tree so the extra segments get their datapoints.\n this.deviceManager.onSegmentCountGrown = device => {\n if (!this.stateManager) {\n return;\n }\n this.stateManager.createSegmentStates(device).catch(e => {\n this.log.warn(`Failed to rebuild segment tree for ${device.name} after count growth: ${errMessage(e)}`);\n });\n };\n\n // Log startup with configured channels\n const startChannels: string[] = [\"LAN\"];\n if (config.apiKey) {\n startChannels.push(\"Cloud\");\n }\n if (config.goveeEmail && config.goveePassword) {\n startChannels.push(\"MQTT\");\n }\n this.log.info(`Starting (${startChannels.join(\", \")})`);\n\n // --- LAN (always active) ---\n this.lanClient = new GoveeLanClient(this.log, this);\n this.deviceManager.setLanClient(this.lanClient);\n\n this.lanClient.start(\n lanDevice => {\n this.deviceManager!.handleLanDiscovery(lanDevice);\n // Poll status only when MQTT is unavailable. With an active MQTT\n // subscription Govee pushes state changes authoritatively, so the\n // LAN devStatus request would be duplicate traffic.\n if (!this.mqttClient?.connected) {\n this.lanClient!.requestStatus(lanDevice.ip);\n }\n },\n (sourceIp, status) => {\n this.deviceManager!.handleLanStatus(sourceIp, status);\n },\n 30_000,\n config.networkInterface || \"\",\n );\n\n // Wait for first LAN scan responses (UDP multicast, devices respond within 1-2s)\n this.lanScanTimer = this.setTimeout(() => {\n this.lanScanDone = true;\n this.checkAllReady();\n }, 3_000);\n\n // --- MQTT (if account credentials provided) ---\n // Initialize MQTT before Cloud so scene library can load on first cycle\n if (config.goveeEmail && config.goveePassword) {\n this.mqttClient = new GoveeMqttClient(config.goveeEmail, config.goveePassword, this.log, this);\n\n // Forward every parsed MQTT op.command into the diagnostics ring buffer\n // so diag.export contains the recent packets per device.\n this.mqttClient.setPacketHook((deviceId, topic, hex) => {\n this.deviceManager?.getDiagnostics().addMqttPacket(deviceId, topic, hex);\n });\n\n // 2FA: forward optional code from settings into the next login attempt;\n // clear the field automatically once Govee has accepted it.\n this.mqttClient.setVerificationCode(config.mqttVerificationCode ?? \"\");\n this.mqttClient.setOnVerificationConsumed(() => {\n this.clearVerificationCodeSetting().catch(e => {\n this.log.warn(`Could not clear mqttVerificationCode: ${errMessage(e)}`);\n });\n });\n this.mqttClient.setOnVerificationFailed(reason => {\n // On 'failed' (455 / 454+code-was-sent) blank the code so the user\n // doesn't keep retrying with a stale value. On 'pending' (454 + no\n // code) we leave the field as-is \u2014 the user is about to fill it.\n if (reason === \"failed\") {\n this.clearVerificationCodeSetting().catch(() => {});\n }\n });\n\n // Re-use cached MQTT credentials across restarts. Stored in the\n // info.mqttCredentials state (NOT in adapter native): writing to\n // system.adapter.X.0 native triggers a js-controller adapter\n // restart, which would loop endlessly on every login. States are\n // restart-safe.\n //\n // One-shot: clean up legacy v2.1.0/v2.1.1/v2.1.2 native fields\n // that contained plaintext credentials. Best-effort.\n await this.cleanupLegacyMqttNativeOnce();\n const cachedCreds = await this.loadPersistedCredsFromState();\n if (cachedCreds) {\n this.mqttClient.setPersistedCredentials(cachedCreds);\n }\n this.mqttClient.setOnCredentialsRefresh(creds => {\n this.persistCredsToState(creds).catch(e => {\n this.log.warn(`Could not persist MQTT credentials: ${errMessage(e)}`);\n });\n });\n\n await this.mqttClient.connect(\n update => this.deviceManager!.handleMqttStatus(update),\n connected => {\n this.setStateAsync(\"info.mqttConnected\", {\n val: connected,\n ack: true,\n }).catch(() => {});\n if (connected) {\n this.checkAllReady();\n }\n this.updateConnectionState();\n },\n // Forward every fresh bearer token \u2014 fires on initial login and on\n // each reconnect-login, so the API client never runs with a stale one.\n token => apiClient.setBearerToken(token),\n );\n }\n\n // --- Device data: Cache first, Cloud only on cache miss ---\n const cachedOk = this.deviceManager.loadFromCache();\n\n if (config.apiKey) {\n this.cloudClient = new GoveeCloudClient(config.apiKey, this.log);\n // Capture the most recent Cloud response per (deviceId, endpoint) for\n // diagnostics \u2014 bounded by the DiagnosticsCollector's response slot cap.\n this.cloudClient.setResponseHook((deviceId, endpoint, body) => {\n this.deviceManager?.getDiagnostics().setApiResponse(deviceId, endpoint, body);\n });\n this.deviceManager.setCloudClient(this.cloudClient);\n\n // Bridge synthetic capabilities (App-API, OpenAPI-MQTT events) into the\n // same setState pipeline as polled Cloud state. Keeps mapCloudStateValue\n // as the single source of truth for value coercion + state-id resolution.\n this.deviceManager.setOnCloudCapabilities((device, caps) => {\n this.applyCloudCapabilities(device, caps).catch(e =>\n this.log.warn(`applyCloudCapabilities failed for ${device.sku}: ${errMessage(e)}`),\n );\n });\n\n this.rateLimiter = new RateLimiter(this.log, this, FULL_LIMITS.perMinute, FULL_LIMITS.perDay);\n this.rateLimiter.start();\n this.deviceManager.setRateLimiter(this.rateLimiter);\n\n // OpenAPI-MQTT \u2014 push channel for appliance/sensor events\n // (lackWater, iceFull, bodyAppeared etc.). API key is enough; no\n // separate credentials required. Connection runs in parallel to\n // the AWS-IoT MQTT used for status push of regular devices.\n this.openapiMqttClient = new GoveeOpenapiMqttClient(config.apiKey, this.log, this);\n this.openapiMqttClient.connect(\n event => this.deviceManager?.handleOpenApiEvent(event),\n connected => {\n this.setStateAsync(\"info.openapiMqttConnected\", {\n val: connected,\n ack: true,\n }).catch(() => {});\n },\n );\n\n // App-API poll \u2014 every 2 minutes, pulls state for sensors like H5179\n // where OpenAPI v2 /device/state returns empty. Bearer token comes\n // from the AWS-IoT MQTT login, so a no-op until that succeeds.\n const triggerAppApiPoll = (): void => {\n this.deviceManager\n ?.pollAppApi()\n .then(() => {\n // H2 \u2014 Mark initial-poll-done und re-check Ready damit der\n // Adapter \u201Eready\" loggen kann sobald Sensor-Werte da sind.\n if (!this.appApiInitialPollDone) {\n this.appApiInitialPollDone = true;\n this.checkAllReady();\n }\n })\n .catch(e => this.log.debug(`pollAppApi failed: ${errMessage(e)}`));\n };\n this.appApiPollTimer = this.setInterval(triggerAppApiPoll, APP_API_POLL_INTERVAL_MS);\n // Initial poll: gibt MQTT Zeit f\u00FCr den Bearer-Login. Ohne diesen\n // Sofort-Poll bleiben Sensoren wie H5179 die ersten 2 Minuten nach\n // Start offline (Online-Signal kommt nur via App-API). Handle in\n // Member-Variable damit onUnload den Timer cleart.\n this.appApiInitialTimer = this.setTimeout(triggerAppApiPoll, APP_API_INITIAL_DELAY_MS);\n\n if (!cachedOk) {\n // No cache \u2014 first start, fetch from Cloud with 60s hard-timeout.\n // If Cloud hangs/fails, we don't want to block adapter startup indefinitely.\n const result = await this.cloudInitWithTimeout();\n this.cloudWasConnected = result.ok;\n this.ensureCloudRetry().setConnected(result.ok);\n this.setStateAsync(\"info.cloudConnected\", {\n val: result.ok,\n ack: true,\n }).catch(() => {});\n this.stateManager?.updateGroupsOnline(result.ok).catch(() => {});\n\n if (result.ok) {\n await this.loadCloudStates();\n } else {\n this.handleCloudFailure(result);\n }\n } else {\n this.log.info(\"Using cached device data \u2014 no Cloud calls needed\");\n this.cloudWasConnected = true;\n this.ensureCloudRetry().setConnected(true);\n this.setStateAsync(\"info.cloudConnected\", {\n val: true,\n ack: true,\n }).catch(() => {});\n this.stateManager?.updateGroupsOnline(true).catch(() => {});\n }\n // Load group membership from undocumented API (needs bearer token + device map)\n await this.deviceManager.loadGroupMembers();\n\n this.cloudInitDone = true;\n }\n\n // Wait for all state creation from cache/cloud load to complete.\n // Drain-loop: a callback that fires during the await (e.g. a late LAN\n // discovery) can push fresh promises into the queue \u2014 we need to await\n // those too before flipping statesReady, otherwise the initial state\n // tree would be incomplete on very fast startups.\n while (this.stateCreationQueue.length > 0) {\n const pending = this.stateCreationQueue;\n this.stateCreationQueue = [];\n await Promise.all(pending);\n }\n this.statesReady = true;\n\n // Subscribe to all writable device and group states\n await this.subscribeStatesAsync(\"devices.*\");\n await this.subscribeStatesAsync(\"groups.*\");\n await this.subscribeStatesAsync(\"info.refresh_cloud_data\");\n\n // Cleanup stale devices after initial discovery (30s delay for LAN scan).\n // Reaps devices from every adapter-level map that was keyed on them so the\n // process doesn't leak memory across Cloud-side device turnover.\n this.cleanupTimer = this.setTimeout(() => {\n this.reapStaleDevices().catch(e => this.log.debug(`Device cleanup failed: ${errMessage(e)}`));\n }, 30_000);\n\n // App-Version-Drift-Monitor \u2014 daily check + initial nach 2 min wenn der\n // Adapter-Start ohne MQTT-Login durchgeschlagen ist (z.B. LAN-only).\n this.appVersionCheckTimer = this.setInterval(\n () => {\n this.checkAppVersionDrift().catch(e => this.log.debug(`App version check error: ${errMessage(e)}`));\n },\n 24 * 60 * 60 * 1000,\n );\n this.setTimeout(\n () => {\n this.checkAppVersionDrift().catch(e => this.log.debug(`App version check error: ${errMessage(e)}`));\n },\n 2 * 60 * 1000,\n );\n\n this.updateConnectionState();\n\n // Check if all channels are ready \u2014 may already be true if MQTT connected fast\n this.checkAllReady();\n // Safety timeout: log ready even if a channel takes too long.\n // 60s deckt normalen MQTT-Connect + 1 Reconnect-Attempt ab.\n this.readyTimer = this.setTimeout(() => {\n if (!this.readyLogged) {\n // Safety-Timeout: log ready trotzdem auch wenn ein Channel zu lange\n // braucht. READY_TIMEOUT_MS deckt normalen MQTT-Connect + 1 Reconnect.\n this.readyLogged = true;\n this.logDeviceSummary();\n }\n }, 60_000);\n }\n\n /**\n * Initial Cloud-Load mit 60-Sekunden-Hardtimeout.\n * Blockiert nicht l\u00E4nger \u2014 wenn Cloud h\u00E4ngt, geht Adapter mit LAN+MQTT weiter,\n * und der Retry-Loop probiert's passend zum Fehlergrund erneut.\n */\n private async cloudInitWithTimeout(): Promise<CloudLoadResult> {\n if (!this.deviceManager) {\n return { ok: false, reason: \"transient\" };\n }\n const loadPromise = this.deviceManager.loadFromCloud();\n const timeoutPromise = new Promise<CloudLoadResult>(resolve => {\n this.cloudInitTimer = this.setTimeout(() => resolve({ ok: false, reason: \"transient\" }), READY_TIMEOUT_MS);\n });\n try {\n return await Promise.race([loadPromise, timeoutPromise]);\n } catch {\n return { ok: false, reason: \"transient\" };\n }\n }\n\n /** Build the host object for {@link CloudRetryLoop}. */\n private buildCloudRetryHost(): CloudRetryHost {\n return {\n log: this.log,\n setTimeout: (cb, ms) => this.setTimeout(cb, ms),\n clearTimeout: h => this.clearTimeout(h as ioBroker.Timeout),\n loadFromCloud: () => this.cloudInitWithTimeout(),\n onCloudRestored: async () => {\n this.cloudWasConnected = true;\n this.setStateAsync(\"info.cloudConnected\", {\n val: true,\n ack: true,\n }).catch(() => {});\n this.stateManager?.updateGroupsOnline(true).catch(() => {});\n await this.loadCloudStates();\n },\n };\n }\n\n /** Lazy-initialise the retry loop on first use. */\n private ensureCloudRetry(): CloudRetryLoop {\n if (!this.cloudRetry) {\n this.cloudRetry = new CloudRetryLoop(this.buildCloudRetryHost());\n this.cloudRetry.setConnected(this.cloudWasConnected);\n }\n return this.cloudRetry;\n }\n\n /**\n * React to a Cloud-load outcome \u2014 delegates to {@link CloudRetryLoop}.\n *\n * @param result CloudLoadResult from initial load or retry attempt\n */\n private handleCloudFailure(result: CloudLoadResult): void {\n this.ensureCloudRetry().handleResult(result);\n }\n\n /**\n * React to the user writing `info.refresh_cloud_data = true`. Performs one\n * full Cloud reload cycle so newly created scenes/snapshots from the Govee\n * Home app show up without an adapter restart.\n */\n private async handleManualCloudRefresh(): Promise<void> {\n if (!this.deviceManager || !this.cloudClient) {\n this.log.info(\"Refresh cloud data: no Cloud client configured (API key missing) \u2014 nothing to do\");\n return;\n }\n this.log.info(\"Refresh cloud data: re-fetching scenes and snapshots for all devices\");\n try {\n const changed = await this.deviceManager.refreshSceneData();\n if (changed) {\n await this.loadCloudStates();\n }\n // G2 \u2014 kein \u201Edone\"-Log: User hat den Button gedr\u00FCckt, sieht das\n // Ergebnis am Adapter-Verhalten. \u201Edone\" auf info-level w\u00E4re bei\n // Erfolg redundant (Fehler-Pfad direkt darunter ist warn).\n } catch (e) {\n this.log.warn(`Refresh cloud data failed: ${errMessage(e)}`);\n }\n }\n\n /**\n * Adapter stopping \u2014 MUST be synchronous.\n *\n * @param callback Completion callback\n */\n private onUnload(callback: () => void): void {\n try {\n if (this.lanScanTimer) {\n this.clearTimeout(this.lanScanTimer);\n }\n if (this.cleanupTimer) {\n this.clearTimeout(this.cleanupTimer);\n }\n if (this.readyTimer) {\n this.clearTimeout(this.readyTimer);\n }\n if (this.appApiPollTimer) {\n this.clearInterval(this.appApiPollTimer);\n this.appApiPollTimer = undefined;\n }\n if (this.appApiInitialTimer) {\n this.clearTimeout(this.appApiInitialTimer);\n this.appApiInitialTimer = undefined;\n }\n if (this.cloudInitTimer) {\n this.clearTimeout(this.cloudInitTimer);\n this.cloudInitTimer = undefined;\n }\n if (this.appVersionCheckTimer) {\n this.clearInterval(this.appVersionCheckTimer);\n this.appVersionCheckTimer = undefined;\n }\n this.cloudRetry?.dispose();\n this.segmentWizard?.dispose();\n this.lanClient?.stop();\n this.mqttClient?.disconnect();\n this.openapiMqttClient?.disconnect();\n this.rateLimiter?.stop();\n // Remove process-level handlers so an adapter restart doesn't stack them.\n if (this.unhandledRejectionHandler) {\n process.off(\"unhandledRejection\", this.unhandledRejectionHandler);\n this.unhandledRejectionHandler = null;\n }\n if (this.uncaughtExceptionHandler) {\n process.off(\"uncaughtException\", this.uncaughtExceptionHandler);\n this.uncaughtExceptionHandler = null;\n }\n // onUnload MUST be synchronous \u2014 don't await, but silence potential\n // promise rejection during teardown to avoid \"unhandled rejection\" warnings.\n this.setState(\"info.connection\", { val: false, ack: true }).catch(() => {});\n this.setState(\"info.mqttConnected\", { val: false, ack: true }).catch(() => {});\n this.setState(\"info.openapiMqttConnected\", {\n val: false,\n ack: true,\n }).catch(() => {});\n this.setState(\"info.cloudConnected\", { val: false, ack: true }).catch(() => {});\n } catch {\n // ignore\n }\n callback();\n }\n\n /**\n * Handle state changes from user (write operations).\n *\n * @param id State ID\n * @param state New state value\n */\n private async onStateChange(id: string, state: ioBroker.State | null | undefined): Promise<void> {\n if (!state || state.ack || !this.deviceManager || !this.stateManager) {\n return;\n }\n\n // Global refresh button \u2014 triggers one fresh cloud fetch across all\n // devices and re-builds the state tree. Handy after creating a new\n // snapshot in the Govee Home app without restarting the adapter.\n if (id === `${this.namespace}.info.refresh_cloud_data` && state.val) {\n await this.handleManualCloudRefresh();\n await this.setStateAsync(id, { val: false, ack: true });\n return;\n }\n\n // Find which device this state belongs to\n const localId = id.replace(`${this.namespace}.`, \"\");\n if (!localId.startsWith(\"devices.\") && !localId.startsWith(\"groups.\")) {\n return;\n }\n\n const device = this.findDeviceForState(localId);\n if (!device) {\n return;\n }\n\n // Determine command from state suffix after device prefix\n const prefix = this.stateManager.devicePrefix(device);\n const stateSuffix = localId.slice(prefix.length + 1);\n\n // Resolve dropdown input \u2014 accept Number, numeric String, or label\n // (case-insensitive) against the state's common.states map. Returns\n // the canonical key as String so the rest of the handler sees the\n // same shape it always saw (e.g. \"1\"). Non-dropdown states are\n // passed through unchanged.\n const resolved = await this.resolveDropdownInput(id, state.val);\n if (!resolved.ok) {\n this.log.warn(`Unknown dropdown value for ${id}: ${String(state.val)} \u2014 ignoring`);\n return;\n }\n const val = resolved.val;\n\n // Group fan-out: route commands to each member device\n if (device.sku === \"BaseGroup\" && device.groupMembers) {\n await this.handleGroupFanOut(device, stateSuffix, val);\n await this.setStateAsync(id, { val, ack: true });\n if (stateSuffix === \"scenes.light_scene\" || stateSuffix === \"music.music_mode\") {\n await this.resetRelatedDropdowns(prefix, stateSuffix === \"scenes.light_scene\" ? \"lightScene\" : \"music\");\n }\n return;\n }\n\n // Handle local snapshot commands (no Cloud/MQTT needed)\n if (stateSuffix === \"snapshots.snapshot_save\" && typeof val === \"string\" && val.trim()) {\n await this.handleSnapshotSave(device, val.trim());\n await this.setStateAsync(id, { val: \"\", ack: true });\n return;\n }\n if (stateSuffix === \"snapshots.snapshot_local\") {\n if (val !== \"0\" && val !== 0) {\n await this.handleSnapshotRestore(device, val);\n await this.resetRelatedDropdowns(prefix, \"snapshotLocal\");\n }\n await this.setStateAsync(id, { val, ack: true });\n return;\n }\n if (stateSuffix === \"snapshots.snapshot_delete\" && typeof val === \"string\" && val.trim()) {\n this.handleSnapshotDelete(device, val.trim());\n await this.setStateAsync(id, { val: \"\", ack: true });\n return;\n }\n\n // Manual segments toggle/list \u2014 handler owns the ack because a parse\n // error rewrites manual_mode to false, and an outer ack with the\n // raw value would resurrect the rejected entry.\n if (stateSuffix === \"segments.manual_mode\" || stateSuffix === \"segments.manual_list\") {\n await this.handleManualSegmentsChange(device, stateSuffix, val);\n return;\n }\n\n if (stateSuffix === \"diag.export\" && val) {\n await this.handleDiagnosticsExport(device, prefix, id);\n return;\n }\n\n const command = this.stateToCommand(stateSuffix);\n\n if (!command) {\n await this.handleGenericCapabilityCommand(device, id, stateSuffix, val);\n return;\n }\n\n // Dropdown reset to \"---\" (value 0) \u2014 acknowledge without sending command\n if ((command === \"lightScene\" || command === \"diyScene\" || command === \"snapshot\") && (val === \"0\" || val === 0)) {\n await this.setStateAsync(id, { val, ack: true });\n return;\n }\n\n // Scene speed: store on device, applied on next scene activation.\n // Persist to SKU cache so the user's choice survives a restart.\n if (command === \"sceneSpeed\") {\n const level = typeof val === \"number\" ? val : parseInt(String(val), 10);\n if (!isNaN(level)) {\n device.sceneSpeed = level;\n this.deviceManager?.persistDeviceToCache(device);\n }\n await this.setStateAsync(id, { val, ack: true });\n return;\n }\n\n try {\n // Music mode: combine all music states into one STRUCT command\n if (command === \"music\") {\n // music_mode \"---\" (value 0) \u2014 acknowledge without sending command\n if (stateSuffix === \"music.music_mode\" && (val === \"0\" || val === 0)) {\n await this.setStateAsync(id, { val, ack: true });\n return;\n }\n await this.sendMusicCommand(device, prefix, stateSuffix, val);\n await this.setStateAsync(id, { val, ack: true });\n // Reset scene/snapshot dropdowns when activating music mode\n if (stateSuffix === \"music.music_mode\") {\n await this.resetRelatedDropdowns(prefix, \"music\");\n }\n return;\n }\n\n await this.deviceManager.sendCommand(device, command, val);\n // Optimistic ack\n await this.setStateAsync(id, { val, ack: true });\n // Reset related dropdowns when switching modes.\n // Power-off is a special case \u2014 the device is off, so no mode is\n // active anymore; reset every mode dropdown so the UI reflects the\n // reality (scene/music/snapshot selections are now just history).\n if (command === \"power\" && val === false) {\n await this.resetModeDropdowns(prefix, \"\");\n } else {\n await this.resetRelatedDropdowns(prefix, command);\n }\n } catch (err) {\n this.log.warn(`Command failed for ${device.name}: ${errMessage(err)}`);\n }\n }\n\n /**\n * Resolve a dropdown-state input value against the state's common.states\n * map. Returns the canonical key (always String form) so a user can write\n * either the index (\"1\"), the index as a number (1) or the label name\n * (\"Aurora\", case-insensitive) \u2014 all three land at the same canonical\n * value for the rest of the handler.\n *\n * Non-dropdown states (no common.states), reset sentinels (0/\"0\"/\"\") and\n * non-string/number inputs are passed through unchanged. A dropdown input\n * that doesn't match any key or label returns ok=false so the caller can\n * warn and skip the command.\n *\n * @param id Full state id\n * @param raw Raw input value as provided by the user/script\n */\n private async resolveDropdownInput(\n id: string,\n raw: ioBroker.StateValue,\n ): Promise<{ val: ioBroker.StateValue; ok: boolean }> {\n if (raw === null || raw === undefined) {\n return { val: raw, ok: true };\n }\n // Reset sentinels \u2014 let the existing branch handle them.\n if (raw === 0 || raw === \"0\" || raw === \"\") {\n return { val: raw, ok: true };\n }\n // Only dropdown candidates have common.states; non-dropdown inputs\n // can't be resolved here so they pass through.\n if (typeof raw !== \"number\" && typeof raw !== \"string\") {\n return { val: raw, ok: true };\n }\n const obj = await this.getObjectAsync(id);\n const states = obj?.common?.states;\n if (!states || typeof states !== \"object\") {\n return { val: raw, ok: true };\n }\n const resolved = resolveStatesValue(raw, states as Record<string, string>);\n if (resolved) {\n return { val: resolved.key, ok: true };\n }\n return { val: raw, ok: false };\n }\n\n /**\n * Build and send a music_setting STRUCT command.\n * Reads sibling music state values and combines them into one API call.\n *\n * @param device Target device\n * @param prefix Device state prefix\n * @param changedSuffix Which music state was changed\n * @param newValue New value for the changed state\n */\n private async sendMusicCommand(\n device: GoveeDevice,\n prefix: string,\n changedSuffix: string,\n newValue: ioBroker.StateValue,\n ): Promise<void> {\n const musicBase = `${this.namespace}.${prefix}.music`;\n\n // Read current sibling values\n const modeState = await this.getStateAsync(`${musicBase}.music_mode`);\n const sensState = await this.getStateAsync(`${musicBase}.music_sensitivity`);\n const autoState = await this.getStateAsync(`${musicBase}.music_auto_color`);\n\n // Apply the changed value, use siblings for the rest\n const musicMode =\n changedSuffix === \"music.music_mode\" ? parseInt(String(newValue), 10) : parseInt(String(modeState?.val ?? 0), 10);\n const sensitivity =\n changedSuffix === \"music.music_sensitivity\" ? (newValue as number) : ((sensState?.val as number) ?? 100);\n const autoColor = changedSuffix === \"music.music_auto_color\" ? (newValue ? 1 : 0) : autoState?.val ? 1 : 0;\n\n if (!musicMode || musicMode === 0) {\n this.log.debug(\"Music mode not selected, skipping command\");\n return;\n }\n\n // LAN first: send via ptReal BLE if device is on LAN\n if (device.lanIp && this.lanClient) {\n // Read current color for RGB-modes (Spectrum=1, Rolling=2)\n let r = 0,\n g = 0,\n b = 0;\n if (musicMode === 1 || musicMode === 2) {\n const colorState = await this.getStateAsync(`${this.namespace}.${prefix}.control.colorRgb`);\n if (colorState?.val && typeof colorState.val === \"string\") {\n ({ r, g, b } = hexToRgb(colorState.val));\n }\n }\n this.lanClient.setMusicMode(device.lanIp, musicMode, r, g, b);\n return;\n }\n\n // Cloud fallback\n const structValue: Record<string, unknown> = {\n musicMode,\n sensitivity,\n autoColor,\n };\n\n await this.deviceManager!.sendCapabilityCommand(\n device,\n \"devices.capabilities.music_setting\",\n \"musicMode\",\n structValue,\n );\n }\n\n /**\n * Called by device-manager when a device state changes\n *\n * @param device Updated device\n * @param state Changed state values\n */\n private onDeviceStateUpdate(device: GoveeDevice, state: Partial<DeviceState>): void {\n if (this.stateManager) {\n this.stateManager.updateDeviceState(device, state).catch(() => {});\n }\n this.updateConnectionState();\n\n // Update group reachability when member online status changes\n if (state.online !== undefined) {\n this.updateGroupReachability();\n }\n\n // Mirror power-off to mode-dropdown reset. Covers MQTT/LAN-initiated\n // power changes (Govee app or physical remote) so the UI stays honest:\n // a device that's off can't be \"playing Aurora-A\" anymore.\n // L11 \u2014 defensive auch 0 als false akzeptieren (Govee schickt Power\n // theoretisch als boolean, aber MQTT-Boundary k\u00F6nnte 0 durchschleusen).\n const powerOff = state.power === false || (state.power as unknown) === 0;\n if (powerOff && this.stateManager) {\n const prefix = this.stateManager.devicePrefix(device);\n this.resetModeDropdowns(prefix, \"\").catch(() => undefined);\n }\n }\n\n /**\n * Fan out a group command to all member devices.\n * Basic controls (power, brightness, color) are sent directly.\n * Scenes/music are matched by name across members.\n *\n * @param group BaseGroup device\n * @param stateSuffix State path suffix (e.g. \"control.power\")\n * @param value Command value\n */\n private async handleGroupFanOut(group: GoveeDevice, stateSuffix: string, value: ioBroker.StateValue): Promise<void> {\n if (!this.deviceManager || !group.groupMembers) {\n return;\n }\n\n const devices = this.deviceManager.getDevices();\n const members = this.resolveGroupMembers(group, devices).filter(d => d.state.online);\n\n if (members.length === 0) {\n this.log.debug(`Group \"${group.name}\": no reachable members for fan-out`);\n return;\n }\n\n const command = this.stateToCommand(stateSuffix);\n if (!command) {\n return;\n }\n\n // Dropdown reset \u2014 no command needed\n if ((command === \"lightScene\" || command === \"music\") && (value === \"0\" || value === 0)) {\n return;\n }\n\n for (const member of members) {\n try {\n if (command === \"lightScene\") {\n await this.fanOutScene(group, member, value);\n } else if (command === \"music\") {\n await this.fanOutMusic(group, member, stateSuffix, value);\n } else {\n await this.deviceManager.sendCommand(member, command, value);\n }\n } catch (err) {\n this.log.debug(`Group fan-out to ${member.name}: ${errMessage(err)}`);\n }\n }\n }\n\n /**\n * Fan out a scene command: match group scene name to member scene index.\n *\n * @param group BaseGroup device\n * @param member Target member device\n * @param value Dropdown index value\n */\n private async fanOutScene(group: GoveeDevice, member: GoveeDevice, value: ioBroker.StateValue): Promise<void> {\n if (!this.deviceManager || !this.stateManager) {\n return;\n }\n\n // Get group scene name from dropdown value (1-based index)\n const groupPrefix = this.stateManager.devicePrefix(group);\n const obj = await this.getObjectAsync(`${this.namespace}.${groupPrefix}.scenes.light_scene`);\n const groupStates = obj?.common?.states as Record<string, string> | undefined;\n const sceneName = groupStates?.[String(value)];\n if (!sceneName) {\n return;\n }\n\n // Find the same scene name in the member's scene list (1-based)\n const memberIdx = member.scenes.findIndex(s => s.name === sceneName);\n if (memberIdx >= 0) {\n await this.deviceManager.sendCommand(member, \"lightScene\", memberIdx + 1);\n }\n }\n\n /**\n * Fan out a music command: match group music name to member music index.\n *\n * @param group BaseGroup device\n * @param member Target member device\n * @param stateSuffix Music state path suffix\n * @param value Command value\n */\n private async fanOutMusic(\n group: GoveeDevice,\n member: GoveeDevice,\n stateSuffix: string,\n value: ioBroker.StateValue,\n ): Promise<void> {\n if (!this.deviceManager || !this.stateManager) {\n return;\n }\n\n // For sensitivity/auto_color, forward directly \u2014 these are numeric values\n if (stateSuffix !== \"music.music_mode\") {\n await this.sendMusicCommand(member, this.stateManager.devicePrefix(member), stateSuffix, value);\n return;\n }\n\n // Get group music name from dropdown value (1-based index)\n const groupPrefix = this.stateManager.devicePrefix(group);\n const obj = await this.getObjectAsync(`${this.namespace}.${groupPrefix}.music.music_mode`);\n const groupStates = obj?.common?.states as Record<string, string> | undefined;\n const musicName = groupStates?.[String(value)];\n if (!musicName) {\n return;\n }\n\n // Find the same music name in the member's music library (1-based)\n const memberIdx = member.musicLibrary.findIndex(m => m.name === musicName);\n if (memberIdx >= 0) {\n // Build the music command struct for the member\n const memberPrefix = this.stateManager.devicePrefix(member);\n // Temporarily write the music mode value to trigger the member's music command\n await this.sendMusicCommand(member, memberPrefix, \"music.music_mode\", memberIdx + 1);\n }\n }\n\n /**\n * Resolve group member references to actual device objects.\n *\n * @param group BaseGroup device with groupMembers\n * @param devices Full device list to search\n */\n private resolveGroupMembers(group: GoveeDevice, devices: GoveeDevice[]): GoveeDevice[] {\n if (!group.groupMembers) {\n return [];\n }\n return group.groupMembers\n .map(m => devices.find(d => d.sku === m.sku && d.deviceId === m.deviceId))\n .filter((d): d is GoveeDevice => d !== undefined);\n }\n\n /**\n * Recalculate info.membersUnreachable for all groups.\n * Called when any device's online status changes.\n */\n private updateGroupReachability(): void {\n if (!this.deviceManager || !this.stateManager) {\n return;\n }\n const devices = this.deviceManager.getDevices();\n for (const group of devices) {\n if (group.sku !== \"BaseGroup\" || !group.groupMembers) {\n continue;\n }\n const memberDevices = this.resolveGroupMembers(group, devices);\n this.stateManager.updateGroupMembersUnreachable(group, memberDevices).catch(() => {});\n }\n }\n\n /**\n * Rebuild state definitions for one device and feed them into StateManager.\n * Used both from the full-list callback and from targeted refreshes\n * (e.g. after a local snapshot was added or removed \u2014 no reason to rebuild\n * the entire tree for every device then).\n *\n * @param device Target device\n * @param allDevices Full device list (needed to resolve group members)\n */\n private refreshDeviceStates(device: GoveeDevice, allDevices: GoveeDevice[]): void {\n if (!this.stateManager) {\n return;\n }\n const localSnaps = this.localSnapshots?.getSnapshots(device.sku, device.deviceId);\n let memberDevices: GoveeDevice[] | undefined;\n if (device.sku === \"BaseGroup\" && device.groupMembers) {\n memberDevices = this.resolveGroupMembers(device, allDevices);\n }\n const stateDefs = buildDeviceStateDefs(device, localSnaps, memberDevices);\n const p = this.stateManager\n .createDeviceStates(device, stateDefs)\n .then(async () => {\n // v2.1.0 \u2192 v2.1.1 layout migration: drop legacy info.diagnostics_*\n // before publishing the new diag.tier value. Idempotent.\n await this.stateManager?.migrateLegacyDiagnostics(device);\n await this.stateManager?.updateDeviceTier(device, getDeviceTier(device.sku));\n })\n .catch(e => {\n this.log.error(`createDeviceStates failed for ${device.name}: ${errMessage(e)}`);\n });\n // Until ready, collect so onReady can await the whole initial batch.\n // After ready, fire-and-forget \u2014 the queue would otherwise keep growing\n // with resolved promises for the lifetime of the adapter.\n if (!this.statesReady) {\n this.stateCreationQueue.push(p);\n } else {\n void p;\n }\n }\n\n /**\n * Called by device-manager when the device list changes\n *\n * @param devices Current list of all devices\n */\n private onDeviceListChanged(devices: GoveeDevice[]): void {\n if (!this.stateManager) {\n return;\n }\n\n for (const device of devices) {\n this.refreshDeviceStates(device, devices);\n }\n\n this.updateConnectionState();\n // Cache sync happens once after the initial setup completes (see\n // checkAllReady) \u2014 triggering here would fire on every device update\n // and spam the log.\n\n // Keep adapter-level per-device maps (diagnosticsLastRun, ...) aligned\n // with the new device list so removed devices don't leave orphan keys.\n // Skip during the initial boot phase \u2014 the startup cleanupTimer handles\n // that pass with proper LAN-scan-settled timing.\n if (this.statesReady) {\n this.reapStaleDevices().catch(() => undefined);\n }\n }\n\n /**\n * Update global `info.connection` \u2014 der ioBroker-IDC-Indikator.\n *\n * Semantik:\n * - Mit Devices: `connected = true` wenn MIND. ein Device online ist.\n * Wenn alle offline \u2192 false (User sieht: kein Device antwortet).\n * - Ohne Devices: `connected = true` wenn der LAN-Stack l\u00E4uft. Sonst\n * false (z.B. EADDRINUSE oder bind-Fehler).\n *\n * H4 (geplant f\u00FCr Phase H): nur bei tats\u00E4chlichem Wechsel schreiben\n * (cache lastConnectedValue). Aktuell l\u00E4uft updateConnectionState bei\n * jedem device-state-update \u2014 fire-and-forget setStateAsync, nur leichter\n * Overhead.\n */\n private updateConnectionState(): void {\n const devices = this.deviceManager?.getDevices() ?? [];\n const hasDevices = devices.length > 0;\n const anyOnline = devices.some(d => d.state.online);\n const lanRunning = this.lanClient !== null;\n const connected = hasDevices ? anyOnline : lanRunning;\n if (connected !== this.lastConnectionState) {\n this.lastConnectionState = connected;\n this.setStateAsync(\"info.connection\", { val: connected, ack: true }).catch(() => {});\n }\n }\n\n /**\n * Delete ioBroker objects for devices no longer present and drop the same\n * devices from adapter-level maps. Called after the initial-discovery\n * window and every time the device list changes.\n *\n * Scope of \"stale\" today: cleanupDevices compares the ioBroker object tree\n * against the live device-manager registry \u2014 it deletes objects that\n * outlive their entry in `DeviceManager.devices`. In v2.0 that registry is\n * monotonically growing within a single adapter lifetime (entries only\n * leave via cache pruning across restarts), so this primarily catches\n * tree leftovers from a previous adapter version after upgrade. The\n * adapter-level `diagnosticsLastRun` map is also reaped so it can't outlive\n * its devices either.\n *\n * A future stale-pruning step that explicitly retires devices from the\n * device-manager registry should also drop the device from\n * `deviceManager.devices` and call `getDiagnostics().forget(deviceId)` for\n * each retired device \u2014 those reaping APIs come in with the pruning patch,\n * not before (Memory `feedback_kein_phantom_schema`).\n */\n /**\n * App-Version-Drift-Check gegen iTunes-Lookup.\n *\n * Govee's app2.govee.com-Endpoints rejecten manchmal sehr alte\n * User-Agent-Strings. Daily-Check fragt iTunes nach der aktuellen\n * iOS-App-Version + vergleicht mit `GOVEE_APP_VERSION`. Bei Drift > 2\n * minor versions: warn-Log + state `info.appVersionDrift` schreiben.\n *\n * Failures (5xx, network) werden silent debug-geloggt \u2014 kein User-Impact.\n */\n private async checkAppVersionDrift(): Promise<void> {\n try {\n const resp = await httpsRequest<{ resultCount?: number; results?: Array<{ version?: string }> }>({\n method: \"GET\",\n url: \"https://itunes.apple.com/lookup?bundleId=com.ihoment.GoVeeSensor\",\n headers: { \"User-Agent\": \"ioBroker.govee-smart\" },\n timeout: 10_000,\n });\n const liveVersion = resp.results?.[0]?.version;\n if (typeof liveVersion !== \"string\" || liveVersion.length === 0) {\n return;\n }\n const localParts = GOVEE_APP_VERSION.split(\".\").map(Number);\n const liveParts = liveVersion.split(\".\").map(Number);\n // Major + Minor vergleichen \u2014 patch-Differenz ist OK, Govee toleriert\n // bis ~2 minor.\n const localMajor = localParts[0] ?? 0;\n const localMinor = localParts[1] ?? 0;\n const liveMajor = liveParts[0] ?? 0;\n const liveMinor = liveParts[1] ?? 0;\n const liveTotal = liveMajor * 100 + liveMinor;\n const localTotal = localMajor * 100 + localMinor;\n const driftMinor = liveTotal - localTotal;\n const driftMessage =\n driftMinor === 0\n ? `current (live=${liveVersion}, local=${GOVEE_APP_VERSION})`\n : driftMinor <= 2\n ? `minor drift (live=${liveVersion}, local=${GOVEE_APP_VERSION})`\n : `STALE (live=${liveVersion}, local=${GOVEE_APP_VERSION}) \u2014 bump GOVEE_APP_VERSION`;\n await this.setStateAsync(\"info.appVersionDrift\", { val: driftMessage, ack: true }).catch(() => undefined);\n if (driftMinor > 2) {\n this.log.warn(\n `Govee app version drift: live ${liveVersion} vs local ${GOVEE_APP_VERSION} \u2014 undocumented endpoints may start failing. Run sync-govee-app-version.py + release a new adapter version.`,\n );\n } else {\n this.log.debug(`App version: ${driftMessage}`);\n }\n } catch (e) {\n this.log.debug(`App version check failed: ${errMessage(e)}`);\n }\n }\n\n private async reapStaleDevices(): Promise<void> {\n if (!this.stateManager || !this.deviceManager) {\n return;\n }\n const currentDevices = this.deviceManager.getDevices();\n await this.stateManager.cleanupDevices(currentDevices);\n\n // Diagnostics-buffer f\u00FCr entfernte Devices droppen damit Logs/Packets/\n // Responses l\u00E4ngst entfernter Ger\u00E4te nicht im Speicher bleiben.\n const liveDeviceIds = new Set(currentDevices.map(d => d.deviceId));\n this.deviceManager.getDiagnostics().pruneOrphans(liveDeviceIds);\n\n const liveKeys = new Set(currentDevices.map(d => `${d.sku}:${d.deviceId}`));\n for (const key of this.diagnosticsLastRun.keys()) {\n if (!liveKeys.has(key)) {\n this.diagnosticsLastRun.delete(key);\n }\n }\n }\n\n /**\n * Check if all configured channels are initialized and log ready message.\n * Called from MQTT onConnection callback and end of onReady.\n */\n private checkAllReady(): void {\n if (this.readyLogged) {\n return;\n }\n // Wait for first LAN scan (always active)\n if (!this.lanScanDone) {\n return;\n }\n // Wait for initial state creation to complete\n if (!this.statesReady) {\n return;\n }\n // Wait for Cloud init if configured\n if (this.cloudClient && !this.cloudInitDone) {\n return;\n }\n // Wait for MQTT connection if configured\n if (this.mqttClient && !this.mqttClient.connected) {\n return;\n }\n // H1 \u2014 Wait for OpenAPI-MQTT (Cloud-events for sensors/appliances)\n // wenn konfiguriert. Vorher fehlte das \u2014 Adapter war \"ready\" obwohl\n // Sensor-Events-Channel nicht connected war.\n if (this.openapiMqttClient && !this.openapiMqttClient.connected) {\n return;\n }\n // H2 \u2014 Wait for first App-API-Poll wenn ein Sensor-Device angemeldet\n // ist. App-API liefert die Sensor-Werte (H5179 Temp/Humidity/Battery).\n // Ohne diesen Check w\u00E4re der Adapter \"ready\" mit leeren Sensor-Werten\n // f\u00FCr ~2 Minuten.\n if (this.deviceManager?.hasDeviceNeedingAppApi() && !this.appApiInitialPollDone) {\n return;\n }\n this.readyLogged = true;\n this.logDeviceSummary();\n // Persist any learned changes from the initial load (e.g. resolveSegmentCount\n // collapsing Cloud's 15 to the real 10 on H70D1). One-shot on first ready;\n // subsequent mutations persist themselves (MQTT bumps, wizard, manual-mode).\n this.deviceManager?.saveDevicesToCache();\n }\n\n /**\n * Log final ready message with device/group/channel summary.\n */\n private logDeviceSummary(): void {\n if (!this.deviceManager) {\n return;\n }\n const all = this.deviceManager.getDevices();\n const devices = all.filter(d => d.sku !== \"BaseGroup\");\n const groups = all.filter(d => d.sku === \"BaseGroup\");\n\n // H5 \u2014 Ready-Log expliziter: Channel-Status (LAN+Cloud+MQTT+Cloud-events)\n // plus Devices-Online-Count plus Sensor-Initial-Poll-Marker. User soll\n // EINEN Blick auf den Log werfen und sehen was wirklich operational ist.\n const channels: string[] = [\"LAN\"];\n if (this.cloudWasConnected) {\n channels.push(\"Cloud\");\n }\n if (this.mqttClient?.connected) {\n channels.push(\"MQTT\");\n }\n if (this.openapiMqttClient?.connected) {\n channels.push(\"Cloud-events\");\n }\n\n // Device-summary mit Online-Count\n const lightDevices = devices.filter(d => d.type === \"devices.types.light\");\n const onlineDevices = devices.filter(d => d.state.online === true);\n const parts: string[] = [];\n if (devices.length > 0) {\n const onlineLights = lightDevices.filter(d => d.state.online === true).length;\n const totalLights = lightDevices.length;\n if (totalLights > 0) {\n parts.push(\n totalLights === onlineLights\n ? `${totalLights} light${totalLights > 1 ? \"s\" : \"\"} online`\n : `${totalLights} light${totalLights > 1 ? \"s\" : \"\"} (${onlineLights} online, ${totalLights - onlineLights} offline)`,\n );\n }\n const sensors = devices.length - lightDevices.length;\n if (sensors > 0) {\n const onlineSensors = onlineDevices.filter(d => d.type !== \"devices.types.light\").length;\n parts.push(`${sensors} sensor${sensors > 1 ? \"s\" : \"\"} (${onlineSensors} with data)`);\n }\n }\n if (groups.length > 0) {\n parts.push(`${groups.length} group${groups.length > 1 ? \"s\" : \"\"}`);\n }\n const summary = parts.length > 0 ? parts.join(\", \") : \"no devices found\";\n this.log.info(`Govee adapter ready \u2014 ${summary} \u2014 channels: ${channels.join(\"+\")}`);\n\n // Surface configured-but-not-connected channels with a concrete reason.\n // Truthful \u2014 never claim \"still pending\" when the channel actually failed.\n if (this.cloudClient && !this.cloudWasConnected) {\n const reason = this.cloudClient.getFailureReason();\n this.log.warn(reason ? `Cloud not connected: ${reason}` : \"Cloud not connected \u2014 see earlier errors\");\n }\n if (this.mqttClient && !this.mqttClient.connected) {\n const reason = this.mqttClient.getFailureReason();\n this.log.warn(reason ? `MQTT not connected: ${reason}` : \"MQTT not connected \u2014 see earlier errors\");\n }\n }\n\n /**\n * Load current state for all Cloud devices and populate state values.\n * Called once after initial Cloud device list load.\n */\n private async loadCloudStates(): Promise<void> {\n if (!this.cloudClient || !this.deviceManager || !this.stateManager) {\n return;\n }\n\n const devices = this.deviceManager.getDevices();\n // LAN-first: never overwrite LAN states with Cloud values\n const lanStateIds = new Set(getDefaultLanStates().map(s => s.id));\n let loaded = 0;\n\n for (const device of devices) {\n if (!device.channels.cloud || device.capabilities.length === 0) {\n continue;\n }\n\n try {\n const caps = await this.cloudClient.getDeviceState(device.sku, device.deviceId);\n const prefix = this.stateManager.devicePrefix(device);\n\n const writes: Promise<unknown>[] = [];\n for (const cap of caps) {\n const mapped = mapCloudStateValue(cap);\n if (!mapped) {\n continue;\n }\n // Skip LAN-covered states for LAN-capable devices\n if (device.lanIp && lanStateIds.has(mapped.stateId)) {\n continue;\n }\n const statePath = this.stateManager.resolveStatePath(prefix, mapped.stateId);\n // Fire-and-forget \u2014 States are created before loadCloudStates runs;\n // a rejection here means the state was deleted out-of-band and\n // can be safely ignored.\n writes.push(\n this.setStateAsync(statePath, {\n val: mapped.value,\n ack: true,\n }).catch(() => undefined),\n );\n }\n await Promise.all(writes);\n loaded++;\n } catch {\n this.log.debug(`Could not load Cloud state for ${device.name} (${device.sku})`);\n }\n }\n\n if (loaded > 0) {\n this.log.debug(`Cloud states loaded for ${loaded} devices`);\n }\n }\n\n /**\n * Apply a list of synthesized Cloud-state capabilities to a single\n * device \u2014 the App-API poll and OpenAPI-MQTT events both use this path\n * so their values flow through the same `mapCloudStateValue` pipeline\n * that polled Cloud states use.\n *\n * @param device Target Govee device\n * @param caps Capabilities to apply\n */\n private async applyCloudCapabilities(device: GoveeDevice, caps: CloudStateCapability[]): Promise<void> {\n if (!this.stateManager) {\n return;\n }\n const lanStateIds = new Set(getDefaultLanStates().map(s => s.id));\n const prefix = this.stateManager.devicePrefix(device);\n const planned = planCloudCapabilityWrites(caps, Boolean(device.lanIp), lanStateIds);\n // App-API and OpenAPI-MQTT deliver state IDs (battery, temperature,\n // humidity, lackWater, \u2026) that the Cloud-capability pipeline doesn't\n // declare for sensor/appliance SKUs \u2014 the state objects therefore\n // don't exist yet on first write. ensureSyntheticStateObject creates\n // them lazily with the right channel + role + unit.\n for (const mapped of planned) {\n await this.stateManager.ensureSyntheticStateObject(prefix, mapped.stateId);\n }\n const writes = planned.map(mapped => {\n const statePath = this.stateManager!.resolveStatePath(prefix, mapped.stateId);\n return this.setStateAsync(statePath, {\n val: mapped.value,\n ack: true,\n }).catch(() => undefined);\n });\n await Promise.all(writes);\n }\n\n /**\n * Find device for a state ID\n *\n * @param localId Local state ID without namespace prefix\n */\n private findDeviceForState(localId: string): GoveeDevice | undefined {\n if (!this.deviceManager || !this.stateManager) {\n return undefined;\n }\n\n for (const device of this.deviceManager.getDevices()) {\n const prefix = this.stateManager.devicePrefix(device);\n if (localId.startsWith(`${prefix}.`)) {\n return device;\n }\n }\n return undefined;\n }\n\n /**\n * Map state suffix to command name.\n *\n * Simple suffixes live in a lookup table, segment indices need regex\n * extraction because they're dynamic. The three music states all route\n * to the same \"music\" command \u2014 the handler reads sibling values.\n *\n * @param suffix State ID suffix (e.g. \"power\", \"brightness\")\n */\n private stateToCommand(suffix: string): string | null {\n const direct = STATE_TO_COMMAND[suffix];\n if (direct) {\n return direct;\n }\n const segColorMatch = /^segments\\.(\\d+)\\.color$/.exec(suffix);\n if (segColorMatch) {\n return `segmentColor:${segColorMatch[1]}`;\n }\n const segBrightMatch = /^segments\\.(\\d+)\\.brightness$/.exec(suffix);\n if (segBrightMatch) {\n return `segmentBrightness:${segBrightMatch[1]}`;\n }\n return null;\n }\n\n /**\n * Central entry point for manual-segment updates. Sets the device flags,\n * rebuilds the segment tree (which writes manual_mode + manual_list with\n * ack=true), and persists to cache. Both the user state-change handler\n * and the wizard route their final decisions here.\n *\n * @param device Target device\n * @param mode Whether manual mode should be active\n * @param indices Physical indices when mode=true, ignored otherwise\n */\n private async applyManualSegments(device: GoveeDevice, mode: boolean, indices?: number[]): Promise<void> {\n if (!this.stateManager) {\n return;\n }\n device.manualMode = mode;\n device.manualSegments = mode && Array.isArray(indices) && indices.length > 0 ? indices.slice() : undefined;\n await this.stateManager.createSegmentStates(device);\n this.deviceManager?.persistDeviceToCache(device);\n }\n\n /**\n * React to manual-segments state changes \u2014 parses list, forwards to\n * {@link applyManualSegments}. On parse error disables manual mode so the\n * rejected value doesn't survive in the state tree.\n *\n * @param device Target device\n * @param suffix State suffix (either \"segments.manual_mode\" or \"segments.manual_list\")\n * @param newValue Written value\n */\n private async handleManualSegmentsChange(device: GoveeDevice, suffix: string, newValue: unknown): Promise<void> {\n // Peer value that wasn't just written comes from the device object\n // (kept in sync via createSegmentStates), not a separate state read.\n const modeVal = suffix === \"segments.manual_mode\" ? Boolean(newValue) : device.manualMode === true;\n const listVal =\n suffix === \"segments.manual_list\"\n ? typeof newValue === \"string\"\n ? newValue\n : \"\"\n : Array.isArray(device.manualSegments)\n ? device.manualSegments.join(\",\")\n : \"\";\n\n if (!modeVal) {\n this.log.info(`${device.name}: manual segments disabled \u2014 strip treated as contiguous`);\n await this.applyManualSegments(device, false);\n return;\n }\n\n // Upper bound: cap at the real length if known, otherwise the protocol limit.\n // Real length can still grow via MQTT discovery, so SEGMENT_HARD_MAX is the\n // absolute safety net.\n const maxIndex =\n typeof device.segmentCount === \"number\" && device.segmentCount > 0 ? device.segmentCount - 1 : SEGMENT_HARD_MAX;\n const parsed = parseSegmentList(listVal, maxIndex);\n if (parsed.error) {\n this.log.warn(`${device.name}: manual_list invalid (${parsed.error}) \u2014 disabling manual mode`);\n await this.applyManualSegments(device, false);\n return;\n }\n\n this.log.debug(`${device.name}: manual segments active \u2014 ${parsed.indices.length} physical indices (${listVal})`);\n await this.applyManualSegments(device, true, parsed.indices);\n }\n\n // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Segment-Detection-Wizard \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Handle incoming sendTo messages (from jsonConfig).\n *\n * @param obj ioBroker message object\n */\n private onMessage(obj: ioBroker.Message): void {\n if (!obj?.command) {\n return;\n }\n // Never let a rejection bubble up from the event handler \u2014 the ioBroker\n // event emitter doesn't catch it, which would crash the adapter.\n this.handleMessage(obj).catch(e => {\n this.log.warn(`onMessage handler crashed for ${obj.command}: ${errMessage(e)}`);\n this.sendMessageResponse(obj, {\n error: e instanceof Error ? e.message : String(e),\n });\n });\n }\n\n private async handleMessage(obj: ioBroker.Message): Promise<void> {\n try {\n if (obj.command === \"getSegmentDevices\") {\n const devices = this.deviceManager?.getDevices() ?? [];\n const list = devices\n .filter(d => d.sku !== \"BaseGroup\" && d.state?.online === true && resolveSegmentCount(d) > 0)\n .map(d => {\n const count = resolveSegmentCount(d);\n return {\n value: this.deviceKeyFor(d),\n label: `${d.name} (${d.sku}, bisher ${count} Segmente)`,\n };\n });\n // selectSendTo expects the array directly, not wrapped in an object\n this.sendMessageResponse(obj, list);\n return;\n }\n if (obj.command === \"segmentWizard\") {\n const payload = (obj.message ?? {}) as {\n action?: string;\n device?: string;\n };\n const response = await this.runWizardStep(payload.action ?? \"\", payload.device ?? \"\");\n this.sendMessageResponse(obj, response);\n return;\n }\n if (obj.command === \"mqttAuth\") {\n const payload = (obj.message ?? {}) as { action?: string };\n const response = await this.runMqttAuthAction(payload.action ?? \"\");\n this.sendMessageResponse(obj, response);\n return;\n }\n } catch (e) {\n this.log.warn(`onMessage failed for ${obj.command}: ${errMessage(e)}`);\n this.sendMessageResponse(obj, {\n error: e instanceof Error ? e.message : String(e),\n });\n }\n }\n\n /**\n * Handle the `mqttAuth` onMessage commands triggered by the admin UI.\n *\n * Two actions:\n * - `test` \u2014 try a one-shot login with the current settings (incl. any code) and\n * report a single-line plaintext result the admin can show as a toast.\n * - `requestCode` \u2014 POST to /account/rest/account/v1/verification so Govee emails a fresh\n * code. 30-second in-memory throttle prevents double-click email spam.\n *\n * @param action Action name from the jsonConfig sendTo button\n */\n private async runMqttAuthAction(action: string): Promise<{ result: string }> {\n const config = this.config as unknown as AdapterConfig;\n if (!config.goveeEmail || !config.goveePassword) {\n return { result: \"Email + Passwort in den Adapter-Einstellungen n\u00F6tig.\" };\n }\n\n if (action === \"test\") {\n // One-shot client: don't reuse this.mqttClient \u2014 we don't want this\n // probe to take over its reconnect timer or auth-failure counter.\n const probe = new GoveeMqttClient(config.goveeEmail, config.goveePassword, this.log, this);\n probe.setVerificationCode(config.mqttVerificationCode ?? \"\");\n try {\n let connected = false;\n await probe.connect(\n () => {},\n isConnected => {\n connected = isConnected;\n },\n );\n probe.disconnect();\n return {\n result: connected\n ? \"Login erfolgreich \u2014 Echtzeit-Status-Updates aktiv.\"\n : \"Login angenommen, MQTT-Verbindung steht aber noch nicht \u2014 Adapter neu starten.\",\n };\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n if (/Verification required/i.test(msg)) {\n return {\n result:\n \"Govee verlangt 2-Faktor-Best\u00E4tigung. Bitte 'Verifizierungs-Code anfordern' klicken, Code aus der E-Mail eintragen und Speichern.\",\n };\n }\n if (/Verification code invalid/i.test(msg)) {\n return {\n result: \"2-Faktor-Code ung\u00FCltig oder abgelaufen \u2014 bitte einen neuen Code anfordern.\",\n };\n }\n if (/email not registered/i.test(msg)) {\n return { result: \"Diese E-Mail ist bei Govee nicht registriert.\" };\n }\n if (/Login failed/i.test(msg)) {\n return { result: \"Passwort wurde von Govee abgelehnt.\" };\n }\n if (/Rate limited/i.test(msg)) {\n return { result: \"Govee meldet Rate-Limit \u2014 bitte sp\u00E4ter erneut versuchen.\" };\n }\n if (/Account temporarily locked/i.test(msg)) {\n return { result: \"Govee-Account vor\u00FCbergehend gesperrt \u2014 Govee Home App \u00F6ffnen und Status pr\u00FCfen.\" };\n }\n return { result: `Login fehlgeschlagen: ${msg}` };\n }\n }\n\n if (action === \"requestCode\") {\n const now = Date.now();\n if (now - this.lastVerificationRequestMs < VERIFICATION_REQUEST_THROTTLE_MS) {\n const wait = Math.ceil((VERIFICATION_REQUEST_THROTTLE_MS - (now - this.lastVerificationRequestMs)) / 1000);\n return { result: `Bitte ${wait}s warten \u2014 gerade wurde schon ein Code angefordert.` };\n }\n this.lastVerificationRequestMs = now;\n const probe = new GoveeMqttClient(config.goveeEmail, config.goveePassword, this.log, this);\n try {\n await probe.requestVerificationCode();\n return { result: \"Code wurde an deine Govee-E-Mail-Adresse gesendet (Spam-Ordner pr\u00FCfen).\" };\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n return { result: `Govee hat den Code-Versand abgelehnt: ${msg}` };\n }\n }\n\n return { result: `Unbekannte Aktion '${action}'.` };\n }\n\n /**\n * Helper: clear `mqttVerificationCode` in adapter native after a successful\n * login or a 455-fail.\n *\n * Idempotent: liest erst den aktuellen Wert, schreibt nur wenn dirty.\n * Verhindert den Adapter-Restart der durch jeden\n * `extendForeignObjectAsync(system.adapter.X, native:...)`-Call ausgel\u00F6st\n * wird (Memory v2.1.3-Bug). Vorher gab es nach jedem 2FA-Login einen\n * unn\u00F6tigen Restart.\n */\n private async clearVerificationCodeSetting(): Promise<void> {\n try {\n const obj = await this.getForeignObjectAsync(`system.adapter.${this.namespace}`);\n const native = (obj?.native ?? {}) as Record<string, unknown>;\n // Skip wenn das Feld eh schon leer (oder undefined) \u2014 kein dirty write,\n // kein Restart-Trigger.\n if (typeof native.mqttVerificationCode !== \"string\" || native.mqttVerificationCode === \"\") {\n return;\n }\n await this.extendForeignObjectAsync(`system.adapter.${this.namespace}`, {\n native: { mqttVerificationCode: \"\" },\n });\n } catch (e) {\n this.log.warn(`Could not clear mqttVerificationCode: ${errMessage(e)}`);\n }\n }\n\n /**\n * Read persisted MQTT credentials from `info.mqttCredentials`. The\n * sensitive fields (bearer + cert + pass) are encrypted with the\n * system secret on save and decrypted here. Returns null if no\n * credentials are stored or the JSON is unparseable.\n *\n * State-based persistence (since v2.1.3) \u2014 writes to a state instead\n * of `system.adapter.X.native` so saving doesn't trigger an adapter\n * restart. The earlier native-based design caused an endless\n * login \u2192 save \u2192 restart \u2192 login loop.\n */\n private async loadPersistedCredsFromState(): Promise<PersistedMqttCredentials | null> {\n try {\n const s = await this.getStateAsync(\"info.mqttCredentials\");\n const raw = typeof s?.val === \"string\" ? s.val : \"\";\n if (!raw) {\n return null;\n }\n const obj = JSON.parse(raw) as {\n bearerToken?: unknown;\n iotEndpoint?: unknown;\n p12Cert?: unknown;\n p12Pass?: unknown;\n accountId?: unknown;\n accountTopic?: unknown;\n tokenExpiresAt?: unknown;\n };\n // typeof-Guards \u2014 JSON.parse liefert raw, this.decrypt() wirft auf\n // non-string-Input. Defensive: wenn das State-Blob durch ein Tool\n // editiert wurde und falsche Typen drin hat, returnen wir null.\n const safeStr = (v: unknown): string => (typeof v === \"string\" ? v : \"\");\n const bearerToken = this.decrypt(safeStr(obj.bearerToken));\n const p12Cert = this.decrypt(safeStr(obj.p12Cert));\n const p12Pass = this.decrypt(safeStr(obj.p12Pass));\n const iotEndpoint = safeStr(obj.iotEndpoint);\n const accountId = safeStr(obj.accountId);\n const accountTopic = safeStr(obj.accountTopic);\n const tokenExpiresAt = typeof obj.tokenExpiresAt === \"number\" ? obj.tokenExpiresAt : 0;\n if (!bearerToken || !iotEndpoint || !p12Cert || !accountId || !accountTopic || !tokenExpiresAt) {\n return null;\n }\n return { bearerToken, iotEndpoint, p12Cert, p12Pass, accountId, accountTopic, tokenExpiresAt };\n } catch {\n return null;\n }\n }\n\n /**\n * Persist freshly-issued MQTT credentials into `info.mqttCredentials`.\n * Sensitive fields go through `this.encrypt()` so the JSON blob is\n * useless without the system secret. State writes do NOT trigger an\n * adapter restart.\n *\n * @param creds The freshly-issued MQTT bundle from a successful login\n */\n private async persistCredsToState(creds: PersistedMqttCredentials): Promise<void> {\n const blob = JSON.stringify({\n bearerToken: this.encrypt(creds.bearerToken),\n iotEndpoint: creds.iotEndpoint,\n p12Cert: this.encrypt(creds.p12Cert),\n p12Pass: this.encrypt(creds.p12Pass),\n accountId: creds.accountId,\n accountTopic: creds.accountTopic,\n tokenExpiresAt: creds.tokenExpiresAt,\n });\n await this.setStateAsync(\"info.mqttCredentials\", { val: blob, ack: true });\n }\n\n /**\n * One-shot cleanup of legacy v2.1.0/v2.1.1/v2.1.2 plaintext credentials\n * sitting in `system.adapter.X.native`.\n *\n * Doppelte Idempotenz:\n * 1. State-Marker `info.legacyMqttCleaned=true` wird NACH erfolgreichem\n * Wipe gesetzt. Bei sp\u00E4teren Starts wird die Funktion \u00FCber den Marker\n * sofort verlassen \u2014 auch wenn das native irgendwie wieder dirty wird.\n * 2. Nur wenn KEINER der Legacy-Marker gesetzt ist UND das native dirty,\n * wird der einmalige extendForeignObjectAsync (Restart-Trigger) gemacht.\n *\n * Dieser Aufruf triggert genau einmal pro Adapter-Lifetime einen\n * Restart \u2014 das ist unvermeidlich, weil die Plaintext-Bytes weg m\u00FCssen.\n * Aber: nach erfolgreichem Cleanup bleibt der Marker, kein erneuter\n * Restart bei flaky writes.\n */\n private async cleanupLegacyMqttNativeOnce(): Promise<void> {\n try {\n // Marker-State zuerst pr\u00FCfen \u2014 wenn schon gecleant, sofort raus.\n const markerState = await this.getStateAsync(\"info.legacyMqttCleaned\");\n if (markerState?.val === true) {\n return;\n }\n const obj = await this.getForeignObjectAsync(`system.adapter.${this.namespace}`);\n const native = (obj?.native ?? {}) as Record<string, unknown>;\n const legacy = [\n \"mqttBearerToken\",\n \"mqttIotEndpoint\",\n \"mqttP12Cert\",\n \"mqttP12Pass\",\n \"mqttAccountId\",\n \"mqttAccountTopic\",\n \"mqttTokenExpiresAt\",\n ];\n const dirty = legacy.some(k => k in native && native[k] !== \"\" && native[k] !== 0);\n if (!dirty) {\n // Native ist clean (z.B. neue Installation oder schon migriert\n // ohne Marker). Marker setzen damit n\u00E4chster Start gar nicht erst\n // den Native-Read macht.\n await this.setStateAsync(\"info.legacyMqttCleaned\", { val: true, ack: true }).catch(() => undefined);\n return;\n }\n this.log.info(\"Removing legacy plaintext MQTT credentials from native (one-time migration)\");\n const wipe: Record<string, unknown> = {};\n for (const k of legacy) {\n wipe[k] = k === \"mqttTokenExpiresAt\" ? 0 : \"\";\n }\n await this.extendForeignObjectAsync(`system.adapter.${this.namespace}`, { native: wipe });\n // Marker setzen NACHDEM der Wipe erfolgreich war \u2014 bei einem etwaigen\n // Crash zwischen extend und setState l\u00E4uft der Cleanup beim n\u00E4chsten\n // Start nochmal (idempotent: native ist dann eh schon clean).\n await this.setStateAsync(\"info.legacyMqttCleaned\", { val: true, ack: true }).catch(() => undefined);\n } catch (e) {\n this.log.debug(`legacy MQTT cleanup skipped: ${errMessage(e)}`);\n }\n }\n\n private sendMessageResponse(obj: ioBroker.Message, data: unknown): void {\n if (obj.callback && obj.from) {\n this.sendTo(obj.from, obj.command, data as Record<string, unknown>, obj.callback);\n }\n }\n\n /**\n * Stable device key for wizard session tracking.\n *\n * @param device Target device\n */\n private deviceKeyFor(device: GoveeDevice): string {\n return `${device.sku}:${device.deviceId}`;\n }\n\n private findDeviceByKey(key: string): GoveeDevice | undefined {\n const devices = this.deviceManager?.getDevices() ?? [];\n return devices.find(d => this.deviceKeyFor(d) === key);\n }\n\n /** Construct the host object passed into SegmentWizard. */\n private buildWizardHost(): WizardHost {\n return {\n log: this.log,\n getState: id => this.getStateAsync(id),\n sendCommand: async (device, command, value) => {\n await this.deviceManager?.sendCommand(device, command, value);\n },\n flashSegmentAtomic: (device, idx) => {\n if (!device.lanIp || !this.lanClient) {\n return Promise.resolve(false);\n }\n this.lanClient.flashSingleSegment(device.lanIp, idx);\n return Promise.resolve(true);\n },\n restoreStripAtomic: (device, total, color, brightness) => {\n if (!device.lanIp || !this.lanClient) {\n return Promise.resolve(false);\n }\n const r = (color >> 16) & 0xff;\n const g = (color >> 8) & 0xff;\n const b = color & 0xff;\n this.lanClient.restoreAllSegments(device.lanIp, total, r, g, b, brightness);\n return Promise.resolve(true);\n },\n findDevice: key => this.findDeviceByKey(key),\n namespace: this.namespace,\n devicePrefix: device => this.stateManager?.devicePrefix(device) ?? \"\",\n setTimeout: (cb, ms) => this.setTimeout(cb, ms),\n clearTimeout: h => this.clearTimeout(h as ioBroker.Timeout),\n applyWizardResult: (device, result) => this.applyWizardResult(device, result),\n getLanguage: () => this.adminLanguage,\n };\n }\n\n /**\n * Apply a finished wizard's measurement: set the real segment count, then\n * route through {@link applyManualSegments} so the same state-tree rebuild\n * and cache-persist path runs for both wizard results and user edits.\n *\n * @param device Target device\n * @param result Wizard's measurement\n */\n private async applyWizardResult(device: GoveeDevice, result: WizardResult): Promise<void> {\n device.segmentCount = result.segmentCount;\n if (result.hasGaps) {\n const parsed = parseSegmentList(result.manualList, result.segmentCount - 1);\n await this.applyManualSegments(device, true, parsed.error ? undefined : parsed.indices);\n } else {\n await this.applyManualSegments(device, false);\n }\n this.log.debug(\n `applyWizardResult: ${device.sku} \u2192 segmentCount=${result.segmentCount}, ` +\n `manualMode=${device.manualMode}, list=\"${result.manualList}\"`,\n );\n }\n\n /**\n * Execute one wizard step (start/yes/no/abort). Delegates to\n * {@link SegmentWizard} \u2014 see `lib/segment-wizard.ts`.\n *\n * @param action \"start\" | \"yes\" | \"no\" | \"abort\"\n * @param deviceKey device identifier (only required for \"start\")\n */\n private async runWizardStep(action: string, deviceKey: string): Promise<Record<string, unknown>> {\n if (!this.segmentWizard) {\n this.segmentWizard = new SegmentWizard(this.buildWizardHost());\n }\n const response = await this.segmentWizard.runStep(action, deviceKey);\n // Mirror the current wizard status into a plain state so admin's\n // `type: \"state\"` component can show it live via state subscription.\n const statusText = this.segmentWizard.getStatusText();\n await this.setStateAsync(\"info.wizardStatus\", {\n val: statusText,\n ack: true,\n });\n return response;\n }\n\n /**\n * Save current device state as a local snapshot.\n *\n * @param device Target device\n * @param name Snapshot name\n */\n private async handleSnapshotSave(device: GoveeDevice, name: string): Promise<void> {\n if (!this.localSnapshots || !this.stateManager) {\n return;\n }\n\n const prefix = this.stateManager.devicePrefix(device);\n const ns = this.namespace;\n\n // Read device-level state in parallel\n const [powerState, brightState, colorState, ctState] = await Promise.all([\n this.getStateAsync(`${ns}.${prefix}.control.power`),\n this.getStateAsync(`${ns}.${prefix}.control.brightness`),\n this.getStateAsync(`${ns}.${prefix}.control.colorRgb`),\n this.getStateAsync(`${ns}.${prefix}.control.colorTemperature`),\n ]);\n\n // Read per-segment states in parallel \u2014 20 segments \u00D7 2 reads used to run\n // sequentially (~80ms), parallel completes in a single round-trip.\n let segments: SnapshotSegment[] | undefined;\n const segCount = device.segmentCount ?? 0;\n if (segCount > 0) {\n const segReads: Promise<[ioBroker.State | null | undefined, ioBroker.State | null | undefined]>[] = [];\n for (let i = 0; i < segCount; i++) {\n segReads.push(\n Promise.all([\n this.getStateAsync(`${ns}.${prefix}.segments.${i}.color`),\n this.getStateAsync(`${ns}.${prefix}.segments.${i}.brightness`),\n ]),\n );\n }\n const segResults = await Promise.all(segReads);\n segments = segResults.map(([segColor, segBright]) => ({\n color: typeof segColor?.val === \"string\" ? segColor.val : \"#000000\",\n brightness: typeof segBright?.val === \"number\" ? segBright.val : 100,\n }));\n }\n\n const snapshot: LocalSnapshot = {\n name,\n power: powerState?.val === true,\n brightness: typeof brightState?.val === \"number\" ? brightState.val : 0,\n colorRgb: typeof colorState?.val === \"string\" ? colorState.val : \"#000000\",\n colorTemperature: typeof ctState?.val === \"number\" ? ctState.val : 0,\n segments,\n savedAt: Date.now(),\n };\n\n this.localSnapshots.saveSnapshot(device.sku, device.deviceId, snapshot);\n this.log.info(`Local snapshot saved: \"${name}\" for ${device.name}`);\n\n // Targeted refresh \u2014 only this device's snapshot_local dropdown changed.\n this.refreshDeviceStates(device, this.deviceManager!.getDevices());\n }\n\n /**\n * Restore a local snapshot by index.\n *\n * @param device Target device\n * @param val Dropdown index value\n */\n private async handleSnapshotRestore(device: GoveeDevice, val: ioBroker.StateValue): Promise<void> {\n if (!this.localSnapshots || !this.deviceManager) {\n return;\n }\n\n const idx = parseInt(String(val), 10);\n if (idx < 1) {\n return;\n }\n\n const snaps = this.localSnapshots.getSnapshots(device.sku, device.deviceId);\n const snap = snaps[idx - 1];\n if (!snap) {\n this.log.warn(`Local snapshot index ${idx} not found for ${device.name}`);\n return;\n }\n\n this.log.info(`Restoring local snapshot \"${snap.name}\" for ${device.name}`);\n\n // Send each state via LAN \u2192 Cloud routing\n await this.deviceManager.sendCommand(device, \"power\", snap.power);\n if (snap.power) {\n await this.deviceManager.sendCommand(device, \"brightness\", snap.brightness);\n if (snap.colorTemperature > 0) {\n await this.deviceManager.sendCommand(device, \"colorTemperature\", snap.colorTemperature);\n } else {\n await this.deviceManager.sendCommand(device, \"colorRgb\", snap.colorRgb);\n }\n\n // Restore per-segment states via ptReal\n if (snap.segments && snap.segments.length > 0) {\n for (let i = 0; i < snap.segments.length; i++) {\n const seg = snap.segments[i];\n await this.deviceManager.sendCommand(device, `segmentColor:${i}`, seg.color);\n await this.deviceManager.sendCommand(device, `segmentBrightness:${i}`, seg.brightness);\n }\n }\n }\n }\n\n /**\n * Delete a local snapshot by name.\n *\n * @param device Target device\n * @param name Snapshot name to delete\n */\n private handleSnapshotDelete(device: GoveeDevice, name: string): void {\n if (!this.localSnapshots) {\n return;\n }\n\n if (this.localSnapshots.deleteSnapshot(device.sku, device.deviceId, name)) {\n this.log.info(`Local snapshot deleted: \"${name}\" for ${device.name}`);\n // Targeted refresh \u2014 only this device's snapshot_local dropdown changed.\n this.refreshDeviceStates(device, this.deviceManager!.getDevices());\n } else {\n this.log.warn(`Local snapshot \"${name}\" not found for ${device.name}`);\n }\n }\n\n /** Dropdowns whose value is a mode-selection \u2014 reset to \"---\" (0) when the mode stops. */\n private static readonly MODE_DROPDOWNS = [\n \"scenes.light_scene\",\n \"scenes.diy_scene\",\n \"snapshots.snapshot_cloud\",\n \"snapshots.snapshot_local\",\n \"music.music_mode\",\n ];\n\n /** Map command \u2192 its own dropdown path (excluded from reset when that mode is the one that was just activated). */\n private static readonly COMMAND_DROPDOWN: Record<string, string> = {\n lightScene: \"scenes.light_scene\",\n diyScene: \"scenes.diy_scene\",\n snapshot: \"snapshots.snapshot_cloud\",\n snapshotLocal: \"snapshots.snapshot_local\",\n music: \"music.music_mode\",\n colorRgb: \"\",\n colorTemperature: \"\",\n };\n\n /**\n * Diagnostics-Export-Button-Handler. Throttled auf 2s pro Device damit\n * wiederholte/Skript-Trigger keinen Burst von JSON-Serialisierungen\n * erzeugen.\n *\n * @param device Govee device\n * @param prefix Device state prefix\n * @param triggerStateId The state-id that triggered the export (so we can ack)\n */\n private async handleDiagnosticsExport(device: GoveeDevice, prefix: string, triggerStateId: string): Promise<void> {\n if (!this.deviceManager) {\n return;\n }\n const deviceKey = `${device.sku}:${device.deviceId}`;\n const now = Date.now();\n const last = this.diagnosticsLastRun.get(deviceKey) ?? 0;\n if (now - last < 2000) {\n this.log.debug(`Diagnostics export throttled for ${device.name} \u2014 last run ${now - last}ms ago`);\n await this.setStateAsync(triggerStateId, { val: false, ack: true });\n return;\n }\n this.diagnosticsLastRun.set(deviceKey, now);\n const diag = this.deviceManager.generateDiagnostics(device, this.version ?? \"unknown\");\n const resultId = `${this.namespace}.${prefix}.diag.result`;\n await this.setStateAsync(resultId, {\n val: JSON.stringify(diag, null, 2),\n ack: true,\n });\n await this.setStateAsync(triggerStateId, { val: false, ack: true });\n this.log.info(`Diagnostics exported for ${device.name} (${device.sku})`);\n }\n\n /**\n * Generischer Capability-Routing-Pfad f\u00FCr States die nicht im STATE_TO_COMMAND\n * Mapping sind. Liest `native.capabilityType`/`capabilityInstance` aus dem\n * State-Object und schickt das via Cloud-API.\n *\n * @param device Target device\n * @param id Full state-id (f\u00FCr ack)\n * @param stateSuffix State-Pfad innerhalb Device (f\u00FCr debug-log)\n * @param val Value zum Senden\n */\n private async handleGenericCapabilityCommand(\n device: GoveeDevice,\n id: string,\n stateSuffix: string,\n val: ioBroker.StateValue,\n ): Promise<void> {\n if (!this.deviceManager) {\n return;\n }\n const obj = await this.getObjectAsync(id);\n const capType = obj?.native?.capabilityType;\n const capInstance = obj?.native?.capabilityInstance;\n if (typeof capType === \"string\" && typeof capInstance === \"string\") {\n try {\n await this.deviceManager.sendCapabilityCommand(device, capType, capInstance, val);\n await this.setStateAsync(id, { val, ack: true });\n } catch (err) {\n this.log.warn(`Command failed for ${device.name}: ${errMessage(err)}`);\n }\n } else {\n this.log.debug(`Unknown writable state: ${stateSuffix}`);\n }\n }\n\n /**\n * Reset related dropdown states when switching between scenes/snapshots/colors.\n * Each mode-switch resets all OTHER mode dropdowns to \"---\" (0).\n *\n * @param prefix Device state prefix\n * @param activeCommand The command that was just executed\n */\n private async resetRelatedDropdowns(prefix: string, activeCommand: string): Promise<void> {\n if (!(activeCommand in GoveeAdapter.COMMAND_DROPDOWN)) {\n return;\n }\n const ownDropdown = GoveeAdapter.COMMAND_DROPDOWN[activeCommand];\n await this.resetModeDropdowns(prefix, ownDropdown);\n }\n\n /**\n * Reset every mode dropdown except `keep` (empty = reset all). Used both for\n * mode-switches (keep the new mode's own dropdown) and for power-off\n * (reset everything \u2014 a device that's off has no active mode).\n *\n * @param prefix Device state prefix\n * @param keep Dropdown path to leave untouched (e.g. \"music.music_mode\"), or \"\" to reset all\n */\n private async resetModeDropdowns(prefix: string, keep: string): Promise<void> {\n await Promise.all(\n GoveeAdapter.MODE_DROPDOWNS.filter(d => d !== keep).map(async dropdown => {\n const stateId = `${this.namespace}.${prefix}.${dropdown}`;\n const current = await this.getStateAsync(stateId);\n if (current?.val && current.val !== \"0\" && current.val !== 0) {\n await this.setStateAsync(stateId, { val: \"0\", ack: true });\n }\n }),\n );\n }\n}\n\nif (require.main !== module) {\n module.exports = (options: Partial<utils.AdapterOptions> | undefined) => new GoveeAdapter(options);\n} else {\n (() => new GoveeAdapter())();\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import * as utils from \"@iobroker/adapter-core\";\nimport {\n buildDeviceStateDefs,\n getDefaultLanStates,\n mapCloudStateValue,\n planCloudCapabilityWrites,\n} from \"./lib/capability-mapper\";\nimport { getDeviceTier, initDeviceRegistry } from \"./lib/device-registry\";\nimport { DeviceManager, resolveSegmentCount, SEGMENT_HARD_MAX } from \"./lib/device-manager\";\nimport { GoveeApiClient } from \"./lib/govee-api-client\";\nimport { GoveeCloudClient } from \"./lib/govee-cloud-client\";\nimport { GoveeLanClient } from \"./lib/govee-lan-client\";\nimport { GoveeMqttClient } from \"./lib/govee-mqtt-client\";\nimport { GoveeOpenapiMqttClient } from \"./lib/govee-openapi-mqtt-client\";\nimport { LocalSnapshotStore } from \"./lib/local-snapshots\";\nimport { SnapshotHandler, type SnapshotHandlerHost } from \"./lib/snapshot-handler\";\nimport { CloudRetryLoop, type CloudRetryHost } from \"./lib/cloud-retry\";\nimport { RateLimiter } from \"./lib/rate-limiter\";\nimport { SegmentWizard, wizardIdleText, type WizardHost, type WizardResult } from \"./lib/segment-wizard\";\nimport { SkuCache } from \"./lib/sku-cache\";\nimport { StateManager } from \"./lib/state-manager\";\nimport {\n hexToRgb,\n errMessage,\n parseSegmentList,\n resolveStatesValue,\n rgbIntToHex,\n rgbToHex,\n type AdapterConfig,\n type CloudLoadResult,\n type CloudStateCapability,\n type DeviceState,\n type GoveeDevice,\n type PersistedMqttCredentials,\n} from \"./lib/types\";\nimport { APP_API_INITIAL_DELAY_MS, APP_API_POLL_INTERVAL_MS, READY_TIMEOUT_MS } from \"./lib/timing-constants\";\nimport { GOVEE_APP_VERSION } from \"./lib/govee-constants\";\nimport { httpsRequest } from \"./lib/http-client\";\n\n/**\n * Rate limit defaults \u2014 full Cloud API budget (8/min, 9000/day). v2 no\n * longer halves this with govee-appliances because that adapter is\n * deprecated and won't run alongside govee-smart.\n */\nconst FULL_LIMITS = { perMinute: 8, perDay: 9000 };\n\n/**\n * Minimum gap between two `mqttAuth: requestCode` calls. Govee returns 200\n * for every request and queues another email \u2014 without a throttle, double-clicking\n * the button in admin would spam the user's inbox. 30 s is short enough that a\n * legitimate retry after a failed delivery still feels responsive.\n */\nconst VERIFICATION_REQUEST_THROTTLE_MS = 30_000;\n\n/**\n * State-suffix \u2192 command-name lookup for writable states. Segment indices\n * are dynamic and handled by regex in stateToCommand \u2014 everything else is\n * a straight string mapping.\n */\nconst STATE_TO_COMMAND: Readonly<Record<string, string>> = {\n \"control.power\": \"power\",\n \"control.brightness\": \"brightness\",\n \"control.colorRgb\": \"colorRgb\",\n \"control.colorTemperature\": \"colorTemperature\",\n \"control.scene\": \"scene\",\n \"control.gradient_toggle\": \"gradientToggle\",\n \"scenes.light_scene\": \"lightScene\",\n \"scenes.diy_scene\": \"diyScene\",\n \"scenes.scene_speed\": \"sceneSpeed\",\n \"music.music_mode\": \"music\",\n \"music.music_sensitivity\": \"music\",\n \"music.music_auto_color\": \"music\",\n \"snapshots.snapshot_cloud\": \"snapshot\",\n \"segments.command\": \"segmentBatch\",\n};\n\nclass GoveeAdapter extends utils.Adapter {\n private deviceManager: DeviceManager | null = null;\n private stateManager: StateManager | null = null;\n private lanClient: GoveeLanClient | null = null;\n private mqttClient: GoveeMqttClient | null = null;\n private openapiMqttClient: GoveeOpenapiMqttClient | null = null;\n private cloudClient: GoveeCloudClient | null = null;\n private rateLimiter: RateLimiter | null = null;\n /** Repeating timer for the App-API poll (sensor-state pull). */\n private appApiPollTimer: ioBroker.Interval | undefined;\n /**\n * One-shot timer for the FIRST app-api poll (5s nach start) \u2014 Handle\n * damit onUnload das wegr\u00E4umen kann bevor es ins Leere feuert.\n */\n private appApiInitialTimer: ioBroker.Timeout | undefined;\n /** One-shot timer for cloud-init 60s safety timeout \u2014 gleiches Pattern. */\n private cloudInitTimer: ioBroker.Timeout | undefined;\n /**\n * Letzter info.connection-Wert \u2014 Cache damit nicht jeder device-update\n * einen unn\u00F6tigen setStateAsync macht (H4).\n */\n private lastConnectionState: boolean | null = null;\n // === Lifecycle-Flags (Adapter-Boot-Sequenz) ===\n // checkAllReady() pr\u00FCft alle 5 Voraussetzungen gleichzeitig \u2014 sie laufen\n // parallel ab, kein lineares STATE_MACHINE-Pattern weil Channels\n // unabh\u00E4ngig connecten.\n /** LAN-Scan-Initial-Wait abgeschlossen (3s nach Start). */\n private lanScanDone = false;\n /** State-Tree-Erstellung f\u00FCr alle Cached/Cloud-Devices fertig. */\n private statesReady = false;\n /** Cloud-Init-Phase abgeschlossen (mit oder ohne Erfolg). */\n private cloudInitDone = false;\n /** True nach dem ersten erfolgreichen App-API-Poll (f\u00FCr Sensoren mit Werten). */\n private appApiInitialPollDone = false;\n /** Verhindert Mehrfach-Ready-Log innerhalb derselben Adapter-Session. */\n private readyLogged = false;\n /** Cloud war mindestens einmal connected \u2014 f\u00FCr \u201Erestored\"-Log nach Down. */\n private cloudWasConnected = false;\n /** T\u00E4gliches Interval f\u00FCr App-Version-Drift-Check gegen App-Store. */\n private appVersionCheckTimer: ioBroker.Interval | undefined;\n // === Sub-Komponenten ===\n private skuCache: SkuCache | null = null;\n private localSnapshots: LocalSnapshotStore | null = null;\n private snapshotHandler: SnapshotHandler | null = null;\n private stateCreationQueue: Promise<void>[] = [];\n private lanScanTimer: ioBroker.Timeout | undefined;\n private cleanupTimer: ioBroker.Timeout | undefined;\n private readyTimer: ioBroker.Timeout | undefined;\n private cloudRetry: CloudRetryLoop | null = null;\n private segmentWizard: SegmentWizard | null = null;\n private unhandledRejectionHandler: ((reason: unknown) => void) | null = null;\n private uncaughtExceptionHandler: ((err: Error) => void) | null = null;\n /** Per-device timestamp of the last diagnostics export \u2014 throttle gate */\n private diagnosticsLastRun = new Map<string, number>();\n /** Cached admin language from system.config \u2014 used for wizard UI text */\n private adminLanguage = \"en\";\n /** Last time `requestCode` was triggered via onMessage \u2014 guards against double-click email spam. */\n private lastVerificationRequestMs = 0;\n\n /** @param options Adapter options */\n public constructor(options: Partial<utils.AdapterOptions> = {}) {\n super({ ...options, name: \"govee-smart\" });\n // Per ioBroker rule: async handlers registered on events MUST .catch,\n // otherwise rejections become unhandled \u2192 SIGKILL code 6 \u2192 restart loop.\n this.on(\"ready\", () =>\n this.onReady().catch(e =>\n this.log.error(`onReady crashed: ${e instanceof Error ? (e.stack ?? e.message) : String(e)}`),\n ),\n );\n this.on(\"stateChange\", (id, state) =>\n this.onStateChange(id, state).catch(e => this.log.warn(`onStateChange crashed for ${id}: ${errMessage(e)}`)),\n );\n this.on(\"message\", obj => this.onMessage(obj));\n this.on(\"unload\", callback => this.onUnload(callback));\n // Last-line-of-defence against unhandled rejections / sync throws from\n // fire-and-forget paths. The per-handler .catch() wrappers cover the\n // direct entry points; this catches whatever slips past them so the\n // adapter logs the cause instead of triggering js-controller SIGKILL.\n this.unhandledRejectionHandler = (reason: unknown) => {\n this.log.error(\n `Unhandled rejection: ${reason instanceof Error ? (reason.stack ?? reason.message) : String(reason)}`,\n );\n };\n this.uncaughtExceptionHandler = (err: Error) => {\n this.log.error(`Uncaught exception: ${err.stack ?? err.message}`);\n };\n process.on(\"unhandledRejection\", this.unhandledRejectionHandler);\n process.on(\"uncaughtException\", this.uncaughtExceptionHandler);\n }\n\n /** Adapter started \u2014 initialize all channels */\n private async onReady(): Promise<void> {\n const config = this.config as unknown as AdapterConfig;\n\n // info channel + states are declared as instanceObjects in\n // io-package.json, so js-controller materialises them on install /\n // upgrade. We only initialise the runtime values here.\n await this.setStateAsync(\"info.connection\", { val: false, ack: true });\n await this.setStateAsync(\"info.mqttConnected\", { val: false, ack: true });\n await this.setStateAsync(\"info.cloudConnected\", { val: false, ack: true });\n await this.setStateAsync(\"info.openapiMqttConnected\", {\n val: false,\n ack: true,\n });\n await this.setStateAsync(\"info.refresh_cloud_data\", {\n val: false,\n ack: true,\n });\n // Load admin language from system.config so wizard prose matches the\n // user's Admin UI. Falls back to English on any lookup failure.\n try {\n const sysConf = await this.getForeignObjectAsync(\"system.config\");\n const lang = (sysConf?.common as { language?: string } | undefined)?.language;\n if (typeof lang === \"string\" && lang.length > 0) {\n this.adminLanguage = lang;\n }\n } catch {\n // Keep default \"en\"\n }\n await this.setStateAsync(\"info.wizardStatus\", {\n val: wizardIdleText(this.adminLanguage),\n ack: true,\n });\n\n this.stateManager = new StateManager(this);\n // General groups online state (reflects Cloud connection)\n await this.stateManager.createGroupsOnlineState(false);\n this.deviceManager = new DeviceManager(this.log, this);\n const dataDir = utils.getAbsoluteInstanceDataDir(this);\n\n // Load device registry from devices.json in the adapter package root.\n // Status filter: verified+reported active by default; seed-status entries\n // require the experimentalQuirks config toggle.\n initDeviceRegistry({\n experimental: config.experimentalQuirks === true,\n log: this.log,\n });\n this.skuCache = new SkuCache(dataDir, this.log);\n this.localSnapshots = new LocalSnapshotStore(dataDir, this.log);\n this.snapshotHandler = new SnapshotHandler(this.buildSnapshotHost());\n this.deviceManager.setSkuCache(this.skuCache);\n\n // API client for undocumented scene/music/DIY libraries (always available)\n const apiClient = new GoveeApiClient();\n apiClient.setEmail(config.goveeEmail);\n this.deviceManager.setApiClient(apiClient);\n\n this.deviceManager.setCallbacks(\n (device, state) => this.onDeviceStateUpdate(device, state),\n devices => this.onDeviceListChanged(devices),\n );\n\n // Update info.ip when LAN IP changes\n this.deviceManager.onLanIpChanged = (device, ip) => {\n const prefix = this.stateManager!.devicePrefix(device);\n this.setStateAsync(`${prefix}.info.ip`, { val: ip, ack: true }).catch(() => {});\n };\n\n // Sync individual segment states after batch command\n this.deviceManager.onSegmentBatchUpdate = (device, batch) => {\n const prefix = this.stateManager!.devicePrefix(device);\n for (const idx of batch.segments) {\n if (batch.color !== undefined) {\n const hex = rgbIntToHex(batch.color);\n this.setStateAsync(`${prefix}.segments.${idx}.color`, {\n val: hex,\n ack: true,\n }).catch(() => {});\n }\n if (batch.brightness !== undefined) {\n this.setStateAsync(`${prefix}.segments.${idx}.brightness`, {\n val: batch.brightness,\n ack: true,\n }).catch(() => {});\n }\n }\n };\n\n // Sync per-segment states from MQTT BLE status push (AA A5 packets)\n this.deviceManager.onMqttSegmentUpdate = (device, segments) => {\n const prefix = this.stateManager!.devicePrefix(device);\n for (const seg of segments) {\n this.setStateAsync(`${prefix}.segments.${seg.index}.color`, {\n val: rgbToHex(seg.r, seg.g, seg.b),\n ack: true,\n }).catch(() => {});\n this.setStateAsync(`${prefix}.segments.${seg.index}.brightness`, {\n val: seg.brightness,\n ack: true,\n }).catch(() => {});\n }\n };\n\n // When MQTT reveals more segments than the Cloud advertised, rebuild\n // the device's state tree so the extra segments get their datapoints.\n this.deviceManager.onSegmentCountGrown = device => {\n if (!this.stateManager) {\n return;\n }\n this.stateManager.createSegmentStates(device).catch(e => {\n this.log.warn(`Failed to rebuild segment tree for ${device.name} after count growth: ${errMessage(e)}`);\n });\n };\n\n // Log startup with configured channels\n const startChannels: string[] = [\"LAN\"];\n if (config.apiKey) {\n startChannels.push(\"Cloud\");\n }\n if (config.goveeEmail && config.goveePassword) {\n startChannels.push(\"MQTT\");\n }\n this.log.info(`Starting (${startChannels.join(\", \")})`);\n\n // --- LAN (always active) ---\n this.lanClient = new GoveeLanClient(this.log, this);\n this.deviceManager.setLanClient(this.lanClient);\n\n this.lanClient.start(\n lanDevice => {\n this.deviceManager!.handleLanDiscovery(lanDevice);\n // Poll status only when MQTT is unavailable. With an active MQTT\n // subscription Govee pushes state changes authoritatively, so the\n // LAN devStatus request would be duplicate traffic.\n if (!this.mqttClient?.connected) {\n this.lanClient!.requestStatus(lanDevice.ip);\n }\n },\n (sourceIp, status) => {\n this.deviceManager!.handleLanStatus(sourceIp, status);\n },\n 30_000,\n config.networkInterface || \"\",\n );\n\n // Wait for first LAN scan responses (UDP multicast, devices respond within 1-2s)\n this.lanScanTimer = this.setTimeout(() => {\n this.lanScanDone = true;\n this.checkAllReady();\n }, 3_000);\n\n // --- MQTT (if account credentials provided) ---\n // Initialize MQTT before Cloud so scene library can load on first cycle\n if (config.goveeEmail && config.goveePassword) {\n this.mqttClient = new GoveeMqttClient(config.goveeEmail, config.goveePassword, this.log, this);\n\n // Forward every parsed MQTT op.command into the diagnostics ring buffer\n // so diag.export contains the recent packets per device.\n this.mqttClient.setPacketHook((deviceId, topic, hex) => {\n this.deviceManager?.getDiagnostics().addMqttPacket(deviceId, topic, hex);\n });\n\n // 2FA: forward optional code from settings into the next login attempt;\n // clear the field automatically once Govee has accepted it.\n this.mqttClient.setVerificationCode(config.mqttVerificationCode ?? \"\");\n this.mqttClient.setOnVerificationConsumed(() => {\n this.clearVerificationCodeSetting().catch(e => {\n this.log.warn(`Could not clear mqttVerificationCode: ${errMessage(e)}`);\n });\n });\n this.mqttClient.setOnVerificationFailed(reason => {\n // On 'failed' (455 / 454+code-was-sent) blank the code so the user\n // doesn't keep retrying with a stale value. On 'pending' (454 + no\n // code) we leave the field as-is \u2014 the user is about to fill it.\n if (reason === \"failed\") {\n this.clearVerificationCodeSetting().catch(() => {});\n }\n });\n\n // Re-use cached MQTT credentials across restarts. Stored in the\n // info.mqttCredentials state (NOT in adapter native): writing to\n // system.adapter.X.0 native triggers a js-controller adapter\n // restart, which would loop endlessly on every login. States are\n // restart-safe.\n //\n // One-shot: clean up legacy v2.1.0/v2.1.1/v2.1.2 native fields\n // that contained plaintext credentials. Best-effort.\n await this.cleanupLegacyMqttNativeOnce();\n const cachedCreds = await this.loadPersistedCredsFromState();\n if (cachedCreds) {\n this.mqttClient.setPersistedCredentials(cachedCreds);\n }\n this.mqttClient.setOnCredentialsRefresh(creds => {\n this.persistCredsToState(creds).catch(e => {\n this.log.warn(`Could not persist MQTT credentials: ${errMessage(e)}`);\n });\n });\n\n await this.mqttClient.connect(\n update => this.deviceManager!.handleMqttStatus(update),\n connected => {\n this.setStateAsync(\"info.mqttConnected\", {\n val: connected,\n ack: true,\n }).catch(() => {});\n if (connected) {\n this.checkAllReady();\n }\n this.updateConnectionState();\n },\n // Forward every fresh bearer token \u2014 fires on initial login and on\n // each reconnect-login, so the API client never runs with a stale one.\n token => apiClient.setBearerToken(token),\n );\n }\n\n // --- Device data: Cache first, Cloud only on cache miss ---\n const cachedOk = this.deviceManager.loadFromCache();\n\n if (config.apiKey) {\n this.cloudClient = new GoveeCloudClient(config.apiKey, this.log);\n // Capture the most recent Cloud response per (deviceId, endpoint) for\n // diagnostics \u2014 bounded by the DiagnosticsCollector's response slot cap.\n this.cloudClient.setResponseHook((deviceId, endpoint, body) => {\n this.deviceManager?.getDiagnostics().setApiResponse(deviceId, endpoint, body);\n });\n this.deviceManager.setCloudClient(this.cloudClient);\n\n // Bridge synthetic capabilities (App-API, OpenAPI-MQTT events) into the\n // same setState pipeline as polled Cloud state. Keeps mapCloudStateValue\n // as the single source of truth for value coercion + state-id resolution.\n this.deviceManager.setOnCloudCapabilities((device, caps) => {\n this.applyCloudCapabilities(device, caps).catch(e =>\n this.log.warn(`applyCloudCapabilities failed for ${device.sku}: ${errMessage(e)}`),\n );\n });\n\n this.rateLimiter = new RateLimiter(this.log, this, FULL_LIMITS.perMinute, FULL_LIMITS.perDay);\n this.rateLimiter.start();\n this.deviceManager.setRateLimiter(this.rateLimiter);\n\n // OpenAPI-MQTT \u2014 push channel for appliance/sensor events\n // (lackWater, iceFull, bodyAppeared etc.). API key is enough; no\n // separate credentials required. Connection runs in parallel to\n // the AWS-IoT MQTT used for status push of regular devices.\n this.openapiMqttClient = new GoveeOpenapiMqttClient(config.apiKey, this.log, this);\n this.openapiMqttClient.connect(\n event => this.deviceManager?.handleOpenApiEvent(event),\n connected => {\n this.setStateAsync(\"info.openapiMqttConnected\", {\n val: connected,\n ack: true,\n }).catch(() => {});\n },\n );\n\n // App-API poll \u2014 every 2 minutes, pulls state for sensors like H5179\n // where OpenAPI v2 /device/state returns empty. Bearer token comes\n // from the AWS-IoT MQTT login, so a no-op until that succeeds.\n const triggerAppApiPoll = (): void => {\n this.deviceManager\n ?.pollAppApi()\n .then(() => {\n // H2 \u2014 Mark initial-poll-done und re-check Ready damit der\n // Adapter \u201Eready\" loggen kann sobald Sensor-Werte da sind.\n if (!this.appApiInitialPollDone) {\n this.appApiInitialPollDone = true;\n this.checkAllReady();\n }\n })\n .catch(e => this.log.debug(`pollAppApi failed: ${errMessage(e)}`));\n };\n this.appApiPollTimer = this.setInterval(triggerAppApiPoll, APP_API_POLL_INTERVAL_MS);\n // Initial poll: gibt MQTT Zeit f\u00FCr den Bearer-Login. Ohne diesen\n // Sofort-Poll bleiben Sensoren wie H5179 die ersten 2 Minuten nach\n // Start offline (Online-Signal kommt nur via App-API). Handle in\n // Member-Variable damit onUnload den Timer cleart.\n this.appApiInitialTimer = this.setTimeout(triggerAppApiPoll, APP_API_INITIAL_DELAY_MS);\n\n if (!cachedOk) {\n // No cache \u2014 first start, fetch from Cloud with 60s hard-timeout.\n // If Cloud hangs/fails, we don't want to block adapter startup indefinitely.\n const result = await this.cloudInitWithTimeout();\n this.cloudWasConnected = result.ok;\n this.ensureCloudRetry().setConnected(result.ok);\n this.setStateAsync(\"info.cloudConnected\", {\n val: result.ok,\n ack: true,\n }).catch(() => {});\n this.stateManager?.updateGroupsOnline(result.ok).catch(() => {});\n\n if (result.ok) {\n await this.loadCloudStates();\n } else {\n this.handleCloudFailure(result);\n }\n } else {\n this.log.info(\"Using cached device data \u2014 no Cloud calls needed\");\n this.cloudWasConnected = true;\n this.ensureCloudRetry().setConnected(true);\n this.setStateAsync(\"info.cloudConnected\", {\n val: true,\n ack: true,\n }).catch(() => {});\n this.stateManager?.updateGroupsOnline(true).catch(() => {});\n }\n // Load group membership from undocumented API (needs bearer token + device map)\n await this.deviceManager.loadGroupMembers();\n\n this.cloudInitDone = true;\n }\n\n // Wait for all state creation from cache/cloud load to complete.\n // Drain-loop: a callback that fires during the await (e.g. a late LAN\n // discovery) can push fresh promises into the queue \u2014 we need to await\n // those too before flipping statesReady, otherwise the initial state\n // tree would be incomplete on very fast startups.\n while (this.stateCreationQueue.length > 0) {\n const pending = this.stateCreationQueue;\n this.stateCreationQueue = [];\n await Promise.all(pending);\n }\n this.statesReady = true;\n\n // Subscribe to all writable device and group states\n await this.subscribeStatesAsync(\"devices.*\");\n await this.subscribeStatesAsync(\"groups.*\");\n await this.subscribeStatesAsync(\"info.refresh_cloud_data\");\n\n // Cleanup stale devices after initial discovery (30s delay for LAN scan).\n // Reaps devices from every adapter-level map that was keyed on them so the\n // process doesn't leak memory across Cloud-side device turnover.\n this.cleanupTimer = this.setTimeout(() => {\n this.reapStaleDevices().catch(e => this.log.debug(`Device cleanup failed: ${errMessage(e)}`));\n }, 30_000);\n\n // App-Version-Drift-Monitor \u2014 daily check + initial nach 2 min wenn der\n // Adapter-Start ohne MQTT-Login durchgeschlagen ist (z.B. LAN-only).\n this.appVersionCheckTimer = this.setInterval(\n () => {\n this.checkAppVersionDrift().catch(e => this.log.debug(`App version check error: ${errMessage(e)}`));\n },\n 24 * 60 * 60 * 1000,\n );\n this.setTimeout(\n () => {\n this.checkAppVersionDrift().catch(e => this.log.debug(`App version check error: ${errMessage(e)}`));\n },\n 2 * 60 * 1000,\n );\n\n this.updateConnectionState();\n\n // Check if all channels are ready \u2014 may already be true if MQTT connected fast\n this.checkAllReady();\n // Safety timeout: log ready even if a channel takes too long.\n // 60s deckt normalen MQTT-Connect + 1 Reconnect-Attempt ab.\n this.readyTimer = this.setTimeout(() => {\n if (!this.readyLogged) {\n // Safety-Timeout: log ready trotzdem auch wenn ein Channel zu lange\n // braucht. READY_TIMEOUT_MS deckt normalen MQTT-Connect + 1 Reconnect.\n this.readyLogged = true;\n this.logDeviceSummary();\n }\n }, 60_000);\n }\n\n /**\n * Initial Cloud-Load mit 60-Sekunden-Hardtimeout.\n * Blockiert nicht l\u00E4nger \u2014 wenn Cloud h\u00E4ngt, geht Adapter mit LAN+MQTT weiter,\n * und der Retry-Loop probiert's passend zum Fehlergrund erneut.\n */\n private async cloudInitWithTimeout(): Promise<CloudLoadResult> {\n if (!this.deviceManager) {\n return { ok: false, reason: \"transient\" };\n }\n const loadPromise = this.deviceManager.loadFromCloud();\n const timeoutPromise = new Promise<CloudLoadResult>(resolve => {\n this.cloudInitTimer = this.setTimeout(() => resolve({ ok: false, reason: \"transient\" }), READY_TIMEOUT_MS);\n });\n try {\n return await Promise.race([loadPromise, timeoutPromise]);\n } catch {\n return { ok: false, reason: \"transient\" };\n }\n }\n\n /** Build the host object for {@link CloudRetryLoop}. */\n private buildCloudRetryHost(): CloudRetryHost {\n return {\n log: this.log,\n setTimeout: (cb, ms) => this.setTimeout(cb, ms),\n clearTimeout: h => this.clearTimeout(h as ioBroker.Timeout),\n loadFromCloud: () => this.cloudInitWithTimeout(),\n onCloudRestored: async () => {\n this.cloudWasConnected = true;\n this.setStateAsync(\"info.cloudConnected\", {\n val: true,\n ack: true,\n }).catch(() => {});\n this.stateManager?.updateGroupsOnline(true).catch(() => {});\n await this.loadCloudStates();\n },\n };\n }\n\n /** Lazy-initialise the retry loop on first use. */\n private ensureCloudRetry(): CloudRetryLoop {\n if (!this.cloudRetry) {\n this.cloudRetry = new CloudRetryLoop(this.buildCloudRetryHost());\n this.cloudRetry.setConnected(this.cloudWasConnected);\n }\n return this.cloudRetry;\n }\n\n /**\n * React to a Cloud-load outcome \u2014 delegates to {@link CloudRetryLoop}.\n *\n * @param result CloudLoadResult from initial load or retry attempt\n */\n private handleCloudFailure(result: CloudLoadResult): void {\n this.ensureCloudRetry().handleResult(result);\n }\n\n /**\n * React to the user writing `info.refresh_cloud_data = true`. Performs one\n * full Cloud reload cycle so newly created scenes/snapshots from the Govee\n * Home app show up without an adapter restart.\n */\n private async handleManualCloudRefresh(): Promise<void> {\n if (!this.deviceManager || !this.cloudClient) {\n this.log.info(\"Refresh cloud data: no Cloud client configured (API key missing) \u2014 nothing to do\");\n return;\n }\n this.log.info(\"Refresh cloud data: re-fetching scenes and snapshots for all devices\");\n try {\n const changed = await this.deviceManager.refreshSceneData();\n if (changed) {\n await this.loadCloudStates();\n }\n // G2 \u2014 kein \u201Edone\"-Log: User hat den Button gedr\u00FCckt, sieht das\n // Ergebnis am Adapter-Verhalten. \u201Edone\" auf info-level w\u00E4re bei\n // Erfolg redundant (Fehler-Pfad direkt darunter ist warn).\n } catch (e) {\n this.log.warn(`Refresh cloud data failed: ${errMessage(e)}`);\n }\n }\n\n /**\n * Adapter stopping \u2014 MUST be synchronous.\n *\n * @param callback Completion callback\n */\n private onUnload(callback: () => void): void {\n try {\n if (this.lanScanTimer) {\n this.clearTimeout(this.lanScanTimer);\n }\n if (this.cleanupTimer) {\n this.clearTimeout(this.cleanupTimer);\n }\n if (this.readyTimer) {\n this.clearTimeout(this.readyTimer);\n }\n if (this.appApiPollTimer) {\n this.clearInterval(this.appApiPollTimer);\n this.appApiPollTimer = undefined;\n }\n if (this.appApiInitialTimer) {\n this.clearTimeout(this.appApiInitialTimer);\n this.appApiInitialTimer = undefined;\n }\n if (this.cloudInitTimer) {\n this.clearTimeout(this.cloudInitTimer);\n this.cloudInitTimer = undefined;\n }\n if (this.appVersionCheckTimer) {\n this.clearInterval(this.appVersionCheckTimer);\n this.appVersionCheckTimer = undefined;\n }\n this.cloudRetry?.dispose();\n this.segmentWizard?.dispose();\n this.lanClient?.stop();\n this.mqttClient?.disconnect();\n this.openapiMqttClient?.disconnect();\n this.rateLimiter?.stop();\n // Remove process-level handlers so an adapter restart doesn't stack them.\n if (this.unhandledRejectionHandler) {\n process.off(\"unhandledRejection\", this.unhandledRejectionHandler);\n this.unhandledRejectionHandler = null;\n }\n if (this.uncaughtExceptionHandler) {\n process.off(\"uncaughtException\", this.uncaughtExceptionHandler);\n this.uncaughtExceptionHandler = null;\n }\n // onUnload MUST be synchronous \u2014 don't await, but silence potential\n // promise rejection during teardown to avoid \"unhandled rejection\" warnings.\n this.setState(\"info.connection\", { val: false, ack: true }).catch(() => {});\n this.setState(\"info.mqttConnected\", { val: false, ack: true }).catch(() => {});\n this.setState(\"info.openapiMqttConnected\", {\n val: false,\n ack: true,\n }).catch(() => {});\n this.setState(\"info.cloudConnected\", { val: false, ack: true }).catch(() => {});\n } catch {\n // ignore\n }\n callback();\n }\n\n /**\n * Handle state changes from user (write operations).\n *\n * @param id State ID\n * @param state New state value\n */\n private async onStateChange(id: string, state: ioBroker.State | null | undefined): Promise<void> {\n if (!state || state.ack || !this.deviceManager || !this.stateManager) {\n return;\n }\n\n // Global refresh button \u2014 triggers one fresh cloud fetch across all\n // devices and re-builds the state tree. Handy after creating a new\n // snapshot in the Govee Home app without restarting the adapter.\n if (id === `${this.namespace}.info.refresh_cloud_data` && state.val) {\n await this.handleManualCloudRefresh();\n await this.setStateAsync(id, { val: false, ack: true });\n return;\n }\n\n // Find which device this state belongs to\n const localId = id.replace(`${this.namespace}.`, \"\");\n if (!localId.startsWith(\"devices.\") && !localId.startsWith(\"groups.\")) {\n return;\n }\n\n const device = this.findDeviceForState(localId);\n if (!device) {\n return;\n }\n\n // Determine command from state suffix after device prefix\n const prefix = this.stateManager.devicePrefix(device);\n const stateSuffix = localId.slice(prefix.length + 1);\n\n // Resolve dropdown input \u2014 accept Number, numeric String, or label\n // (case-insensitive) against the state's common.states map. Returns\n // the canonical key as String so the rest of the handler sees the\n // same shape it always saw (e.g. \"1\"). Non-dropdown states are\n // passed through unchanged.\n const resolved = await this.resolveDropdownInput(id, state.val);\n if (!resolved.ok) {\n this.log.warn(`Unknown dropdown value for ${id}: ${String(state.val)} \u2014 ignoring`);\n return;\n }\n const val = resolved.val;\n\n // Group fan-out: route commands to each member device\n if (device.sku === \"BaseGroup\" && device.groupMembers) {\n await this.handleGroupFanOut(device, stateSuffix, val);\n await this.setStateAsync(id, { val, ack: true });\n if (stateSuffix === \"scenes.light_scene\" || stateSuffix === \"music.music_mode\") {\n await this.resetRelatedDropdowns(prefix, stateSuffix === \"scenes.light_scene\" ? \"lightScene\" : \"music\");\n }\n return;\n }\n\n // Handle local snapshot commands (no Cloud/MQTT needed)\n if (stateSuffix === \"snapshots.snapshot_save\" && typeof val === \"string\" && val.trim()) {\n await this.snapshotHandler!.save(device, val.trim());\n await this.setStateAsync(id, { val: \"\", ack: true });\n return;\n }\n if (stateSuffix === \"snapshots.snapshot_local\") {\n if (val !== \"0\" && val !== 0) {\n await this.snapshotHandler!.restore(device, val);\n await this.resetRelatedDropdowns(prefix, \"snapshotLocal\");\n }\n await this.setStateAsync(id, { val, ack: true });\n return;\n }\n if (stateSuffix === \"snapshots.snapshot_delete\" && typeof val === \"string\" && val.trim()) {\n this.snapshotHandler!.delete(device, val.trim());\n await this.setStateAsync(id, { val: \"\", ack: true });\n return;\n }\n\n // Manual segments toggle/list \u2014 handler owns the ack because a parse\n // error rewrites manual_mode to false, and an outer ack with the\n // raw value would resurrect the rejected entry.\n if (stateSuffix === \"segments.manual_mode\" || stateSuffix === \"segments.manual_list\") {\n await this.handleManualSegmentsChange(device, stateSuffix, val);\n return;\n }\n\n if (stateSuffix === \"diag.export\" && val) {\n await this.handleDiagnosticsExport(device, prefix, id);\n return;\n }\n\n const command = this.stateToCommand(stateSuffix);\n\n if (!command) {\n await this.handleGenericCapabilityCommand(device, id, stateSuffix, val);\n return;\n }\n\n // Dropdown reset to \"---\" (value 0) \u2014 acknowledge without sending command\n if ((command === \"lightScene\" || command === \"diyScene\" || command === \"snapshot\") && (val === \"0\" || val === 0)) {\n await this.setStateAsync(id, { val, ack: true });\n return;\n }\n\n // Scene speed: store on device, applied on next scene activation.\n // Persist to SKU cache so the user's choice survives a restart.\n if (command === \"sceneSpeed\") {\n const level = typeof val === \"number\" ? val : parseInt(String(val), 10);\n if (!isNaN(level)) {\n device.sceneSpeed = level;\n this.deviceManager?.persistDeviceToCache(device);\n }\n await this.setStateAsync(id, { val, ack: true });\n return;\n }\n\n try {\n // Music mode: combine all music states into one STRUCT command\n if (command === \"music\") {\n // music_mode \"---\" (value 0) \u2014 acknowledge without sending command\n if (stateSuffix === \"music.music_mode\" && (val === \"0\" || val === 0)) {\n await this.setStateAsync(id, { val, ack: true });\n return;\n }\n await this.sendMusicCommand(device, prefix, stateSuffix, val);\n await this.setStateAsync(id, { val, ack: true });\n // Reset scene/snapshot dropdowns when activating music mode\n if (stateSuffix === \"music.music_mode\") {\n await this.resetRelatedDropdowns(prefix, \"music\");\n }\n return;\n }\n\n await this.deviceManager.sendCommand(device, command, val);\n // Optimistic ack\n await this.setStateAsync(id, { val, ack: true });\n // Reset related dropdowns when switching modes.\n // Power-off is a special case \u2014 the device is off, so no mode is\n // active anymore; reset every mode dropdown so the UI reflects the\n // reality (scene/music/snapshot selections are now just history).\n if (command === \"power\" && val === false) {\n await this.resetModeDropdowns(prefix, \"\");\n } else {\n await this.resetRelatedDropdowns(prefix, command);\n }\n } catch (err) {\n this.log.warn(`Command failed for ${device.name}: ${errMessage(err)}`);\n }\n }\n\n /**\n * Resolve a dropdown-state input value against the state's common.states\n * map. Returns the canonical key (always String form) so a user can write\n * either the index (\"1\"), the index as a number (1) or the label name\n * (\"Aurora\", case-insensitive) \u2014 all three land at the same canonical\n * value for the rest of the handler.\n *\n * Non-dropdown states (no common.states), reset sentinels (0/\"0\"/\"\") and\n * non-string/number inputs are passed through unchanged. A dropdown input\n * that doesn't match any key or label returns ok=false so the caller can\n * warn and skip the command.\n *\n * @param id Full state id\n * @param raw Raw input value as provided by the user/script\n */\n private async resolveDropdownInput(\n id: string,\n raw: ioBroker.StateValue,\n ): Promise<{ val: ioBroker.StateValue; ok: boolean }> {\n if (raw === null || raw === undefined) {\n return { val: raw, ok: true };\n }\n // Reset sentinels \u2014 let the existing branch handle them.\n if (raw === 0 || raw === \"0\" || raw === \"\") {\n return { val: raw, ok: true };\n }\n // Only dropdown candidates have common.states; non-dropdown inputs\n // can't be resolved here so they pass through.\n if (typeof raw !== \"number\" && typeof raw !== \"string\") {\n return { val: raw, ok: true };\n }\n const obj = await this.getObjectAsync(id);\n const states = obj?.common?.states;\n if (!states || typeof states !== \"object\") {\n return { val: raw, ok: true };\n }\n const resolved = resolveStatesValue(raw, states as Record<string, string>);\n if (resolved) {\n return { val: resolved.key, ok: true };\n }\n return { val: raw, ok: false };\n }\n\n /**\n * Build and send a music_setting STRUCT command.\n * Reads sibling music state values and combines them into one API call.\n *\n * @param device Target device\n * @param prefix Device state prefix\n * @param changedSuffix Which music state was changed\n * @param newValue New value for the changed state\n */\n private async sendMusicCommand(\n device: GoveeDevice,\n prefix: string,\n changedSuffix: string,\n newValue: ioBroker.StateValue,\n ): Promise<void> {\n const musicBase = `${this.namespace}.${prefix}.music`;\n\n // Read current sibling values\n const modeState = await this.getStateAsync(`${musicBase}.music_mode`);\n const sensState = await this.getStateAsync(`${musicBase}.music_sensitivity`);\n const autoState = await this.getStateAsync(`${musicBase}.music_auto_color`);\n\n // Apply the changed value, use siblings for the rest\n const musicMode =\n changedSuffix === \"music.music_mode\" ? parseInt(String(newValue), 10) : parseInt(String(modeState?.val ?? 0), 10);\n const sensitivity =\n changedSuffix === \"music.music_sensitivity\" ? (newValue as number) : ((sensState?.val as number) ?? 100);\n const autoColor = changedSuffix === \"music.music_auto_color\" ? (newValue ? 1 : 0) : autoState?.val ? 1 : 0;\n\n if (!musicMode || musicMode === 0) {\n this.log.debug(\"Music mode not selected, skipping command\");\n return;\n }\n\n // LAN first: send via ptReal BLE if device is on LAN\n if (device.lanIp && this.lanClient) {\n // Read current color for RGB-modes (Spectrum=1, Rolling=2)\n let r = 0,\n g = 0,\n b = 0;\n if (musicMode === 1 || musicMode === 2) {\n const colorState = await this.getStateAsync(`${this.namespace}.${prefix}.control.colorRgb`);\n if (colorState?.val && typeof colorState.val === \"string\") {\n ({ r, g, b } = hexToRgb(colorState.val));\n }\n }\n this.lanClient.setMusicMode(device.lanIp, musicMode, r, g, b);\n return;\n }\n\n // Cloud fallback\n const structValue: Record<string, unknown> = {\n musicMode,\n sensitivity,\n autoColor,\n };\n\n await this.deviceManager!.sendCapabilityCommand(\n device,\n \"devices.capabilities.music_setting\",\n \"musicMode\",\n structValue,\n );\n }\n\n /**\n * Called by device-manager when a device state changes\n *\n * @param device Updated device\n * @param state Changed state values\n */\n private onDeviceStateUpdate(device: GoveeDevice, state: Partial<DeviceState>): void {\n if (this.stateManager) {\n this.stateManager.updateDeviceState(device, state).catch(() => {});\n }\n this.updateConnectionState();\n\n // Update group reachability when member online status changes\n if (state.online !== undefined) {\n this.updateGroupReachability();\n }\n\n // Mirror power-off to mode-dropdown reset. Covers MQTT/LAN-initiated\n // power changes (Govee app or physical remote) so the UI stays honest:\n // a device that's off can't be \"playing Aurora-A\" anymore.\n // L11 \u2014 defensive auch 0 als false akzeptieren (Govee schickt Power\n // theoretisch als boolean, aber MQTT-Boundary k\u00F6nnte 0 durchschleusen).\n const powerOff = state.power === false || (state.power as unknown) === 0;\n if (powerOff && this.stateManager) {\n const prefix = this.stateManager.devicePrefix(device);\n this.resetModeDropdowns(prefix, \"\").catch(() => undefined);\n }\n }\n\n /**\n * Fan out a group command to all member devices.\n * Basic controls (power, brightness, color) are sent directly.\n * Scenes/music are matched by name across members.\n *\n * @param group BaseGroup device\n * @param stateSuffix State path suffix (e.g. \"control.power\")\n * @param value Command value\n */\n private async handleGroupFanOut(group: GoveeDevice, stateSuffix: string, value: ioBroker.StateValue): Promise<void> {\n if (!this.deviceManager || !group.groupMembers) {\n return;\n }\n\n const devices = this.deviceManager.getDevices();\n const members = this.resolveGroupMembers(group, devices).filter(d => d.state.online);\n\n if (members.length === 0) {\n this.log.debug(`Group \"${group.name}\": no reachable members for fan-out`);\n return;\n }\n\n const command = this.stateToCommand(stateSuffix);\n if (!command) {\n return;\n }\n\n // Dropdown reset \u2014 no command needed\n if ((command === \"lightScene\" || command === \"music\") && (value === \"0\" || value === 0)) {\n return;\n }\n\n for (const member of members) {\n try {\n if (command === \"lightScene\") {\n await this.fanOutScene(group, member, value);\n } else if (command === \"music\") {\n await this.fanOutMusic(group, member, stateSuffix, value);\n } else {\n await this.deviceManager.sendCommand(member, command, value);\n }\n } catch (err) {\n this.log.debug(`Group fan-out to ${member.name}: ${errMessage(err)}`);\n }\n }\n }\n\n /**\n * Fan out a scene command: match group scene name to member scene index.\n *\n * @param group BaseGroup device\n * @param member Target member device\n * @param value Dropdown index value\n */\n private async fanOutScene(group: GoveeDevice, member: GoveeDevice, value: ioBroker.StateValue): Promise<void> {\n if (!this.deviceManager || !this.stateManager) {\n return;\n }\n\n // Get group scene name from dropdown value (1-based index)\n const groupPrefix = this.stateManager.devicePrefix(group);\n const obj = await this.getObjectAsync(`${this.namespace}.${groupPrefix}.scenes.light_scene`);\n const groupStates = obj?.common?.states as Record<string, string> | undefined;\n const sceneName = groupStates?.[String(value)];\n if (!sceneName) {\n return;\n }\n\n // Find the same scene name in the member's scene list (1-based)\n const memberIdx = member.scenes.findIndex(s => s.name === sceneName);\n if (memberIdx >= 0) {\n await this.deviceManager.sendCommand(member, \"lightScene\", memberIdx + 1);\n }\n }\n\n /**\n * Fan out a music command: match group music name to member music index.\n *\n * @param group BaseGroup device\n * @param member Target member device\n * @param stateSuffix Music state path suffix\n * @param value Command value\n */\n private async fanOutMusic(\n group: GoveeDevice,\n member: GoveeDevice,\n stateSuffix: string,\n value: ioBroker.StateValue,\n ): Promise<void> {\n if (!this.deviceManager || !this.stateManager) {\n return;\n }\n\n // For sensitivity/auto_color, forward directly \u2014 these are numeric values\n if (stateSuffix !== \"music.music_mode\") {\n await this.sendMusicCommand(member, this.stateManager.devicePrefix(member), stateSuffix, value);\n return;\n }\n\n // Get group music name from dropdown value (1-based index)\n const groupPrefix = this.stateManager.devicePrefix(group);\n const obj = await this.getObjectAsync(`${this.namespace}.${groupPrefix}.music.music_mode`);\n const groupStates = obj?.common?.states as Record<string, string> | undefined;\n const musicName = groupStates?.[String(value)];\n if (!musicName) {\n return;\n }\n\n // Find the same music name in the member's music library (1-based)\n const memberIdx = member.musicLibrary.findIndex(m => m.name === musicName);\n if (memberIdx >= 0) {\n // Build the music command struct for the member\n const memberPrefix = this.stateManager.devicePrefix(member);\n // Temporarily write the music mode value to trigger the member's music command\n await this.sendMusicCommand(member, memberPrefix, \"music.music_mode\", memberIdx + 1);\n }\n }\n\n /**\n * Resolve group member references to actual device objects.\n *\n * @param group BaseGroup device with groupMembers\n * @param devices Full device list to search\n */\n private resolveGroupMembers(group: GoveeDevice, devices: GoveeDevice[]): GoveeDevice[] {\n if (!group.groupMembers) {\n return [];\n }\n return group.groupMembers\n .map(m => devices.find(d => d.sku === m.sku && d.deviceId === m.deviceId))\n .filter((d): d is GoveeDevice => d !== undefined);\n }\n\n /**\n * Recalculate info.membersUnreachable for all groups.\n * Called when any device's online status changes.\n */\n private updateGroupReachability(): void {\n if (!this.deviceManager || !this.stateManager) {\n return;\n }\n const devices = this.deviceManager.getDevices();\n for (const group of devices) {\n if (group.sku !== \"BaseGroup\" || !group.groupMembers) {\n continue;\n }\n const memberDevices = this.resolveGroupMembers(group, devices);\n this.stateManager.updateGroupMembersUnreachable(group, memberDevices).catch(() => {});\n }\n }\n\n /**\n * Rebuild state definitions for one device and feed them into StateManager.\n * Used both from the full-list callback and from targeted refreshes\n * (e.g. after a local snapshot was added or removed \u2014 no reason to rebuild\n * the entire tree for every device then).\n *\n * @param device Target device\n * @param allDevices Full device list (needed to resolve group members)\n */\n private refreshDeviceStates(device: GoveeDevice, allDevices: GoveeDevice[]): void {\n if (!this.stateManager) {\n return;\n }\n const localSnaps = this.localSnapshots?.getSnapshots(device.sku, device.deviceId);\n let memberDevices: GoveeDevice[] | undefined;\n if (device.sku === \"BaseGroup\" && device.groupMembers) {\n memberDevices = this.resolveGroupMembers(device, allDevices);\n }\n const stateDefs = buildDeviceStateDefs(device, localSnaps, memberDevices);\n const p = this.stateManager\n .createDeviceStates(device, stateDefs)\n .then(async () => {\n // v2.1.0 \u2192 v2.1.1 layout migration: drop legacy info.diagnostics_*\n // before publishing the new diag.tier value. Idempotent.\n await this.stateManager?.migrateLegacyDiagnostics(device);\n await this.stateManager?.updateDeviceTier(device, getDeviceTier(device.sku));\n })\n .catch(e => {\n this.log.error(`createDeviceStates failed for ${device.name}: ${errMessage(e)}`);\n });\n // Until ready, collect so onReady can await the whole initial batch.\n // After ready, fire-and-forget \u2014 the queue would otherwise keep growing\n // with resolved promises for the lifetime of the adapter.\n if (!this.statesReady) {\n this.stateCreationQueue.push(p);\n } else {\n void p;\n }\n }\n\n /**\n * Called by device-manager when the device list changes\n *\n * @param devices Current list of all devices\n */\n private onDeviceListChanged(devices: GoveeDevice[]): void {\n if (!this.stateManager) {\n return;\n }\n\n for (const device of devices) {\n this.refreshDeviceStates(device, devices);\n }\n\n this.updateConnectionState();\n // Cache sync happens once after the initial setup completes (see\n // checkAllReady) \u2014 triggering here would fire on every device update\n // and spam the log.\n\n // Keep adapter-level per-device maps (diagnosticsLastRun, ...) aligned\n // with the new device list so removed devices don't leave orphan keys.\n // Skip during the initial boot phase \u2014 the startup cleanupTimer handles\n // that pass with proper LAN-scan-settled timing.\n if (this.statesReady) {\n this.reapStaleDevices().catch(() => undefined);\n }\n }\n\n /**\n * Update global `info.connection` \u2014 der ioBroker-IDC-Indikator.\n *\n * Semantik:\n * - Mit Devices: `connected = true` wenn MIND. ein Device online ist.\n * Wenn alle offline \u2192 false (User sieht: kein Device antwortet).\n * - Ohne Devices: `connected = true` wenn der LAN-Stack l\u00E4uft. Sonst\n * false (z.B. EADDRINUSE oder bind-Fehler).\n *\n * H4 (geplant f\u00FCr Phase H): nur bei tats\u00E4chlichem Wechsel schreiben\n * (cache lastConnectedValue). Aktuell l\u00E4uft updateConnectionState bei\n * jedem device-state-update \u2014 fire-and-forget setStateAsync, nur leichter\n * Overhead.\n */\n private updateConnectionState(): void {\n const devices = this.deviceManager?.getDevices() ?? [];\n const hasDevices = devices.length > 0;\n const anyOnline = devices.some(d => d.state.online);\n const lanRunning = this.lanClient !== null;\n const connected = hasDevices ? anyOnline : lanRunning;\n if (connected !== this.lastConnectionState) {\n this.lastConnectionState = connected;\n this.setStateAsync(\"info.connection\", { val: connected, ack: true }).catch(() => {});\n }\n }\n\n /**\n * Delete ioBroker objects for devices no longer present and drop the same\n * devices from adapter-level maps. Called after the initial-discovery\n * window and every time the device list changes.\n *\n * Scope of \"stale\" today: cleanupDevices compares the ioBroker object tree\n * against the live device-manager registry \u2014 it deletes objects that\n * outlive their entry in `DeviceManager.devices`. In v2.0 that registry is\n * monotonically growing within a single adapter lifetime (entries only\n * leave via cache pruning across restarts), so this primarily catches\n * tree leftovers from a previous adapter version after upgrade. The\n * adapter-level `diagnosticsLastRun` map is also reaped so it can't outlive\n * its devices either.\n *\n * A future stale-pruning step that explicitly retires devices from the\n * device-manager registry should also drop the device from\n * `deviceManager.devices` and call `getDiagnostics().forget(deviceId)` for\n * each retired device \u2014 those reaping APIs come in with the pruning patch,\n * not before (Memory `feedback_kein_phantom_schema`).\n */\n /**\n * App-Version-Drift-Check gegen iTunes-Lookup.\n *\n * Govee's app2.govee.com-Endpoints rejecten manchmal sehr alte\n * User-Agent-Strings. Daily-Check fragt iTunes nach der aktuellen\n * iOS-App-Version + vergleicht mit `GOVEE_APP_VERSION`. Bei Drift > 2\n * minor versions: warn-Log + state `info.appVersionDrift` schreiben.\n *\n * Failures (5xx, network) werden silent debug-geloggt \u2014 kein User-Impact.\n */\n private async checkAppVersionDrift(): Promise<void> {\n try {\n const resp = await httpsRequest<{ resultCount?: number; results?: Array<{ version?: string }> }>({\n method: \"GET\",\n url: \"https://itunes.apple.com/lookup?bundleId=com.ihoment.GoVeeSensor\",\n headers: { \"User-Agent\": \"ioBroker.govee-smart\" },\n timeout: 10_000,\n });\n const liveVersion = resp.results?.[0]?.version;\n if (typeof liveVersion !== \"string\" || liveVersion.length === 0) {\n return;\n }\n const localParts = GOVEE_APP_VERSION.split(\".\").map(Number);\n const liveParts = liveVersion.split(\".\").map(Number);\n // Major + Minor vergleichen \u2014 patch-Differenz ist OK, Govee toleriert\n // bis ~2 minor.\n const localMajor = localParts[0] ?? 0;\n const localMinor = localParts[1] ?? 0;\n const liveMajor = liveParts[0] ?? 0;\n const liveMinor = liveParts[1] ?? 0;\n const liveTotal = liveMajor * 100 + liveMinor;\n const localTotal = localMajor * 100 + localMinor;\n const driftMinor = liveTotal - localTotal;\n const driftMessage =\n driftMinor === 0\n ? `current (live=${liveVersion}, local=${GOVEE_APP_VERSION})`\n : driftMinor <= 2\n ? `minor drift (live=${liveVersion}, local=${GOVEE_APP_VERSION})`\n : `STALE (live=${liveVersion}, local=${GOVEE_APP_VERSION}) \u2014 bump GOVEE_APP_VERSION`;\n await this.setStateAsync(\"info.appVersionDrift\", { val: driftMessage, ack: true }).catch(() => undefined);\n if (driftMinor > 2) {\n this.log.warn(\n `Govee app version drift: live ${liveVersion} vs local ${GOVEE_APP_VERSION} \u2014 undocumented endpoints may start failing. Run sync-govee-app-version.py + release a new adapter version.`,\n );\n } else {\n this.log.debug(`App version: ${driftMessage}`);\n }\n } catch (e) {\n this.log.debug(`App version check failed: ${errMessage(e)}`);\n }\n }\n\n private async reapStaleDevices(): Promise<void> {\n if (!this.stateManager || !this.deviceManager) {\n return;\n }\n const currentDevices = this.deviceManager.getDevices();\n await this.stateManager.cleanupDevices(currentDevices);\n\n // Diagnostics-buffer f\u00FCr entfernte Devices droppen damit Logs/Packets/\n // Responses l\u00E4ngst entfernter Ger\u00E4te nicht im Speicher bleiben.\n const liveDeviceIds = new Set(currentDevices.map(d => d.deviceId));\n this.deviceManager.getDiagnostics().pruneOrphans(liveDeviceIds);\n\n const liveKeys = new Set(currentDevices.map(d => `${d.sku}:${d.deviceId}`));\n for (const key of this.diagnosticsLastRun.keys()) {\n if (!liveKeys.has(key)) {\n this.diagnosticsLastRun.delete(key);\n }\n }\n }\n\n /**\n * Check if all configured channels are initialized and log ready message.\n * Called from MQTT onConnection callback and end of onReady.\n */\n private checkAllReady(): void {\n if (this.readyLogged) {\n return;\n }\n // Wait for first LAN scan (always active)\n if (!this.lanScanDone) {\n return;\n }\n // Wait for initial state creation to complete\n if (!this.statesReady) {\n return;\n }\n // Wait for Cloud init if configured\n if (this.cloudClient && !this.cloudInitDone) {\n return;\n }\n // Wait for MQTT connection if configured\n if (this.mqttClient && !this.mqttClient.connected) {\n return;\n }\n // H1 \u2014 Wait for OpenAPI-MQTT (Cloud-events for sensors/appliances)\n // wenn konfiguriert. Vorher fehlte das \u2014 Adapter war \"ready\" obwohl\n // Sensor-Events-Channel nicht connected war.\n if (this.openapiMqttClient && !this.openapiMqttClient.connected) {\n return;\n }\n // H2 \u2014 Wait for first App-API-Poll wenn ein Sensor-Device angemeldet\n // ist. App-API liefert die Sensor-Werte (H5179 Temp/Humidity/Battery).\n // Ohne diesen Check w\u00E4re der Adapter \"ready\" mit leeren Sensor-Werten\n // f\u00FCr ~2 Minuten.\n if (this.deviceManager?.hasDeviceNeedingAppApi() && !this.appApiInitialPollDone) {\n return;\n }\n this.readyLogged = true;\n this.logDeviceSummary();\n // Persist any learned changes from the initial load (e.g. resolveSegmentCount\n // collapsing Cloud's 15 to the real 10 on H70D1). One-shot on first ready;\n // subsequent mutations persist themselves (MQTT bumps, wizard, manual-mode).\n this.deviceManager?.saveDevicesToCache();\n }\n\n /**\n * Log final ready message with device/group/channel summary.\n */\n private logDeviceSummary(): void {\n if (!this.deviceManager) {\n return;\n }\n const all = this.deviceManager.getDevices();\n const devices = all.filter(d => d.sku !== \"BaseGroup\");\n const groups = all.filter(d => d.sku === \"BaseGroup\");\n\n // H5 \u2014 Ready-Log expliziter: Channel-Status (LAN+Cloud+MQTT+Cloud-events)\n // plus Devices-Online-Count plus Sensor-Initial-Poll-Marker. User soll\n // EINEN Blick auf den Log werfen und sehen was wirklich operational ist.\n const channels: string[] = [\"LAN\"];\n if (this.cloudWasConnected) {\n channels.push(\"Cloud\");\n }\n if (this.mqttClient?.connected) {\n channels.push(\"MQTT\");\n }\n if (this.openapiMqttClient?.connected) {\n channels.push(\"Cloud-events\");\n }\n\n // Device-summary mit Online-Count\n const lightDevices = devices.filter(d => d.type === \"devices.types.light\");\n const onlineDevices = devices.filter(d => d.state.online === true);\n const parts: string[] = [];\n if (devices.length > 0) {\n const onlineLights = lightDevices.filter(d => d.state.online === true).length;\n const totalLights = lightDevices.length;\n if (totalLights > 0) {\n parts.push(\n totalLights === onlineLights\n ? `${totalLights} light${totalLights > 1 ? \"s\" : \"\"} online`\n : `${totalLights} light${totalLights > 1 ? \"s\" : \"\"} (${onlineLights} online, ${totalLights - onlineLights} offline)`,\n );\n }\n const sensors = devices.length - lightDevices.length;\n if (sensors > 0) {\n const onlineSensors = onlineDevices.filter(d => d.type !== \"devices.types.light\").length;\n parts.push(`${sensors} sensor${sensors > 1 ? \"s\" : \"\"} (${onlineSensors} with data)`);\n }\n }\n if (groups.length > 0) {\n parts.push(`${groups.length} group${groups.length > 1 ? \"s\" : \"\"}`);\n }\n const summary = parts.length > 0 ? parts.join(\", \") : \"no devices found\";\n this.log.info(`Govee adapter ready \u2014 ${summary} \u2014 channels: ${channels.join(\"+\")}`);\n\n // Surface configured-but-not-connected channels with a concrete reason.\n // Truthful \u2014 never claim \"still pending\" when the channel actually failed.\n if (this.cloudClient && !this.cloudWasConnected) {\n const reason = this.cloudClient.getFailureReason();\n this.log.warn(reason ? `Cloud not connected: ${reason}` : \"Cloud not connected \u2014 see earlier errors\");\n }\n if (this.mqttClient && !this.mqttClient.connected) {\n const reason = this.mqttClient.getFailureReason();\n this.log.warn(reason ? `MQTT not connected: ${reason}` : \"MQTT not connected \u2014 see earlier errors\");\n }\n }\n\n /**\n * Load current state for all Cloud devices and populate state values.\n * Called once after initial Cloud device list load.\n */\n private async loadCloudStates(): Promise<void> {\n if (!this.cloudClient || !this.deviceManager || !this.stateManager) {\n return;\n }\n\n const devices = this.deviceManager.getDevices();\n // LAN-first: never overwrite LAN states with Cloud values\n const lanStateIds = new Set(getDefaultLanStates().map(s => s.id));\n let loaded = 0;\n\n for (const device of devices) {\n if (!device.channels.cloud || device.capabilities.length === 0) {\n continue;\n }\n\n try {\n const caps = await this.cloudClient.getDeviceState(device.sku, device.deviceId);\n const prefix = this.stateManager.devicePrefix(device);\n\n const writes: Promise<unknown>[] = [];\n for (const cap of caps) {\n const mapped = mapCloudStateValue(cap);\n if (!mapped) {\n continue;\n }\n // Skip LAN-covered states for LAN-capable devices\n if (device.lanIp && lanStateIds.has(mapped.stateId)) {\n continue;\n }\n const statePath = this.stateManager.resolveStatePath(prefix, mapped.stateId);\n // Fire-and-forget \u2014 States are created before loadCloudStates runs;\n // a rejection here means the state was deleted out-of-band and\n // can be safely ignored.\n writes.push(\n this.setStateAsync(statePath, {\n val: mapped.value,\n ack: true,\n }).catch(() => undefined),\n );\n }\n await Promise.all(writes);\n loaded++;\n } catch {\n this.log.debug(`Could not load Cloud state for ${device.name} (${device.sku})`);\n }\n }\n\n if (loaded > 0) {\n this.log.debug(`Cloud states loaded for ${loaded} devices`);\n }\n }\n\n /**\n * Apply a list of synthesized Cloud-state capabilities to a single\n * device \u2014 the App-API poll and OpenAPI-MQTT events both use this path\n * so their values flow through the same `mapCloudStateValue` pipeline\n * that polled Cloud states use.\n *\n * @param device Target Govee device\n * @param caps Capabilities to apply\n */\n private async applyCloudCapabilities(device: GoveeDevice, caps: CloudStateCapability[]): Promise<void> {\n if (!this.stateManager) {\n return;\n }\n const lanStateIds = new Set(getDefaultLanStates().map(s => s.id));\n const prefix = this.stateManager.devicePrefix(device);\n const planned = planCloudCapabilityWrites(caps, Boolean(device.lanIp), lanStateIds);\n // App-API and OpenAPI-MQTT deliver state IDs (battery, temperature,\n // humidity, lackWater, \u2026) that the Cloud-capability pipeline doesn't\n // declare for sensor/appliance SKUs \u2014 the state objects therefore\n // don't exist yet on first write. ensureSyntheticStateObject creates\n // them lazily with the right channel + role + unit.\n for (const mapped of planned) {\n await this.stateManager.ensureSyntheticStateObject(prefix, mapped.stateId);\n }\n const writes = planned.map(mapped => {\n const statePath = this.stateManager!.resolveStatePath(prefix, mapped.stateId);\n return this.setStateAsync(statePath, {\n val: mapped.value,\n ack: true,\n }).catch(() => undefined);\n });\n await Promise.all(writes);\n }\n\n /**\n * Find device for a state ID\n *\n * @param localId Local state ID without namespace prefix\n */\n private findDeviceForState(localId: string): GoveeDevice | undefined {\n if (!this.deviceManager || !this.stateManager) {\n return undefined;\n }\n\n for (const device of this.deviceManager.getDevices()) {\n const prefix = this.stateManager.devicePrefix(device);\n if (localId.startsWith(`${prefix}.`)) {\n return device;\n }\n }\n return undefined;\n }\n\n /**\n * Map state suffix to command name.\n *\n * Simple suffixes live in a lookup table, segment indices need regex\n * extraction because they're dynamic. The three music states all route\n * to the same \"music\" command \u2014 the handler reads sibling values.\n *\n * @param suffix State ID suffix (e.g. \"power\", \"brightness\")\n */\n private stateToCommand(suffix: string): string | null {\n const direct = STATE_TO_COMMAND[suffix];\n if (direct) {\n return direct;\n }\n const segColorMatch = /^segments\\.(\\d+)\\.color$/.exec(suffix);\n if (segColorMatch) {\n return `segmentColor:${segColorMatch[1]}`;\n }\n const segBrightMatch = /^segments\\.(\\d+)\\.brightness$/.exec(suffix);\n if (segBrightMatch) {\n return `segmentBrightness:${segBrightMatch[1]}`;\n }\n return null;\n }\n\n /**\n * Central entry point for manual-segment updates. Sets the device flags,\n * rebuilds the segment tree (which writes manual_mode + manual_list with\n * ack=true), and persists to cache. Both the user state-change handler\n * and the wizard route their final decisions here.\n *\n * @param device Target device\n * @param mode Whether manual mode should be active\n * @param indices Physical indices when mode=true, ignored otherwise\n */\n private async applyManualSegments(device: GoveeDevice, mode: boolean, indices?: number[]): Promise<void> {\n if (!this.stateManager) {\n return;\n }\n device.manualMode = mode;\n device.manualSegments = mode && Array.isArray(indices) && indices.length > 0 ? indices.slice() : undefined;\n await this.stateManager.createSegmentStates(device);\n this.deviceManager?.persistDeviceToCache(device);\n }\n\n /**\n * React to manual-segments state changes \u2014 parses list, forwards to\n * {@link applyManualSegments}. On parse error disables manual mode so the\n * rejected value doesn't survive in the state tree.\n *\n * @param device Target device\n * @param suffix State suffix (either \"segments.manual_mode\" or \"segments.manual_list\")\n * @param newValue Written value\n */\n private async handleManualSegmentsChange(device: GoveeDevice, suffix: string, newValue: unknown): Promise<void> {\n // Peer value that wasn't just written comes from the device object\n // (kept in sync via createSegmentStates), not a separate state read.\n const modeVal = suffix === \"segments.manual_mode\" ? Boolean(newValue) : device.manualMode === true;\n const listVal =\n suffix === \"segments.manual_list\"\n ? typeof newValue === \"string\"\n ? newValue\n : \"\"\n : Array.isArray(device.manualSegments)\n ? device.manualSegments.join(\",\")\n : \"\";\n\n if (!modeVal) {\n this.log.info(`${device.name}: manual segments disabled \u2014 strip treated as contiguous`);\n await this.applyManualSegments(device, false);\n return;\n }\n\n // Upper bound: cap at the real length if known, otherwise the protocol limit.\n // Real length can still grow via MQTT discovery, so SEGMENT_HARD_MAX is the\n // absolute safety net.\n const maxIndex =\n typeof device.segmentCount === \"number\" && device.segmentCount > 0 ? device.segmentCount - 1 : SEGMENT_HARD_MAX;\n const parsed = parseSegmentList(listVal, maxIndex);\n if (parsed.error) {\n this.log.warn(`${device.name}: manual_list invalid (${parsed.error}) \u2014 disabling manual mode`);\n await this.applyManualSegments(device, false);\n return;\n }\n\n this.log.debug(`${device.name}: manual segments active \u2014 ${parsed.indices.length} physical indices (${listVal})`);\n await this.applyManualSegments(device, true, parsed.indices);\n }\n\n // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Segment-Detection-Wizard \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Handle incoming sendTo messages (from jsonConfig).\n *\n * @param obj ioBroker message object\n */\n private onMessage(obj: ioBroker.Message): void {\n if (!obj?.command) {\n return;\n }\n // Never let a rejection bubble up from the event handler \u2014 the ioBroker\n // event emitter doesn't catch it, which would crash the adapter.\n this.handleMessage(obj).catch(e => {\n this.log.warn(`onMessage handler crashed for ${obj.command}: ${errMessage(e)}`);\n this.sendMessageResponse(obj, {\n error: e instanceof Error ? e.message : String(e),\n });\n });\n }\n\n private async handleMessage(obj: ioBroker.Message): Promise<void> {\n try {\n if (obj.command === \"getSegmentDevices\") {\n const devices = this.deviceManager?.getDevices() ?? [];\n const list = devices\n .filter(d => d.sku !== \"BaseGroup\" && d.state?.online === true && resolveSegmentCount(d) > 0)\n .map(d => {\n const count = resolveSegmentCount(d);\n return {\n value: this.deviceKeyFor(d),\n label: `${d.name} (${d.sku}, bisher ${count} Segmente)`,\n };\n });\n // selectSendTo expects the array directly, not wrapped in an object\n this.sendMessageResponse(obj, list);\n return;\n }\n if (obj.command === \"segmentWizard\") {\n const payload = (obj.message ?? {}) as {\n action?: string;\n device?: string;\n };\n const response = await this.runWizardStep(payload.action ?? \"\", payload.device ?? \"\");\n this.sendMessageResponse(obj, response);\n return;\n }\n if (obj.command === \"mqttAuth\") {\n const payload = (obj.message ?? {}) as { action?: string };\n const response = await this.runMqttAuthAction(payload.action ?? \"\");\n this.sendMessageResponse(obj, response);\n return;\n }\n } catch (e) {\n this.log.warn(`onMessage failed for ${obj.command}: ${errMessage(e)}`);\n this.sendMessageResponse(obj, {\n error: e instanceof Error ? e.message : String(e),\n });\n }\n }\n\n /**\n * Handle the `mqttAuth` onMessage commands triggered by the admin UI.\n *\n * Two actions:\n * - `test` \u2014 try a one-shot login with the current settings (incl. any code) and\n * report a single-line plaintext result the admin can show as a toast.\n * - `requestCode` \u2014 POST to /account/rest/account/v1/verification so Govee emails a fresh\n * code. 30-second in-memory throttle prevents double-click email spam.\n *\n * @param action Action name from the jsonConfig sendTo button\n */\n private async runMqttAuthAction(action: string): Promise<{ result: string }> {\n const config = this.config as unknown as AdapterConfig;\n if (!config.goveeEmail || !config.goveePassword) {\n return { result: \"Email + Passwort in den Adapter-Einstellungen n\u00F6tig.\" };\n }\n\n if (action === \"test\") {\n // One-shot client: don't reuse this.mqttClient \u2014 we don't want this\n // probe to take over its reconnect timer or auth-failure counter.\n const probe = new GoveeMqttClient(config.goveeEmail, config.goveePassword, this.log, this);\n probe.setVerificationCode(config.mqttVerificationCode ?? \"\");\n try {\n let connected = false;\n await probe.connect(\n () => {},\n isConnected => {\n connected = isConnected;\n },\n );\n probe.disconnect();\n return {\n result: connected\n ? \"Login erfolgreich \u2014 Echtzeit-Status-Updates aktiv.\"\n : \"Login angenommen, MQTT-Verbindung steht aber noch nicht \u2014 Adapter neu starten.\",\n };\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n if (/Verification required/i.test(msg)) {\n return {\n result:\n \"Govee verlangt 2-Faktor-Best\u00E4tigung. Bitte 'Verifizierungs-Code anfordern' klicken, Code aus der E-Mail eintragen und Speichern.\",\n };\n }\n if (/Verification code invalid/i.test(msg)) {\n return {\n result: \"2-Faktor-Code ung\u00FCltig oder abgelaufen \u2014 bitte einen neuen Code anfordern.\",\n };\n }\n if (/email not registered/i.test(msg)) {\n return { result: \"Diese E-Mail ist bei Govee nicht registriert.\" };\n }\n if (/Login failed/i.test(msg)) {\n return { result: \"Passwort wurde von Govee abgelehnt.\" };\n }\n if (/Rate limited/i.test(msg)) {\n return { result: \"Govee meldet Rate-Limit \u2014 bitte sp\u00E4ter erneut versuchen.\" };\n }\n if (/Account temporarily locked/i.test(msg)) {\n return { result: \"Govee-Account vor\u00FCbergehend gesperrt \u2014 Govee Home App \u00F6ffnen und Status pr\u00FCfen.\" };\n }\n return { result: `Login fehlgeschlagen: ${msg}` };\n }\n }\n\n if (action === \"requestCode\") {\n const now = Date.now();\n if (now - this.lastVerificationRequestMs < VERIFICATION_REQUEST_THROTTLE_MS) {\n const wait = Math.ceil((VERIFICATION_REQUEST_THROTTLE_MS - (now - this.lastVerificationRequestMs)) / 1000);\n return { result: `Bitte ${wait}s warten \u2014 gerade wurde schon ein Code angefordert.` };\n }\n this.lastVerificationRequestMs = now;\n const probe = new GoveeMqttClient(config.goveeEmail, config.goveePassword, this.log, this);\n try {\n await probe.requestVerificationCode();\n return { result: \"Code wurde an deine Govee-E-Mail-Adresse gesendet (Spam-Ordner pr\u00FCfen).\" };\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n return { result: `Govee hat den Code-Versand abgelehnt: ${msg}` };\n }\n }\n\n return { result: `Unbekannte Aktion '${action}'.` };\n }\n\n /**\n * Helper: clear `mqttVerificationCode` in adapter native after a successful\n * login or a 455-fail.\n *\n * Idempotent: liest erst den aktuellen Wert, schreibt nur wenn dirty.\n * Verhindert den Adapter-Restart der durch jeden\n * `extendForeignObjectAsync(system.adapter.X, native:...)`-Call ausgel\u00F6st\n * wird (Memory v2.1.3-Bug). Vorher gab es nach jedem 2FA-Login einen\n * unn\u00F6tigen Restart.\n */\n private async clearVerificationCodeSetting(): Promise<void> {\n try {\n const obj = await this.getForeignObjectAsync(`system.adapter.${this.namespace}`);\n const native = (obj?.native ?? {}) as Record<string, unknown>;\n // Skip wenn das Feld eh schon leer (oder undefined) \u2014 kein dirty write,\n // kein Restart-Trigger.\n if (typeof native.mqttVerificationCode !== \"string\" || native.mqttVerificationCode === \"\") {\n return;\n }\n await this.extendForeignObjectAsync(`system.adapter.${this.namespace}`, {\n native: { mqttVerificationCode: \"\" },\n });\n } catch (e) {\n this.log.warn(`Could not clear mqttVerificationCode: ${errMessage(e)}`);\n }\n }\n\n /**\n * Read persisted MQTT credentials from `info.mqttCredentials`. The\n * sensitive fields (bearer + cert + pass) are encrypted with the\n * system secret on save and decrypted here. Returns null if no\n * credentials are stored or the JSON is unparseable.\n *\n * State-based persistence (since v2.1.3) \u2014 writes to a state instead\n * of `system.adapter.X.native` so saving doesn't trigger an adapter\n * restart. The earlier native-based design caused an endless\n * login \u2192 save \u2192 restart \u2192 login loop.\n */\n private async loadPersistedCredsFromState(): Promise<PersistedMqttCredentials | null> {\n try {\n const s = await this.getStateAsync(\"info.mqttCredentials\");\n const raw = typeof s?.val === \"string\" ? s.val : \"\";\n if (!raw) {\n return null;\n }\n const obj = JSON.parse(raw) as {\n bearerToken?: unknown;\n iotEndpoint?: unknown;\n p12Cert?: unknown;\n p12Pass?: unknown;\n accountId?: unknown;\n accountTopic?: unknown;\n tokenExpiresAt?: unknown;\n };\n // typeof-Guards \u2014 JSON.parse liefert raw, this.decrypt() wirft auf\n // non-string-Input. Defensive: wenn das State-Blob durch ein Tool\n // editiert wurde und falsche Typen drin hat, returnen wir null.\n const safeStr = (v: unknown): string => (typeof v === \"string\" ? v : \"\");\n const bearerToken = this.decrypt(safeStr(obj.bearerToken));\n const p12Cert = this.decrypt(safeStr(obj.p12Cert));\n const p12Pass = this.decrypt(safeStr(obj.p12Pass));\n const iotEndpoint = safeStr(obj.iotEndpoint);\n const accountId = safeStr(obj.accountId);\n const accountTopic = safeStr(obj.accountTopic);\n const tokenExpiresAt = typeof obj.tokenExpiresAt === \"number\" ? obj.tokenExpiresAt : 0;\n if (!bearerToken || !iotEndpoint || !p12Cert || !accountId || !accountTopic || !tokenExpiresAt) {\n return null;\n }\n return { bearerToken, iotEndpoint, p12Cert, p12Pass, accountId, accountTopic, tokenExpiresAt };\n } catch {\n return null;\n }\n }\n\n /**\n * Persist freshly-issued MQTT credentials into `info.mqttCredentials`.\n * Sensitive fields go through `this.encrypt()` so the JSON blob is\n * useless without the system secret. State writes do NOT trigger an\n * adapter restart.\n *\n * @param creds The freshly-issued MQTT bundle from a successful login\n */\n private async persistCredsToState(creds: PersistedMqttCredentials): Promise<void> {\n const blob = JSON.stringify({\n bearerToken: this.encrypt(creds.bearerToken),\n iotEndpoint: creds.iotEndpoint,\n p12Cert: this.encrypt(creds.p12Cert),\n p12Pass: this.encrypt(creds.p12Pass),\n accountId: creds.accountId,\n accountTopic: creds.accountTopic,\n tokenExpiresAt: creds.tokenExpiresAt,\n });\n await this.setStateAsync(\"info.mqttCredentials\", { val: blob, ack: true });\n }\n\n /**\n * One-shot cleanup of legacy v2.1.0/v2.1.1/v2.1.2 plaintext credentials\n * sitting in `system.adapter.X.native`.\n *\n * Doppelte Idempotenz:\n * 1. State-Marker `info.legacyMqttCleaned=true` wird NACH erfolgreichem\n * Wipe gesetzt. Bei sp\u00E4teren Starts wird die Funktion \u00FCber den Marker\n * sofort verlassen \u2014 auch wenn das native irgendwie wieder dirty wird.\n * 2. Nur wenn KEINER der Legacy-Marker gesetzt ist UND das native dirty,\n * wird der einmalige extendForeignObjectAsync (Restart-Trigger) gemacht.\n *\n * Dieser Aufruf triggert genau einmal pro Adapter-Lifetime einen\n * Restart \u2014 das ist unvermeidlich, weil die Plaintext-Bytes weg m\u00FCssen.\n * Aber: nach erfolgreichem Cleanup bleibt der Marker, kein erneuter\n * Restart bei flaky writes.\n */\n private async cleanupLegacyMqttNativeOnce(): Promise<void> {\n try {\n // Marker-State zuerst pr\u00FCfen \u2014 wenn schon gecleant, sofort raus.\n const markerState = await this.getStateAsync(\"info.legacyMqttCleaned\");\n if (markerState?.val === true) {\n return;\n }\n const obj = await this.getForeignObjectAsync(`system.adapter.${this.namespace}`);\n const native = (obj?.native ?? {}) as Record<string, unknown>;\n const legacy = [\n \"mqttBearerToken\",\n \"mqttIotEndpoint\",\n \"mqttP12Cert\",\n \"mqttP12Pass\",\n \"mqttAccountId\",\n \"mqttAccountTopic\",\n \"mqttTokenExpiresAt\",\n ];\n const dirty = legacy.some(k => k in native && native[k] !== \"\" && native[k] !== 0);\n if (!dirty) {\n // Native ist clean (z.B. neue Installation oder schon migriert\n // ohne Marker). Marker setzen damit n\u00E4chster Start gar nicht erst\n // den Native-Read macht.\n await this.setStateAsync(\"info.legacyMqttCleaned\", { val: true, ack: true }).catch(() => undefined);\n return;\n }\n this.log.info(\"Removing legacy plaintext MQTT credentials from native (one-time migration)\");\n const wipe: Record<string, unknown> = {};\n for (const k of legacy) {\n wipe[k] = k === \"mqttTokenExpiresAt\" ? 0 : \"\";\n }\n await this.extendForeignObjectAsync(`system.adapter.${this.namespace}`, { native: wipe });\n // Marker setzen NACHDEM der Wipe erfolgreich war \u2014 bei einem etwaigen\n // Crash zwischen extend und setState l\u00E4uft der Cleanup beim n\u00E4chsten\n // Start nochmal (idempotent: native ist dann eh schon clean).\n await this.setStateAsync(\"info.legacyMqttCleaned\", { val: true, ack: true }).catch(() => undefined);\n } catch (e) {\n this.log.debug(`legacy MQTT cleanup skipped: ${errMessage(e)}`);\n }\n }\n\n private sendMessageResponse(obj: ioBroker.Message, data: unknown): void {\n if (obj.callback && obj.from) {\n this.sendTo(obj.from, obj.command, data as Record<string, unknown>, obj.callback);\n }\n }\n\n /**\n * Stable device key for wizard session tracking.\n *\n * @param device Target device\n */\n private deviceKeyFor(device: GoveeDevice): string {\n return `${device.sku}:${device.deviceId}`;\n }\n\n private findDeviceByKey(key: string): GoveeDevice | undefined {\n const devices = this.deviceManager?.getDevices() ?? [];\n return devices.find(d => this.deviceKeyFor(d) === key);\n }\n\n /** Construct the host object passed into SegmentWizard. */\n private buildWizardHost(): WizardHost {\n return {\n log: this.log,\n getState: id => this.getStateAsync(id),\n sendCommand: async (device, command, value) => {\n await this.deviceManager?.sendCommand(device, command, value);\n },\n flashSegmentAtomic: (device, idx) => {\n if (!device.lanIp || !this.lanClient) {\n return Promise.resolve(false);\n }\n this.lanClient.flashSingleSegment(device.lanIp, idx);\n return Promise.resolve(true);\n },\n restoreStripAtomic: (device, total, color, brightness) => {\n if (!device.lanIp || !this.lanClient) {\n return Promise.resolve(false);\n }\n const r = (color >> 16) & 0xff;\n const g = (color >> 8) & 0xff;\n const b = color & 0xff;\n this.lanClient.restoreAllSegments(device.lanIp, total, r, g, b, brightness);\n return Promise.resolve(true);\n },\n findDevice: key => this.findDeviceByKey(key),\n namespace: this.namespace,\n devicePrefix: device => this.stateManager?.devicePrefix(device) ?? \"\",\n setTimeout: (cb, ms) => this.setTimeout(cb, ms),\n clearTimeout: h => this.clearTimeout(h as ioBroker.Timeout),\n applyWizardResult: (device, result) => this.applyWizardResult(device, result),\n getLanguage: () => this.adminLanguage,\n };\n }\n\n /**\n * Apply a finished wizard's measurement: set the real segment count, then\n * route through {@link applyManualSegments} so the same state-tree rebuild\n * and cache-persist path runs for both wizard results and user edits.\n *\n * @param device Target device\n * @param result Wizard's measurement\n */\n private async applyWizardResult(device: GoveeDevice, result: WizardResult): Promise<void> {\n device.segmentCount = result.segmentCount;\n if (result.hasGaps) {\n const parsed = parseSegmentList(result.manualList, result.segmentCount - 1);\n await this.applyManualSegments(device, true, parsed.error ? undefined : parsed.indices);\n } else {\n await this.applyManualSegments(device, false);\n }\n this.log.debug(\n `applyWizardResult: ${device.sku} \u2192 segmentCount=${result.segmentCount}, ` +\n `manualMode=${device.manualMode}, list=\"${result.manualList}\"`,\n );\n }\n\n /**\n * Execute one wizard step (start/yes/no/abort). Delegates to\n * {@link SegmentWizard} \u2014 see `lib/segment-wizard.ts`.\n *\n * @param action \"start\" | \"yes\" | \"no\" | \"abort\"\n * @param deviceKey device identifier (only required for \"start\")\n */\n private async runWizardStep(action: string, deviceKey: string): Promise<Record<string, unknown>> {\n if (!this.segmentWizard) {\n this.segmentWizard = new SegmentWizard(this.buildWizardHost());\n }\n const response = await this.segmentWizard.runStep(action, deviceKey);\n // Mirror the current wizard status into a plain state so admin's\n // `type: \"state\"` component can show it live via state subscription.\n const statusText = this.segmentWizard.getStatusText();\n await this.setStateAsync(\"info.wizardStatus\", {\n val: statusText,\n ack: true,\n });\n return response;\n }\n\n /** Construct host object for SnapshotHandler \u2014 adapter dependencies injected. */\n private buildSnapshotHost(): SnapshotHandlerHost {\n return {\n log: this.log,\n store: this.localSnapshots!,\n namespace: this.namespace,\n devicePrefix: device => this.stateManager?.devicePrefix(device) ?? \"\",\n getState: id => this.getStateAsync(id),\n sendCommand: async (device, command, value) => {\n await this.deviceManager?.sendCommand(device, command, value);\n },\n refreshDeviceStates: device => {\n this.refreshDeviceStates(device, this.deviceManager?.getDevices() ?? []);\n },\n };\n }\n\n /** Dropdowns whose value is a mode-selection \u2014 reset to \"---\" (0) when the mode stops. */\n private static readonly MODE_DROPDOWNS = [\n \"scenes.light_scene\",\n \"scenes.diy_scene\",\n \"snapshots.snapshot_cloud\",\n \"snapshots.snapshot_local\",\n \"music.music_mode\",\n ];\n\n /** Map command \u2192 its own dropdown path (excluded from reset when that mode is the one that was just activated). */\n private static readonly COMMAND_DROPDOWN: Record<string, string> = {\n lightScene: \"scenes.light_scene\",\n diyScene: \"scenes.diy_scene\",\n snapshot: \"snapshots.snapshot_cloud\",\n snapshotLocal: \"snapshots.snapshot_local\",\n music: \"music.music_mode\",\n colorRgb: \"\",\n colorTemperature: \"\",\n };\n\n /**\n * Diagnostics-Export-Button-Handler. Throttled auf 2s pro Device damit\n * wiederholte/Skript-Trigger keinen Burst von JSON-Serialisierungen\n * erzeugen.\n *\n * @param device Govee device\n * @param prefix Device state prefix\n * @param triggerStateId The state-id that triggered the export (so we can ack)\n */\n private async handleDiagnosticsExport(device: GoveeDevice, prefix: string, triggerStateId: string): Promise<void> {\n if (!this.deviceManager) {\n return;\n }\n const deviceKey = `${device.sku}:${device.deviceId}`;\n const now = Date.now();\n const last = this.diagnosticsLastRun.get(deviceKey) ?? 0;\n if (now - last < 2000) {\n this.log.debug(`Diagnostics export throttled for ${device.name} \u2014 last run ${now - last}ms ago`);\n await this.setStateAsync(triggerStateId, { val: false, ack: true });\n return;\n }\n this.diagnosticsLastRun.set(deviceKey, now);\n const diag = this.deviceManager.generateDiagnostics(device, this.version ?? \"unknown\");\n const resultId = `${this.namespace}.${prefix}.diag.result`;\n await this.setStateAsync(resultId, {\n val: JSON.stringify(diag, null, 2),\n ack: true,\n });\n await this.setStateAsync(triggerStateId, { val: false, ack: true });\n this.log.info(`Diagnostics exported for ${device.name} (${device.sku})`);\n }\n\n /**\n * Generischer Capability-Routing-Pfad f\u00FCr States die nicht im STATE_TO_COMMAND\n * Mapping sind. Liest `native.capabilityType`/`capabilityInstance` aus dem\n * State-Object und schickt das via Cloud-API.\n *\n * @param device Target device\n * @param id Full state-id (f\u00FCr ack)\n * @param stateSuffix State-Pfad innerhalb Device (f\u00FCr debug-log)\n * @param val Value zum Senden\n */\n private async handleGenericCapabilityCommand(\n device: GoveeDevice,\n id: string,\n stateSuffix: string,\n val: ioBroker.StateValue,\n ): Promise<void> {\n if (!this.deviceManager) {\n return;\n }\n const obj = await this.getObjectAsync(id);\n const capType = obj?.native?.capabilityType;\n const capInstance = obj?.native?.capabilityInstance;\n if (typeof capType === \"string\" && typeof capInstance === \"string\") {\n try {\n await this.deviceManager.sendCapabilityCommand(device, capType, capInstance, val);\n await this.setStateAsync(id, { val, ack: true });\n } catch (err) {\n this.log.warn(`Command failed for ${device.name}: ${errMessage(err)}`);\n }\n } else {\n this.log.debug(`Unknown writable state: ${stateSuffix}`);\n }\n }\n\n /**\n * Reset related dropdown states when switching between scenes/snapshots/colors.\n * Each mode-switch resets all OTHER mode dropdowns to \"---\" (0).\n *\n * @param prefix Device state prefix\n * @param activeCommand The command that was just executed\n */\n private async resetRelatedDropdowns(prefix: string, activeCommand: string): Promise<void> {\n if (!(activeCommand in GoveeAdapter.COMMAND_DROPDOWN)) {\n return;\n }\n const ownDropdown = GoveeAdapter.COMMAND_DROPDOWN[activeCommand];\n await this.resetModeDropdowns(prefix, ownDropdown);\n }\n\n /**\n * Reset every mode dropdown except `keep` (empty = reset all). Used both for\n * mode-switches (keep the new mode's own dropdown) and for power-off\n * (reset everything \u2014 a device that's off has no active mode).\n *\n * @param prefix Device state prefix\n * @param keep Dropdown path to leave untouched (e.g. \"music.music_mode\"), or \"\" to reset all\n */\n private async resetModeDropdowns(prefix: string, keep: string): Promise<void> {\n await Promise.all(\n GoveeAdapter.MODE_DROPDOWNS.filter(d => d !== keep).map(async dropdown => {\n const stateId = `${this.namespace}.${prefix}.${dropdown}`;\n const current = await this.getStateAsync(stateId);\n if (current?.val && current.val !== \"0\" && current.val !== 0) {\n await this.setStateAsync(stateId, { val: \"0\", ack: true });\n }\n }),\n );\n }\n}\n\nif (require.main !== module) {\n module.exports = (options: Partial<utils.AdapterOptions> | undefined) => new GoveeAdapter(options);\n} else {\n (() => new GoveeAdapter())();\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["_a"]
|
|
7
7
|
}
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "govee-smart",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.4.0",
|
|
5
5
|
"news": {
|
|
6
|
+
"2.4.0": {
|
|
7
|
+
"en": "Code-hygiene: local snapshot manager (save/restore/delete) now lives in its own class with a host interface. main.ts shrinks; behaviour unchanged.",
|
|
8
|
+
"de": "Code-Hygiene: lokaler Snapshot-Manager (save/restore/delete) hat jetzt eine eigene Klasse mit Host-Interface. main.ts wird kleiner; Verhalten identisch.",
|
|
9
|
+
"ru": "Гигиена кода: локальный snapshot-менеджер вынесен в отдельный класс с host-интерфейсом. main.ts уменьшается; поведение неизменно.",
|
|
10
|
+
"pt": "Higiene de codigo: o gerenciador local de snapshots agora vive em sua propria classe com host-interface. main.ts encolhe; comportamento inalterado.",
|
|
11
|
+
"nl": "Code-hygiene: lokale snapshot-manager (save/restore/delete) heeft nu een eigen klasse met host-interface. main.ts wordt kleiner; gedrag ongewijzigd.",
|
|
12
|
+
"fr": "Hygiene de code : le gestionnaire de snapshot local vit maintenant dans sa propre classe avec une host-interface. main.ts retrecit; comportement inchange.",
|
|
13
|
+
"it": "Igiene del codice: il gestore snapshot locale vive ora nella propria classe con un host-interface. main.ts si riduce; comportamento invariato.",
|
|
14
|
+
"es": "Higiene de codigo: el gestor de snapshots locales vive ahora en su propia clase con host-interface. main.ts se reduce; comportamiento sin cambios.",
|
|
15
|
+
"pl": "Higiena kodu: lokalny snapshot-manager zyskal wlasna klase z host-interface. main.ts maleje; zachowanie bez zmian.",
|
|
16
|
+
"uk": "Гігієна коду: локальний snapshot-менеджер тепер живе у власному класі з host-інтерфейсом. main.ts зменшується; поведінка без змін.",
|
|
17
|
+
"zh-cn": "代码卫生:本地 snapshot 管理器现已拆分为独立类,使用 host 接口。main.ts 缩小;行为不变。"
|
|
18
|
+
},
|
|
19
|
+
"2.3.1": {
|
|
20
|
+
"en": "Test coverage: smoke tests for GoveeCloudClient + GoveeMqttClient. Initial-state and setter checks now in place; full mock-based test paths come in a later release.",
|
|
21
|
+
"de": "Test-Coverage: Smoke-Tests für GoveeCloudClient + GoveeMqttClient. Initial-State + Setter-Checks drin; volle Mock-basierte Test-Pfade kommen separat.",
|
|
22
|
+
"ru": "Покрытие тестами: smoke-тесты для GoveeCloudClient + GoveeMqttClient. Initial-state и setter-проверки добавлены; полные mock-пути позже.",
|
|
23
|
+
"pt": "Cobertura de testes: smoke tests para GoveeCloudClient + GoveeMqttClient. Initial-state e setter-checks adicionados; caminhos completos com mock serao em release separado.",
|
|
24
|
+
"nl": "Test-coverage: smoke tests voor GoveeCloudClient + GoveeMqttClient. Initial-state en setter-checks toegevoegd; volle mock-paths komen apart.",
|
|
25
|
+
"fr": "Couverture de tests : smoke tests pour GoveeCloudClient + GoveeMqttClient. Initial-state et setter-checks ajoutes; chemins complets avec mocks plus tard.",
|
|
26
|
+
"it": "Test coverage: smoke tests per GoveeCloudClient + GoveeMqttClient. Controlli initial-state e setter aggiunti; percorsi completi con mock separati.",
|
|
27
|
+
"es": "Cobertura de tests: smoke tests para GoveeCloudClient + GoveeMqttClient. Initial-state y setter-checks agregados; rutas completas con mocks por separado.",
|
|
28
|
+
"pl": "Pokrycie testami: smoke testy dla GoveeCloudClient + GoveeMqttClient. Initial-state i setter-checks dodane; pelne sciezki mock w osobnym release.",
|
|
29
|
+
"uk": "Покриття тестами: smoke-тести для GoveeCloudClient + GoveeMqttClient. Initial-state та setter-перевірки додані; повні mock-шляхи окремо.",
|
|
30
|
+
"zh-cn": "测试覆盖:GoveeCloudClient + GoveeMqttClient 的 smoke 测试。初始状态和 setter 检查已加入;完整 mock 路径将在后续发布。"
|
|
31
|
+
},
|
|
6
32
|
"2.3.0": {
|
|
7
33
|
"en": "App-version drift monitor: daily iTunes lookup warns when local Govee-App-Version constant gets too stale. Plus code-hygiene: onStateChange handlers split out, timing-constants module.",
|
|
8
34
|
"de": "App-Version-Drift-Monitor: täglicher iTunes-Lookup warnt wenn lokale Govee-App-Version zu alt ist. Plus Code-Hygiene: onStateChange-Handler aufgeteilt, timing-constants-Modul.",
|
|
@@ -67,32 +93,6 @@
|
|
|
67
93
|
"pl": "Komunikat weryfikacji nie twierdzi juz, ze konto ma 2FA, gdy nie ma. Stare dane z v2.1.0 sa czyszczone przy pierwszym starcie. H61D5 dodano do katalogu.",
|
|
68
94
|
"uk": "Повідомлення верифікації більше не стверджує, що у акаунта 2FA, якщо її немає. Старі креди з v2.1.0 чистяться при першому старті. H61D5 у каталозі.",
|
|
69
95
|
"zh-cn": "验证消息不再误称账户开启了 2FA。来自 v2.1.0 的旧凭据在首次启动时清理。H61D5 加入支持目录。"
|
|
70
|
-
},
|
|
71
|
-
"2.1.1": {
|
|
72
|
-
"en": "Security fix: in v2.1.0 your MQTT login was stored unencrypted by mistake — now encrypted. Diagnostics renamed to diag.export/result/tier. 2FA warning no longer repeats on every reconnect.",
|
|
73
|
-
"de": "Sicherheitsfix: in v2.1.0 wurde dein MQTT-Login versehentlich unverschluesselt gespeichert — jetzt verschluesselt. Diagnose-Datenpunkte heissen jetzt diag.*. 2FA-Warnung wiederholt sich nicht mehr.",
|
|
74
|
-
"ru": "Фикс безопасности: в v2.1.0 MQTT-логин ошибочно сохранялся незашифрованным — теперь шифруется. Диагностика переименована в diag.export/result/tier. 2FA-warn не повторяется при каждом реконнекте.",
|
|
75
|
-
"pt": "Correcao de seguranca: em v2.1.0 o login MQTT foi guardado sem cifrar por engano — agora cifrado. Diagnostico renomeado para diag.export/result/tier. Aviso 2FA nao se repete a cada reconexao.",
|
|
76
|
-
"nl": "Beveiligingsfix: in v2.1.0 werd je MQTT-login per ongeluk onversleuteld bewaard — nu versleuteld. Diagnose-datapoints heten nu diag.*. 2FA-waarschuwing herhaalt zich niet meer.",
|
|
77
|
-
"fr": "Correctif de securite: en v2.1.0 les identifiants MQTT etaient stockes en clair par erreur — maintenant chiffres. Diagnostics renommes en diag.*. L'avertissement 2FA ne se repete plus.",
|
|
78
|
-
"it": "Fix di sicurezza: in v2.1.0 il login MQTT era salvato non cifrato per errore — ora cifrato. Diagnostica rinominata in diag.export/result/tier. L'avviso 2FA non si ripete piu a ogni riconnessione.",
|
|
79
|
-
"es": "Fix de seguridad: en v2.1.0 el login MQTT se guardaba sin cifrar por error — ahora cifrado. Diagnostico renombrado a diag.export/result/tier. El aviso 2FA ya no se repite en cada reconexion.",
|
|
80
|
-
"pl": "Fix bezpieczenstwa: w v2.1.0 logowanie MQTT bylo bez szyfrowania przez pomylke — teraz szyfrowane. Diagnostyka przemianowana na diag.*. Ostrzezenie 2FA nie powtarza sie juz.",
|
|
81
|
-
"uk": "Фікс безпеки: у v2.1.0 MQTT-логін помилково зберігався незашифрованим — тепер шифрується. Діагностика перейменована на diag.export/result/tier. 2FA-warn не повторюється при кожному реконекті.",
|
|
82
|
-
"zh-cn": "安全修复:v2.1.0 中 MQTT 登录信息因错误而未加密保存 — 现已加密。诊断数据点改名为 diag.export/result/tier。2FA 警告不再在每次重连时重复。"
|
|
83
|
-
},
|
|
84
|
-
"2.1.0": {
|
|
85
|
-
"en": "Govee accounts that need email verification can now be used. Sensors no longer stuck offline (e.g. H5179 thermometer). Scene dropdowns visible from the first start. New diag.tier per device.",
|
|
86
|
-
"de": "Govee-Konten mit Mail-Verifizierung lassen sich jetzt nutzen. Sensoren bleiben nicht mehr offline (z.B. H5179). Szenen-Dropdown ab erstem Start sichtbar. Neuer diag.tier pro Geraet.",
|
|
87
|
-
"ru": "Аккаунты Govee с email-верификацией теперь работают. Сенсоры не зависают как offline (напр. H5179). Дропдауны сцен видны с первого старта. Новый diag.tier на устройство.",
|
|
88
|
-
"pt": "Contas Govee com verificacao por email agora funcionam. Sensores nao ficam offline por engano (ex. H5179). Dropdowns de cenas visiveis no primeiro start. Novo diag.tier por dispositivo.",
|
|
89
|
-
"nl": "Govee-accounts met e-mailverificatie werken nu. Sensoren blijven niet meer ten onrechte offline (bv. H5179). Scene-dropdowns zichtbaar vanaf de eerste start. Nieuwe diag.tier per apparaat.",
|
|
90
|
-
"fr": "Les comptes Govee avec verification email fonctionnent maintenant. Capteurs plus bloques offline (ex. H5179). Listes de scenes visibles au premier demarrage. Nouveau diag.tier par appareil.",
|
|
91
|
-
"it": "Gli account Govee con verifica email ora funzionano. Sensori non restano piu offline per errore (es. H5179). Dropdown scene visibili al primo avvio. Nuovo diag.tier per dispositivo.",
|
|
92
|
-
"es": "Cuentas Govee con verificacion email ya funcionan. Sensores ya no se quedan offline por error (p.ej. H5179). Desplegables de escenas visibles al primer arranque. Nuevo diag.tier por dispositivo.",
|
|
93
|
-
"pl": "Konta Govee z weryfikacja email mozna teraz uzywac. Czujniki nie zostaja juz blednie offline (np. termometr H5179). Dropdowny scen widoczne od pierwszego startu. Nowy diag.tier na urzadzenie.",
|
|
94
|
-
"uk": "Облікові записи Govee з email-верифікацією тепер працюють. Сенсори не залишаються офлайн помилково (напр. H5179). Випадаючі списки сцен видно з першого старту. Новий diag.tier на пристрій.",
|
|
95
|
-
"zh-cn": "需要邮件验证的 Govee 账户现在可以使用。传感器不再错误地卡在离线状态(如 H5179 温度计)。场景下拉菜单从首次启动起就可见。每设备新增 diag.tier。"
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
98
|
"titleLang": {
|