iobroker.govee-local 0.4.7 → 0.5.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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025-2026 Børge Grunicke <boerge@grunicke.de>
3
+ Copyright (c) 2025-2026 Børge Grunicke <apps@grunicke.com>
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -24,6 +24,22 @@ For **Windows** Users: Please go to the settings and make sure you choose the co
24
24
  Placeholder for the next version (at the beginning of the line):
25
25
  ### **WORK IN PROGRESS**
26
26
  -->
27
+ ### 0.5.2 (2026-11-05)
28
+ - update dependencies: `node-forge` (1.4.3) and `follow-redirects` (1.16.2) to fix security vulnerabilities
29
+
30
+ ### 0.5.1 (2026-11-04)
31
+ - update dependencies: `@iobroker/testing` (6.3.0)
32
+ - fix states briefly jumping back to the old value after a command, until the device confirms it (#327)
33
+ - acknowledge commanded states once the device confirms them, even if the value did not change
34
+
35
+ ### 0.5.0 (2026-09-11)
36
+
37
+ - add HomeKit-compatible hue and saturation states, and a mired color temperature state
38
+ - Adapter requires node.js >= 22.19.0 now
39
+ - update dependencies: `@iobroker/testing` (6.2.1)
40
+ - removed the now obsolete npm overrides for `mocha` subdependencies `diff` and `serialize-javascript`
41
+ - update dependencies
42
+
27
43
  ### 0.4.7 (2026-06-02)
28
44
 
29
45
  - update dependencies
@@ -40,26 +56,13 @@ For **Windows** Users: Please go to the settings and make sure you choose the co
40
56
  - update dependencies: @types/node (25.5.0), eslint (10.1.0), @alcalzone/release-script (5.1.1), admin (7.6.20)
41
57
  - update dependencies: @alcalzone/release-script (5.2.0), @alcalzone/release-script-plugin-iobroker (5.2.0), @alcalzone/release-script-plugin-license (5.2.0), @alcalzone/release-script-plugin-manual-review (5.2.0), @iobroker/eslint-config (2.3.4), @types/node (25.6.2), eslint (10.3.0), prettier (3.8.3), typescript (6.0.3)
42
58
 
43
- ### 0.4.5 (2025-11-16)
44
-
45
- - fix scan mode inconsistency
46
- enhance log output behavior
47
-
48
- ### 0.4.4 (2025-11-13)
49
-
50
- - add option to run auto discovery once on startup, in interval or completly disable it
51
-
52
- ### 0.4.3 (2025-11-11)
53
-
54
- - fix responsive design in the settings
55
- - revert forbidden characters checking
56
- - update dependencies
59
+ Older entries are available in [CHANGELOG_OLD.md](CHANGELOG_OLD.md).
57
60
 
58
- Older entries are available in [CHANGELOG_OLD.md](CHANGELOG_OLD.md).## License
61
+ ## License
59
62
 
60
63
  MIT License
61
64
 
62
- Copyright (c) 2025-2026 Børge Grunicke
65
+ Copyright (c) 2025-2026 Børge Grunicke <apps@grunicke.com>
63
66
 
64
67
  Permission is hereby granted, free of charge, to any person obtaining a copy
65
68
  of this software and associated documentation files (the "Software"), to deal
@@ -33,6 +33,7 @@ __export(goveeService_exports, {
33
33
  module.exports = __toCommonJS(goveeService_exports);
34
34
  var dgram = __toESM(require("node:dgram"));
35
35
  var import_node_events = require("node:events");
36
+ var import_colorConversion = require("./tools/colorConversion");
36
37
  var import_hexTool = require("./tools/hexTool");
37
38
  var import_ipValidation = require("./tools/ipValidation");
38
39
  class GoveeService extends import_node_events.EventEmitter {
@@ -272,6 +273,13 @@ class GoveeService extends import_node_events.EventEmitter {
272
273
  case "colorTemInKelvin":
273
274
  this.sendColorTempCommand(receiver, Number(state.val));
274
275
  break;
276
+ case "colorTemperature": {
277
+ const mired = Number(state.val);
278
+ if (Number.isFinite(mired) && mired > 0) {
279
+ this.sendColorTempCommand(receiver, (0, import_colorConversion.miredToKelvin)(mired));
280
+ }
281
+ break;
282
+ }
275
283
  case "color": {
276
284
  const colorValue = (_a = state.val) == null ? void 0 : _a.toString();
277
285
  if (colorValue) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/goveeService.ts"],
4
- "sourcesContent": ["/**\n * GoveeService handles all business logic for device discovery, status updates, and UDP communication.\n * This class is independent from ioBroker.Adapter and can be tested separately.\n */\n\nimport * as dgram from 'node:dgram';\nimport { EventEmitter } from 'node:events';\nimport type { GoveeServiceOptions } from './goveeServiceOptions';\nimport { componentToHex, hexToRgb } from './tools/hexTool';\nimport { isValidIpAddress } from './tools/ipValidation';\n\n/**\n * Device discovery event data.\n */\nexport interface DeviceDiscoveryEvent {\n /** The IP address of the discovered device. */\n ip: string;\n /** The sanitized device name. */\n deviceName: string;\n /** The device model. */\n deviceModel: string;\n}\n\n/**\n * Device status update event data.\n */\nexport interface DeviceStatusEvent {\n /** The sanitized device name. */\n deviceName: string;\n /** The IP address of the device. */\n ip: string;\n /** The current status of the device. */\n status: {\n /** Current on/off state. */\n onOff: boolean;\n /** Current brightness level. */\n brightness: number;\n /** Current color as hex string. */\n color: string;\n /** Current color temperature in Kelvin. */\n colorTemInKelvin: number;\n };\n}\n\n/**\n * GoveeService handles all business logic for device discovery, status updates, and UDP communication.\n * This class is independent from ioBroker.Adapter and can be tested separately.\n *\n * Events:\n * - 'deviceDiscovered': Emitted when a new device is found\n * - 'deviceStatusUpdate': Emitted when device status is updated\n *\n * Usage:\n * const service = new GoveeService(options);\n * service.on('deviceDiscovered', (data) => { ... });\n * service.on('deviceStatusUpdate', (data) => { ... });\n * service.start();\n * // ...\n * service.stop();\n */\nexport class GoveeService extends EventEmitter {\n private socket: dgram.Socket;\n private options: GoveeServiceOptions;\n private devices: { [ip: string]: string } = {};\n private loggedDevices: string[] = [];\n private lastStatusLog: { [ip: string]: string } = {};\n private searchInterval?: unknown;\n private refreshInterval?: unknown;\n private scanMode: 'interval' | 'once' | 'never' = 'interval';\n\n static readonly LOCAL_PORT = 4002;\n static readonly SEND_SCAN_PORT = 4001;\n static readonly CONTROL_PORT = 4003;\n static readonly M_CAST = '239.255.255.250';\n static readonly scanMessage = {\n msg: { cmd: 'scan', data: { account_topic: 'reserved' } },\n };\n static readonly requestStatusMessage = {\n msg: { cmd: 'devStatus', data: {} },\n };\n\n /**\n * Create a new GoveeService instance.\n *\n * @param options Configuration options for the service.\n */\n constructor(options: GoveeServiceOptions) {\n super();\n this.options = options;\n this.socket = dgram.createSocket({ type: 'udp4' });\n // Get scanMode from options, fallback to interval\n const providedScanMode = options.scanMode;\n\n if (providedScanMode === 'once' || providedScanMode === 'never') {\n this.scanMode = providedScanMode;\n } else {\n this.scanMode = 'interval';\n }\n }\n\n /**\n * Bind UDP socket and start device search/refresh intervals.\n */\n public start(): void {\n this.socket.on('message', this.onUdpMessage.bind(this));\n this.socket.on('error', error => {\n this.options.logger?.error(`server bind error : ${error.message}`);\n });\n this.socket.bind(\n { address: this.options.interface, port: GoveeService.LOCAL_PORT },\n this.serverBound.bind(this),\n );\n }\n\n private serverBound(): void {\n this.socket.setBroadcast(true);\n this.socket.setMulticastTTL(128);\n this.socket.setMulticastInterface(this.options.interface);\n this.socket.addMembership(GoveeService.M_CAST);\n\n // Add manual IP addresses if provided\n if (this.options.manualIpAddresses && this.options.manualIpAddresses.length > 0) {\n this.addManualDevices(this.options.manualIpAddresses);\n }\n\n // Start discovery according to scanMode\n this.options.logger?.debug(`Device discovery mode: \"${this.scanMode}\"`);\n\n const setIntervalFn = this.options.setInterval ?? setInterval;\n\n if (this.scanMode === 'interval') {\n this.searchInterval = setIntervalFn(() => this.sendScan(), this.options.searchInterval * 1000);\n } else if (this.scanMode === 'once') {\n this.sendScan();\n }\n\n this.refreshInterval = setIntervalFn(\n () => this.refreshAllDevices(),\n this.options.deviceStatusRefreshInterval * 1000,\n );\n\n // Emit serviceStarted event after successful startup\n this.emit('serviceStarted');\n }\n\n /**\n * Handle incoming UDP messages.\n *\n * @param message The message buffer.\n * @param remote The sender info.\n */\n private onUdpMessage(message: Buffer, remote: dgram.RemoteInfo): void {\n let messageObject: { msg: { cmd: string; data: any } };\n try {\n messageObject = JSON.parse(message.toString());\n } catch (err) {\n this.options.logger?.error(\n `Malformed UDP message from ${remote.address}:${remote.port}: ${err instanceof Error ? err.message : String(err)}`,\n );\n throw err;\n }\n switch (messageObject.msg.cmd) {\n case 'scan': {\n if (messageObject.msg.data.device) {\n const deviceName = messageObject.msg.data.device.replace(\n this.options.forbiddenChars ?? /[^a-zA-Z0-9_-]/g,\n '_',\n );\n this.devices[remote.address] = deviceName;\n if (this.options.extendedLogging && !this.loggedDevices.includes(remote.address.toString())) {\n this.options.logger?.info(\n `Discovered device: ${deviceName} at ${remote.address} (model: ${messageObject.msg.data.sku})`,\n );\n this.loggedDevices.push(remote.address.toString());\n }\n this.emit('deviceDiscovered', {\n ip: remote.address,\n deviceName: deviceName,\n deviceModel: messageObject.msg.data.sku,\n } as DeviceDiscoveryEvent);\n }\n break;\n }\n case 'devStatus': {\n const sendingDevice = this.devices[remote.address];\n if (sendingDevice) {\n if (this.options.extendedLogging) {\n const statusString = JSON.stringify(messageObject);\n if (this.lastStatusLog[remote.address] !== statusString) {\n this.options.logger?.info(`device status message data: ${statusString}`);\n this.lastStatusLog[remote.address] = statusString;\n }\n }\n this.emitDeviceStatusUpdate(sendingDevice, remote.address, messageObject);\n }\n break;\n }\n default: {\n this.options.logger?.debug(`message from: ${remote.address}:${remote.port} - ${message.toString()}`);\n }\n }\n }\n\n /**\n * Send device status request to all known devices.\n */\n public refreshAllDevices(): void {\n for (const ip in this.devices) {\n this.requestDeviceStatus(ip);\n }\n }\n\n /**\n * Send device status request to a specific device.\n *\n * @param receiver The IP address or hostname of the device.\n */\n public requestDeviceStatus(receiver: string): void {\n const requestDeviceStatusBuffer = Buffer.from(JSON.stringify(GoveeService.requestStatusMessage));\n this.socket.send(\n requestDeviceStatusBuffer,\n 0,\n requestDeviceStatusBuffer.length,\n GoveeService.CONTROL_PORT,\n receiver,\n );\n }\n\n /**\n * Send scan message to the UDP multicast address.\n */\n public sendScan(): void {\n if (this.options.extendedLogging) {\n this.options.logger?.debug('sending scan message');\n }\n const scanMessageBuffer = Buffer.from(JSON.stringify(GoveeService.scanMessage));\n this.socket.send(\n scanMessageBuffer,\n 0,\n scanMessageBuffer.length,\n GoveeService.SEND_SCAN_PORT,\n GoveeService.M_CAST,\n );\n }\n\n /**\n * Add manual devices by IP address without discovery.\n * These devices will be added to the device list and can be controlled.\n *\n * @param ipAddresses Array of IP addresses to add as manual devices.\n */\n public addManualDevices(ipAddresses: string[]): void {\n for (const ip of ipAddresses) {\n if (!ip || ip.trim().length === 0) {\n continue;\n }\n const trimmedIp = ip.trim();\n\n // Validate IP address format\n if (!isValidIpAddress(trimmedIp)) {\n this.options.logger?.error(`Invalid IP address format: \"${trimmedIp}\" - skipping this entry`);\n continue;\n }\n\n // Generate a device name based on the IP address\n const deviceName = `Manual_${trimmedIp.replace(/\\./g, '_')}`;\n this.devices[trimmedIp] = deviceName;\n this.options.logger?.info(`Added manual device: ${deviceName} at ${trimmedIp}`);\n\n // Emit discovery event for manual device\n this.emit('deviceDiscovered', {\n ip: trimmedIp,\n deviceName: deviceName,\n } as DeviceDiscoveryEvent);\n\n // Request initial status\n this.requestDeviceStatus(trimmedIp);\n }\n }\n\n /**\n * Stop all intervals and close the socket.\n */\n public stop(): void {\n this.options.logger?.debug('Stopping GoveeService and closing UDP socket.');\n const clearIntervalFn = this.options.clearInterval ?? (clearInterval as (t: unknown) => void);\n\n if (this.searchInterval) {\n clearIntervalFn(this.searchInterval);\n }\n if (this.refreshInterval) {\n clearIntervalFn(this.refreshInterval);\n }\n this.socket.close();\n }\n\n /**\n * Get all known devices.\n */\n public getDevices(): { [ip: string]: string } {\n return this.devices;\n }\n\n /**\n * Handles a state change and sends the appropriate command to the device.\n *\n * @param id The state ID\n * @param state The new state object\n * @param receiver The device IP as string\n */\n public handleStateChange(id: string, state: ioBroker.State, receiver: string): void {\n const stateKey = id.split('.')[4];\n switch (stateKey) {\n case 'onOff':\n this.sendTurnCommand(receiver, !!state.val);\n break;\n case 'brightness':\n this.sendBrightnessCommand(receiver, Number(state.val));\n break;\n case 'colorTemInKelvin':\n this.sendColorTempCommand(receiver, Number(state.val));\n break;\n case 'color': {\n const colorValue = state.val?.toString();\n if (colorValue) {\n this.sendColorCommand(receiver, colorValue);\n }\n break;\n }\n }\n }\n /**\n * Send a turn on/off command to a device.\n *\n * @param receiver IP address or hostname\n * @param value true for on, false for off\n */\n public sendTurnCommand(receiver: string, value: boolean): void {\n const turnMessage = {\n msg: { cmd: 'turn', data: { value: value ? 1 : 0 } },\n };\n const turnMessageBuffer = Buffer.from(JSON.stringify(turnMessage));\n this.socket.send(turnMessageBuffer, 0, turnMessageBuffer.length, GoveeService.CONTROL_PORT, receiver);\n }\n\n /**\n * Send a brightness command to a device.\n *\n * @param receiver IP address or hostname\n * @param value Brightness value\n */\n public sendBrightnessCommand(receiver: string, value: number): void {\n const brightnessMessage = { msg: { cmd: 'brightness', data: { value } } };\n const brightnessMessageBuffer = Buffer.from(JSON.stringify(brightnessMessage));\n this.socket.send(\n brightnessMessageBuffer,\n 0,\n brightnessMessageBuffer.length,\n GoveeService.CONTROL_PORT,\n receiver,\n );\n }\n\n /**\n * Send a color temperature command to a device.\n *\n * @param receiver IP address or hostname\n * @param kelvin Color temperature in Kelvin\n */\n public sendColorTempCommand(receiver: string, kelvin: number): void {\n const colorTempMessageBuffer = Buffer.from(\n JSON.stringify({\n msg: {\n cmd: 'colorwc',\n data: { color: { r: 0, g: 0, b: 0 }, colorTemInKelvin: kelvin },\n },\n }),\n );\n this.socket.send(colorTempMessageBuffer, 0, colorTempMessageBuffer.length, GoveeService.CONTROL_PORT, receiver);\n }\n\n /**\n * Send a color command to a device.\n *\n * @param receiver IP address or hostname\n * @param hexColor Color as hex string (e.g. #FFAABB)\n */\n public sendColorCommand(receiver: string, hexColor: string): void {\n const rgb = hexToRgb(hexColor);\n const colorMessage = { msg: { cmd: 'colorwc', data: { color: rgb } } };\n const colorMessageBuffer = Buffer.from(JSON.stringify(colorMessage));\n this.socket.send(colorMessageBuffer, 0, colorMessageBuffer.length, GoveeService.CONTROL_PORT, receiver);\n }\n\n /**\n * Emit device status update event with parsed status data.\n *\n * @param deviceName The device name.\n * @param ip The device IP address.\n * @param messageObject The parsed message object containing device status.\n */\n private emitDeviceStatusUpdate(deviceName: string, ip: string, messageObject: any): void {\n const deviceData = messageObject.msg.data;\n // Robust: Fallback for missing or invalid color data\n let colorString = '#000000';\n if (deviceData.color && typeof deviceData.color === 'object') {\n const r = typeof deviceData.color.r === 'number' ? deviceData.color.r : 0;\n const g = typeof deviceData.color.g === 'number' ? deviceData.color.g : 0;\n const b = typeof deviceData.color.b === 'number' ? deviceData.color.b : 0;\n colorString = `#${componentToHex(r)}${componentToHex(g)}${componentToHex(b)}`;\n }\n this.emit('deviceStatusUpdate', {\n deviceName: deviceName,\n ip: ip,\n status: {\n onOff: deviceData.onOff === 1,\n brightness: deviceData.brightness,\n color: colorString,\n colorTemInKelvin: deviceData.colorTemInKelvin,\n },\n } as DeviceStatusEvent);\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * GoveeService handles all business logic for device discovery, status updates, and UDP communication.\n * This class is independent from ioBroker.Adapter and can be tested separately.\n */\n\nimport * as dgram from 'node:dgram';\nimport { EventEmitter } from 'node:events';\nimport type { GoveeServiceOptions } from './goveeServiceOptions';\nimport { miredToKelvin } from './tools/colorConversion';\nimport { componentToHex, hexToRgb } from './tools/hexTool';\nimport { isValidIpAddress } from './tools/ipValidation';\n\n/**\n * Device discovery event data.\n */\nexport interface DeviceDiscoveryEvent {\n /** The IP address of the discovered device. */\n ip: string;\n /** The sanitized device name. */\n deviceName: string;\n /** The device model. */\n deviceModel: string;\n}\n\n/**\n * Device status update event data.\n */\nexport interface DeviceStatusEvent {\n /** The sanitized device name. */\n deviceName: string;\n /** The IP address of the device. */\n ip: string;\n /** The current status of the device. */\n status: {\n /** Current on/off state. */\n onOff: boolean;\n /** Current brightness level. */\n brightness: number;\n /** Current color as hex string. */\n color: string;\n /** Current color temperature in Kelvin. */\n colorTemInKelvin: number;\n };\n}\n\n/**\n * GoveeService handles all business logic for device discovery, status updates, and UDP communication.\n * This class is independent from ioBroker.Adapter and can be tested separately.\n *\n * Events:\n * - 'deviceDiscovered': Emitted when a new device is found\n * - 'deviceStatusUpdate': Emitted when device status is updated\n *\n * Usage:\n * const service = new GoveeService(options);\n * service.on('deviceDiscovered', (data) => { ... });\n * service.on('deviceStatusUpdate', (data) => { ... });\n * service.start();\n * // ...\n * service.stop();\n */\nexport class GoveeService extends EventEmitter {\n private socket: dgram.Socket;\n private options: GoveeServiceOptions;\n private devices: { [ip: string]: string } = {};\n private loggedDevices: string[] = [];\n private lastStatusLog: { [ip: string]: string } = {};\n private searchInterval?: unknown;\n private refreshInterval?: unknown;\n private scanMode: 'interval' | 'once' | 'never' = 'interval';\n\n static readonly LOCAL_PORT = 4002;\n static readonly SEND_SCAN_PORT = 4001;\n static readonly CONTROL_PORT = 4003;\n static readonly M_CAST = '239.255.255.250';\n static readonly scanMessage = {\n msg: { cmd: 'scan', data: { account_topic: 'reserved' } },\n };\n static readonly requestStatusMessage = {\n msg: { cmd: 'devStatus', data: {} },\n };\n\n /**\n * Create a new GoveeService instance.\n *\n * @param options Configuration options for the service.\n */\n constructor(options: GoveeServiceOptions) {\n super();\n this.options = options;\n this.socket = dgram.createSocket({ type: 'udp4' });\n // Get scanMode from options, fallback to interval\n const providedScanMode = options.scanMode;\n\n if (providedScanMode === 'once' || providedScanMode === 'never') {\n this.scanMode = providedScanMode;\n } else {\n this.scanMode = 'interval';\n }\n }\n\n /**\n * Bind UDP socket and start device search/refresh intervals.\n */\n public start(): void {\n this.socket.on('message', this.onUdpMessage.bind(this));\n this.socket.on('error', error => {\n this.options.logger?.error(`server bind error : ${error.message}`);\n });\n this.socket.bind(\n { address: this.options.interface, port: GoveeService.LOCAL_PORT },\n this.serverBound.bind(this),\n );\n }\n\n private serverBound(): void {\n this.socket.setBroadcast(true);\n this.socket.setMulticastTTL(128);\n this.socket.setMulticastInterface(this.options.interface);\n this.socket.addMembership(GoveeService.M_CAST);\n\n // Add manual IP addresses if provided\n if (this.options.manualIpAddresses && this.options.manualIpAddresses.length > 0) {\n this.addManualDevices(this.options.manualIpAddresses);\n }\n\n // Start discovery according to scanMode\n this.options.logger?.debug(`Device discovery mode: \"${this.scanMode}\"`);\n\n const setIntervalFn = this.options.setInterval ?? setInterval;\n\n if (this.scanMode === 'interval') {\n this.searchInterval = setIntervalFn(() => this.sendScan(), this.options.searchInterval * 1000);\n } else if (this.scanMode === 'once') {\n this.sendScan();\n }\n\n this.refreshInterval = setIntervalFn(\n () => this.refreshAllDevices(),\n this.options.deviceStatusRefreshInterval * 1000,\n );\n\n // Emit serviceStarted event after successful startup\n this.emit('serviceStarted');\n }\n\n /**\n * Handle incoming UDP messages.\n *\n * @param message The message buffer.\n * @param remote The sender info.\n */\n private onUdpMessage(message: Buffer, remote: dgram.RemoteInfo): void {\n let messageObject: { msg: { cmd: string; data: any } };\n try {\n messageObject = JSON.parse(message.toString());\n } catch (err) {\n this.options.logger?.error(\n `Malformed UDP message from ${remote.address}:${remote.port}: ${err instanceof Error ? err.message : String(err)}`,\n );\n throw err;\n }\n switch (messageObject.msg.cmd) {\n case 'scan': {\n if (messageObject.msg.data.device) {\n const deviceName = messageObject.msg.data.device.replace(\n this.options.forbiddenChars ?? /[^a-zA-Z0-9_-]/g,\n '_',\n );\n this.devices[remote.address] = deviceName;\n if (this.options.extendedLogging && !this.loggedDevices.includes(remote.address.toString())) {\n this.options.logger?.info(\n `Discovered device: ${deviceName} at ${remote.address} (model: ${messageObject.msg.data.sku})`,\n );\n this.loggedDevices.push(remote.address.toString());\n }\n this.emit('deviceDiscovered', {\n ip: remote.address,\n deviceName: deviceName,\n deviceModel: messageObject.msg.data.sku,\n });\n }\n break;\n }\n case 'devStatus': {\n const sendingDevice = this.devices[remote.address];\n if (sendingDevice) {\n if (this.options.extendedLogging) {\n const statusString = JSON.stringify(messageObject);\n if (this.lastStatusLog[remote.address] !== statusString) {\n this.options.logger?.info(`device status message data: ${statusString}`);\n this.lastStatusLog[remote.address] = statusString;\n }\n }\n this.emitDeviceStatusUpdate(sendingDevice, remote.address, messageObject);\n }\n break;\n }\n default: {\n this.options.logger?.debug(`message from: ${remote.address}:${remote.port} - ${message.toString()}`);\n }\n }\n }\n\n /**\n * Send device status request to all known devices.\n */\n public refreshAllDevices(): void {\n for (const ip in this.devices) {\n this.requestDeviceStatus(ip);\n }\n }\n\n /**\n * Send device status request to a specific device.\n *\n * @param receiver The IP address or hostname of the device.\n */\n public requestDeviceStatus(receiver: string): void {\n const requestDeviceStatusBuffer = Buffer.from(JSON.stringify(GoveeService.requestStatusMessage));\n this.socket.send(\n requestDeviceStatusBuffer,\n 0,\n requestDeviceStatusBuffer.length,\n GoveeService.CONTROL_PORT,\n receiver,\n );\n }\n\n /**\n * Send scan message to the UDP multicast address.\n */\n public sendScan(): void {\n if (this.options.extendedLogging) {\n this.options.logger?.debug('sending scan message');\n }\n const scanMessageBuffer = Buffer.from(JSON.stringify(GoveeService.scanMessage));\n this.socket.send(\n scanMessageBuffer,\n 0,\n scanMessageBuffer.length,\n GoveeService.SEND_SCAN_PORT,\n GoveeService.M_CAST,\n );\n }\n\n /**\n * Add manual devices by IP address without discovery.\n * These devices will be added to the device list and can be controlled.\n *\n * @param ipAddresses Array of IP addresses to add as manual devices.\n */\n public addManualDevices(ipAddresses: string[]): void {\n for (const ip of ipAddresses) {\n if (!ip || ip.trim().length === 0) {\n continue;\n }\n const trimmedIp = ip.trim();\n\n // Validate IP address format\n if (!isValidIpAddress(trimmedIp)) {\n this.options.logger?.error(`Invalid IP address format: \"${trimmedIp}\" - skipping this entry`);\n continue;\n }\n\n // Generate a device name based on the IP address\n const deviceName = `Manual_${trimmedIp.replace(/\\./g, '_')}`;\n this.devices[trimmedIp] = deviceName;\n this.options.logger?.info(`Added manual device: ${deviceName} at ${trimmedIp}`);\n\n // Emit discovery event for manual device\n this.emit('deviceDiscovered', {\n ip: trimmedIp,\n deviceName: deviceName,\n });\n\n // Request initial status\n this.requestDeviceStatus(trimmedIp);\n }\n }\n\n /**\n * Stop all intervals and close the socket.\n */\n public stop(): void {\n this.options.logger?.debug('Stopping GoveeService and closing UDP socket.');\n const clearIntervalFn = this.options.clearInterval ?? (clearInterval as (t: unknown) => void);\n\n if (this.searchInterval) {\n clearIntervalFn(this.searchInterval);\n }\n if (this.refreshInterval) {\n clearIntervalFn(this.refreshInterval);\n }\n this.socket.close();\n }\n\n /**\n * Get all known devices.\n */\n public getDevices(): { [ip: string]: string } {\n return this.devices;\n }\n\n /**\n * Handles a state change and sends the appropriate command to the device.\n *\n * @param id The state ID\n * @param state The new state object\n * @param receiver The device IP as string\n */\n public handleStateChange(id: string, state: ioBroker.State, receiver: string): void {\n const stateKey = id.split('.')[4];\n switch (stateKey) {\n case 'onOff':\n this.sendTurnCommand(receiver, !!state.val);\n break;\n case 'brightness':\n this.sendBrightnessCommand(receiver, Number(state.val));\n break;\n case 'colorTemInKelvin':\n this.sendColorTempCommand(receiver, Number(state.val));\n break;\n case 'colorTemperature': {\n const mired = Number(state.val);\n if (Number.isFinite(mired) && mired > 0) {\n this.sendColorTempCommand(receiver, miredToKelvin(mired));\n }\n break;\n }\n case 'color': {\n const colorValue = state.val?.toString();\n if (colorValue) {\n this.sendColorCommand(receiver, colorValue);\n }\n break;\n }\n }\n }\n /**\n * Send a turn on/off command to a device.\n *\n * @param receiver IP address or hostname\n * @param value true for on, false for off\n */\n public sendTurnCommand(receiver: string, value: boolean): void {\n const turnMessage = {\n msg: { cmd: 'turn', data: { value: value ? 1 : 0 } },\n };\n const turnMessageBuffer = Buffer.from(JSON.stringify(turnMessage));\n this.socket.send(turnMessageBuffer, 0, turnMessageBuffer.length, GoveeService.CONTROL_PORT, receiver);\n }\n\n /**\n * Send a brightness command to a device.\n *\n * @param receiver IP address or hostname\n * @param value Brightness value\n */\n public sendBrightnessCommand(receiver: string, value: number): void {\n const brightnessMessage = { msg: { cmd: 'brightness', data: { value } } };\n const brightnessMessageBuffer = Buffer.from(JSON.stringify(brightnessMessage));\n this.socket.send(\n brightnessMessageBuffer,\n 0,\n brightnessMessageBuffer.length,\n GoveeService.CONTROL_PORT,\n receiver,\n );\n }\n\n /**\n * Send a color temperature command to a device.\n *\n * @param receiver IP address or hostname\n * @param kelvin Color temperature in Kelvin\n */\n public sendColorTempCommand(receiver: string, kelvin: number): void {\n const colorTempMessageBuffer = Buffer.from(\n JSON.stringify({\n msg: {\n cmd: 'colorwc',\n data: { color: { r: 0, g: 0, b: 0 }, colorTemInKelvin: kelvin },\n },\n }),\n );\n this.socket.send(colorTempMessageBuffer, 0, colorTempMessageBuffer.length, GoveeService.CONTROL_PORT, receiver);\n }\n\n /**\n * Send a color command to a device.\n *\n * @param receiver IP address or hostname\n * @param hexColor Color as hex string (e.g. #FFAABB)\n */\n public sendColorCommand(receiver: string, hexColor: string): void {\n const rgb = hexToRgb(hexColor);\n const colorMessage = { msg: { cmd: 'colorwc', data: { color: rgb } } };\n const colorMessageBuffer = Buffer.from(JSON.stringify(colorMessage));\n this.socket.send(colorMessageBuffer, 0, colorMessageBuffer.length, GoveeService.CONTROL_PORT, receiver);\n }\n\n /**\n * Emit device status update event with parsed status data.\n *\n * @param deviceName The device name.\n * @param ip The device IP address.\n * @param messageObject The parsed message object containing device status.\n */\n private emitDeviceStatusUpdate(deviceName: string, ip: string, messageObject: any): void {\n const deviceData = messageObject.msg.data;\n // Robust: Fallback for missing or invalid color data\n let colorString = '#000000';\n if (deviceData.color && typeof deviceData.color === 'object') {\n const r = typeof deviceData.color.r === 'number' ? deviceData.color.r : 0;\n const g = typeof deviceData.color.g === 'number' ? deviceData.color.g : 0;\n const b = typeof deviceData.color.b === 'number' ? deviceData.color.b : 0;\n colorString = `#${componentToHex(r)}${componentToHex(g)}${componentToHex(b)}`;\n }\n this.emit('deviceStatusUpdate', {\n deviceName: deviceName,\n ip: ip,\n status: {\n onOff: deviceData.onOff === 1,\n brightness: deviceData.brightness,\n color: colorString,\n colorTemInKelvin: deviceData.colorTemInKelvin,\n },\n });\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,117 @@
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 colorConversion_exports = {};
20
+ __export(colorConversion_exports, {
21
+ hsvToRgb: () => hsvToRgb,
22
+ kelvinToMired: () => kelvinToMired,
23
+ miredToKelvin: () => miredToKelvin,
24
+ rgbToHsv: () => rgbToHsv
25
+ });
26
+ module.exports = __toCommonJS(colorConversion_exports);
27
+ function rgbToHsv(rgb) {
28
+ const r = clampComponent(rgb.r) / 255;
29
+ const g = clampComponent(rgb.g) / 255;
30
+ const b = clampComponent(rgb.b) / 255;
31
+ const max = Math.max(r, g, b);
32
+ const min = Math.min(r, g, b);
33
+ const delta = max - min;
34
+ let hue = 0;
35
+ if (delta !== 0) {
36
+ switch (max) {
37
+ case r:
38
+ hue = (g - b) / delta % 6;
39
+ break;
40
+ case g:
41
+ hue = (b - r) / delta + 2;
42
+ break;
43
+ default:
44
+ hue = (r - g) / delta + 4;
45
+ }
46
+ hue *= 60;
47
+ if (hue < 0) {
48
+ hue += 360;
49
+ }
50
+ }
51
+ return {
52
+ hue: Math.round(hue) % 360,
53
+ saturation: Math.round((max === 0 ? 0 : delta / max) * 100),
54
+ value: Math.round(max * 100)
55
+ };
56
+ }
57
+ function hsvToRgb(hue, saturation, value) {
58
+ const sector = (hue % 360 + 360) % 360 / 60;
59
+ const s = clampPercent(saturation) / 100;
60
+ const v = clampPercent(value) / 100;
61
+ const chroma = v * s;
62
+ const secondary = chroma * (1 - Math.abs(sector % 2 - 1));
63
+ const offset = v - chroma;
64
+ let r = 0;
65
+ let g = 0;
66
+ let b = 0;
67
+ if (sector < 1) {
68
+ r = chroma;
69
+ g = secondary;
70
+ } else if (sector < 2) {
71
+ r = secondary;
72
+ g = chroma;
73
+ } else if (sector < 3) {
74
+ g = chroma;
75
+ b = secondary;
76
+ } else if (sector < 4) {
77
+ g = secondary;
78
+ b = chroma;
79
+ } else if (sector < 5) {
80
+ r = secondary;
81
+ b = chroma;
82
+ } else {
83
+ r = chroma;
84
+ b = secondary;
85
+ }
86
+ return {
87
+ r: Math.round((r + offset) * 255),
88
+ g: Math.round((g + offset) * 255),
89
+ b: Math.round((b + offset) * 255)
90
+ };
91
+ }
92
+ function clampComponent(component) {
93
+ if (!Number.isFinite(component)) {
94
+ return 0;
95
+ }
96
+ return Math.min(Math.max(component, 0), 255);
97
+ }
98
+ function clampPercent(percent) {
99
+ if (!Number.isFinite(percent)) {
100
+ return 0;
101
+ }
102
+ return Math.min(Math.max(percent, 0), 100);
103
+ }
104
+ function kelvinToMired(kelvin) {
105
+ return Math.round(1e6 / kelvin);
106
+ }
107
+ function miredToKelvin(mired) {
108
+ return Math.round(1e6 / mired);
109
+ }
110
+ // Annotate the CommonJS export names for ESM import in node:
111
+ 0 && (module.exports = {
112
+ hsvToRgb,
113
+ kelvinToMired,
114
+ miredToKelvin,
115
+ rgbToHsv
116
+ });
117
+ //# sourceMappingURL=colorConversion.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/lib/tools/colorConversion.ts"],
4
+ "sourcesContent": ["/**\n * Converts an RGB color to HSV (Hue-Saturation-Value), the model behind HomeKit's\n * Hue / Saturation / Brightness characteristics.\n *\n * HSV is used here rather than HSL on purpose: the adapter exposes hue and saturation\n * alongside the device's own brightness state, which is exactly an HSB triple. HSL would\n * report a washed-out color such as rgb(255, 128, 128) as fully saturated, because in HSL\n * the paleness is carried by the lightness component that this adapter does not expose.\n *\n * @param rgb The RGB color to convert.\n * @param rgb.r Red component (0-255).\n * @param rgb.g Green component (0-255).\n * @param rgb.b Blue component (0-255).\n * @returns An object with hue (0-360), saturation (0-100) and value (0-100).\n */\nexport function rgbToHsv(rgb: { r: number; g: number; b: number }): {\n hue: number;\n saturation: number;\n value: number;\n} {\n const r = clampComponent(rgb.r) / 255;\n const g = clampComponent(rgb.g) / 255;\n const b = clampComponent(rgb.b) / 255;\n\n const max = Math.max(r, g, b);\n const min = Math.min(r, g, b);\n const delta = max - min;\n\n let hue = 0;\n if (delta !== 0) {\n switch (max) {\n case r:\n hue = ((g - b) / delta) % 6;\n break;\n case g:\n hue = (b - r) / delta + 2;\n break;\n default:\n hue = (r - g) / delta + 4;\n }\n hue *= 60;\n if (hue < 0) {\n hue += 360;\n }\n }\n\n return {\n hue: Math.round(hue) % 360,\n saturation: Math.round((max === 0 ? 0 : delta / max) * 100),\n value: Math.round(max * 100),\n };\n}\n\n/**\n * Converts an HSV color to RGB. Hue wraps around 360 degrees; saturation and value are\n * percentages (0-100) and are clamped into that range.\n *\n * @param hue Hue in degrees.\n * @param saturation Saturation in percent.\n * @param value Value (brightness of the color itself) in percent.\n * @returns An object with r, g, b properties (0-255).\n */\nexport function hsvToRgb(hue: number, saturation: number, value: number): { r: number; g: number; b: number } {\n const sector = (((hue % 360) + 360) % 360) / 60;\n const s = clampPercent(saturation) / 100;\n const v = clampPercent(value) / 100;\n\n const chroma = v * s;\n const secondary = chroma * (1 - Math.abs((sector % 2) - 1));\n const offset = v - chroma;\n\n let r = 0;\n let g = 0;\n let b = 0;\n if (sector < 1) {\n r = chroma;\n g = secondary;\n } else if (sector < 2) {\n r = secondary;\n g = chroma;\n } else if (sector < 3) {\n g = chroma;\n b = secondary;\n } else if (sector < 4) {\n g = secondary;\n b = chroma;\n } else if (sector < 5) {\n r = secondary;\n b = chroma;\n } else {\n r = chroma;\n b = secondary;\n }\n\n return {\n r: Math.round((r + offset) * 255),\n g: Math.round((g + offset) * 255),\n b: Math.round((b + offset) * 255),\n };\n}\n\n/**\n * Clamps a single RGB component into the valid 0-255 range, treating non-numeric input as 0.\n *\n * @param component The raw component value.\n * @returns The component clamped to 0-255.\n */\nfunction clampComponent(component: number): number {\n if (!Number.isFinite(component)) {\n return 0;\n }\n return Math.min(Math.max(component, 0), 255);\n}\n\n/**\n * Clamps a percentage into the valid 0-100 range, treating non-numeric input as 0.\n *\n * @param percent The raw percentage value.\n * @returns The percentage clamped to 0-100.\n */\nfunction clampPercent(percent: number): number {\n if (!Number.isFinite(percent)) {\n return 0;\n }\n return Math.min(Math.max(percent, 0), 100);\n}\n\n/**\n * Converts a color temperature in Kelvin to mired (micro reciprocal degrees), the unit HomeKit uses.\n *\n * @param kelvin Color temperature in Kelvin. Must be greater than 0.\n * @returns Color temperature in mired.\n */\nexport function kelvinToMired(kelvin: number): number {\n return Math.round(1_000_000 / kelvin);\n}\n\n/**\n * Converts a color temperature in mired (micro reciprocal degrees) to Kelvin.\n *\n * @param mired Color temperature in mired. Must be greater than 0.\n * @returns Color temperature in Kelvin.\n */\nexport function miredToKelvin(mired: number): number {\n return Math.round(1_000_000 / mired);\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAeO,SAAS,SAAS,KAIvB;AACE,QAAM,IAAI,eAAe,IAAI,CAAC,IAAI;AAClC,QAAM,IAAI,eAAe,IAAI,CAAC,IAAI;AAClC,QAAM,IAAI,eAAe,IAAI,CAAC,IAAI;AAElC,QAAM,MAAM,KAAK,IAAI,GAAG,GAAG,CAAC;AAC5B,QAAM,MAAM,KAAK,IAAI,GAAG,GAAG,CAAC;AAC5B,QAAM,QAAQ,MAAM;AAEpB,MAAI,MAAM;AACV,MAAI,UAAU,GAAG;AACb,YAAQ,KAAK;AAAA,MACT,KAAK;AACD,eAAQ,IAAI,KAAK,QAAS;AAC1B;AAAA,MACJ,KAAK;AACD,eAAO,IAAI,KAAK,QAAQ;AACxB;AAAA,MACJ;AACI,eAAO,IAAI,KAAK,QAAQ;AAAA,IAChC;AACA,WAAO;AACP,QAAI,MAAM,GAAG;AACT,aAAO;AAAA,IACX;AAAA,EACJ;AAEA,SAAO;AAAA,IACH,KAAK,KAAK,MAAM,GAAG,IAAI;AAAA,IACvB,YAAY,KAAK,OAAO,QAAQ,IAAI,IAAI,QAAQ,OAAO,GAAG;AAAA,IAC1D,OAAO,KAAK,MAAM,MAAM,GAAG;AAAA,EAC/B;AACJ;AAWO,SAAS,SAAS,KAAa,YAAoB,OAAoD;AAC1G,QAAM,UAAY,MAAM,MAAO,OAAO,MAAO;AAC7C,QAAM,IAAI,aAAa,UAAU,IAAI;AACrC,QAAM,IAAI,aAAa,KAAK,IAAI;AAEhC,QAAM,SAAS,IAAI;AACnB,QAAM,YAAY,UAAU,IAAI,KAAK,IAAK,SAAS,IAAK,CAAC;AACzD,QAAM,SAAS,IAAI;AAEnB,MAAI,IAAI;AACR,MAAI,IAAI;AACR,MAAI,IAAI;AACR,MAAI,SAAS,GAAG;AACZ,QAAI;AACJ,QAAI;AAAA,EACR,WAAW,SAAS,GAAG;AACnB,QAAI;AACJ,QAAI;AAAA,EACR,WAAW,SAAS,GAAG;AACnB,QAAI;AACJ,QAAI;AAAA,EACR,WAAW,SAAS,GAAG;AACnB,QAAI;AACJ,QAAI;AAAA,EACR,WAAW,SAAS,GAAG;AACnB,QAAI;AACJ,QAAI;AAAA,EACR,OAAO;AACH,QAAI;AACJ,QAAI;AAAA,EACR;AAEA,SAAO;AAAA,IACH,GAAG,KAAK,OAAO,IAAI,UAAU,GAAG;AAAA,IAChC,GAAG,KAAK,OAAO,IAAI,UAAU,GAAG;AAAA,IAChC,GAAG,KAAK,OAAO,IAAI,UAAU,GAAG;AAAA,EACpC;AACJ;AAQA,SAAS,eAAe,WAA2B;AAC/C,MAAI,CAAC,OAAO,SAAS,SAAS,GAAG;AAC7B,WAAO;AAAA,EACX;AACA,SAAO,KAAK,IAAI,KAAK,IAAI,WAAW,CAAC,GAAG,GAAG;AAC/C;AAQA,SAAS,aAAa,SAAyB;AAC3C,MAAI,CAAC,OAAO,SAAS,OAAO,GAAG;AAC3B,WAAO;AAAA,EACX;AACA,SAAO,KAAK,IAAI,KAAK,IAAI,SAAS,CAAC,GAAG,GAAG;AAC7C;AAQO,SAAS,cAAc,QAAwB;AAClD,SAAO,KAAK,MAAM,MAAY,MAAM;AACxC;AAQO,SAAS,cAAc,OAAuB;AACjD,SAAO,KAAK,MAAM,MAAY,KAAK;AACvC;",
6
+ "names": []
7
+ }
package/build/main.js CHANGED
@@ -33,7 +33,21 @@ __export(main_exports, {
33
33
  module.exports = __toCommonJS(main_exports);
34
34
  var utils = __toESM(require("@iobroker/adapter-core"));
35
35
  var import_goveeService = require("./lib/goveeService");
36
+ var import_colorConversion = require("./lib/tools/colorConversion");
37
+ var import_hexTool = require("./lib/tools/hexTool");
36
38
  class GoveeLocal extends utils.Adapter {
39
+ /**
40
+ * Value (the V in HSV) used when converting a hue/saturation change back to RGB.
41
+ * Kept at 100 so the color is always sent at full scale and the peak RGB channel
42
+ * stays 255 regardless of saturation; overall output is controlled separately via
43
+ * the device's own brightness command.
44
+ */
45
+ static FULL_COLOR_VALUE = 100;
46
+ /**
47
+ * How long a user command (ack = false) is protected against being overwritten by a differing
48
+ * device status. Such a status was most likely requested before the command was applied.
49
+ */
50
+ static COMMAND_CONFIRMATION_TIMEOUT_MS = 2e3;
37
51
  /** Instance of GoveeService for device communication */
38
52
  goveeService;
39
53
  /**
@@ -125,15 +139,44 @@ class GoveeLocal extends utils.Adapter {
125
139
  async onStateChange(id, state) {
126
140
  var _a;
127
141
  if (state && !state.ack) {
128
- const ipOfDevice = await this.getStateAsync(`${id.split(".")[2]}.deviceInfo.ip`);
142
+ const deviceName = id.split(".")[2];
143
+ const stateKey = id.split(".")[4];
144
+ const ipOfDevice = await this.getStateAsync(`${deviceName}.deviceInfo.ip`);
129
145
  const receiver = (_a = ipOfDevice == null ? void 0 : ipOfDevice.val) == null ? void 0 : _a.toString();
130
146
  if (typeof receiver === "string") {
131
- this.goveeService.handleStateChange(id, state, receiver);
147
+ if (stateKey === "hue" || stateKey === "saturation") {
148
+ await this.handleHueSaturationChange(deviceName, stateKey, state, receiver);
149
+ } else {
150
+ this.goveeService.handleStateChange(id, state, receiver);
151
+ }
132
152
  } else {
133
153
  this.log.error("device not found or IP is not a string");
134
154
  }
135
155
  }
136
156
  }
157
+ /**
158
+ * Handles a hue or saturation change by combining it with the sibling state and
159
+ * sending the resulting color as an RGB command. The HSV value is fixed at full scale;
160
+ * overall brightness is controlled separately via the brightness state.
161
+ *
162
+ * @param deviceName The sanitized device name.
163
+ * @param changedKey Which of the two states triggered the change.
164
+ * @param state The new state value.
165
+ * @param receiver The device IP address.
166
+ */
167
+ async handleHueSaturationChange(deviceName, changedKey, state, receiver) {
168
+ var _a, _b;
169
+ const otherKey = changedKey === "hue" ? "saturation" : "hue";
170
+ const otherState = await this.getStateAsync(`${deviceName}.devStatus.${otherKey}`);
171
+ const hue = Number(changedKey === "hue" ? state.val : (_a = otherState == null ? void 0 : otherState.val) != null ? _a : 0);
172
+ const saturation = Number(changedKey === "saturation" ? state.val : (_b = otherState == null ? void 0 : otherState.val) != null ? _b : 0);
173
+ if (!Number.isFinite(hue) || !Number.isFinite(saturation)) {
174
+ this.log.error(`Ignoring ${changedKey} change for ${deviceName}: hue/saturation is not a number`);
175
+ return;
176
+ }
177
+ const { r, g, b } = (0, import_colorConversion.hsvToRgb)(hue, saturation, GoveeLocal.FULL_COLOR_VALUE);
178
+ this.goveeService.sendColorCommand(receiver, `#${(0, import_hexTool.componentToHex)(r)}${(0, import_hexTool.componentToHex)(g)}${(0, import_hexTool.componentToHex)(b)}`);
179
+ }
137
180
  /**
138
181
  * Called when the adapter shuts down. Cleans up resources and stops services.
139
182
  *
@@ -157,7 +200,11 @@ class GoveeLocal extends utils.Adapter {
157
200
  }
158
201
  }
159
202
  /**
160
- * Updates a state only if the value has changed.
203
+ * Updates a state only if its value or acknowledgement has changed.
204
+ * Comparing the ack flag as well ensures a user command (ack = false) gets acknowledged
205
+ * once the device confirms it, even though the value itself stays the same.
206
+ * A recent user command is not overwritten by a differing value, so a stale device status
207
+ * cannot make the state jump back before the device has applied the command.
161
208
  *
162
209
  * @param fullName Full object path
163
210
  * @param state New value
@@ -165,12 +212,19 @@ class GoveeLocal extends utils.Adapter {
165
212
  */
166
213
  async updateStateAsync(fullName, state, acknowledged = true) {
167
214
  const currentState = await this.getStateAsync(fullName);
168
- if ((currentState == null ? void 0 : currentState.val) !== state) {
169
- void this.setState(fullName, {
170
- val: state,
171
- ack: acknowledged
172
- });
215
+ if (currentState) {
216
+ if (currentState.val === state && currentState.ack === acknowledged) {
217
+ return;
218
+ }
219
+ const awaitingConfirmation = !currentState.ack && Date.now() - currentState.ts < GoveeLocal.COMMAND_CONFIRMATION_TIMEOUT_MS;
220
+ if (awaitingConfirmation && currentState.val !== state) {
221
+ return;
222
+ }
173
223
  }
224
+ await this.setState(fullName, {
225
+ val: state,
226
+ ack: acknowledged
227
+ });
174
228
  }
175
229
  /**
176
230
  * Handles device discovery event from GoveeService.
@@ -270,6 +324,37 @@ class GoveeLocal extends utils.Adapter {
270
324
  native: {}
271
325
  });
272
326
  await this.updateStateAsync(`${deviceName}.devStatus.color`, status.color);
327
+ await this.setObjectNotExistsAsync(`${deviceName}.devStatus.hue`, {
328
+ type: "state",
329
+ common: {
330
+ name: "Hue of the lamp (HomeKit compatible)",
331
+ type: "number",
332
+ role: "level.color.hue",
333
+ unit: "\xB0",
334
+ min: 0,
335
+ max: 360,
336
+ read: true,
337
+ write: true
338
+ },
339
+ native: {}
340
+ });
341
+ await this.setObjectNotExistsAsync(`${deviceName}.devStatus.saturation`, {
342
+ type: "state",
343
+ common: {
344
+ name: "Saturation of the lamp (HomeKit compatible)",
345
+ type: "number",
346
+ role: "level.color.saturation",
347
+ unit: "%",
348
+ min: 0,
349
+ max: 100,
350
+ read: true,
351
+ write: true
352
+ },
353
+ native: {}
354
+ });
355
+ const { hue, saturation } = (0, import_colorConversion.rgbToHsv)((0, import_hexTool.hexToRgb)(status.color));
356
+ await this.updateStateAsync(`${deviceName}.devStatus.hue`, hue);
357
+ await this.updateStateAsync(`${deviceName}.devStatus.saturation`, saturation);
273
358
  await this.setObjectNotExistsAsync(`${deviceName}.devStatus.colorTemInKelvin`, {
274
359
  type: "state",
275
360
  common: {
@@ -282,10 +367,30 @@ class GoveeLocal extends utils.Adapter {
282
367
  native: {}
283
368
  });
284
369
  await this.updateStateAsync(`${deviceName}.devStatus.colorTemInKelvin`, status.colorTemInKelvin);
370
+ await this.setObjectNotExistsAsync(`${deviceName}.devStatus.colorTemperature`, {
371
+ type: "state",
372
+ common: {
373
+ name: "Color temperature of the lamp in mired (HomeKit compatible)",
374
+ type: "number",
375
+ role: "level.color.temperature",
376
+ unit: "mired",
377
+ read: true,
378
+ write: true
379
+ },
380
+ native: {}
381
+ });
382
+ if (status.colorTemInKelvin > 0) {
383
+ await this.updateStateAsync(
384
+ `${deviceName}.devStatus.colorTemperature`,
385
+ (0, import_colorConversion.kelvinToMired)(status.colorTemInKelvin)
386
+ );
387
+ }
285
388
  }
286
389
  }
287
390
  if (require.main !== module) {
288
- module.exports = (options) => new GoveeLocal(options);
391
+ module.exports = Object.assign((options) => new GoveeLocal(options), {
392
+ GoveeLocal
393
+ });
289
394
  } else {
290
395
  (() => new GoveeLocal())();
291
396
  }
package/build/main.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/main.ts"],
4
- "sourcesContent": ["/*\n * Created with @iobroker/create-adapter v2.3.0\n */\n\nimport * as utils from '@iobroker/adapter-core';\nimport { GoveeService, type DeviceDiscoveryEvent, type DeviceStatusEvent } from './lib/goveeService';\n\n/**\n * Main adapter class for ioBroker Govee Local.\n * Handles initialization, event wiring, and communication with GoveeService.\n */\nexport class GoveeLocal extends utils.Adapter {\n /** Instance of GoveeService for device communication */\n private goveeService!: GoveeService;\n /**\n * Adapter constructor. Registers lifecycle event handlers.\n *\n * @param options Optional adapter options to override defaults.\n */\n public constructor(options: Partial<utils.AdapterOptions> = {}) {\n super({\n ...options,\n name: 'govee-local',\n });\n // Register event handlers for adapter lifecycle\n this.on('ready', this.onReady.bind(this));\n this.on('stateChange', this.onStateChange.bind(this));\n // Uncomment if you want to handle object changes or messages:\n // this.on('objectChange', this.onObjectChange.bind(this));\n // this.on('message', this.onMessage.bind(this));\n this.on('unload', this.onUnload.bind(this));\n }\n\n /**\n * Called when databases are connected and adapter received configuration.\n * Initializes GoveeService and sets up event listeners for device events.\n */\n private onReady(): void {\n // Create connection indicator state if it doesn't exist\n void this.setObjectNotExists('info.connection', {\n type: 'state',\n common: {\n name: 'Device discovery running',\n type: 'boolean',\n role: 'indicator.connected',\n read: true,\n write: false,\n },\n native: {},\n });\n\n // Create state for discovered devices\n void this.setObjectNotExists('info.discoveredDevices', {\n type: 'state',\n common: {\n name: 'List of discovered devices',\n type: 'string',\n role: 'json',\n read: true,\n write: false,\n desc: 'JSON object with IP addresses and device names of all discovered devices',\n },\n native: {},\n });\n\n // Initialize GoveeService with adapter config and logger\n this.goveeService = new GoveeService({\n interface: this.config.interface,\n searchInterval: this.config.searchInterval,\n deviceStatusRefreshInterval: this.config.deviceStatusRefreshInterval,\n extendedLogging: this.config.extendedLogging,\n forbiddenChars: this.FORBIDDEN_CHARS,\n manualIpAddresses: this.config.manualIpTable,\n scanMode: this.config.scanMode,\n setInterval: (callback: () => void, ms: number) => this.setInterval(callback, ms),\n clearInterval: (timeout: unknown) => this.clearInterval(timeout as ioBroker.Interval | undefined),\n logger: {\n debug: msg => this.log.debug(msg),\n info: msg => this.log.info(msg),\n error: msg => this.log.error(msg),\n },\n });\n\n // Listen for device discovery and status update events\n this.goveeService.on('deviceDiscovered', data => {\n this.handleDeviceDiscovered(data).catch((err: unknown) => {\n this.log.error(`Error handling device discovery: ${err instanceof Error ? err.message : String(err)}`);\n });\n });\n\n this.goveeService.on('deviceStatusUpdate', data => {\n this.handleDeviceStatusUpdate(data).catch((err: unknown) => {\n this.log.error(\n `Error handling device status update: ${err instanceof Error ? err.message : String(err)}`,\n );\n });\n });\n\n // Listen for serviceStarted event to set connection state to true\n this.goveeService.on('serviceStarted', () => {\n void this.setState('info.connection', { val: true, ack: true });\n });\n\n // Start device discovery and status polling\n this.goveeService.start();\n\n if (this.config.extendedLogging) {\n this.log.debug('running with extended logging');\n }\n\n // Subscribe to all device status state changes\n this.subscribeStates('*.devStatus.*');\n }\n\n /**\n * Called if a subscribed state changes (e.g. user toggles a switch in ioBroker UI).\n * Forwards the change to the GoveeService for device communication.\n *\n * @param id The ID of the changed state.\n * @param state The new state object or null/undefined.\n */\n private async onStateChange(id: string, state: ioBroker.State | null | undefined): Promise<void> {\n if (state && !state.ack) {\n // Extract device name from state ID and get its IP address\n const ipOfDevice = await this.getStateAsync(`${id.split('.')[2]}.deviceInfo.ip`);\n const receiver = ipOfDevice?.val?.toString();\n if (typeof receiver === 'string') {\n // Forward the state change to the GoveeService\n this.goveeService.handleStateChange(id, state, receiver);\n } else {\n this.log.error('device not found or IP is not a string');\n }\n }\n }\n\n /**\n * Called when the adapter shuts down. Cleans up resources and stops services.\n *\n * @param callback Callback function after unload process.\n */\n private onUnload(callback: () => void): void {\n try {\n if (this.goveeService) {\n this.goveeService.removeAllListeners();\n this.goveeService.stop();\n }\n // Set connection state to false\n void this.updateStateAsync('info.connection', false).catch((err: unknown) => {\n this.log.error(\n `Failed to update connection state during unload: ${err instanceof Error ? err.message : String(err)}`,\n );\n });\n callback();\n } catch (e: unknown) {\n this.log.error(e instanceof Error ? e.message : String(e));\n callback();\n }\n }\n\n /**\n * Updates a state only if the value has changed.\n *\n * @param fullName Full object path\n * @param state New value\n * @param acknowledged Whether the value is acknowledged (default: true)\n */\n private async updateStateAsync(fullName: string, state: any, acknowledged = true): Promise<void> {\n const currentState = await this.getStateAsync(fullName);\n if (currentState?.val !== state) {\n void this.setState(fullName, {\n val: state,\n ack: acknowledged,\n });\n }\n }\n\n /**\n * Handles device discovery event from GoveeService.\n * Creates all necessary objects and states for the new device.\n *\n * @param event The device discovery event data.\n */\n private async handleDeviceDiscovered(event: DeviceDiscoveryEvent): Promise<void> {\n const { ip, deviceName, deviceModel } = event;\n\n // Create device folder\n await this.setObjectNotExistsAsync(deviceName, {\n type: 'device',\n common: {\n name: deviceModel,\n role: 'group',\n },\n native: {},\n });\n\n // Create deviceInfo folder\n await this.setObjectNotExistsAsync(`${deviceName}.deviceInfo`, {\n type: 'folder',\n common: {\n name: 'Device Info',\n },\n native: {},\n });\n\n // Create IP address state\n await this.setObjectNotExistsAsync(`${deviceName}.deviceInfo.ip`, {\n type: 'state',\n common: {\n name: 'IP address of the Lamp',\n type: 'string',\n role: 'info.ip',\n read: true,\n write: false,\n },\n native: {},\n });\n await this.updateStateAsync(`${deviceName}.deviceInfo.ip`, ip);\n\n // Create devStatus folder\n await this.setObjectNotExistsAsync(`${deviceName}.devStatus`, {\n type: 'folder',\n common: {\n name: 'Device Status',\n },\n native: {},\n });\n\n // Update the list of discovered devices\n await this.updateDiscoveredDevicesList();\n }\n\n /**\n * Updates the info.discoveredDevices state with the current list of all discovered devices.\n */\n private async updateDiscoveredDevicesList(): Promise<void> {\n const devices = this.goveeService.getDevices();\n await this.setState('info.discoveredDevices', {\n val: JSON.stringify(devices, null, 2),\n ack: true,\n });\n }\n\n /**\n * Handles device status update event from GoveeService.\n * Creates and updates all relevant states for the device.\n *\n * @param event The device status update event data.\n */\n private async handleDeviceStatusUpdate(event: DeviceStatusEvent): Promise<void> {\n const { deviceName, status } = event;\n\n // Create and update onOff state\n await this.setObjectNotExistsAsync(`${deviceName}.devStatus.onOff`, {\n type: 'state',\n common: {\n name: 'On / Off state of the lamp',\n type: 'boolean',\n role: 'switch',\n read: true,\n write: true,\n },\n native: {},\n });\n await this.updateStateAsync(`${deviceName}.devStatus.onOff`, status.onOff);\n\n // Create and update brightness state\n await this.setObjectNotExistsAsync(`${deviceName}.devStatus.brightness`, {\n type: 'state',\n common: {\n name: 'Brightness of the light',\n type: 'number',\n role: 'level.dimmer',\n read: true,\n write: true,\n },\n native: {},\n });\n await this.updateStateAsync(`${deviceName}.devStatus.brightness`, status.brightness);\n\n // Create and update color state\n await this.setObjectNotExistsAsync(`${deviceName}.devStatus.color`, {\n type: 'state',\n common: {\n name: 'Current showing color of the lamp',\n type: 'string',\n role: 'level.color.rgb',\n read: true,\n write: true,\n },\n native: {},\n });\n await this.updateStateAsync(`${deviceName}.devStatus.color`, status.color);\n\n // Create and update color temperature state\n await this.setObjectNotExistsAsync(`${deviceName}.devStatus.colorTemInKelvin`, {\n type: 'state',\n common: {\n name: 'If staying in white light, the color temperature',\n type: 'number',\n role: 'level.color.temperature',\n read: true,\n write: true,\n },\n native: {},\n });\n await this.updateStateAsync(`${deviceName}.devStatus.colorTemInKelvin`, status.colorTemInKelvin);\n }\n}\n\n// Export factory function for ioBroker or start instance directly\nif (require.main !== module) {\n // Export factory function for ioBroker, also available as ES6 export\n module.exports = (options: Partial<utils.AdapterOptions> | undefined) => new GoveeLocal(options);\n} else {\n // Otherwise start the instance directly\n (() => new GoveeLocal())();\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/*\n * Created with @iobroker/create-adapter v2.3.0\n */\n\nimport * as utils from '@iobroker/adapter-core';\nimport { GoveeService, type DeviceDiscoveryEvent, type DeviceStatusEvent } from './lib/goveeService';\nimport { hsvToRgb, kelvinToMired, rgbToHsv } from './lib/tools/colorConversion';\nimport { componentToHex, hexToRgb } from './lib/tools/hexTool';\n\n/**\n * Main adapter class for ioBroker Govee Local.\n * Handles initialization, event wiring, and communication with GoveeService.\n */\nexport class GoveeLocal extends utils.Adapter {\n /**\n * Value (the V in HSV) used when converting a hue/saturation change back to RGB.\n * Kept at 100 so the color is always sent at full scale and the peak RGB channel\n * stays 255 regardless of saturation; overall output is controlled separately via\n * the device's own brightness command.\n */\n private static readonly FULL_COLOR_VALUE = 100;\n /**\n * How long a user command (ack = false) is protected against being overwritten by a differing\n * device status. Such a status was most likely requested before the command was applied.\n */\n private static readonly COMMAND_CONFIRMATION_TIMEOUT_MS = 2000;\n /** Instance of GoveeService for device communication */\n private goveeService!: GoveeService;\n /**\n * Adapter constructor. Registers lifecycle event handlers.\n *\n * @param options Optional adapter options to override defaults.\n */\n public constructor(options: Partial<utils.AdapterOptions> = {}) {\n super({\n ...options,\n name: 'govee-local',\n });\n // Register event handlers for adapter lifecycle\n this.on('ready', this.onReady.bind(this));\n this.on('stateChange', this.onStateChange.bind(this));\n // Uncomment if you want to handle object changes or messages:\n // this.on('objectChange', this.onObjectChange.bind(this));\n // this.on('message', this.onMessage.bind(this));\n this.on('unload', this.onUnload.bind(this));\n }\n\n /**\n * Called when databases are connected and adapter received configuration.\n * Initializes GoveeService and sets up event listeners for device events.\n */\n private onReady(): void {\n // Create connection indicator state if it doesn't exist\n void this.setObjectNotExists('info.connection', {\n type: 'state',\n common: {\n name: 'Device discovery running',\n type: 'boolean',\n role: 'indicator.connected',\n read: true,\n write: false,\n },\n native: {},\n });\n\n // Create state for discovered devices\n void this.setObjectNotExists('info.discoveredDevices', {\n type: 'state',\n common: {\n name: 'List of discovered devices',\n type: 'string',\n role: 'json',\n read: true,\n write: false,\n desc: 'JSON object with IP addresses and device names of all discovered devices',\n },\n native: {},\n });\n\n // Initialize GoveeService with adapter config and logger\n this.goveeService = new GoveeService({\n interface: this.config.interface,\n searchInterval: this.config.searchInterval,\n deviceStatusRefreshInterval: this.config.deviceStatusRefreshInterval,\n extendedLogging: this.config.extendedLogging,\n forbiddenChars: this.FORBIDDEN_CHARS,\n manualIpAddresses: this.config.manualIpTable,\n scanMode: this.config.scanMode,\n setInterval: (callback: () => void, ms: number) => this.setInterval(callback, ms),\n clearInterval: (timeout: unknown) => this.clearInterval(timeout as ioBroker.Interval | undefined),\n logger: {\n debug: msg => this.log.debug(msg),\n info: msg => this.log.info(msg),\n error: msg => this.log.error(msg),\n },\n });\n\n // Listen for device discovery and status update events\n this.goveeService.on('deviceDiscovered', data => {\n this.handleDeviceDiscovered(data).catch((err: unknown) => {\n this.log.error(`Error handling device discovery: ${err instanceof Error ? err.message : String(err)}`);\n });\n });\n\n this.goveeService.on('deviceStatusUpdate', data => {\n this.handleDeviceStatusUpdate(data).catch((err: unknown) => {\n this.log.error(\n `Error handling device status update: ${err instanceof Error ? err.message : String(err)}`,\n );\n });\n });\n\n // Listen for serviceStarted event to set connection state to true\n this.goveeService.on('serviceStarted', () => {\n void this.setState('info.connection', { val: true, ack: true });\n });\n\n // Start device discovery and status polling\n this.goveeService.start();\n\n if (this.config.extendedLogging) {\n this.log.debug('running with extended logging');\n }\n\n // Subscribe to all device status state changes\n this.subscribeStates('*.devStatus.*');\n }\n\n /**\n * Called if a subscribed state changes (e.g. user toggles a switch in ioBroker UI).\n * Forwards the change to the GoveeService for device communication.\n *\n * @param id The ID of the changed state.\n * @param state The new state object or null/undefined.\n */\n private async onStateChange(id: string, state: ioBroker.State | null | undefined): Promise<void> {\n if (state && !state.ack) {\n const deviceName = id.split('.')[2];\n const stateKey = id.split('.')[4];\n // Extract device name from state ID and get its IP address\n const ipOfDevice = await this.getStateAsync(`${deviceName}.deviceInfo.ip`);\n const receiver = ipOfDevice?.val?.toString();\n if (typeof receiver === 'string') {\n if (stateKey === 'hue' || stateKey === 'saturation') {\n await this.handleHueSaturationChange(deviceName, stateKey, state, receiver);\n } else {\n // Forward the state change to the GoveeService\n this.goveeService.handleStateChange(id, state, receiver);\n }\n } else {\n this.log.error('device not found or IP is not a string');\n }\n }\n }\n\n /**\n * Handles a hue or saturation change by combining it with the sibling state and\n * sending the resulting color as an RGB command. The HSV value is fixed at full scale;\n * overall brightness is controlled separately via the brightness state.\n *\n * @param deviceName The sanitized device name.\n * @param changedKey Which of the two states triggered the change.\n * @param state The new state value.\n * @param receiver The device IP address.\n */\n private async handleHueSaturationChange(\n deviceName: string,\n changedKey: 'hue' | 'saturation',\n state: ioBroker.State,\n receiver: string,\n ): Promise<void> {\n const otherKey = changedKey === 'hue' ? 'saturation' : 'hue';\n const otherState = await this.getStateAsync(`${deviceName}.devStatus.${otherKey}`);\n const hue = Number(changedKey === 'hue' ? state.val : (otherState?.val ?? 0));\n const saturation = Number(changedKey === 'saturation' ? state.val : (otherState?.val ?? 0));\n\n if (!Number.isFinite(hue) || !Number.isFinite(saturation)) {\n this.log.error(`Ignoring ${changedKey} change for ${deviceName}: hue/saturation is not a number`);\n return;\n }\n\n const { r, g, b } = hsvToRgb(hue, saturation, GoveeLocal.FULL_COLOR_VALUE);\n this.goveeService.sendColorCommand(receiver, `#${componentToHex(r)}${componentToHex(g)}${componentToHex(b)}`);\n }\n\n /**\n * Called when the adapter shuts down. Cleans up resources and stops services.\n *\n * @param callback Callback function after unload process.\n */\n private onUnload(callback: () => void): void {\n try {\n if (this.goveeService) {\n this.goveeService.removeAllListeners();\n this.goveeService.stop();\n }\n // Set connection state to false\n void this.updateStateAsync('info.connection', false).catch((err: unknown) => {\n this.log.error(\n `Failed to update connection state during unload: ${err instanceof Error ? err.message : String(err)}`,\n );\n });\n callback();\n } catch (e: unknown) {\n this.log.error(e instanceof Error ? e.message : String(e));\n callback();\n }\n }\n\n /**\n * Updates a state only if its value or acknowledgement has changed.\n * Comparing the ack flag as well ensures a user command (ack = false) gets acknowledged\n * once the device confirms it, even though the value itself stays the same.\n * A recent user command is not overwritten by a differing value, so a stale device status\n * cannot make the state jump back before the device has applied the command.\n *\n * @param fullName Full object path\n * @param state New value\n * @param acknowledged Whether the value is acknowledged (default: true)\n */\n private async updateStateAsync(fullName: string, state: any, acknowledged = true): Promise<void> {\n const currentState = await this.getStateAsync(fullName);\n if (currentState) {\n if (currentState.val === state && currentState.ack === acknowledged) {\n return;\n }\n const awaitingConfirmation =\n !currentState.ack && Date.now() - currentState.ts < GoveeLocal.COMMAND_CONFIRMATION_TIMEOUT_MS;\n if (awaitingConfirmation && currentState.val !== state) {\n return;\n }\n }\n await this.setState(fullName, {\n val: state,\n ack: acknowledged,\n });\n }\n\n /**\n * Handles device discovery event from GoveeService.\n * Creates all necessary objects and states for the new device.\n *\n * @param event The device discovery event data.\n */\n private async handleDeviceDiscovered(event: DeviceDiscoveryEvent): Promise<void> {\n const { ip, deviceName, deviceModel } = event;\n\n // Create device folder\n await this.setObjectNotExistsAsync(deviceName, {\n type: 'device',\n common: {\n name: deviceModel,\n role: 'group',\n },\n native: {},\n });\n\n // Create deviceInfo folder\n await this.setObjectNotExistsAsync(`${deviceName}.deviceInfo`, {\n type: 'folder',\n common: {\n name: 'Device Info',\n },\n native: {},\n });\n\n // Create IP address state\n await this.setObjectNotExistsAsync(`${deviceName}.deviceInfo.ip`, {\n type: 'state',\n common: {\n name: 'IP address of the Lamp',\n type: 'string',\n role: 'info.ip',\n read: true,\n write: false,\n },\n native: {},\n });\n await this.updateStateAsync(`${deviceName}.deviceInfo.ip`, ip);\n\n // Create devStatus folder\n await this.setObjectNotExistsAsync(`${deviceName}.devStatus`, {\n type: 'folder',\n common: {\n name: 'Device Status',\n },\n native: {},\n });\n\n // Update the list of discovered devices\n await this.updateDiscoveredDevicesList();\n }\n\n /**\n * Updates the info.discoveredDevices state with the current list of all discovered devices.\n */\n private async updateDiscoveredDevicesList(): Promise<void> {\n const devices = this.goveeService.getDevices();\n await this.setState('info.discoveredDevices', {\n val: JSON.stringify(devices, null, 2),\n ack: true,\n });\n }\n\n /**\n * Handles device status update event from GoveeService.\n * Creates and updates all relevant states for the device.\n *\n * @param event The device status update event data.\n */\n private async handleDeviceStatusUpdate(event: DeviceStatusEvent): Promise<void> {\n const { deviceName, status } = event;\n\n // Create and update onOff state\n await this.setObjectNotExistsAsync(`${deviceName}.devStatus.onOff`, {\n type: 'state',\n common: {\n name: 'On / Off state of the lamp',\n type: 'boolean',\n role: 'switch',\n read: true,\n write: true,\n },\n native: {},\n });\n await this.updateStateAsync(`${deviceName}.devStatus.onOff`, status.onOff);\n\n // Create and update brightness state\n await this.setObjectNotExistsAsync(`${deviceName}.devStatus.brightness`, {\n type: 'state',\n common: {\n name: 'Brightness of the light',\n type: 'number',\n role: 'level.dimmer',\n read: true,\n write: true,\n },\n native: {},\n });\n await this.updateStateAsync(`${deviceName}.devStatus.brightness`, status.brightness);\n\n // Create and update color state\n await this.setObjectNotExistsAsync(`${deviceName}.devStatus.color`, {\n type: 'state',\n common: {\n name: 'Current showing color of the lamp',\n type: 'string',\n role: 'level.color.rgb',\n read: true,\n write: true,\n },\n native: {},\n });\n await this.updateStateAsync(`${deviceName}.devStatus.color`, status.color);\n\n // Create and update hue state (HomeKit compatible)\n await this.setObjectNotExistsAsync(`${deviceName}.devStatus.hue`, {\n type: 'state',\n common: {\n name: 'Hue of the lamp (HomeKit compatible)',\n type: 'number',\n role: 'level.color.hue',\n unit: '\u00B0',\n min: 0,\n max: 360,\n read: true,\n write: true,\n },\n native: {},\n });\n\n // Create and update saturation state (HomeKit compatible)\n await this.setObjectNotExistsAsync(`${deviceName}.devStatus.saturation`, {\n type: 'state',\n common: {\n name: 'Saturation of the lamp (HomeKit compatible)',\n type: 'number',\n role: 'level.color.saturation',\n unit: '%',\n min: 0,\n max: 100,\n read: true,\n write: true,\n },\n native: {},\n });\n\n const { hue, saturation } = rgbToHsv(hexToRgb(status.color));\n await this.updateStateAsync(`${deviceName}.devStatus.hue`, hue);\n await this.updateStateAsync(`${deviceName}.devStatus.saturation`, saturation);\n\n // Create and update color temperature state\n await this.setObjectNotExistsAsync(`${deviceName}.devStatus.colorTemInKelvin`, {\n type: 'state',\n common: {\n name: 'If staying in white light, the color temperature',\n type: 'number',\n role: 'level.color.temperature',\n read: true,\n write: true,\n },\n native: {},\n });\n await this.updateStateAsync(`${deviceName}.devStatus.colorTemInKelvin`, status.colorTemInKelvin);\n\n // Create and update color temperature in mired (HomeKit compatible)\n await this.setObjectNotExistsAsync(`${deviceName}.devStatus.colorTemperature`, {\n type: 'state',\n common: {\n name: 'Color temperature of the lamp in mired (HomeKit compatible)',\n type: 'number',\n role: 'level.color.temperature',\n unit: 'mired',\n read: true,\n write: true,\n },\n native: {},\n });\n // Govee devices report colorTemInKelvin as 0 while in RGB color mode; a mired value\n // is only meaningful while the lamp is actually in white/temperature mode.\n if (status.colorTemInKelvin > 0) {\n await this.updateStateAsync(\n `${deviceName}.devStatus.colorTemperature`,\n kelvinToMired(status.colorTemInKelvin),\n );\n }\n }\n}\n\n// Export factory function for ioBroker or start instance directly\nif (require.main !== module) {\n // Export factory function for ioBroker. GoveeLocal is attached to the same function\n // object so it stays available as a named export (`import { GoveeLocal } from './main'`)\n // for tests, instead of being lost when module.exports is replaced.\n module.exports = Object.assign((options: Partial<utils.AdapterOptions> | undefined) => new GoveeLocal(options), {\n GoveeLocal,\n });\n} else {\n // Otherwise start the instance directly\n (() => new GoveeLocal())();\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/io-package.json CHANGED
@@ -1,8 +1,47 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "govee-local",
4
- "version": "0.4.7",
4
+ "version": "0.5.2",
5
5
  "news": {
6
+ "0.5.2": {
7
+ "en": "update dependencies: `node-forge` (1.4.3) and `follow-redirects` (1.16.2) to fix security vulnerabilities",
8
+ "de": "Abhängigkeiten aktualisieren: `Node-Forge` (1.4.3) und `Follow-Redirects` (1.16.2), um Sicherheitslücken zu schließen",
9
+ "ru": "обновите зависимости: `nodeforge` (1.4.3) и `follow-redirects` (1.16.2) для устранения уязвимостей безопасности",
10
+ "pt": "atualize as dependências: `node-forge` (1.4.3) e `follow-redirects` (1.16.2) para corrigir falhas",
11
+ "nl": "update-afhankelijkheden: `node-forge` (1.4.3) en `follow-redirects` (1.16.2) om beveiligingsproblemen te verhelpen",
12
+ "fr": "dépendances de mise à jour : « node-forge » (1.4.3) et « follow-redirections » (1.16.2) pour corriger les failles de sécurité",
13
+ "it": "aggiornare le dipendenze: `node-forge` (1.4.3) e `follow-redirects` (1.16.2) per correggere falle",
14
+ "es": "actualizar dependencias: `node-forge` (1.4.3) y `follow-redirects` (1.16.2) para corregir vulnerabilidades de seguridad",
15
+ "pl": "aktualizacja zależności: `node-forge` (1.4.3) i `follow-redirects` (1.16.2) w celu naprawienia luk w zabezpieczeniach",
16
+ "uk": "оновлення залежностей: `node-forge` (1.4.3) та `follow-redirects` (1.16.2) для виправлення вразливостей безпеки",
17
+ "zh-cn": "更新依赖关系:`node-forge` (1.4.3) 和 `follow-redirects` (1.16.2) 以修复安全漏洞"
18
+ },
19
+ "0.5.1": {
20
+ "en": "update dependencies: `@iobroker/testing` (6.3.0)\nfix states briefly jumping back to the old value after a command, until the device confirms it (#327)\nacknowledge commanded states once the device confirms them, even if the value did not change",
21
+ "de": "Updates: `@iobroker /testing` (6.3.0)\nFix: Status springen nach einem Befehl kurz auf den alten Wert zurück, bis das Gerät ihn bestätigt (#327)\nBestätigen Sie Befehle, wenn das Gerät sie bestätigt, auch wenn sich der Wert nicht ändert",
22
+ "ru": "обновления зависимостей: `@iobroker /testing` (6.3.0)\nисправление состояний, ненадолго возвращающихся к старому значению после выполнения команды, пока устройство не подтвердит это (#327)\nподтвердите командные состояния, как только устройство подтвердит их, даже если значение не изменилось",
23
+ "pt": "atualizar dependências: `@iobroker /testing` (6.3.0)\nfixa estados que retornam ao valor antigo após um comando, até que o dispositivo o confirme (#327)\nconfirme os estados de comando quando o dispositivo os confirma, mesmo que o valor não mude",
24
+ "nl": "afhankelijkheden voor updates: `@iobroker /testing` (6.3.0)\nstatussen corrigeren waarbij kort wordt teruggesprongen naar de oude waarde na een opdracht, totdat het apparaat de opdracht bevestigt (#327)\nerken de commandostaten zodra het apparaat ze bevestigt, zelfs als de waarde niet is gewijzigd",
25
+ "fr": "dépendances de mise à jour : @iobroker /testing (6.3.0)\nfix indique de revenir brièvement à l'ancienne valeur après une commande, jusqu'à confirmation (#327)\naccepter les états des commandes une fois que l'appareil les confirme, même si la valeur n'a pas changé",
26
+ "it": "aggiorna dipendenze: `@iobroker /test` (6.3.0)\nfix torna brevemente al vecchio valore dopo un comando, finché il dispositivo non lo conferma (#327)\nconferma gli stati comandati dal dispositivo, anche se il valore non è cambiato",
27
+ "es": "actualizar dependencias: `@iobroker /testing` (6.3.0)\ncorregir estados que vuelven brevemente al valor anterior tras un comando, hasta que el dispositivo lo confirme (#327)\nreconocer los estados del comando una vez que el dispositivo los confirme, aunque el valor no cambie",
28
+ "pl": "zależności aktualizacji: `@iobroker /testing` (6.3.0)\nstany poprawiające krótko przeskakujące z powrotem do starej wartości po poleceniu, aż urządzenie to potwierdzi (#327)\npotwierdzić polecone stany, gdy urządzenie je potwierdzi, nawet jeśli wartość nie uległa zmianie",
29
+ "uk": "залежності оновлення: `@iobroker /тестування` (6.3.0)\nвиправлення станів короткочасного переходу до старого значення після команди, поки пристрій не підтвердить це (#327)\nпідтвердити командні стани, коли пристрій підтвердить їх, навіть якщо значення не змінилося",
30
+ "zh-cn": "更新依赖关系:`@iobroker /testing` (6.3.0)\n修复在设备确认命令后短暂跳回旧值的状态 (#327)\n一旦设备确认命令状态,即使值没有改变,也要确认这些状态"
31
+ },
32
+ "0.5.0": {
33
+ "en": "add HomeKit-compatible hue and saturation states, and a mired color temperature state\nAdapter requires node.js >= 22.19.0 now\nupdate dependencies: `@iobroker/testing` (6.2.1)\nremoved the now obsolete npm overrides for `mocha` subdependencies `diff` and `serialize-javascript`\nupdate dependencies",
34
+ "de": "HomeKit-kompatiblen Farbton, Sättigung und Farbtemperatur hinzufügen\nAdapter benötigt jetzt node.js >= 22.19.0\nUpdates: `@iobroker /testing` (6.2.1)\nentfernte die veralteten NPM-Überschreibungen für Mocha, Diff und Serialize-Javascript\nAbhängigkeiten updaten",
35
+ "ru": "добавьте совместимые с HomeKit состояния оттенка и насыщенности, а также состояние смешанной цветовой температуры\nТеперь для адаптера требуется файл node.js >= 22.19.0\nобновления зависимостей: `@iobroker /testing` (6.2.1)\nудалены устаревшие переопределения npm для подзависимостей `mocha` `diff` и `serialize-javascript`\nобновить зависимости",
36
+ "pt": "adicione estados de matiz e saturação compatíveis com o HomeKit\nO adaptador exige node.js >= 22.19.0\natualizar dependências: `@iobroker /testing` (6.2.1)\nremoveu as substituições npm obsoletas das subdependências `diff` e `serialize-javascript`\natualizar dependências",
37
+ "nl": "voeg tint- en verzadigingstoestanden toe die compatibel zijn met HomeKit, en een verwarde kleurtemperatuur\nVoor de adapter is nu node.js >= 22.19.0 nodig\nafhankelijkheden voor updates: `@iobroker /testing` (6.2.1)\nheeft de nu verouderde npm-overschrijvingen verwijderd voor de subafhankelijkheden `diff` en `serialize-javascript`\nafhankelijkheden bijwerken",
38
+ "fr": "ajouter des états de teinte et de saturation compatibles HomeKit et une température de couleur\nAdapter nécessite node.js >= 22.19.0\ndépendances de mise à jour : @iobroker /testing (6.2.1)\nsupprimé les remplacements npm obsolètes pour les sous-dépendances de « mocha », « diff» et « serialize-javascript »\nmettre à jour les dépendances",
39
+ "it": "aggiungere tonalità e saturazione compatibili con HomeKit e temperatura del colore\nAdapter richiede node.js >= 22.19.0 ora\naggiorna dipendenze: `@iobroker /test` (6.2.1)\nrimosso gli ormai obsoleti npm override per le sottodipendenze mocha diff e serialize-javascript\naggiorna dipendenze",
40
+ "es": "añadir estados de tono y saturación compatibles con HomeKit y un estado de temperatura de color opaco\nEl adaptador ahora necesita node.js >= 22.19.0\nactualizar dependencias: `@iobroker /testing` (6.2.1)\neliminó las ahora obsoletas anulaciones de npm para las subdependencias de «mocha», «diff» y «serialize-javascript»\nactualizar dependencias",
41
+ "pl": "dodaj stany odcienia i nasycenia zgodne z HomeKit oraz stan temperatury barwowej\nAdapter wymaga teraz node.js >= 22.19.0\nzależności aktualizacji: `@iobroker /testing` (6.2.1)\nusunięto przestarzałe nadpisania npm dla podzależności `mocha` `diff` i `serialize-javascript`\naktualizuj zależności",
42
+ "uk": "додати сумісні з HomeKit стани відтінку та насиченості, а також запеклий стан колірної температури\nАдаптер вимагає node.js >= 22.19.0 зараз\nзалежності оновлення: `@iobroker /тестування` (6.2.1)\nвидалено застарілі перевизначення npm для субзалежностей `mocha` `diff` та `serialize-javascript`\nоновлення залежностей",
43
+ "zh-cn": "添加与 HomeKit 兼容的色调和饱和度状态以及深色色温状态\n适配器现在需要 node.js >= 22.19.0\n更新依赖关系:@iobroker /testing (6.2.1)\n删除了 `mocha` 子依赖项 `diff` 和 `serialize-javascript`的现已过时的 npm 替代\n更新依赖关系"
44
+ },
6
45
  "0.4.7": {
7
46
  "en": "update dependencies\nmaintenance refactoring",
8
47
  "de": "aktualisierung der abhängigkeiten\nwartung refactoring",
@@ -54,45 +93,6 @@
54
93
  "pl": "dodaj opcję, aby uruchomić automatyczne wykrywanie raz na starcie, w przedziale lub całkowicie go wyłączyć",
55
94
  "uk": "додати можливість запустити автовідкриття один раз на запуску, в інтервалі або комплетувати його",
56
95
  "zh-cn": "添加在启动、间隔或完全禁用时运行自动发现的选项"
57
- },
58
- "0.4.3": {
59
- "en": "fix responsive design in the settings\nrevert forbidden characters checking\nupdate dependencies",
60
- "de": "reagierendes design in den einstellungen beheben\nzurücksetzen verbotene zeichen überprüfen\naktualisierung der abhängigkeiten",
61
- "ru": "исправить отзывчивый дизайн в настройках\nпроверка запрещенных персонажей\nобновление зависимостей",
62
- "pt": "corrigir o design responsivo nas configurações\nreverter a verificação de caracteres proibidos\natualizar dependências",
63
- "nl": "repareren responsief ontwerp in de instellingen\n_verboden tekens controleren\nafhankelijkheden bijwerken",
64
- "fr": "fixer la conception réactive dans les paramètres\nretourner la vérification des caractères interdits\nmettre à jour les dépendances",
65
- "it": "fissare il design reattivo nelle impostazioni\nrevert i caratteri vietati controllando\naggiornamento dipendenze",
66
- "es": "fijar diseño sensible en la configuración\nrevertir caracteres prohibidos\ndependencias de actualización",
67
- "pl": "naprawić reagujący projekt w ustawieniach\nrevert zabronione sprawdzanie znaków\naktualizacji zależności",
68
- "uk": "виправити адаптивний дизайн в налаштуваннях\nперевернути заборонені символи перевірки\nоновлення залежності",
69
- "zh-cn": "在设置中固定响应性设计\n返回禁用字符检查\n更新依赖关系"
70
- },
71
- "0.4.2": {
72
- "en": "update dependencies\nfix device name / model identifier in the objects",
73
- "de": "aktualisierung der abhängigkeiten\ngerätename / modellkennung in den objekten fixieren",
74
- "ru": "обновление зависимостей\nфиксировать имя устройства / идентификатор модели в объектах",
75
- "pt": "atualizar dependências\ncorrigir o nome do dispositivo / identificador do modelo nos objetos",
76
- "nl": "afhankelijkheden bijwerken\nfix apparaatnaam / modelidentificatie in de objecten",
77
- "fr": "mettre à jour les dépendances\ncorrection du nom du périphérique / de l'identificateur du modèle dans les objets",
78
- "it": "aggiornamento dipendenze\nfix nome del dispositivo / modello identificatore negli oggetti",
79
- "es": "dependencias de actualización\nfijar el nombre del dispositivo / identificador modelo en los objetos",
80
- "pl": "aktualizacji zależności\nnaprawić nazwę urządzenia / identyfikator modelu w obiektach",
81
- "uk": "оновлення залежності\nвиправити назву пристрою / ідентифікатор моделі в об'єктах",
82
- "zh-cn": "更新依赖关系\n对象中的固定设备名称/ 模型标识符"
83
- },
84
- "0.4.1": {
85
- "en": "fix logging\nfix connection state",
86
- "de": "fehlerbehebung\nbefestigungszustand",
87
- "ru": "фиксировать лесозаготовки\nфиксировать состояние соединения",
88
- "pt": "corrigir o registo\ncorrigir o estado da ligação",
89
- "nl": "loggen herstellen\nfix verbindingstoestand",
90
- "fr": "correction de l'enregistrement\nfixer l'état de connexion",
91
- "it": "correggere la registrazione\ncorrezione dello stato di connessione",
92
- "es": "corrección de registro\nestado de conexión",
93
- "pl": "naprawić logowanie\nstan połączenia",
94
- "uk": "фіксувати забір\nфіксувати стан з'єднання",
95
- "zh-cn": "固定记录\n固定连接状态"
96
96
  }
97
97
  },
98
98
  "titleLang": {
@@ -122,7 +122,7 @@
122
122
  "uk": "Керуйте пристроями Govee через локальний доступ (без хмари)"
123
123
  },
124
124
  "authors": [
125
- "Børge Grunicke <boerge@grunicke.de>"
125
+ "Børge Grunicke <apps@grunicke.com>"
126
126
  ],
127
127
  "keywords": [
128
128
  "govee",
@@ -143,6 +143,7 @@
143
143
  "type": "lighting",
144
144
  "tier": 2,
145
145
  "compact": true,
146
+ "nogit": true,
146
147
  "connectionType": "local",
147
148
  "dataSource": "poll",
148
149
  "adminUI": {
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "iobroker.govee-local",
3
- "version": "0.4.7",
3
+ "version": "0.5.2",
4
4
  "description": "Control Govee devices via local access (no cloud)",
5
5
  "author": {
6
6
  "name": "Børge Grunicke",
7
- "email": "boerge@grunicke.de"
7
+ "email": "apps@grunicke.com"
8
8
  },
9
9
  "homepage": "https://github.com/boergegrunicke/ioBroker.govee-local",
10
10
  "license": "MIT",
@@ -19,21 +19,21 @@
19
19
  "url": "git@github.com:boergegrunicke/ioBroker.govee-local.git"
20
20
  },
21
21
  "engines": {
22
- "node": ">= 22.0"
22
+ "node": ">= 22.19.0"
23
23
  },
24
24
  "dependencies": {
25
- "@iobroker/adapter-core": "^3.3.2"
25
+ "@iobroker/adapter-core": "^3.4.3"
26
26
  },
27
27
  "devDependencies": {
28
- "@alcalzone/release-script": "^5.2.0",
28
+ "@alcalzone/release-script": "^5.2.1",
29
29
  "@alcalzone/release-script-plugin-iobroker": "^5.2.0",
30
- "@alcalzone/release-script-plugin-license": "^5.2.0",
30
+ "@alcalzone/release-script-plugin-license": "^5.2.2",
31
31
  "@alcalzone/release-script-plugin-manual-review": "^5.2.0",
32
32
  "@iobroker/adapter-dev": "^1.5.0",
33
33
  "@iobroker/eslint-config": "^2.2.0",
34
- "@iobroker/testing": "^5.2.2",
35
- "@tsconfig/node22": "^22.0.5",
36
- "@types/node": "^22.0.0",
34
+ "@iobroker/testing": "^6.3.0",
35
+ "@tsconfig/node22": "^22.0.6",
36
+ "@types/node": "^22.20.4",
37
37
  "@types/proxyquire": "^1.3.31",
38
38
  "proxyquire": "^2.1.3",
39
39
  "rimraf": "^6.1.2",
@@ -42,9 +42,9 @@
42
42
  "typescript": "^6.0.3"
43
43
  },
44
44
  "overrides": {
45
- "mocha": {
46
- "diff": "^8.0.3",
47
- "serialize-javascript": "^7.0.5"
45
+ "esbuild": "0.28.1",
46
+ "minimatch@3": {
47
+ "brace-expansion": "^1.1.18"
48
48
  }
49
49
  },
50
50
  "main": "build/main.js",
@@ -57,13 +57,14 @@
57
57
  "LICENSE"
58
58
  ],
59
59
  "scripts": {
60
+ "prepack": "npm run build",
60
61
  "prebuild": "rimraf build",
61
62
  "build": "build-adapter ts",
62
63
  "watch": "build-adapter ts --watch",
63
64
  "prebuild:ts": "rimraf build",
64
65
  "build:ts": "build-adapter ts",
65
66
  "watch:ts": "build-adapter ts --watch",
66
- "test:ts": "mocha --config test/mocharc.custom.json src/**/*.test.ts",
67
+ "test:ts": "mocha --config test/mocharc.custom.json",
67
68
  "test:package": "mocha test/package --exit",
68
69
  "test:integration": "mocha test/integration --exit",
69
70
  "test": "npm run test:ts && npm run test:package",