iobroker.govee-smart 2.16.1 → 2.16.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -94,6 +94,9 @@ This adapter's MQTT authentication and BLE-over-LAN (ptReal) protocol implementa
|
|
|
94
94
|
Placeholder for the next version (at the beginning of the line):
|
|
95
95
|
### **WORK IN PROGRESS**
|
|
96
96
|
-->
|
|
97
|
+
### 2.16.2 (2026-06-16)
|
|
98
|
+
- On hosts with multiple network interfaces, LAN device discovery now uses the selected interface for outgoing traffic, so it no longer misses devices by scanning on the wrong one.
|
|
99
|
+
|
|
97
100
|
### 2.16.1 (2026-06-11)
|
|
98
101
|
|
|
99
102
|
- Running in compact mode no longer intercepts errors from other adapters in the same process — error reporting stays correctly attributed per adapter.
|
|
@@ -115,11 +118,6 @@ This adapter's MQTT authentication and BLE-over-LAN (ptReal) protocol implementa
|
|
|
115
118
|
|
|
116
119
|
- Corrected the ioBroker role of the scene, mode and snapshot selector states (no change to how they work)
|
|
117
120
|
|
|
118
|
-
### 2.14.0 (2026-06-04)
|
|
119
|
-
|
|
120
|
-
- The segment-detection wizard and the Cloud login test now show their messages in all admin languages instead of falling back to English or German.
|
|
121
|
-
- For LAN-only lights, brightness, color and state are now recorded only when they actually change, keeping their history cleaner.
|
|
122
|
-
|
|
123
121
|
[Older changelogs can be found there](CHANGELOG_OLD.md)
|
|
124
122
|
|
|
125
123
|
## Support
|
|
@@ -57,7 +57,7 @@ const GOVEE_DEVICE_TYPE = {
|
|
|
57
57
|
ICE_MAKER: "devices.types.ice_maker",
|
|
58
58
|
AROMA_DIFFUSER: "devices.types.aroma_diffuser"
|
|
59
59
|
};
|
|
60
|
-
const GOVEE_APP_VERSION = "7.5.
|
|
60
|
+
const GOVEE_APP_VERSION = "7.5.12";
|
|
61
61
|
const GOVEE_CLIENT_TYPE = "1";
|
|
62
62
|
const GOVEE_USER_AGENT = `GoveeHome/${GOVEE_APP_VERSION} (com.ihoment.GoVeeSensor; build:8; iOS 26.5.0) Alamofire/5.11.0`;
|
|
63
63
|
const GOVEE_APP_BASE_URL = "https://app2.govee.com";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/govee-constants.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Shared Govee app-impersonation constants + capability/device-type strings.\n *\n * Capability and device-type constants replace inline string literals so a\n * typo at the call site becomes a TypeScript compile error instead of a\n * silent runtime miss. The values are dictated by Govee's Cloud API \u2014 we\n * mirror them 1:1.\n */\n\nimport { v5 as uuidv5, NIL as UUID_NIL } from \"uuid\";\n\n/** Govee Cloud API capability type strings (`capability.type`). */\nexport const GOVEE_CAP_TYPE = {\n ON_OFF: \"devices.capabilities.on_off\",\n RANGE: \"devices.capabilities.range\",\n COLOR_SETTING: \"devices.capabilities.color_setting\",\n SEGMENT_COLOR_SETTING: \"devices.capabilities.segment_color_setting\",\n DYNAMIC_SCENE: \"devices.capabilities.dynamic_scene\",\n PROPERTY: \"devices.capabilities.property\",\n TOGGLE: \"devices.capabilities.toggle\",\n MUSIC_SETTING: \"devices.capabilities.music_setting\",\n MODE: \"devices.capabilities.mode\",\n ONLINE: \"devices.capabilities.online\",\n WORK_MODE: \"devices.capabilities.work_mode\",\n TEMPERATURE_SETTING: \"devices.capabilities.temperature_setting\",\n EVENT: \"devices.capabilities.event\",\n} as const;\n\n/** Govee Cloud API device type strings (`device.type`). */\nexport const GOVEE_DEVICE_TYPE = {\n LIGHT: \"devices.types.light\",\n THERMOMETER: \"devices.types.thermometer\",\n SENSOR: \"devices.types.sensor\",\n HEATER: \"devices.types.heater\",\n HUMIDIFIER: \"devices.types.humidifier\",\n DEHUMIDIFIER: \"devices.types.dehumidifier\",\n FAN: \"devices.types.fan\",\n AIR_PURIFIER: \"devices.types.air_purifier\",\n SOCKET: \"devices.types.socket\",\n KETTLE: \"devices.types.kettle\",\n ICE_MAKER: \"devices.types.ice_maker\",\n AROMA_DIFFUSER: \"devices.types.aroma_diffuser\",\n} as const;\n\nexport const GOVEE_APP_VERSION = \"7.5.
|
|
4
|
+
"sourcesContent": ["/**\n * Shared Govee app-impersonation constants + capability/device-type strings.\n *\n * Capability and device-type constants replace inline string literals so a\n * typo at the call site becomes a TypeScript compile error instead of a\n * silent runtime miss. The values are dictated by Govee's Cloud API \u2014 we\n * mirror them 1:1.\n */\n\nimport { v5 as uuidv5, NIL as UUID_NIL } from \"uuid\";\n\n/** Govee Cloud API capability type strings (`capability.type`). */\nexport const GOVEE_CAP_TYPE = {\n ON_OFF: \"devices.capabilities.on_off\",\n RANGE: \"devices.capabilities.range\",\n COLOR_SETTING: \"devices.capabilities.color_setting\",\n SEGMENT_COLOR_SETTING: \"devices.capabilities.segment_color_setting\",\n DYNAMIC_SCENE: \"devices.capabilities.dynamic_scene\",\n PROPERTY: \"devices.capabilities.property\",\n TOGGLE: \"devices.capabilities.toggle\",\n MUSIC_SETTING: \"devices.capabilities.music_setting\",\n MODE: \"devices.capabilities.mode\",\n ONLINE: \"devices.capabilities.online\",\n WORK_MODE: \"devices.capabilities.work_mode\",\n TEMPERATURE_SETTING: \"devices.capabilities.temperature_setting\",\n EVENT: \"devices.capabilities.event\",\n} as const;\n\n/** Govee Cloud API device type strings (`device.type`). */\nexport const GOVEE_DEVICE_TYPE = {\n LIGHT: \"devices.types.light\",\n THERMOMETER: \"devices.types.thermometer\",\n SENSOR: \"devices.types.sensor\",\n HEATER: \"devices.types.heater\",\n HUMIDIFIER: \"devices.types.humidifier\",\n DEHUMIDIFIER: \"devices.types.dehumidifier\",\n FAN: \"devices.types.fan\",\n AIR_PURIFIER: \"devices.types.air_purifier\",\n SOCKET: \"devices.types.socket\",\n KETTLE: \"devices.types.kettle\",\n ICE_MAKER: \"devices.types.ice_maker\",\n AROMA_DIFFUSER: \"devices.types.aroma_diffuser\",\n} as const;\n\nexport const GOVEE_APP_VERSION = \"7.5.12\";\nexport const GOVEE_CLIENT_TYPE = \"1\";\nexport const GOVEE_USER_AGENT = `GoveeHome/${GOVEE_APP_VERSION} (com.ihoment.GoVeeSensor; build:8; iOS 26.5.0) Alamofire/5.11.0`;\n\n/** Base URL for the undocumented Govee app API (devices/v1/list, scene library, etc.). */\nexport const GOVEE_APP_BASE_URL = \"https://app2.govee.com\";\n\n/**\n * Derive a stable, account-specific client ID from the user's email.\n *\n * The previous hardcoded constant looked like a single bot account from Govee's\n * side, which is the kind of thing that gets rate-limited or flagged.\n * Three reference implementations (homebridge-govee, govee2mqtt PR #652, PR #656)\n * all use UUIDv5(email) \u2014 same input always returns the same UUID, so each user\n * has one stable ID across restarts but each account is distinct.\n *\n * @param email - Govee account email address. Empty/undefined returns a deterministic\n * fallback so existing call sites that build the ID before login\n * don't crash; the fallback is never sent to Govee in practice.\n */\nexport function deriveGoveeClientId(email: string | undefined): string {\n const seed = (email ?? \"\").trim().toLowerCase() || \"anonymous\";\n return uuidv5(seed, UUID_NIL).replace(/-/g, \"\");\n}\n"],
|
|
5
5
|
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASA,kBAA8C;AAGvC,MAAM,iBAAiB;AAAA,EAC5B,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,eAAe;AAAA,EACf,uBAAuB;AAAA,EACvB,eAAe;AAAA,EACf,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,eAAe;AAAA,EACf,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,qBAAqB;AAAA,EACrB,OAAO;AACT;AAGO,MAAM,oBAAoB;AAAA,EAC/B,OAAO;AAAA,EACP,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,KAAK;AAAA,EACL,cAAc;AAAA,EACd,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,gBAAgB;AAClB;AAEO,MAAM,oBAAoB;AAC1B,MAAM,oBAAoB;AAC1B,MAAM,mBAAmB,aAAa,iBAAiB;AAGvD,MAAM,qBAAqB;AAe3B,SAAS,oBAAoB,OAAmC;AACrE,QAAM,QAAQ,wBAAS,IAAI,KAAK,EAAE,YAAY,KAAK;AACnD,aAAO,YAAAA,IAAO,MAAM,YAAAC,GAAQ,EAAE,QAAQ,MAAM,EAAE;AAChD;",
|
|
6
6
|
"names": ["uuidv5", "UUID_NIL"]
|
|
7
7
|
}
|
|
@@ -145,6 +145,9 @@ class GoveeLanClient {
|
|
|
145
145
|
this.sendSocket.on("error", (err) => {
|
|
146
146
|
this.log.debug(`LAN send socket error: ${err.message}`);
|
|
147
147
|
});
|
|
148
|
+
if (bindAddr) {
|
|
149
|
+
this.sendSocket.bind(0, bindAddr);
|
|
150
|
+
}
|
|
148
151
|
this.listenSocket = dgram.createSocket({ type: "udp4", reuseAddr: true });
|
|
149
152
|
this.listenSocket.on("message", (msg, rinfo) => {
|
|
150
153
|
this.handleMessage(msg, rinfo.address);
|
|
@@ -167,7 +170,7 @@ class GoveeLanClient {
|
|
|
167
170
|
this.log.debug(`LAN scan socket error: ${err.message}`);
|
|
168
171
|
});
|
|
169
172
|
this.scanSocket.bind(0, bindAddr, () => {
|
|
170
|
-
var _a, _b;
|
|
173
|
+
var _a, _b, _c;
|
|
171
174
|
if (this.stopped) {
|
|
172
175
|
return;
|
|
173
176
|
}
|
|
@@ -179,6 +182,15 @@ class GoveeLanClient {
|
|
|
179
182
|
`LAN: could not join multicast group on ${bindAddr != null ? bindAddr : "default interface"} \u2014 discovery may be incomplete`
|
|
180
183
|
);
|
|
181
184
|
}
|
|
185
|
+
if (bindAddr) {
|
|
186
|
+
try {
|
|
187
|
+
(_c = this.scanSocket) == null ? void 0 : _c.setMulticastInterface(bindAddr);
|
|
188
|
+
} catch {
|
|
189
|
+
this.log.info(
|
|
190
|
+
`LAN: could not pin multicast egress to ${bindAddr} \u2014 outgoing discovery may use the default interface`
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
182
194
|
this.sendScan();
|
|
183
195
|
});
|
|
184
196
|
if (!this.stopped) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/govee-lan-client.ts"],
|
|
4
|
-
"sourcesContent": ["import * as dgram from \"node:dgram\";\nimport { clampByte, type LanDevice, type LanMessage, type LanStatus, type TimerAdapter } from \"./types\";\nimport { FORCE_COLOR_MODE_SETTLE_MS } from \"./timing-constants\";\nimport {\n SEGMENT_BRIGHTNESS_BITMASK_BYTES,\n SEGMENT_COLOR_BITMASK_BYTES,\n SEGMENT_COUNT_MAX,\n} from \"./device-manager/lookups\";\n\nconst MULTICAST_ADDR = \"239.255.255.250\";\nconst SCAN_PORT = 4001;\nconst LISTEN_PORT = 4002;\nconst COMMAND_PORT = 4003;\n\n/** Callback for discovered LAN devices */\nexport type LanDiscoveryCallback = (device: LanDevice) => void;\n\n/** Callback for status updates (matched by source IP, not device ID) */\nexport type LanStatusCallback = (sourceIp: string, status: LanStatus) => void;\n\n/**\n * Callback fired for every outgoing UDP datagram. Wired to the DiagnosticsCollector\n * via main.ts so `diag.export` carries the verbatim bytes the adapter put on the\n * wire \u2014 closes the \"did the adapter even send the snapshot?\" diag blind spot.\n *\n * Note: ip is the destination, not a device id. main.ts resolves to deviceId.\n */\nexport type LanSendCallback = (ip: string, cmd: string, payload: unknown, bytes: number, error?: string) => void;\n\n/**\n * Callback fired for every parsed devStatus reply. Wired to the\n * DiagnosticsCollector so a \"device ack'd the command but didn't render\"\n * report has the actual response payload in the diag JSON.\n */\nexport type LanStatusRecordCallback = (sourceIp: string, status: LanStatus) => void;\n\n/**\n * Callback fired for every parsed scan reply. Diag-only side-channel that\n * lets the DiagnosticsCollector log the raw LAN discovery handshake \u2014 useful\n * for \"device shows up in scan but the adapter doesn't add it\" reports.\n */\nexport type LanScanRecordCallback = (lanDevice: LanDevice) => void;\n\n/**\n * Govee LAN UDP client for device discovery and control.\n * Handles multicast discovery on port 4001, listens on 4002, sends commands to 4003.\n */\nexport class GoveeLanClient {\n private scanSocket: dgram.Socket | null = null;\n private listenSocket: dgram.Socket | null = null;\n /**\n * Persistent send-socket \u2014 previously a new dgram socket was created, used to\n * send, and closed per command. On adapter stop mid-send the callback could\n * fire into a half-torn-down adapter.\n */\n private sendSocket: dgram.Socket | null = null;\n private scanTimer: ioBroker.Interval | undefined = undefined;\n /**\n * True after `stop()` was called \u2014 bind-callbacks check this flag before\n * starting timers/scans, so a `stop()` during the async listen+scan bind\n * sequence cannot leave a runaway scanTimer behind.\n */\n private stopped = false;\n /**\n * Pending one-shot timeouts created by {@link flashSingleSegment} \u2014 kept\n * so {@link stop} can cancel them before the deferred ptReal burst fires\n * into a torn-down LAN client.\n */\n private readonly pendingFlashTimers = new Set<ioBroker.Timeout>();\n private readonly timers: TimerAdapter;\n private readonly log: ioBroker.Logger;\n private onDiscovery: LanDiscoveryCallback | null = null;\n private onStatus: LanStatusCallback | null = null;\n private onSend: LanSendCallback | null = null;\n private onStatusRecord: LanStatusRecordCallback | null = null;\n private onScanRecord: LanScanRecordCallback | null = null;\n private readonly seenDeviceIps = new Set<string>();\n /**\n * Per-IP timestamp of the last command we sent (ptReal/setScene/etc).\n * Used to annotate incoming LAN-status responses with the \u0394t \u2014 gives an\n * approximate \"did this status follow a command of ours?\" signal in the\n * debug log. Proximity, not proof: Govee's UDP protocol doesn't carry\n * command IDs, so a small \u0394 is *probably* a response but could be\n * unrelated polling.\n */\n private readonly lastCommandSentMs = new Map<string, number>();\n /** Multicast membership address \u2014 remembered for dropMembership in stop(). */\n private multicastBind: string | undefined;\n\n /**\n * @param log ioBroker logger\n * @param timers Timer adapter for setInterval/setTimeout\n */\n constructor(log: ioBroker.Logger, timers: TimerAdapter) {\n this.log = log;\n this.timers = timers;\n }\n\n /**\n * Register a send hook called for every outgoing UDP datagram. main.ts\n * resolves the destination IP to a deviceId and forwards into the\n * DiagnosticsCollector \u2014 closes the v2.9.0 diag blind spot where ptReal\n * sends were only visible in the adapter log, not in per-device diag.\n *\n * @param cb Callback receiving (ip, cmd, payload, bytes, error?)\n */\n setSendHook(cb: LanSendCallback | null): void {\n this.onSend = cb;\n }\n\n /**\n * Register a hook called for every parsed devStatus reply. Used for diag\n * capture \u2014 adapter looks up the device by IP and records the payload as\n * a pseudo-endpoint (`lan://devStatus`).\n *\n * @param cb Callback receiving (sourceIp, status)\n */\n setStatusRecordHook(cb: LanStatusRecordCallback | null): void {\n this.onStatusRecord = cb;\n }\n\n /**\n * Register a hook called for every parsed scan reply. Diag-only.\n *\n * @param cb Callback receiving (lanDevice)\n */\n setScanRecordHook(cb: LanScanRecordCallback | null): void {\n this.onScanRecord = cb;\n }\n\n /**\n * Start LAN discovery and listening for responses.\n *\n * @param onDiscovery Called when a new device is found\n * @param onStatus Called when a status response arrives\n * @param scanIntervalMs How often to send multicast scan (default 30s)\n * @param networkInterface IP of network interface to bind to (empty = all)\n */\n start(\n onDiscovery: LanDiscoveryCallback,\n onStatus: LanStatusCallback,\n scanIntervalMs = 30_000,\n networkInterface = \"\",\n ): void {\n this.onDiscovery = onDiscovery;\n this.onStatus = onStatus;\n\n const bindAddr = networkInterface && networkInterface !== \"0.0.0.0\" ? networkInterface : undefined;\n if (bindAddr) {\n this.log.info(`LAN binding to network interface ${bindAddr}`);\n }\n\n this.multicastBind = bindAddr;\n\n // Persistent send-socket for commands \u2014 create once, close in stop().\n this.sendSocket = dgram.createSocket(\"udp4\");\n this.sendSocket.on(\"error\", err => {\n this.log.debug(`LAN send socket error: ${err.message}`);\n });\n\n // Listen socket for responses (port 4002) \u2014 must be ready before first scan\n this.listenSocket = dgram.createSocket({ type: \"udp4\", reuseAddr: true });\n this.listenSocket.on(\"message\", (msg, rinfo) => {\n this.handleMessage(msg, rinfo.address);\n });\n this.listenSocket.on(\"error\", err => {\n // EADDRINUSE = port 4002 already taken (a second adapter instance?). The\n // user needs to know \u2014 otherwise the adapter is half-dead (discovery via\n // scan works, status replies are lost).\n const code = (err as NodeJS.ErrnoException).code;\n if (code === \"EADDRINUSE\") {\n this.log.warn(`LAN listen port ${LISTEN_PORT} already in use \u2014 second instance? Status updates will be lost.`);\n } else {\n this.log.debug(`LAN listen socket error: ${err.message}`);\n }\n });\n this.listenSocket.bind(LISTEN_PORT, bindAddr, () => {\n // stop() may have been called between listenSocket.bind and this\n // callback firing \u2014 bail out so we don't spin up a scan socket and a\n // recurring timer behind the back of a torn-down LAN client.\n if (this.stopped) {\n return;\n }\n this.log.debug(`LAN listening on port ${LISTEN_PORT}`);\n\n // Scan socket for multicast discovery (port 4001) \u2014 started after listen is ready\n this.scanSocket = dgram.createSocket({ type: \"udp4\", reuseAddr: true });\n this.scanSocket.on(\"error\", err => {\n this.log.debug(`LAN scan socket error: ${err.message}`);\n });\n // bind(0, bindAddr) \u2014 ephemeral port, but bound to the chosen interface.\n // Previously bind() without an address \u2192 ANY \u2192 asymmetric to the listen\n // socket that uses bindAddr.\n this.scanSocket.bind(0, bindAddr, () => {\n if (this.stopped) {\n return;\n }\n this.scanSocket?.setBroadcast(true);\n try {\n this.scanSocket?.addMembership(MULTICAST_ADDR, bindAddr);\n } catch {\n // A membership fail is typically an OS routing issue (e.g. interface\n // bindAddr not in the multicast routing table). LAN discovery stays\n // partially functional via setBroadcast(true), but that is asymmetric\n // \u2014 replies may not come back. Log at info level so the user can find\n // the cause of the \"no devices\" symptom in the logs.\n this.log.info(\n `LAN: could not join multicast group on ${bindAddr ?? \"default interface\"} \u2014 discovery may be incomplete`,\n );\n }\n this.sendScan();\n });\n\n // Periodic scan \u2014 guard the timer creation in case stop() raced with\n // the bind callback, otherwise we'd leave a setInterval running\n // against a torn-down LAN client.\n if (!this.stopped) {\n this.scanTimer = this.timers.setInterval(() => {\n this.sendScan();\n }, scanIntervalMs);\n }\n });\n }\n\n /**\n * Snapshot of the discovery cache + last-command-sent timestamps for\n * the runtime-state diag export. Returns plain serialisable shapes so\n * the DiagnosticsCollector can clone-and-cap them safely.\n */\n getDiagSnapshot(): { seenDeviceIps: string[]; lastCommandSentMs: Record<string, number> } {\n return {\n seenDeviceIps: Array.from(this.seenDeviceIps),\n lastCommandSentMs: Object.fromEntries(this.lastCommandSentMs),\n };\n }\n\n /** Stop all sockets and timers */\n stop(): void {\n this.stopped = true;\n if (this.scanTimer) {\n this.timers.clearInterval(this.scanTimer);\n this.scanTimer = undefined;\n }\n // Cancel any deferred wizard-flash bursts that haven't fired yet.\n for (const handle of this.pendingFlashTimers) {\n this.timers.clearTimeout(handle);\n }\n this.pendingFlashTimers.clear();\n if (this.scanSocket) {\n // dropMembership symmetric to addMembership \u2014 on macOS/Windows the\n // multicast filter could otherwise linger on the NIC until process exit.\n try {\n if (this.multicastBind) {\n this.scanSocket.dropMembership(MULTICAST_ADDR, this.multicastBind);\n }\n } catch {\n /* ignore \u2014 best-effort */\n }\n try {\n this.scanSocket.close();\n } catch {\n /* ignore */\n }\n this.scanSocket = null;\n }\n if (this.listenSocket) {\n try {\n this.listenSocket.close();\n } catch {\n /* ignore */\n }\n this.listenSocket = null;\n }\n if (this.sendSocket) {\n try {\n this.sendSocket.close();\n } catch {\n /* ignore */\n }\n this.sendSocket = null;\n }\n // L4 \u2014 clear seenDeviceIps, otherwise entries survive between start/stop\n // cycles in the same process (correctness unaffected, but the\n // discovery-log info on an IP change would be lost).\n this.seenDeviceIps.clear();\n this.multicastBind = undefined;\n }\n\n /**\n * Send a control command to a device via LAN.\n *\n * @param ip Device IP address\n * @param cmd Command name (turn, brightness, colorwc, devStatus)\n * @param data Command data\n */\n private sendCommand(ip: string, cmd: string, data: Record<string, unknown>): void {\n if (!this.sendSocket) {\n this.log.debug(`LAN send dropped (socket not ready): ${cmd} \u2192 ${ip}`);\n this.onSend?.(ip, cmd, data, 0, \"socket not ready\");\n return;\n }\n const message: LanMessage = {\n msg: { cmd, data },\n };\n const buf = Buffer.from(JSON.stringify(message));\n // L5 \u2014 the Govee spec is UDP-PMTU-bound. Warn on very large ptReal payloads\n // (many BLE packets in JSON) so the user knows why something gets lost.\n if (buf.length > 1400) {\n this.log.debug(`LAN payload large (${buf.length} bytes) \u2014 may be PMTU-fragmented for ${ip}`);\n }\n this.sendSocket.send(buf, 0, buf.length, COMMAND_PORT, ip, err => {\n if (err) {\n this.log.debug(`LAN send error to ${ip}: ${err.message}`);\n this.onSend?.(ip, cmd, data, buf.length, err.message);\n } else {\n this.lastCommandSentMs.set(ip, Date.now());\n this.onSend?.(ip, cmd, data, buf.length);\n }\n });\n }\n\n /**\n * Send power command\n *\n * @param ip Device IP address\n * @param on Power state\n */\n setPower(ip: string, on: boolean): void {\n this.sendCommand(ip, \"turn\", { value: on ? 1 : 0 });\n }\n\n /**\n * Send brightness command\n *\n * @param ip Device IP address\n * @param brightness Brightness 0-100\n */\n setBrightness(ip: string, brightness: number): void {\n this.sendCommand(ip, \"brightness\", {\n value: clampByte0_100(brightness),\n });\n }\n\n /**\n * Send color command. Inputs are clamped to 0-255 \u2014 out-of-range values\n * from upstream coercion paths (capability-mapper, command-router) would\n * otherwise be sent verbatim and produce undefined-behaviour at the device.\n *\n * @param ip Device IP address\n * @param r Red channel 0-255\n * @param g Green channel 0-255\n * @param b Blue channel 0-255\n */\n setColor(ip: string, r: number, g: number, b: number): void {\n this.sendCommand(ip, \"colorwc\", {\n color: { r: clampByte(r), g: clampByte(g), b: clampByte(b) },\n colorTemInKelvin: 0,\n });\n }\n\n /**\n * Send color temperature command. Out-of-band kelvin values are clamped\n * to Govee's published 2000-9000 K range (per-device range may be tighter,\n * those are corrected via {@link applyColorTempQuirk} upstream).\n *\n * @param ip Device IP address\n * @param kelvin Color temperature in Kelvin\n */\n setColorTemperature(ip: string, kelvin: number): void {\n const clamped = Number.isFinite(kelvin) ? Math.max(2000, Math.min(9000, Math.round(kelvin))) : 2000;\n this.sendCommand(ip, \"colorwc\", {\n color: { r: 0, g: 0, b: 0 },\n colorTemInKelvin: clamped,\n });\n }\n\n /**\n * Send a scene via ptReal BLE-passthrough.\n * Builds multi-packet BLE data from scenceParam + final scene-code packet.\n *\n * @param ip Device IP address\n * @param sceneCode Scene code from scene library (must be > 0)\n * @param scenceParam Base64-encoded scene parameter data (may be empty for simple presets)\n */\n setScene(ip: string, sceneCode: number, scenceParam: string): void {\n if (sceneCode <= 0) {\n return;\n }\n const packets = buildScenePackets(sceneCode, scenceParam);\n this.sendPtReal(ip, packets);\n }\n\n /**\n * Send raw ptReal BLE-passthrough packets to a device.\n *\n * @param ip Device IP address\n * @param base64Packets Array of Base64-encoded 20-byte BLE packets\n */\n sendPtReal(ip: string, base64Packets: string[]): void {\n if (!this.sendSocket) {\n this.log.debug(`LAN ptReal dropped (socket not ready): ${ip}`);\n this.onSend?.(ip, \"ptReal\", { command: base64Packets }, 0, \"socket not ready\");\n return;\n }\n const message = {\n msg: { cmd: \"ptReal\", data: { command: base64Packets } },\n };\n const buf = Buffer.from(JSON.stringify(message));\n if (buf.length > 1400) {\n this.log.debug(`ptReal payload large (${buf.length} bytes) \u2014 may be PMTU-fragmented for ${ip}`);\n }\n this.sendSocket.send(buf, 0, buf.length, COMMAND_PORT, ip, err => {\n if (err) {\n // warn (was: debug) \u2014 a silent UDP-send failure leaves the user\n // wondering why a scene/snapshot \"did nothing\". One log per failed\n // send is acceptable noise; recurring sends to the same offline IP\n // will repeat, which is the right signal.\n this.log.warn(`LAN ptReal error to ${ip}: ${err.message}`);\n this.onSend?.(ip, \"ptReal\", { command: base64Packets }, buf.length, err.message);\n } else {\n // Success on debug: confirms the UDP datagram left the socket so\n // a \"snapshot/scene did not activate\" report can be triaged\n // without enabling silly-level wire logging.\n this.log.debug(`LAN ptReal sent to ${ip}: ${base64Packets.length} packet(s), ${buf.length} bytes`);\n this.lastCommandSentMs.set(ip, Date.now());\n this.onSend?.(ip, \"ptReal\", { command: base64Packets }, buf.length);\n }\n });\n }\n\n /**\n * Set gradient toggle via ptReal BLE-passthrough.\n *\n * @param ip Device IP address\n * @param on Gradient on/off\n */\n setGradient(ip: string, on: boolean): void {\n this.sendPtReal(ip, [buildGradientPacket(on)]);\n }\n\n /**\n * Activate a DIY scene via ptReal BLE-passthrough.\n * Sends A1 multi-packet data (if provided) + activation command.\n *\n * @param ip Device IP address\n * @param scenceParam Base64-encoded DIY parameter data (may be empty to activate last DIY)\n */\n setDiyScene(ip: string, scenceParam: string): void {\n const packets = buildDiyPackets(scenceParam);\n this.sendPtReal(ip, packets);\n }\n\n /**\n * Set music mode via ptReal BLE-passthrough.\n * Sub-modes 1 (Spectrum) and 2 (Rolling) use RGB color.\n *\n * @param ip Device IP address\n * @param subMode Music sub-mode (0-3)\n * @param r Red channel 0-255 (used by modes 1, 2)\n * @param g Green channel 0-255\n * @param b Blue channel 0-255\n */\n setMusicMode(ip: string, subMode: number, r = 0, g = 0, b = 0): void {\n this.sendPtReal(ip, [buildMusicModePacket(subMode, r, g, b)]);\n }\n\n /**\n * Set segment color via ptReal BLE-passthrough (command 33 05 15 01).\n *\n * @param ip Device IP address\n * @param r Red 0-255\n * @param g Green 0-255\n * @param b Blue 0-255\n * @param segments Array of 0-based segment indices\n */\n setSegmentColor(ip: string, r: number, g: number, b: number, segments: number[]): void {\n this.sendPtReal(ip, [buildSegmentColorPacket(r, g, b, segments)]);\n }\n\n /**\n * Set segment brightness via ptReal BLE-passthrough (command 33 05 15 02).\n *\n * @param ip Device IP address\n * @param brightness Brightness 0-100\n * @param segments Array of 0-based segment indices\n */\n setSegmentBrightness(ip: string, brightness: number, segments: number[]): void {\n this.sendPtReal(ip, [buildSegmentBrightnessPacket(brightness, segments)]);\n }\n\n /**\n * Flash a single segment bright white and dim all other segments, in ONE\n * atomic ptReal transmission. All three required BLE packets are bundled\n * into a single UDP datagram so the device cannot drop intermediate steps.\n *\n * The \"dim everything else\" packet targets the full bitmask width (56\n * segments \u2014 the Govee protocol's upper bound: 7 bytes \u00D7 8 bits). This\n * covers under-report cases where the Cloud says \"15 segments\" but the\n * strip physically has more. Without this the unreported segments keep\n * shining at whatever brightness they had before the wizard started.\n *\n * Packet order:\n * 0. `colorwc` \u2014 force static-color mode (segment_color_setting packets\n * are ignored while the device is in Scene/Gradient/Music mode)\n * 1. All segments except idx (up to idx 55) \u2192 brightness 0\n * 2. Target segment \u2192 color 0xFFFFFF (full white)\n * 3. Target segment \u2192 brightness 100 (make it bright)\n *\n * @param ip Device IP address\n * @param idx Target segment index (0-based) to flash white\n */\n flashSingleSegment(ip: string, idx: number): void {\n if (idx < 0 || idx >= SEGMENT_COUNT_MAX) {\n return;\n }\n const MAX_SEGMENTS = SEGMENT_COUNT_MAX;\n const others = Array.from({ length: MAX_SEGMENTS }, (_, i) => i).filter(i => i !== idx);\n // Step 0: force color mode. Without this, the strip stays in whatever\n // mode it was (Scene/Gradient/Music) and silently ignores the three\n // ptReal packets below. The colorwc command resets to a known static\n // state that accepts segment-level overrides.\n this.setColor(ip, 0xff, 0xff, 0xff);\n // Small delay so the firmware can apply the mode switch before the\n // next UDP burst \u2014 Govee's observed minimum is ~50 ms. The handle is\n // tracked so stop() can cancel it explicitly: ioBroker timers eventually\n // fire into a torn-down adapter otherwise (the wrapper-tracked handles\n // get cleared in onUnload, but only via this Set).\n const delayMs = FORCE_COLOR_MODE_SETTLE_MS;\n const handle = this.timers.setTimeout(() => {\n if (handle !== undefined) {\n this.pendingFlashTimers.delete(handle);\n }\n if (this.stopped || !this.sendSocket) {\n return;\n }\n this.sendPtReal(ip, [\n buildSegmentBrightnessPacket(0, others),\n buildSegmentColorPacket(0xff, 0xff, 0xff, [idx]),\n buildSegmentBrightnessPacket(100, [idx]),\n ]);\n }, delayMs);\n if (handle !== undefined) {\n this.pendingFlashTimers.add(handle);\n }\n }\n\n /**\n * Restore a segment strip to a uniform color + brightness in one atomic\n * ptReal transmission. Used at wizard end/abort to put the strip back to\n * the captured baseline.\n *\n * @param ip Device IP address\n * @param total Total number of segments\n * @param r Red 0-255\n * @param g Green 0-255\n * @param b Blue 0-255\n * @param brightness Brightness 0-100\n */\n restoreAllSegments(ip: string, total: number, r: number, g: number, b: number, brightness: number): void {\n if (total <= 0) {\n return;\n }\n const all = Array.from({ length: total }, (_, i) => i);\n this.sendPtReal(ip, [buildSegmentColorPacket(r, g, b, all), buildSegmentBrightnessPacket(brightness, all)]);\n }\n\n /**\n * Request device status\n *\n * @param ip Device IP address\n */\n requestStatus(ip: string): void {\n this.sendCommand(ip, \"devStatus\", {});\n }\n\n /** Send multicast scan */\n private sendScan(): void {\n const scanMsg: LanMessage = {\n msg: { cmd: \"scan\", data: { account_topic: \"reserve\" } },\n };\n const buf = Buffer.from(JSON.stringify(scanMsg));\n this.scanSocket?.send(buf, 0, buf.length, SCAN_PORT, MULTICAST_ADDR, err => {\n if (err) {\n this.log.debug(`LAN scan send error: ${err.message}`);\n }\n });\n }\n\n /**\n * Parse incoming UDP message\n *\n * @param msg Raw UDP message buffer\n * @param sourceIp Source IP address from UDP rinfo\n */\n private handleMessage(msg: Buffer, sourceIp: string): void {\n // L9 \u2014 size bound. Pathological packets (several reassembled UDP frames)\n // could be 64KB+. JSON.parse of 64KB+ blocks the event loop for ms spans \u2014\n // noticeable with many devices at once.\n if (msg.length > 8192) {\n this.log.debug(`LAN message dropped from ${sourceIp}: oversize ${msg.length} bytes`);\n return;\n }\n try {\n const data = JSON.parse(msg.toString()) as {\n msg?: { cmd?: string; data?: Record<string, unknown> };\n };\n if (!data.msg?.cmd || typeof data.msg.cmd !== \"string\") {\n return;\n }\n\n const cmd: string = data.msg.cmd;\n const rawPayload = data.msg.data;\n const payload: Record<string, unknown> =\n rawPayload && typeof rawPayload === \"object\" && !Array.isArray(rawPayload) ? rawPayload : {};\n\n if (cmd === \"scan\") {\n this.handleScanResponse(payload);\n } else if (cmd === \"devStatus\") {\n this.handleStatusResponse(payload, sourceIp);\n }\n } catch {\n this.log.debug(`LAN: Failed to parse message: ${msg.toString().slice(0, 200)}`);\n }\n }\n\n /**\n * Handle scan response \u2014 new device found\n *\n * @param data Parsed scan response payload\n */\n private handleScanResponse(data: Record<string, unknown>): void {\n // Defensive type checks \u2014 LAN payload comes over the wire, treat as untrusted\n if (\n typeof data.ip !== \"string\" ||\n typeof data.device !== \"string\" ||\n typeof data.sku !== \"string\" ||\n !data.ip ||\n !data.device ||\n !data.sku\n ) {\n return;\n }\n\n const lanDevice: LanDevice = {\n ip: data.ip,\n device: data.device,\n sku: data.sku,\n };\n\n const key = `${lanDevice.device}:${lanDevice.ip}`;\n if (!this.seenDeviceIps.has(key)) {\n // Evict any stale entries for the same device at different IPs so the\n // set stays bounded by the actual number of devices, not the full\n // history of IPs they ever had.\n const staleSuffix = `${lanDevice.device}:`;\n for (const existing of this.seenDeviceIps) {\n if (existing.startsWith(staleSuffix) && existing !== key) {\n this.seenDeviceIps.delete(existing);\n }\n }\n this.seenDeviceIps.add(key);\n this.log.debug(`LAN: Found ${lanDevice.sku} (${lanDevice.device}) at ${lanDevice.ip}`);\n }\n this.onScanRecord?.(lanDevice);\n this.onDiscovery?.(lanDevice);\n }\n\n /**\n * Handle status response \u2014 matched to device by source IP.\n * Defensive against malformed/partial payloads \u2014 all fields coerced to safe defaults.\n *\n * @param data Parsed status response payload\n * @param sourceIp Source IP address from UDP message\n */\n private handleStatusResponse(data: Record<string, unknown>, sourceIp: string): void {\n const toNum = (v: unknown): number => (typeof v === \"number\" && Number.isFinite(v) ? v : 0);\n const colorRaw = data.color;\n const color =\n colorRaw && typeof colorRaw === \"object\"\n ? {\n r: toNum((colorRaw as Record<string, unknown>).r),\n g: toNum((colorRaw as Record<string, unknown>).g),\n b: toNum((colorRaw as Record<string, unknown>).b),\n }\n : { r: 0, g: 0, b: 0 };\n\n const status: LanStatus = {\n onOff: toNum(data.onOff),\n brightness: toNum(data.brightness),\n color,\n colorTemInKelvin: toNum(data.colorTemInKelvin),\n };\n\n // Approximate \"did this follow a command of ours?\" signal \u2014 small \u0394 is\n // *probably* a response, but UDP carries no command ID so it can't be\n // proven. Honest framing: proximity, not proof.\n const lastSend = this.lastCommandSentMs.get(sourceIp);\n if (lastSend !== undefined) {\n const dt = Date.now() - lastSend;\n this.log.debug(`LAN status from ${sourceIp}: \u0394 ${dt}ms since last command (proximity, not ack)`);\n }\n\n this.onStatusRecord?.(sourceIp, status);\n this.onStatus?.(sourceIp, status);\n }\n}\n\n// --- BLE Packet Builder for ptReal ---\n\n/**\n * Clamp a value to 0-100. NaN / non-numeric \u2192 0.\n *\n * @param v Input value\n */\nfunction clampByte0_100(v: number): number {\n if (typeof v !== \"number\" || !Number.isFinite(v)) {\n return 0;\n }\n return Math.max(0, Math.min(100, Math.round(v)));\n}\n\n/**\n * XOR checksum over all bytes\n *\n * @param data Array of byte values\n */\nfunction xorChecksum(data: number[]): number {\n let checksum = 0;\n for (const b of data) {\n checksum ^= b;\n }\n return checksum;\n}\n\n/**\n * Pad data to 19 bytes + append XOR checksum = 20-byte BLE packet\n *\n * @param data Array of byte values to pad and checksum\n */\nfunction finishPacket(data: number[]): number[] {\n while (data.length < 19) {\n data.push(0);\n }\n data.push(xorChecksum(data));\n return data;\n}\n\n/**\n * Build Base64-encoded BLE packets for scene activation via ptReal.\n *\n * @param sceneCode Scene code from library (> 0)\n * @param scenceParam Base64-encoded scene parameter data (may be empty)\n */\nexport function buildScenePackets(sceneCode: number, scenceParam: string): string[] {\n const packets: string[] = [];\n\n // Multi-packet scene data from scenceParam (A3 header protocol)\n if (scenceParam) {\n const paramBytes = Array.from(Buffer.from(scenceParam, \"base64\"));\n // Build A3-framed packets: first chunk starts with A3 00 01 00 02\n const rawData: number[] = [0xa3, 0x00, 0x01, 0x00, 0x02];\n let numLines = 0;\n let lastLineMarker = 1;\n\n for (const b of paramBytes) {\n if (rawData.length % 19 === 0) {\n numLines++;\n rawData.push(0xa3);\n lastLineMarker = rawData.length;\n rawData.push(numLines);\n }\n rawData.push(b);\n }\n rawData[lastLineMarker] = 0xff;\n rawData[3] = numLines + 1;\n\n // Split into 19-byte chunks, pad + checksum each\n for (let i = 0; i < rawData.length; i += 19) {\n const chunk = rawData.slice(i, i + 19);\n const pkt = finishPacket([...chunk]);\n packets.push(Buffer.from(pkt).toString(\"base64\"));\n }\n }\n\n // Final scene-code activation packet: 33 05 04 lo hi\n const lo = sceneCode & 0xff;\n const hi = (sceneCode >> 8) & 0xff;\n const activatePacket = finishPacket([0x33, 0x05, 0x04, lo, hi]);\n packets.push(Buffer.from(activatePacket).toString(\"base64\"));\n\n return packets;\n}\n\n/**\n * Build Base64-encoded BLE packets for DIY scene activation via ptReal.\n * Uses A1 framing for multi-packet data, then sends activation command.\n *\n * @param scenceParam Base64-encoded DIY parameter data (may be empty)\n */\nexport function buildDiyPackets(scenceParam: string): string[] {\n const packets: string[] = [];\n\n if (scenceParam) {\n const paramBytes = Array.from(Buffer.from(scenceParam, \"base64\"));\n // A1-framed packets: start A1 02 00 <total>\n const rawData: number[] = [0xa1, 0x02, 0x00, 0x00];\n let numLines = 0;\n let lastLineMarker = 2;\n\n for (const b of paramBytes) {\n if (rawData.length % 19 === 0) {\n numLines++;\n rawData.push(0xa1, 0x02);\n lastLineMarker = rawData.length - 1;\n rawData.push(numLines);\n }\n rawData.push(b);\n }\n rawData[lastLineMarker] = 0xff;\n rawData[3] = numLines + 1;\n\n for (let i = 0; i < rawData.length; i += 19) {\n const chunk = rawData.slice(i, i + 19);\n packets.push(Buffer.from(finishPacket([...chunk])).toString(\"base64\"));\n }\n }\n\n // Activation: 33 05 0A\n packets.push(Buffer.from(finishPacket([0x33, 0x05, 0x0a])).toString(\"base64\"));\n return packets;\n}\n\n/**\n * Build a Base64-encoded BLE packet for gradient toggle via ptReal.\n *\n * @param on Gradient on/off\n */\nexport function buildGradientPacket(on: boolean): string {\n return Buffer.from(finishPacket([0x33, 0x14, on ? 0x01 : 0x00])).toString(\"base64\");\n}\n\n/**\n * Build a Base64-encoded BLE packet for music mode via ptReal.\n * Sub-modes 1 (Spectrum) and 2 (Rolling) include RGB color.\n *\n * @param subMode Music sub-mode (0=Energic, 1=Spectrum, 2=Rolling, 3=Rhythm)\n * @param r Red channel 0-255\n * @param g Green channel 0-255\n * @param b Blue channel 0-255\n */\nexport function buildMusicModePacket(subMode: number, r = 0, g = 0, b = 0): string {\n const data = [0x33, 0x05, 0x01, subMode & 0xff];\n if (subMode === 1 || subMode === 2) {\n data.push(r & 0xff, g & 0xff, b & 0xff);\n }\n return Buffer.from(finishPacket(data)).toString(\"base64\");\n}\n\n/**\n * Build a little-endian segment bitmask.\n * Segment 0 = byte[0] bit 0, Segment 8 = byte[1] bit 0, etc.\n *\n * @param segments Array of 0-based segment indices\n * @param byteCount Number of bitmask bytes (7 for color, 14 for brightness)\n */\nexport function buildSegmentBitmask(segments: number[], byteCount: number): number[] {\n const mask = new Array<number>(byteCount).fill(0);\n for (const seg of segments) {\n const byteIdx = Math.floor(seg / 8);\n const bitIdx = seg % 8;\n if (byteIdx < byteCount) {\n mask[byteIdx] |= 1 << bitIdx;\n }\n }\n return mask;\n}\n\n/**\n * Build a Base64-encoded BLE packet for segment color via ptReal.\n * Command: 33 05 15 01 RR GG BB 00\u00D75 bitmask\u00D77\n *\n * @param r Red 0-255\n * @param g Green 0-255\n * @param b Blue 0-255\n * @param segments Array of 0-based segment indices\n */\nexport function buildSegmentColorPacket(r: number, g: number, b: number, segments: number[]): string {\n const data = [\n 0x33,\n 0x05,\n 0x15,\n 0x01,\n r & 0xff,\n g & 0xff,\n b & 0xff,\n 0x00,\n 0x00,\n 0x00,\n 0x00,\n 0x00,\n ...buildSegmentBitmask(segments, SEGMENT_COLOR_BITMASK_BYTES),\n ];\n return Buffer.from(finishPacket(data)).toString(\"base64\");\n}\n\n/**\n * Build a Base64-encoded BLE packet for segment brightness via ptReal.\n * Command: 33 05 15 02 BB bitmask\u00D714\n *\n * @param brightness Brightness 0-100\n * @param segments Array of 0-based segment indices\n */\nexport function buildSegmentBrightnessPacket(brightness: number, segments: number[]): string {\n const data = [\n 0x33,\n 0x05,\n 0x15,\n 0x02,\n Math.max(0, Math.min(100, brightness)),\n ...buildSegmentBitmask(segments, SEGMENT_BRIGHTNESS_BITMASK_BYTES),\n ];\n return Buffer.from(finishPacket(data)).toString(\"base64\");\n}\n\n/**\n * Apply speed level to a scene's scenceParam by replacing speed bytes in each page.\n * scenceParam structure: byte[0] = page count, then per page: 1 byte length + N bytes data.\n * Speed byte position within each page: pageLength - 5.\n *\n * @param scenceParam Base64-encoded scene parameter data\n * @param speedLevel Speed level index (0-based)\n * @param speedConfig JSON config string from speedInfo.config\n * @returns Modified Base64-encoded scenceParam with speed bytes replaced\n */\nexport function applySceneSpeed(scenceParam: string, speedLevel: number, speedConfig: string): string {\n if (!scenceParam || !speedConfig) {\n return scenceParam;\n }\n\n let configEntries: Array<{\n page: number;\n moveIn?: number[];\n }>;\n try {\n configEntries = JSON.parse(speedConfig);\n } catch {\n // Govee's speedInfo.config schema can drift \u2014 this is a pure helper\n // without a logger, so a malformed config falls back silently: the\n // un-modified scenceParam keeps the activation working at default\n // speed instead of failing the whole scene command.\n return scenceParam;\n }\n\n if (!Array.isArray(configEntries) || configEntries.length === 0) {\n return scenceParam;\n }\n\n const bytes = Array.from(Buffer.from(scenceParam, \"base64\"));\n if (bytes.length === 0) {\n return scenceParam;\n }\n\n const pageCount = bytes[0];\n let offset = 1;\n\n for (let pageIdx = 0; pageIdx < pageCount && offset < bytes.length; pageIdx++) {\n const pageLen = bytes[offset];\n if (offset + 1 + pageLen > bytes.length) {\n break;\n }\n\n const cfg = configEntries.find(c => c.page === pageIdx);\n if (cfg?.moveIn && speedLevel >= 0 && speedLevel < cfg.moveIn.length) {\n const speedBytePos = offset + 1 + (pageLen - 5);\n if (speedBytePos > offset && speedBytePos < offset + 1 + pageLen) {\n bytes[speedBytePos] = cfg.moveIn[speedLevel];\n }\n }\n\n offset += 1 + pageLen;\n }\n\n return Buffer.from(bytes).toString(\"base64\");\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import * as dgram from \"node:dgram\";\nimport { clampByte, type LanDevice, type LanMessage, type LanStatus, type TimerAdapter } from \"./types\";\nimport { FORCE_COLOR_MODE_SETTLE_MS } from \"./timing-constants\";\nimport {\n SEGMENT_BRIGHTNESS_BITMASK_BYTES,\n SEGMENT_COLOR_BITMASK_BYTES,\n SEGMENT_COUNT_MAX,\n} from \"./device-manager/lookups\";\n\nconst MULTICAST_ADDR = \"239.255.255.250\";\nconst SCAN_PORT = 4001;\nconst LISTEN_PORT = 4002;\nconst COMMAND_PORT = 4003;\n\n/** Callback for discovered LAN devices */\nexport type LanDiscoveryCallback = (device: LanDevice) => void;\n\n/** Callback for status updates (matched by source IP, not device ID) */\nexport type LanStatusCallback = (sourceIp: string, status: LanStatus) => void;\n\n/**\n * Callback fired for every outgoing UDP datagram. Wired to the DiagnosticsCollector\n * via main.ts so `diag.export` carries the verbatim bytes the adapter put on the\n * wire \u2014 closes the \"did the adapter even send the snapshot?\" diag blind spot.\n *\n * Note: ip is the destination, not a device id. main.ts resolves to deviceId.\n */\nexport type LanSendCallback = (ip: string, cmd: string, payload: unknown, bytes: number, error?: string) => void;\n\n/**\n * Callback fired for every parsed devStatus reply. Wired to the\n * DiagnosticsCollector so a \"device ack'd the command but didn't render\"\n * report has the actual response payload in the diag JSON.\n */\nexport type LanStatusRecordCallback = (sourceIp: string, status: LanStatus) => void;\n\n/**\n * Callback fired for every parsed scan reply. Diag-only side-channel that\n * lets the DiagnosticsCollector log the raw LAN discovery handshake \u2014 useful\n * for \"device shows up in scan but the adapter doesn't add it\" reports.\n */\nexport type LanScanRecordCallback = (lanDevice: LanDevice) => void;\n\n/**\n * Govee LAN UDP client for device discovery and control.\n * Handles multicast discovery on port 4001, listens on 4002, sends commands to 4003.\n */\nexport class GoveeLanClient {\n private scanSocket: dgram.Socket | null = null;\n private listenSocket: dgram.Socket | null = null;\n /**\n * Persistent send-socket \u2014 previously a new dgram socket was created, used to\n * send, and closed per command. On adapter stop mid-send the callback could\n * fire into a half-torn-down adapter.\n */\n private sendSocket: dgram.Socket | null = null;\n private scanTimer: ioBroker.Interval | undefined = undefined;\n /**\n * True after `stop()` was called \u2014 bind-callbacks check this flag before\n * starting timers/scans, so a `stop()` during the async listen+scan bind\n * sequence cannot leave a runaway scanTimer behind.\n */\n private stopped = false;\n /**\n * Pending one-shot timeouts created by {@link flashSingleSegment} \u2014 kept\n * so {@link stop} can cancel them before the deferred ptReal burst fires\n * into a torn-down LAN client.\n */\n private readonly pendingFlashTimers = new Set<ioBroker.Timeout>();\n private readonly timers: TimerAdapter;\n private readonly log: ioBroker.Logger;\n private onDiscovery: LanDiscoveryCallback | null = null;\n private onStatus: LanStatusCallback | null = null;\n private onSend: LanSendCallback | null = null;\n private onStatusRecord: LanStatusRecordCallback | null = null;\n private onScanRecord: LanScanRecordCallback | null = null;\n private readonly seenDeviceIps = new Set<string>();\n /**\n * Per-IP timestamp of the last command we sent (ptReal/setScene/etc).\n * Used to annotate incoming LAN-status responses with the \u0394t \u2014 gives an\n * approximate \"did this status follow a command of ours?\" signal in the\n * debug log. Proximity, not proof: Govee's UDP protocol doesn't carry\n * command IDs, so a small \u0394 is *probably* a response but could be\n * unrelated polling.\n */\n private readonly lastCommandSentMs = new Map<string, number>();\n /** Multicast membership address \u2014 remembered for dropMembership in stop(). */\n private multicastBind: string | undefined;\n\n /**\n * @param log ioBroker logger\n * @param timers Timer adapter for setInterval/setTimeout\n */\n constructor(log: ioBroker.Logger, timers: TimerAdapter) {\n this.log = log;\n this.timers = timers;\n }\n\n /**\n * Register a send hook called for every outgoing UDP datagram. main.ts\n * resolves the destination IP to a deviceId and forwards into the\n * DiagnosticsCollector \u2014 closes the v2.9.0 diag blind spot where ptReal\n * sends were only visible in the adapter log, not in per-device diag.\n *\n * @param cb Callback receiving (ip, cmd, payload, bytes, error?)\n */\n setSendHook(cb: LanSendCallback | null): void {\n this.onSend = cb;\n }\n\n /**\n * Register a hook called for every parsed devStatus reply. Used for diag\n * capture \u2014 adapter looks up the device by IP and records the payload as\n * a pseudo-endpoint (`lan://devStatus`).\n *\n * @param cb Callback receiving (sourceIp, status)\n */\n setStatusRecordHook(cb: LanStatusRecordCallback | null): void {\n this.onStatusRecord = cb;\n }\n\n /**\n * Register a hook called for every parsed scan reply. Diag-only.\n *\n * @param cb Callback receiving (lanDevice)\n */\n setScanRecordHook(cb: LanScanRecordCallback | null): void {\n this.onScanRecord = cb;\n }\n\n /**\n * Start LAN discovery and listening for responses.\n *\n * @param onDiscovery Called when a new device is found\n * @param onStatus Called when a status response arrives\n * @param scanIntervalMs How often to send multicast scan (default 30s)\n * @param networkInterface IP of network interface to bind to (empty = all)\n */\n start(\n onDiscovery: LanDiscoveryCallback,\n onStatus: LanStatusCallback,\n scanIntervalMs = 30_000,\n networkInterface = \"\",\n ): void {\n this.onDiscovery = onDiscovery;\n this.onStatus = onStatus;\n\n const bindAddr = networkInterface && networkInterface !== \"0.0.0.0\" ? networkInterface : undefined;\n if (bindAddr) {\n this.log.info(`LAN binding to network interface ${bindAddr}`);\n }\n\n this.multicastBind = bindAddr;\n\n // Persistent send-socket for commands \u2014 create once, close in stop().\n this.sendSocket = dgram.createSocket(\"udp4\");\n this.sendSocket.on(\"error\", err => {\n this.log.debug(`LAN send socket error: ${err.message}`);\n });\n // Pin the command socket's source to the chosen interface so unicast\n // commands/devStatus egress it AND devices reply to bindAddr:4002 (where\n // listenSocket is bound). Without binding, the OS auto-selects the source on\n // a multi-homed host and replies can miss the listen socket. Skipped for the\n // all-interfaces default (bindAddr undefined) \u2014 behaviour unchanged there.\n if (bindAddr) {\n this.sendSocket.bind(0, bindAddr);\n }\n\n // Listen socket for responses (port 4002) \u2014 must be ready before first scan\n this.listenSocket = dgram.createSocket({ type: \"udp4\", reuseAddr: true });\n this.listenSocket.on(\"message\", (msg, rinfo) => {\n this.handleMessage(msg, rinfo.address);\n });\n this.listenSocket.on(\"error\", err => {\n // EADDRINUSE = port 4002 already taken (a second adapter instance?). The\n // user needs to know \u2014 otherwise the adapter is half-dead (discovery via\n // scan works, status replies are lost).\n const code = (err as NodeJS.ErrnoException).code;\n if (code === \"EADDRINUSE\") {\n this.log.warn(`LAN listen port ${LISTEN_PORT} already in use \u2014 second instance? Status updates will be lost.`);\n } else {\n this.log.debug(`LAN listen socket error: ${err.message}`);\n }\n });\n this.listenSocket.bind(LISTEN_PORT, bindAddr, () => {\n // stop() may have been called between listenSocket.bind and this\n // callback firing \u2014 bail out so we don't spin up a scan socket and a\n // recurring timer behind the back of a torn-down LAN client.\n if (this.stopped) {\n return;\n }\n this.log.debug(`LAN listening on port ${LISTEN_PORT}`);\n\n // Scan socket for multicast discovery (port 4001) \u2014 started after listen is ready\n this.scanSocket = dgram.createSocket({ type: \"udp4\", reuseAddr: true });\n this.scanSocket.on(\"error\", err => {\n this.log.debug(`LAN scan socket error: ${err.message}`);\n });\n // bind(0, bindAddr) \u2014 ephemeral port, but bound to the chosen interface.\n // Previously bind() without an address \u2192 ANY \u2192 asymmetric to the listen\n // socket that uses bindAddr.\n this.scanSocket.bind(0, bindAddr, () => {\n if (this.stopped) {\n return;\n }\n this.scanSocket?.setBroadcast(true);\n try {\n this.scanSocket?.addMembership(MULTICAST_ADDR, bindAddr);\n } catch {\n // A membership fail is typically an OS routing issue (e.g. interface\n // bindAddr not in the multicast routing table). LAN discovery stays\n // partially functional via setBroadcast(true), but that is asymmetric\n // \u2014 replies may not come back. Log at info level so the user can find\n // the cause of the \"no devices\" symptom in the logs.\n this.log.info(\n `LAN: could not join multicast group on ${bindAddr ?? \"default interface\"} \u2014 discovery may be incomplete`,\n );\n }\n // Pin OUTGOING multicast to the chosen interface. Binding the socket to\n // bindAddr only sets the source address \u2014 the multicast egress interface\n // is controlled by IP_MULTICAST_IF (setMulticastInterface); without it the\n // OS picks via its default route, so the discovery scan can leave the\n // wrong interface on a multi-homed host despite an explicit selection\n // (Node dgram docs). Skipped for the all-interfaces default.\n if (bindAddr) {\n try {\n this.scanSocket?.setMulticastInterface(bindAddr);\n } catch {\n this.log.info(\n `LAN: could not pin multicast egress to ${bindAddr} \u2014 outgoing discovery may use the default interface`,\n );\n }\n }\n this.sendScan();\n });\n\n // Periodic scan \u2014 guard the timer creation in case stop() raced with\n // the bind callback, otherwise we'd leave a setInterval running\n // against a torn-down LAN client.\n if (!this.stopped) {\n this.scanTimer = this.timers.setInterval(() => {\n this.sendScan();\n }, scanIntervalMs);\n }\n });\n }\n\n /**\n * Snapshot of the discovery cache + last-command-sent timestamps for\n * the runtime-state diag export. Returns plain serialisable shapes so\n * the DiagnosticsCollector can clone-and-cap them safely.\n */\n getDiagSnapshot(): { seenDeviceIps: string[]; lastCommandSentMs: Record<string, number> } {\n return {\n seenDeviceIps: Array.from(this.seenDeviceIps),\n lastCommandSentMs: Object.fromEntries(this.lastCommandSentMs),\n };\n }\n\n /** Stop all sockets and timers */\n stop(): void {\n this.stopped = true;\n if (this.scanTimer) {\n this.timers.clearInterval(this.scanTimer);\n this.scanTimer = undefined;\n }\n // Cancel any deferred wizard-flash bursts that haven't fired yet.\n for (const handle of this.pendingFlashTimers) {\n this.timers.clearTimeout(handle);\n }\n this.pendingFlashTimers.clear();\n if (this.scanSocket) {\n // dropMembership symmetric to addMembership \u2014 on macOS/Windows the\n // multicast filter could otherwise linger on the NIC until process exit.\n try {\n if (this.multicastBind) {\n this.scanSocket.dropMembership(MULTICAST_ADDR, this.multicastBind);\n }\n } catch {\n /* ignore \u2014 best-effort */\n }\n try {\n this.scanSocket.close();\n } catch {\n /* ignore */\n }\n this.scanSocket = null;\n }\n if (this.listenSocket) {\n try {\n this.listenSocket.close();\n } catch {\n /* ignore */\n }\n this.listenSocket = null;\n }\n if (this.sendSocket) {\n try {\n this.sendSocket.close();\n } catch {\n /* ignore */\n }\n this.sendSocket = null;\n }\n // L4 \u2014 clear seenDeviceIps, otherwise entries survive between start/stop\n // cycles in the same process (correctness unaffected, but the\n // discovery-log info on an IP change would be lost).\n this.seenDeviceIps.clear();\n this.multicastBind = undefined;\n }\n\n /**\n * Send a control command to a device via LAN.\n *\n * @param ip Device IP address\n * @param cmd Command name (turn, brightness, colorwc, devStatus)\n * @param data Command data\n */\n private sendCommand(ip: string, cmd: string, data: Record<string, unknown>): void {\n if (!this.sendSocket) {\n this.log.debug(`LAN send dropped (socket not ready): ${cmd} \u2192 ${ip}`);\n this.onSend?.(ip, cmd, data, 0, \"socket not ready\");\n return;\n }\n const message: LanMessage = {\n msg: { cmd, data },\n };\n const buf = Buffer.from(JSON.stringify(message));\n // L5 \u2014 the Govee spec is UDP-PMTU-bound. Warn on very large ptReal payloads\n // (many BLE packets in JSON) so the user knows why something gets lost.\n if (buf.length > 1400) {\n this.log.debug(`LAN payload large (${buf.length} bytes) \u2014 may be PMTU-fragmented for ${ip}`);\n }\n this.sendSocket.send(buf, 0, buf.length, COMMAND_PORT, ip, err => {\n if (err) {\n this.log.debug(`LAN send error to ${ip}: ${err.message}`);\n this.onSend?.(ip, cmd, data, buf.length, err.message);\n } else {\n this.lastCommandSentMs.set(ip, Date.now());\n this.onSend?.(ip, cmd, data, buf.length);\n }\n });\n }\n\n /**\n * Send power command\n *\n * @param ip Device IP address\n * @param on Power state\n */\n setPower(ip: string, on: boolean): void {\n this.sendCommand(ip, \"turn\", { value: on ? 1 : 0 });\n }\n\n /**\n * Send brightness command\n *\n * @param ip Device IP address\n * @param brightness Brightness 0-100\n */\n setBrightness(ip: string, brightness: number): void {\n this.sendCommand(ip, \"brightness\", {\n value: clampByte0_100(brightness),\n });\n }\n\n /**\n * Send color command. Inputs are clamped to 0-255 \u2014 out-of-range values\n * from upstream coercion paths (capability-mapper, command-router) would\n * otherwise be sent verbatim and produce undefined-behaviour at the device.\n *\n * @param ip Device IP address\n * @param r Red channel 0-255\n * @param g Green channel 0-255\n * @param b Blue channel 0-255\n */\n setColor(ip: string, r: number, g: number, b: number): void {\n this.sendCommand(ip, \"colorwc\", {\n color: { r: clampByte(r), g: clampByte(g), b: clampByte(b) },\n colorTemInKelvin: 0,\n });\n }\n\n /**\n * Send color temperature command. Out-of-band kelvin values are clamped\n * to Govee's published 2000-9000 K range (per-device range may be tighter,\n * those are corrected via {@link applyColorTempQuirk} upstream).\n *\n * @param ip Device IP address\n * @param kelvin Color temperature in Kelvin\n */\n setColorTemperature(ip: string, kelvin: number): void {\n const clamped = Number.isFinite(kelvin) ? Math.max(2000, Math.min(9000, Math.round(kelvin))) : 2000;\n this.sendCommand(ip, \"colorwc\", {\n color: { r: 0, g: 0, b: 0 },\n colorTemInKelvin: clamped,\n });\n }\n\n /**\n * Send a scene via ptReal BLE-passthrough.\n * Builds multi-packet BLE data from scenceParam + final scene-code packet.\n *\n * @param ip Device IP address\n * @param sceneCode Scene code from scene library (must be > 0)\n * @param scenceParam Base64-encoded scene parameter data (may be empty for simple presets)\n */\n setScene(ip: string, sceneCode: number, scenceParam: string): void {\n if (sceneCode <= 0) {\n return;\n }\n const packets = buildScenePackets(sceneCode, scenceParam);\n this.sendPtReal(ip, packets);\n }\n\n /**\n * Send raw ptReal BLE-passthrough packets to a device.\n *\n * @param ip Device IP address\n * @param base64Packets Array of Base64-encoded 20-byte BLE packets\n */\n sendPtReal(ip: string, base64Packets: string[]): void {\n if (!this.sendSocket) {\n this.log.debug(`LAN ptReal dropped (socket not ready): ${ip}`);\n this.onSend?.(ip, \"ptReal\", { command: base64Packets }, 0, \"socket not ready\");\n return;\n }\n const message = {\n msg: { cmd: \"ptReal\", data: { command: base64Packets } },\n };\n const buf = Buffer.from(JSON.stringify(message));\n if (buf.length > 1400) {\n this.log.debug(`ptReal payload large (${buf.length} bytes) \u2014 may be PMTU-fragmented for ${ip}`);\n }\n this.sendSocket.send(buf, 0, buf.length, COMMAND_PORT, ip, err => {\n if (err) {\n // warn (was: debug) \u2014 a silent UDP-send failure leaves the user\n // wondering why a scene/snapshot \"did nothing\". One log per failed\n // send is acceptable noise; recurring sends to the same offline IP\n // will repeat, which is the right signal.\n this.log.warn(`LAN ptReal error to ${ip}: ${err.message}`);\n this.onSend?.(ip, \"ptReal\", { command: base64Packets }, buf.length, err.message);\n } else {\n // Success on debug: confirms the UDP datagram left the socket so\n // a \"snapshot/scene did not activate\" report can be triaged\n // without enabling silly-level wire logging.\n this.log.debug(`LAN ptReal sent to ${ip}: ${base64Packets.length} packet(s), ${buf.length} bytes`);\n this.lastCommandSentMs.set(ip, Date.now());\n this.onSend?.(ip, \"ptReal\", { command: base64Packets }, buf.length);\n }\n });\n }\n\n /**\n * Set gradient toggle via ptReal BLE-passthrough.\n *\n * @param ip Device IP address\n * @param on Gradient on/off\n */\n setGradient(ip: string, on: boolean): void {\n this.sendPtReal(ip, [buildGradientPacket(on)]);\n }\n\n /**\n * Activate a DIY scene via ptReal BLE-passthrough.\n * Sends A1 multi-packet data (if provided) + activation command.\n *\n * @param ip Device IP address\n * @param scenceParam Base64-encoded DIY parameter data (may be empty to activate last DIY)\n */\n setDiyScene(ip: string, scenceParam: string): void {\n const packets = buildDiyPackets(scenceParam);\n this.sendPtReal(ip, packets);\n }\n\n /**\n * Set music mode via ptReal BLE-passthrough.\n * Sub-modes 1 (Spectrum) and 2 (Rolling) use RGB color.\n *\n * @param ip Device IP address\n * @param subMode Music sub-mode (0-3)\n * @param r Red channel 0-255 (used by modes 1, 2)\n * @param g Green channel 0-255\n * @param b Blue channel 0-255\n */\n setMusicMode(ip: string, subMode: number, r = 0, g = 0, b = 0): void {\n this.sendPtReal(ip, [buildMusicModePacket(subMode, r, g, b)]);\n }\n\n /**\n * Set segment color via ptReal BLE-passthrough (command 33 05 15 01).\n *\n * @param ip Device IP address\n * @param r Red 0-255\n * @param g Green 0-255\n * @param b Blue 0-255\n * @param segments Array of 0-based segment indices\n */\n setSegmentColor(ip: string, r: number, g: number, b: number, segments: number[]): void {\n this.sendPtReal(ip, [buildSegmentColorPacket(r, g, b, segments)]);\n }\n\n /**\n * Set segment brightness via ptReal BLE-passthrough (command 33 05 15 02).\n *\n * @param ip Device IP address\n * @param brightness Brightness 0-100\n * @param segments Array of 0-based segment indices\n */\n setSegmentBrightness(ip: string, brightness: number, segments: number[]): void {\n this.sendPtReal(ip, [buildSegmentBrightnessPacket(brightness, segments)]);\n }\n\n /**\n * Flash a single segment bright white and dim all other segments, in ONE\n * atomic ptReal transmission. All three required BLE packets are bundled\n * into a single UDP datagram so the device cannot drop intermediate steps.\n *\n * The \"dim everything else\" packet targets the full bitmask width (56\n * segments \u2014 the Govee protocol's upper bound: 7 bytes \u00D7 8 bits). This\n * covers under-report cases where the Cloud says \"15 segments\" but the\n * strip physically has more. Without this the unreported segments keep\n * shining at whatever brightness they had before the wizard started.\n *\n * Packet order:\n * 0. `colorwc` \u2014 force static-color mode (segment_color_setting packets\n * are ignored while the device is in Scene/Gradient/Music mode)\n * 1. All segments except idx (up to idx 55) \u2192 brightness 0\n * 2. Target segment \u2192 color 0xFFFFFF (full white)\n * 3. Target segment \u2192 brightness 100 (make it bright)\n *\n * @param ip Device IP address\n * @param idx Target segment index (0-based) to flash white\n */\n flashSingleSegment(ip: string, idx: number): void {\n if (idx < 0 || idx >= SEGMENT_COUNT_MAX) {\n return;\n }\n const MAX_SEGMENTS = SEGMENT_COUNT_MAX;\n const others = Array.from({ length: MAX_SEGMENTS }, (_, i) => i).filter(i => i !== idx);\n // Step 0: force color mode. Without this, the strip stays in whatever\n // mode it was (Scene/Gradient/Music) and silently ignores the three\n // ptReal packets below. The colorwc command resets to a known static\n // state that accepts segment-level overrides.\n this.setColor(ip, 0xff, 0xff, 0xff);\n // Small delay so the firmware can apply the mode switch before the\n // next UDP burst \u2014 Govee's observed minimum is ~50 ms. The handle is\n // tracked so stop() can cancel it explicitly: ioBroker timers eventually\n // fire into a torn-down adapter otherwise (the wrapper-tracked handles\n // get cleared in onUnload, but only via this Set).\n const delayMs = FORCE_COLOR_MODE_SETTLE_MS;\n const handle = this.timers.setTimeout(() => {\n if (handle !== undefined) {\n this.pendingFlashTimers.delete(handle);\n }\n if (this.stopped || !this.sendSocket) {\n return;\n }\n this.sendPtReal(ip, [\n buildSegmentBrightnessPacket(0, others),\n buildSegmentColorPacket(0xff, 0xff, 0xff, [idx]),\n buildSegmentBrightnessPacket(100, [idx]),\n ]);\n }, delayMs);\n if (handle !== undefined) {\n this.pendingFlashTimers.add(handle);\n }\n }\n\n /**\n * Restore a segment strip to a uniform color + brightness in one atomic\n * ptReal transmission. Used at wizard end/abort to put the strip back to\n * the captured baseline.\n *\n * @param ip Device IP address\n * @param total Total number of segments\n * @param r Red 0-255\n * @param g Green 0-255\n * @param b Blue 0-255\n * @param brightness Brightness 0-100\n */\n restoreAllSegments(ip: string, total: number, r: number, g: number, b: number, brightness: number): void {\n if (total <= 0) {\n return;\n }\n const all = Array.from({ length: total }, (_, i) => i);\n this.sendPtReal(ip, [buildSegmentColorPacket(r, g, b, all), buildSegmentBrightnessPacket(brightness, all)]);\n }\n\n /**\n * Request device status\n *\n * @param ip Device IP address\n */\n requestStatus(ip: string): void {\n this.sendCommand(ip, \"devStatus\", {});\n }\n\n /** Send multicast scan */\n private sendScan(): void {\n const scanMsg: LanMessage = {\n msg: { cmd: \"scan\", data: { account_topic: \"reserve\" } },\n };\n const buf = Buffer.from(JSON.stringify(scanMsg));\n this.scanSocket?.send(buf, 0, buf.length, SCAN_PORT, MULTICAST_ADDR, err => {\n if (err) {\n this.log.debug(`LAN scan send error: ${err.message}`);\n }\n });\n }\n\n /**\n * Parse incoming UDP message\n *\n * @param msg Raw UDP message buffer\n * @param sourceIp Source IP address from UDP rinfo\n */\n private handleMessage(msg: Buffer, sourceIp: string): void {\n // L9 \u2014 size bound. Pathological packets (several reassembled UDP frames)\n // could be 64KB+. JSON.parse of 64KB+ blocks the event loop for ms spans \u2014\n // noticeable with many devices at once.\n if (msg.length > 8192) {\n this.log.debug(`LAN message dropped from ${sourceIp}: oversize ${msg.length} bytes`);\n return;\n }\n try {\n const data = JSON.parse(msg.toString()) as {\n msg?: { cmd?: string; data?: Record<string, unknown> };\n };\n if (!data.msg?.cmd || typeof data.msg.cmd !== \"string\") {\n return;\n }\n\n const cmd: string = data.msg.cmd;\n const rawPayload = data.msg.data;\n const payload: Record<string, unknown> =\n rawPayload && typeof rawPayload === \"object\" && !Array.isArray(rawPayload) ? rawPayload : {};\n\n if (cmd === \"scan\") {\n this.handleScanResponse(payload);\n } else if (cmd === \"devStatus\") {\n this.handleStatusResponse(payload, sourceIp);\n }\n } catch {\n this.log.debug(`LAN: Failed to parse message: ${msg.toString().slice(0, 200)}`);\n }\n }\n\n /**\n * Handle scan response \u2014 new device found\n *\n * @param data Parsed scan response payload\n */\n private handleScanResponse(data: Record<string, unknown>): void {\n // Defensive type checks \u2014 LAN payload comes over the wire, treat as untrusted\n if (\n typeof data.ip !== \"string\" ||\n typeof data.device !== \"string\" ||\n typeof data.sku !== \"string\" ||\n !data.ip ||\n !data.device ||\n !data.sku\n ) {\n return;\n }\n\n const lanDevice: LanDevice = {\n ip: data.ip,\n device: data.device,\n sku: data.sku,\n };\n\n const key = `${lanDevice.device}:${lanDevice.ip}`;\n if (!this.seenDeviceIps.has(key)) {\n // Evict any stale entries for the same device at different IPs so the\n // set stays bounded by the actual number of devices, not the full\n // history of IPs they ever had.\n const staleSuffix = `${lanDevice.device}:`;\n for (const existing of this.seenDeviceIps) {\n if (existing.startsWith(staleSuffix) && existing !== key) {\n this.seenDeviceIps.delete(existing);\n }\n }\n this.seenDeviceIps.add(key);\n this.log.debug(`LAN: Found ${lanDevice.sku} (${lanDevice.device}) at ${lanDevice.ip}`);\n }\n this.onScanRecord?.(lanDevice);\n this.onDiscovery?.(lanDevice);\n }\n\n /**\n * Handle status response \u2014 matched to device by source IP.\n * Defensive against malformed/partial payloads \u2014 all fields coerced to safe defaults.\n *\n * @param data Parsed status response payload\n * @param sourceIp Source IP address from UDP message\n */\n private handleStatusResponse(data: Record<string, unknown>, sourceIp: string): void {\n const toNum = (v: unknown): number => (typeof v === \"number\" && Number.isFinite(v) ? v : 0);\n const colorRaw = data.color;\n const color =\n colorRaw && typeof colorRaw === \"object\"\n ? {\n r: toNum((colorRaw as Record<string, unknown>).r),\n g: toNum((colorRaw as Record<string, unknown>).g),\n b: toNum((colorRaw as Record<string, unknown>).b),\n }\n : { r: 0, g: 0, b: 0 };\n\n const status: LanStatus = {\n onOff: toNum(data.onOff),\n brightness: toNum(data.brightness),\n color,\n colorTemInKelvin: toNum(data.colorTemInKelvin),\n };\n\n // Approximate \"did this follow a command of ours?\" signal \u2014 small \u0394 is\n // *probably* a response, but UDP carries no command ID so it can't be\n // proven. Honest framing: proximity, not proof.\n const lastSend = this.lastCommandSentMs.get(sourceIp);\n if (lastSend !== undefined) {\n const dt = Date.now() - lastSend;\n this.log.debug(`LAN status from ${sourceIp}: \u0394 ${dt}ms since last command (proximity, not ack)`);\n }\n\n this.onStatusRecord?.(sourceIp, status);\n this.onStatus?.(sourceIp, status);\n }\n}\n\n// --- BLE Packet Builder for ptReal ---\n\n/**\n * Clamp a value to 0-100. NaN / non-numeric \u2192 0.\n *\n * @param v Input value\n */\nfunction clampByte0_100(v: number): number {\n if (typeof v !== \"number\" || !Number.isFinite(v)) {\n return 0;\n }\n return Math.max(0, Math.min(100, Math.round(v)));\n}\n\n/**\n * XOR checksum over all bytes\n *\n * @param data Array of byte values\n */\nfunction xorChecksum(data: number[]): number {\n let checksum = 0;\n for (const b of data) {\n checksum ^= b;\n }\n return checksum;\n}\n\n/**\n * Pad data to 19 bytes + append XOR checksum = 20-byte BLE packet\n *\n * @param data Array of byte values to pad and checksum\n */\nfunction finishPacket(data: number[]): number[] {\n while (data.length < 19) {\n data.push(0);\n }\n data.push(xorChecksum(data));\n return data;\n}\n\n/**\n * Build Base64-encoded BLE packets for scene activation via ptReal.\n *\n * @param sceneCode Scene code from library (> 0)\n * @param scenceParam Base64-encoded scene parameter data (may be empty)\n */\nexport function buildScenePackets(sceneCode: number, scenceParam: string): string[] {\n const packets: string[] = [];\n\n // Multi-packet scene data from scenceParam (A3 header protocol)\n if (scenceParam) {\n const paramBytes = Array.from(Buffer.from(scenceParam, \"base64\"));\n // Build A3-framed packets: first chunk starts with A3 00 01 00 02\n const rawData: number[] = [0xa3, 0x00, 0x01, 0x00, 0x02];\n let numLines = 0;\n let lastLineMarker = 1;\n\n for (const b of paramBytes) {\n if (rawData.length % 19 === 0) {\n numLines++;\n rawData.push(0xa3);\n lastLineMarker = rawData.length;\n rawData.push(numLines);\n }\n rawData.push(b);\n }\n rawData[lastLineMarker] = 0xff;\n rawData[3] = numLines + 1;\n\n // Split into 19-byte chunks, pad + checksum each\n for (let i = 0; i < rawData.length; i += 19) {\n const chunk = rawData.slice(i, i + 19);\n const pkt = finishPacket([...chunk]);\n packets.push(Buffer.from(pkt).toString(\"base64\"));\n }\n }\n\n // Final scene-code activation packet: 33 05 04 lo hi\n const lo = sceneCode & 0xff;\n const hi = (sceneCode >> 8) & 0xff;\n const activatePacket = finishPacket([0x33, 0x05, 0x04, lo, hi]);\n packets.push(Buffer.from(activatePacket).toString(\"base64\"));\n\n return packets;\n}\n\n/**\n * Build Base64-encoded BLE packets for DIY scene activation via ptReal.\n * Uses A1 framing for multi-packet data, then sends activation command.\n *\n * @param scenceParam Base64-encoded DIY parameter data (may be empty)\n */\nexport function buildDiyPackets(scenceParam: string): string[] {\n const packets: string[] = [];\n\n if (scenceParam) {\n const paramBytes = Array.from(Buffer.from(scenceParam, \"base64\"));\n // A1-framed packets: start A1 02 00 <total>\n const rawData: number[] = [0xa1, 0x02, 0x00, 0x00];\n let numLines = 0;\n let lastLineMarker = 2;\n\n for (const b of paramBytes) {\n if (rawData.length % 19 === 0) {\n numLines++;\n rawData.push(0xa1, 0x02);\n lastLineMarker = rawData.length - 1;\n rawData.push(numLines);\n }\n rawData.push(b);\n }\n rawData[lastLineMarker] = 0xff;\n rawData[3] = numLines + 1;\n\n for (let i = 0; i < rawData.length; i += 19) {\n const chunk = rawData.slice(i, i + 19);\n packets.push(Buffer.from(finishPacket([...chunk])).toString(\"base64\"));\n }\n }\n\n // Activation: 33 05 0A\n packets.push(Buffer.from(finishPacket([0x33, 0x05, 0x0a])).toString(\"base64\"));\n return packets;\n}\n\n/**\n * Build a Base64-encoded BLE packet for gradient toggle via ptReal.\n *\n * @param on Gradient on/off\n */\nexport function buildGradientPacket(on: boolean): string {\n return Buffer.from(finishPacket([0x33, 0x14, on ? 0x01 : 0x00])).toString(\"base64\");\n}\n\n/**\n * Build a Base64-encoded BLE packet for music mode via ptReal.\n * Sub-modes 1 (Spectrum) and 2 (Rolling) include RGB color.\n *\n * @param subMode Music sub-mode (0=Energic, 1=Spectrum, 2=Rolling, 3=Rhythm)\n * @param r Red channel 0-255\n * @param g Green channel 0-255\n * @param b Blue channel 0-255\n */\nexport function buildMusicModePacket(subMode: number, r = 0, g = 0, b = 0): string {\n const data = [0x33, 0x05, 0x01, subMode & 0xff];\n if (subMode === 1 || subMode === 2) {\n data.push(r & 0xff, g & 0xff, b & 0xff);\n }\n return Buffer.from(finishPacket(data)).toString(\"base64\");\n}\n\n/**\n * Build a little-endian segment bitmask.\n * Segment 0 = byte[0] bit 0, Segment 8 = byte[1] bit 0, etc.\n *\n * @param segments Array of 0-based segment indices\n * @param byteCount Number of bitmask bytes (7 for color, 14 for brightness)\n */\nexport function buildSegmentBitmask(segments: number[], byteCount: number): number[] {\n const mask = new Array<number>(byteCount).fill(0);\n for (const seg of segments) {\n const byteIdx = Math.floor(seg / 8);\n const bitIdx = seg % 8;\n if (byteIdx < byteCount) {\n mask[byteIdx] |= 1 << bitIdx;\n }\n }\n return mask;\n}\n\n/**\n * Build a Base64-encoded BLE packet for segment color via ptReal.\n * Command: 33 05 15 01 RR GG BB 00\u00D75 bitmask\u00D77\n *\n * @param r Red 0-255\n * @param g Green 0-255\n * @param b Blue 0-255\n * @param segments Array of 0-based segment indices\n */\nexport function buildSegmentColorPacket(r: number, g: number, b: number, segments: number[]): string {\n const data = [\n 0x33,\n 0x05,\n 0x15,\n 0x01,\n r & 0xff,\n g & 0xff,\n b & 0xff,\n 0x00,\n 0x00,\n 0x00,\n 0x00,\n 0x00,\n ...buildSegmentBitmask(segments, SEGMENT_COLOR_BITMASK_BYTES),\n ];\n return Buffer.from(finishPacket(data)).toString(\"base64\");\n}\n\n/**\n * Build a Base64-encoded BLE packet for segment brightness via ptReal.\n * Command: 33 05 15 02 BB bitmask\u00D714\n *\n * @param brightness Brightness 0-100\n * @param segments Array of 0-based segment indices\n */\nexport function buildSegmentBrightnessPacket(brightness: number, segments: number[]): string {\n const data = [\n 0x33,\n 0x05,\n 0x15,\n 0x02,\n Math.max(0, Math.min(100, brightness)),\n ...buildSegmentBitmask(segments, SEGMENT_BRIGHTNESS_BITMASK_BYTES),\n ];\n return Buffer.from(finishPacket(data)).toString(\"base64\");\n}\n\n/**\n * Apply speed level to a scene's scenceParam by replacing speed bytes in each page.\n * scenceParam structure: byte[0] = page count, then per page: 1 byte length + N bytes data.\n * Speed byte position within each page: pageLength - 5.\n *\n * @param scenceParam Base64-encoded scene parameter data\n * @param speedLevel Speed level index (0-based)\n * @param speedConfig JSON config string from speedInfo.config\n * @returns Modified Base64-encoded scenceParam with speed bytes replaced\n */\nexport function applySceneSpeed(scenceParam: string, speedLevel: number, speedConfig: string): string {\n if (!scenceParam || !speedConfig) {\n return scenceParam;\n }\n\n let configEntries: Array<{\n page: number;\n moveIn?: number[];\n }>;\n try {\n configEntries = JSON.parse(speedConfig);\n } catch {\n // Govee's speedInfo.config schema can drift \u2014 this is a pure helper\n // without a logger, so a malformed config falls back silently: the\n // un-modified scenceParam keeps the activation working at default\n // speed instead of failing the whole scene command.\n return scenceParam;\n }\n\n if (!Array.isArray(configEntries) || configEntries.length === 0) {\n return scenceParam;\n }\n\n const bytes = Array.from(Buffer.from(scenceParam, \"base64\"));\n if (bytes.length === 0) {\n return scenceParam;\n }\n\n const pageCount = bytes[0];\n let offset = 1;\n\n for (let pageIdx = 0; pageIdx < pageCount && offset < bytes.length; pageIdx++) {\n const pageLen = bytes[offset];\n if (offset + 1 + pageLen > bytes.length) {\n break;\n }\n\n const cfg = configEntries.find(c => c.page === pageIdx);\n if (cfg?.moveIn && speedLevel >= 0 && speedLevel < cfg.moveIn.length) {\n const speedBytePos = offset + 1 + (pageLen - 5);\n if (speedBytePos > offset && speedBytePos < offset + 1 + pageLen) {\n bytes[speedBytePos] = cfg.moveIn[speedLevel];\n }\n }\n\n offset += 1 + pageLen;\n }\n\n return Buffer.from(bytes).toString(\"base64\");\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["_a"]
|
|
7
7
|
}
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "govee-smart",
|
|
4
|
-
"version": "2.16.
|
|
4
|
+
"version": "2.16.2",
|
|
5
5
|
"news": {
|
|
6
|
+
"2.16.2": {
|
|
7
|
+
"en": "On hosts with multiple network interfaces, LAN device discovery now uses the selected interface for outgoing traffic, so it no longer misses devices by scanning on the wrong one.",
|
|
8
|
+
"de": "Auf Hosts mit mehreren Netzwerkschnittstellen nutzt die LAN-Erkennung jetzt die gewählte Schnittstelle für ausgehenden Verkehr und verpasst so keine Geräte mehr durch Scannen über die falsche.",
|
|
9
|
+
"ru": "На хостах с несколькими сетевыми интерфейсами LAN-обнаружение теперь использует выбранный интерфейс для исходящего трафика и больше не пропускает устройства из-за неверного.",
|
|
10
|
+
"pt": "Em hosts com várias interfaces de rede, a deteção de dispositivos LAN agora usa a interface selecionada para o tráfego de saída e deixa de perder dispositivos por procurar na errada.",
|
|
11
|
+
"nl": "Op hosts met meerdere netwerkinterfaces gebruikt LAN-detectie nu de geselecteerde interface voor uitgaand verkeer en mist zo geen apparaten meer door op de verkeerde te scannen.",
|
|
12
|
+
"fr": "Sur les hôtes à plusieurs interfaces réseau, la détection LAN utilise désormais l'interface sélectionnée pour le trafic sortant et ne manque plus d'appareils en analysant la mauvaise.",
|
|
13
|
+
"it": "Sugli host con più interfacce di rete, il rilevamento LAN ora usa l'interfaccia selezionata per il traffico in uscita e non perde più dispositivi cercando su quella sbagliata.",
|
|
14
|
+
"es": "En hosts con varias interfaces de red, la detección de dispositivos LAN ahora usa la interfaz seleccionada para el tráfico saliente y ya no pierde dispositivos al buscar en la incorrecta.",
|
|
15
|
+
"pl": "Na hostach z wieloma interfejsami sieciowymi wykrywanie urządzeń LAN używa teraz wybranego interfejsu dla ruchu wychodzącego i nie pomija już urządzeń przez skanowanie przez zły.",
|
|
16
|
+
"uk": "На хостах із кількома мережевими інтерфейсами LAN-виявлення тепер використовує вибраний інтерфейс для вихідного трафіку й не пропускає пристрої через неправильний.",
|
|
17
|
+
"zh-cn": "在具有多个网络接口的主机上,LAN 设备发现现在使用所选接口发送流量,不再因在错误接口上扫描而漏掉设备。"
|
|
18
|
+
},
|
|
6
19
|
"2.16.1": {
|
|
7
20
|
"en": "Compact mode no longer intercepts errors from other adapters, and device info entries keep a clean state history. Cloud commands no longer pile up without limit during rate-limit pauses.",
|
|
8
21
|
"de": "Compact Mode fängt keine Fehler fremder Adapter mehr ab, und Geräte-Info-Einträge behalten eine saubere State-History. Cloud-Befehle stauen sich bei Rate-Limit-Pausen nicht mehr unbegrenzt.",
|
|
@@ -80,19 +93,6 @@
|
|
|
80
93
|
"pl": "Dziennik zmian przepisany w stylu zorientowanym na użytkownika we wszystkich wersjach.",
|
|
81
94
|
"uk": "Журнал змін переписано у стилі, орієнтованому на користувача, для всіх версій.",
|
|
82
95
|
"zh-cn": "更新日志在所有版本中以面向用户的风格重写。"
|
|
83
|
-
},
|
|
84
|
-
"2.13.3": {
|
|
85
|
-
"en": "Internal cleanup. No user-facing changes.",
|
|
86
|
-
"de": "Interne Bereinigung. Keine für den Benutzer sichtbaren Änderungen.",
|
|
87
|
-
"ru": "Внутренняя очистка. Нет видимых пользователю изменений.",
|
|
88
|
-
"pt": "Limpeza interna. Sem alterações visíveis ao utilizador.",
|
|
89
|
-
"nl": "Interne opschoning. Geen zichtbare wijzigingen voor de gebruiker.",
|
|
90
|
-
"fr": "Nettoyage interne. Aucun changement visible pour l'utilisateur.",
|
|
91
|
-
"it": "Pulizia interna. Nessuna modifica visibile all'utente.",
|
|
92
|
-
"es": "Limpieza interna. Sin cambios visibles para el usuario.",
|
|
93
|
-
"pl": "Wewnętrzne porządki. Brak zmian widocznych dla użytkownika.",
|
|
94
|
-
"uk": "Внутрішнє очищення. Без видимих змін для користувача.",
|
|
95
|
-
"zh-cn": "内部清理。无用户可见的更改。"
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
98
|
"plugins": {
|