iobroker.govee-local 0.4.0 → 0.4.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
@@ -24,102 +24,108 @@ 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.4.0 (2025-11-02)
27
+ ### 0.4.2 (2025-11-08)
28
28
 
29
- - Updated translations for datapoint descriptions in all supported languages
30
- - Refreshed and modernized the admin UI: clearer sections, manual IP address list, Windows hint, and option to disable auto-discovery
31
- - Possibility to manually add devices via IP-Address and Option to disable auto discovery
32
- - Save auto discovered devices in info datapoint and show in admin UI
29
+ - update dependencies
30
+ - fix device name / model identifier in the objects
33
31
 
34
- ### 0.3.1 (2025-10-19)
32
+ ### 0.4.1 (2025-11-07)
33
+
34
+ - fix logging
35
+ - fix connection state
35
36
 
36
- - updated release script run configuration
37
+ ### 0.4.0 (2025-11-02)
38
+
39
+ - Updated translations for datapoint descriptions in all supported languages
40
+ - Refreshed and modernized the admin UI: clearer sections, manual IP address list, Windows hint, and option to disable auto-discovery
41
+ - Possibility to manually add devices via IP-Address and Option to disable auto discovery
42
+ - Save auto discovered devices in info datapoint and show in admin UI
37
43
 
38
- ### 0.3.0 (2025-10-19)
44
+ ### 0.3.1 (2025-10-19)
39
45
 
40
- - Refactored: Moved all device logic to a dedicated GoveeService class, modularized utilities
41
- - Improved test coverage
42
- - Dependencies updated
46
+ - Refactored: Moved all device logic to a dedicated GoveeService class, modularized utilities
47
+ - Improved test coverage
48
+ - Dependencies updated
43
49
 
44
50
  ### 0.2.9 (2025-08-22)
45
51
 
46
- - update dependencies
47
- - add support for node 24
48
- - update to ESlint 9
52
+ - update dependencies
53
+ - add support for node 24
54
+ - update to ESlint 9
49
55
 
50
56
  ### 0.2.8 (2025-03-28)
51
57
 
52
- - update dependencies
53
- - avoid repeated overriding of states
58
+ - update dependencies
59
+ - avoid repeated overriding of states
54
60
 
55
61
  ### 0.2.7 (2024-08-18)
56
62
 
57
- - update core dependencies and min required node version to 18
63
+ - update core dependencies and min required node version to 18
58
64
 
59
65
  ### 0.2.6 (2024-02-06)
60
66
 
61
- - fix brightness change also changing white temperature
67
+ - fix brightness change also changing white temperature
62
68
 
63
69
  ### 0.2.5 (2024-01-13)
64
70
 
65
- - create only one socket, as the second seems not to be necessary
66
- - refactoring the code for better structure
67
- - possibility to choose listen interface in settings
71
+ - create only one socket, as the second seems not to be necessary
72
+ - refactoring the code for better structure
73
+ - possibility to choose listen interface in settings
68
74
 
69
75
  ### 0.2.4 (2024-01-05)
70
76
 
71
- - fix access
77
+ - fix access
72
78
 
73
79
  ### 0.2.2 (2024-01-05)
74
80
 
75
- - fix color temperature message
81
+ - fix color temperature message
76
82
 
77
83
  ### 0.2.1 (2023-12-24)
78
84
 
79
- - repair onOff / all other actions Fixes: [#65](https://github.com/boergegrunicke/ioBroker.govee-local/issues/65)
80
- - fix log spamming because of wildcard
85
+ - repair onOff / all other actions Fixes: [#65](https://github.com/boergegrunicke/ioBroker.govee-local/issues/65)
86
+ - fix log spamming because of wildcard
81
87
 
82
88
  ### 0.2.0 (2023-12-17)
83
89
 
84
- - support controlling the color
85
- - extended logging mode
90
+ - support controlling the color
91
+ - extended logging mode
86
92
 
87
93
  ### 0.1.2 (2023-09-06)
88
94
 
89
- - change icon path and resolution
95
+ - change icon path and resolution
90
96
 
91
97
  ### 0.1.1 (2023-08-21)
92
98
 
93
- - fix image
99
+ - fix image
94
100
 
95
101
  ### 0.1.0 (2023-08-09)
96
102
 
97
- - make search intervals configurable
98
- - clear all timeouts, when adapter is stopped
99
- - replace forbidden characters in names
100
- - update translations
101
- - update dependencies
103
+ - make search intervals configurable
104
+ - clear all timeouts, when adapter is stopped
105
+ - replace forbidden characters in names
106
+ - update translations
107
+ - update dependencies
102
108
 
103
109
  ### 0.0.6 (2023-05-18)
104
110
 
105
- - update dependencies
111
+ - update dependencies
106
112
 
107
113
  ### 0.0.5 (2023-04-02)
108
114
 
109
- - make pipeline run
115
+ - make pipeline run
110
116
 
111
117
  ### 0.0.4 (2023-04-02)
112
118
 
113
- - make device status refresh independent from device search interval
119
+ - make device status refresh independent from device search interval
114
120
 
115
121
  ### 0.0.3 (2023-04-01)
116
122
 
117
- - update dependencies
123
+ - update dependencies
118
124
 
119
125
  ### 0.0.2
120
126
 
121
- - frequently searching for devices and requesting their specific state
122
- - on / off state, brightness and and color temperature can be controlled
127
+ - frequently searching for devices and requesting their specific state
128
+ - on / off state, brightness and and color temperature can be controlled
123
129
 
124
130
  ## License
125
131
 
@@ -1,21 +1,21 @@
1
1
  {
2
- "govee-local adapter settings": "Adaptereinstellungen für govee-local",
3
- "_searchInterval": "Suchintervall",
4
- "_deviceStatusRefreshInterval": "Aktualisierungsintervall für den Gerätestatus",
5
- "_configDescription": "Hier muss nichts konfiguriert werden. Bitte die Intervalle nur ändern, wenn dies explizit gewollt ist.",
6
- "_extendedLoggingDescription": "Die erweiterte Protokollierung druckt alle vom Gerät empfangenen Daten mit Ebeneninformationen in das Protokoll. Da dies zu einem großen Log-Spam führt, wird empfohlen, die Funktion nur bei Bedarf einzuschalten und anschließend wieder auszuschalten.",
7
- "_extendedLogging": "Erweiterte Protokollierung aktiviert",
8
- "_networkInterface": "Netzwerkschnittstelle",
9
- "_networkHint": "Windows-Nutzer: Sie müssen eventuell Ihre IP-Adresse manuell auswählen, da das Lauschen auf allen Schnittstellen unter Umständen nicht zuverlässig funktioniert.",
10
- "_manualIpAddresses": "Manuelle IP-Adressen",
11
- "_manualIpHint": "Hier können Sie IP-Adressen für Geräte eintragen, die nicht automatisch gefunden wurden.",
12
- "_discoveredDevicesHint": "Alle gefundenen Geräte (automatisch und manuell) werden unten angezeigt:",
13
- "_discoveredDevices": "Gefundene Geräte",
14
- "_disableAutoDiscovery": "Automatische Suche deaktivieren",
15
- "datapoint.model": "Spezifisches Modell der Lampe",
16
- "datapoint.ip": "IP-Adresse der Lampe",
17
- "datapoint.bleVersionHard": "Bluetooth Low Energy Hardware-Version",
18
- "datapoint.bleVersionSoft": "Bluetooth Low Energy Software-Version",
19
- "datapoint.wifiVersionHard": "WiFi-Hardware-Version",
20
- "datapoint.wifiVersionSoft": "WiFi-Software-Version"
2
+ "govee-local adapter settings": "Adaptereinstellungen für govee-local",
3
+ "_searchInterval": "Suchintervall",
4
+ "_deviceStatusRefreshInterval": "Aktualisierungsintervall für den Gerätestatus",
5
+ "_configDescription": "Hier muss nichts konfiguriert werden. Bitte die Intervalle nur ändern, wenn dies explizit gewollt ist.",
6
+ "_extendedLoggingDescription": "Die erweiterte Protokollierung druckt alle vom Gerät empfangenen Daten mit Ebeneninformationen in das Protokoll. Da dies zu einem großen Log-Spam führt, wird empfohlen, die Funktion nur bei Bedarf einzuschalten und anschließend wieder auszuschalten.",
7
+ "_extendedLogging": "Erweiterte Protokollierung aktiviert",
8
+ "_networkInterface": "Netzwerkschnittstelle",
9
+ "_networkHint": "Windows-Nutzer: Sie müssen eventuell Ihre IP-Adresse manuell auswählen, da das Lauschen auf allen Schnittstellen unter Umständen nicht zuverlässig funktioniert.",
10
+ "_manualIpAddresses": "Manuelle IP-Adressen",
11
+ "_manualIpHint": "Hier können Sie IP-Adressen für Geräte eintragen, die nicht automatisch gefunden wurden.",
12
+ "_discoveredDevicesHint": "Alle gefundenen Geräte (automatisch und manuell) werden unten angezeigt:",
13
+ "_discoveredDevices": "Gefundene Geräte",
14
+ "_disableAutoDiscovery": "Automatische Suche deaktivieren",
15
+ "datapoint.model": "Spezifisches Modell der Lampe",
16
+ "datapoint.ip": "IP-Adresse der Lampe",
17
+ "datapoint.bleVersionHard": "Bluetooth Low Energy Hardware-Version",
18
+ "datapoint.bleVersionSoft": "Bluetooth Low Energy Software-Version",
19
+ "datapoint.wifiVersionHard": "WiFi-Hardware-Version",
20
+ "datapoint.wifiVersionSoft": "WiFi-Software-Version"
21
21
  }
@@ -11,7 +11,11 @@
11
11
  "networkHint": {
12
12
  "type": "staticText",
13
13
  "text": "_networkHint",
14
- "xs": 12
14
+ "xs": 12,
15
+ "sm": 12,
16
+ "md": 12,
17
+ "lg": 12,
18
+ "xl": 12
15
19
  },
16
20
  "interface": {
17
21
  "type": "ip",
@@ -62,12 +66,20 @@
62
66
  "manualIpHint": {
63
67
  "type": "staticText",
64
68
  "text": "_manualIpHint",
65
- "xs": 12
69
+ "xs": 12,
70
+ "sm": 12,
71
+ "md": 12,
72
+ "lg": 12,
73
+ "xl": 12
66
74
  },
67
75
  "discoveredDevicesHint": {
68
76
  "type": "staticText",
69
77
  "text": "_discoveredDevicesHint",
70
78
  "xs": 12,
79
+ "sm": 12,
80
+ "md": 12,
81
+ "lg": 12,
82
+ "xl": 12,
71
83
  "newLine": true
72
84
  },
73
85
  "discoveredDevicesState": {
@@ -78,12 +90,20 @@
78
90
  "label": "_discoveredDevices",
79
91
  "readOnly": true,
80
92
  "xs": 12,
93
+ "sm": 12,
94
+ "md": 12,
95
+ "lg": 12,
96
+ "xl": 12,
81
97
  "newLine": true
82
98
  },
83
99
  "manualIpTable": {
84
100
  "type": "chips",
85
101
  "label": "_manualIpAddresses",
86
- "xs": 12
102
+ "xs": 12,
103
+ "sm": 12,
104
+ "md": 12,
105
+ "lg": 12,
106
+ "xl": 12
87
107
  },
88
108
  "disableAutoDiscovery": {
89
109
  "type": "checkbox",
@@ -89,14 +89,15 @@ class GoveeService extends import_node_events.EventEmitter {
89
89
  }
90
90
  if (!this.options.disableAutoDiscovery) {
91
91
  this.searchInterval = setInterval(() => this.sendScan(), this.options.searchInterval * 1e3);
92
- (_c = this.options.logger) == null ? void 0 : _c.info(`Auto-discovery started with interval ${this.options.searchInterval} seconds.`);
92
+ (_c = this.options.logger) == null ? void 0 : _c.debug(`Auto-discovery started with interval ${this.options.searchInterval} seconds.`);
93
93
  } else {
94
- (_d = this.options.logger) == null ? void 0 : _d.info("Auto-discovery is disabled. Only manual devices will be used.");
94
+ (_d = this.options.logger) == null ? void 0 : _d.debug("Auto-discovery is disabled. Only manual devices will be used.");
95
95
  }
96
96
  this.refreshInterval = setInterval(
97
97
  () => this.refreshAllDevices(),
98
98
  this.options.deviceStatusRefreshInterval * 1e3
99
99
  );
100
+ this.emit("serviceStarted");
100
101
  }
101
102
  /**
102
103
  * Handle incoming UDP messages.
@@ -105,7 +106,7 @@ class GoveeService extends import_node_events.EventEmitter {
105
106
  * @param remote The sender info.
106
107
  */
107
108
  onUdpMessage(message, remote) {
108
- var _a, _b, _c;
109
+ var _a, _b, _c, _d;
109
110
  const messageObject = JSON.parse(message.toString());
110
111
  switch (messageObject.msg.cmd) {
111
112
  case "scan": {
@@ -115,9 +116,15 @@ class GoveeService extends import_node_events.EventEmitter {
115
116
  "_"
116
117
  );
117
118
  this.devices[remote.address] = deviceName;
119
+ if (this.options.extendedLogging) {
120
+ (_b = this.options.logger) == null ? void 0 : _b.info(
121
+ `Discovered device: ${deviceName} at ${remote.address} (model: ${messageObject.msg.data.sku})`
122
+ );
123
+ }
118
124
  this.emit("deviceDiscovered", {
119
125
  ip: remote.address,
120
- deviceName
126
+ deviceName,
127
+ deviceModel: messageObject.msg.data.sku
121
128
  });
122
129
  }
123
130
  break;
@@ -126,7 +133,7 @@ class GoveeService extends import_node_events.EventEmitter {
126
133
  const sendingDevice = this.devices[remote.address];
127
134
  if (sendingDevice) {
128
135
  if (this.options.extendedLogging && !this.loggedDevices.includes(remote.address.toString())) {
129
- (_b = this.options.logger) == null ? void 0 : _b.info(`device status message data: ${JSON.stringify(messageObject)}`);
136
+ (_c = this.options.logger) == null ? void 0 : _c.info(`device status message data: ${JSON.stringify(messageObject)}`);
130
137
  this.loggedDevices.push(remote.address.toString());
131
138
  }
132
139
  this.emitDeviceStatusUpdate(sendingDevice, remote.address, messageObject);
@@ -134,7 +141,7 @@ class GoveeService extends import_node_events.EventEmitter {
134
141
  break;
135
142
  }
136
143
  default: {
137
- (_c = this.options.logger) == null ? void 0 : _c.debug(`message from: ${remote.address}:${remote.port} - ${message.toString()}`);
144
+ (_d = this.options.logger) == null ? void 0 : _d.debug(`message from: ${remote.address}:${remote.port} - ${message.toString()}`);
138
145
  }
139
146
  }
140
147
  }
@@ -166,7 +173,9 @@ class GoveeService extends import_node_events.EventEmitter {
166
173
  */
167
174
  sendScan() {
168
175
  var _a;
169
- (_a = this.options.logger) == null ? void 0 : _a.debug("sending scan message");
176
+ if (this.options.extendedLogging) {
177
+ (_a = this.options.logger) == null ? void 0 : _a.debug("sending scan message");
178
+ }
170
179
  const scanMessageBuffer = Buffer.from(JSON.stringify(GoveeService.scanMessage));
171
180
  this.socket.send(
172
181
  scanMessageBuffer,
@@ -207,6 +216,8 @@ class GoveeService extends import_node_events.EventEmitter {
207
216
  * Stop all intervals and close the socket.
208
217
  */
209
218
  stop() {
219
+ var _a;
220
+ (_a = this.options.logger) == null ? void 0 : _a.debug("Stopping GoveeService and closing UDP socket.");
210
221
  if (this.searchInterval) {
211
222
  clearInterval(this.searchInterval);
212
223
  }
@@ -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\t/** The IP address of the discovered device. */\n\tip: string;\n\t/** The sanitized device name. */\n\tdeviceName: string;\n}\n\n/**\n * Device status update event data.\n */\nexport interface DeviceStatusEvent {\n\t/** The sanitized device name. */\n\tdeviceName: string;\n\t/** The IP address of the device. */\n\tip: string;\n\t/** The current status of the device. */\n\tstatus: {\n\t\t/** Current on/off state. */\n\t\tonOff: boolean;\n\t\t/** Current brightness level. */\n\t\tbrightness: number;\n\t\t/** Current color as hex string. */\n\t\tcolor: string;\n\t\t/** Current color temperature in Kelvin. */\n\t\tcolorTemInKelvin: number;\n\t};\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\tprivate socket: dgram.Socket;\n\tprivate options: GoveeServiceOptions;\n\tprivate devices: { [ip: string]: string } = {};\n\tprivate loggedDevices: string[] = [];\n\tprivate searchInterval?: NodeJS.Timeout;\n\tprivate refreshInterval?: NodeJS.Timeout;\n\n\tstatic readonly LOCAL_PORT = 4002;\n\tstatic readonly SEND_SCAN_PORT = 4001;\n\tstatic readonly CONTROL_PORT = 4003;\n\tstatic readonly M_CAST = '239.255.255.250';\n\tstatic readonly scanMessage = { msg: { cmd: 'scan', data: { account_topic: 'reserved' } } };\n\tstatic readonly requestStatusMessage = { msg: { cmd: 'devStatus', data: {} } };\n\n\t/**\n\t * Create a new GoveeService instance.\n\t *\n\t * @param options Configuration options for the service.\n\t */\n\tconstructor(options: GoveeServiceOptions) {\n\t\tsuper();\n\t\tthis.options = options;\n\t\tthis.socket = dgram.createSocket({ type: 'udp4' });\n\t}\n\n\t/**\n\t * Bind UDP socket and start device search/refresh intervals.\n\t */\n\tpublic start(): void {\n\t\tthis.socket.on('message', this.onUdpMessage.bind(this));\n\t\tthis.socket.on('error', (error) => {\n\t\t\tthis.options.logger?.error(`server bind error : ${error.message}`);\n\t\t});\n\t\tthis.socket.bind(\n\t\t\t{ address: this.options.interface, port: GoveeService.LOCAL_PORT },\n\t\t\tthis.serverBound.bind(this),\n\t\t);\n\t}\n\n\tprivate serverBound(): void {\n\t\tthis.socket.setBroadcast(true);\n\t\tthis.socket.setMulticastTTL(128);\n\t\tthis.socket.setMulticastInterface(this.options.interface);\n\t\tthis.socket.addMembership(GoveeService.M_CAST);\n\t\tthis.options.logger?.debug(\n\t\t\t`UDP listening on ${(this.socket.address() as any).address}:${(this.socket.address() as any).port}`,\n\t\t);\n\n\t\t// Add manual IP addresses if provided\n\t\tif (this.options.manualIpAddresses && this.options.manualIpAddresses.length > 0) {\n\t\t\tthis.addManualDevices(this.options.manualIpAddresses);\n\t\t\tthis.options.logger?.info(\n\t\t\t\t`Added ${this.options.manualIpAddresses.length} manual device(s) from configuration.`,\n\t\t\t);\n\t\t}\n\n\t\t// Only start auto-discovery if not disabled\n\t\tif (!this.options.disableAutoDiscovery) {\n\t\t\tthis.searchInterval = setInterval(() => this.sendScan(), this.options.searchInterval * 1000);\n\t\t\tthis.options.logger?.info(`Auto-discovery started with interval ${this.options.searchInterval} seconds.`);\n\t\t} else {\n\t\t\tthis.options.logger?.info('Auto-discovery is disabled. Only manual devices will be used.');\n\t\t}\n\n\t\tthis.refreshInterval = setInterval(\n\t\t\t() => this.refreshAllDevices(),\n\t\t\tthis.options.deviceStatusRefreshInterval * 1000,\n\t\t);\n\t}\n\n\t/**\n\t * Handle incoming UDP messages.\n\t *\n\t * @param message The message buffer.\n\t * @param remote The sender info.\n\t */\n\tprivate onUdpMessage(message: Buffer, remote: dgram.RemoteInfo): void {\n\t\tconst messageObject = JSON.parse(message.toString());\n\t\tswitch (messageObject.msg.cmd) {\n\t\t\tcase 'scan': {\n\t\t\t\tif (messageObject.msg.data.device) {\n\t\t\t\t\tconst deviceName = messageObject.msg.data.device.replace(\n\t\t\t\t\t\tthis.options.forbiddenChars ?? /[^a-zA-Z0-9_-]/g,\n\t\t\t\t\t\t'_',\n\t\t\t\t\t);\n\t\t\t\t\tthis.devices[remote.address] = deviceName;\n\t\t\t\t\tthis.emit('deviceDiscovered', {\n\t\t\t\t\t\tip: remote.address,\n\t\t\t\t\t\tdeviceName: deviceName,\n\t\t\t\t\t} as DeviceDiscoveryEvent);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'devStatus': {\n\t\t\t\tconst sendingDevice = this.devices[remote.address];\n\t\t\t\tif (sendingDevice) {\n\t\t\t\t\tif (this.options.extendedLogging && !this.loggedDevices.includes(remote.address.toString())) {\n\t\t\t\t\t\tthis.options.logger?.info(`device status message data: ${JSON.stringify(messageObject)}`);\n\t\t\t\t\t\tthis.loggedDevices.push(remote.address.toString());\n\t\t\t\t\t}\n\t\t\t\t\tthis.emitDeviceStatusUpdate(sendingDevice, remote.address, messageObject);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault: {\n\t\t\t\tthis.options.logger?.debug(`message from: ${remote.address}:${remote.port} - ${message.toString()}`);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Send device status request to all known devices.\n\t */\n\tpublic refreshAllDevices(): void {\n\t\tfor (const ip in this.devices) {\n\t\t\tthis.requestDeviceStatus(ip);\n\t\t}\n\t}\n\n\t/**\n\t * Send device status request to a specific device.\n\t *\n\t * @param receiver The IP address or hostname of the device.\n\t */\n\tpublic requestDeviceStatus(receiver: string): void {\n\t\tconst requestDeviceStatusBuffer = Buffer.from(JSON.stringify(GoveeService.requestStatusMessage));\n\t\tthis.socket.send(\n\t\t\trequestDeviceStatusBuffer,\n\t\t\t0,\n\t\t\trequestDeviceStatusBuffer.length,\n\t\t\tGoveeService.CONTROL_PORT,\n\t\t\treceiver,\n\t\t);\n\t}\n\n\t/**\n\t * Send scan message to the UDP multicast address.\n\t */\n\tpublic sendScan(): void {\n\t\tthis.options.logger?.debug('sending scan message');\n\t\tconst scanMessageBuffer = Buffer.from(JSON.stringify(GoveeService.scanMessage));\n\t\tthis.socket.send(\n\t\t\tscanMessageBuffer,\n\t\t\t0,\n\t\t\tscanMessageBuffer.length,\n\t\t\tGoveeService.SEND_SCAN_PORT,\n\t\t\tGoveeService.M_CAST,\n\t\t);\n\t}\n\n\t/**\n\t * Add manual devices by IP address without discovery.\n\t * These devices will be added to the device list and can be controlled.\n\t *\n\t * @param ipAddresses Array of IP addresses to add as manual devices.\n\t */\n\tpublic addManualDevices(ipAddresses: string[]): void {\n\t\tfor (const ip of ipAddresses) {\n\t\t\tif (!ip || ip.trim().length === 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst trimmedIp = ip.trim();\n\n\t\t\t// Validate IP address format\n\t\t\tif (!isValidIpAddress(trimmedIp)) {\n\t\t\t\tthis.options.logger?.error(`Invalid IP address format: \"${trimmedIp}\" - skipping this entry`);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Generate a device name based on the IP address\n\t\t\tconst deviceName = `Manual_${trimmedIp.replace(/\\./g, '_')}`;\n\t\t\tthis.devices[trimmedIp] = deviceName;\n\t\t\tthis.options.logger?.info(`Added manual device: ${deviceName} at ${trimmedIp}`);\n\n\t\t\t// Emit discovery event for manual device\n\t\t\tthis.emit('deviceDiscovered', {\n\t\t\t\tip: trimmedIp,\n\t\t\t\tdeviceName: deviceName,\n\t\t\t} as DeviceDiscoveryEvent);\n\n\t\t\t// Request initial status\n\t\t\tthis.requestDeviceStatus(trimmedIp);\n\t\t}\n\t}\n\n\t/**\n\t * Stop all intervals and close the socket.\n\t */\n\tpublic stop(): void {\n\t\tif (this.searchInterval) {\n\t\t\tclearInterval(this.searchInterval);\n\t\t}\n\t\tif (this.refreshInterval) {\n\t\t\tclearInterval(this.refreshInterval);\n\t\t}\n\t\tthis.socket.close();\n\t}\n\n\t/**\n\t * Get all known devices.\n\t */\n\tpublic getDevices(): { [ip: string]: string } {\n\t\treturn this.devices;\n\t}\n\n\t/**\n\t * Handles a state change and sends the appropriate command to the device.\n\t *\n\t * @param id The state ID\n\t * @param state The new state object\n\t * @param receiver The device IP as string\n\t */\n\tpublic handleStateChange(id: string, state: ioBroker.State, receiver: string): void {\n\t\tconst stateKey = id.split('.')[4];\n\t\tswitch (stateKey) {\n\t\t\tcase 'onOff':\n\t\t\t\tthis.sendTurnCommand(receiver, !!state.val);\n\t\t\t\tbreak;\n\t\t\tcase 'brightness':\n\t\t\t\tthis.sendBrightnessCommand(receiver, Number(state.val));\n\t\t\t\tbreak;\n\t\t\tcase 'colorTemInKelvin':\n\t\t\t\tthis.sendColorTempCommand(receiver, Number(state.val));\n\t\t\t\tbreak;\n\t\t\tcase 'color': {\n\t\t\t\tconst colorValue = state.val?.toString();\n\t\t\t\tif (colorValue) {\n\t\t\t\t\tthis.sendColorCommand(receiver, colorValue);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\t/**\n\t * Send a turn on/off command to a device.\n\t *\n\t * @param receiver IP address or hostname\n\t * @param value true for on, false for off\n\t */\n\tpublic sendTurnCommand(receiver: string, value: boolean): void {\n\t\tconst turnMessage = { msg: { cmd: 'turn', data: { value: value ? 1 : 0 } } };\n\t\tconst turnMessageBuffer = Buffer.from(JSON.stringify(turnMessage));\n\t\tthis.socket.send(turnMessageBuffer, 0, turnMessageBuffer.length, GoveeService.CONTROL_PORT, receiver);\n\t}\n\n\t/**\n\t * Send a brightness command to a device.\n\t *\n\t * @param receiver IP address or hostname\n\t * @param value Brightness value\n\t */\n\tpublic sendBrightnessCommand(receiver: string, value: number): void {\n\t\tconst brightnessMessage = { msg: { cmd: 'brightness', data: { value } } };\n\t\tconst brightnessMessageBuffer = Buffer.from(JSON.stringify(brightnessMessage));\n\t\tthis.socket.send(\n\t\t\tbrightnessMessageBuffer,\n\t\t\t0,\n\t\t\tbrightnessMessageBuffer.length,\n\t\t\tGoveeService.CONTROL_PORT,\n\t\t\treceiver,\n\t\t);\n\t}\n\n\t/**\n\t * Send a color temperature command to a device.\n\t *\n\t * @param receiver IP address or hostname\n\t * @param kelvin Color temperature in Kelvin\n\t */\n\tpublic sendColorTempCommand(receiver: string, kelvin: number): void {\n\t\tconst colorTempMessageBuffer = Buffer.from(\n\t\t\tJSON.stringify({\n\t\t\t\tmsg: {\n\t\t\t\t\tcmd: 'colorwc',\n\t\t\t\t\tdata: { color: { r: 0, g: 0, b: 0 }, colorTemInKelvin: kelvin },\n\t\t\t\t},\n\t\t\t}),\n\t\t);\n\t\tthis.socket.send(colorTempMessageBuffer, 0, colorTempMessageBuffer.length, GoveeService.CONTROL_PORT, receiver);\n\t}\n\n\t/**\n\t * Send a color command to a device.\n\t *\n\t * @param receiver IP address or hostname\n\t * @param hexColor Color as hex string (e.g. #FFAABB)\n\t */\n\tpublic sendColorCommand(receiver: string, hexColor: string): void {\n\t\tconst rgb = hexToRgb(hexColor);\n\t\tconst colorMessage = { msg: { cmd: 'colorwc', data: { color: rgb } } };\n\t\tconst colorMessageBuffer = Buffer.from(JSON.stringify(colorMessage));\n\t\tthis.socket.send(colorMessageBuffer, 0, colorMessageBuffer.length, GoveeService.CONTROL_PORT, receiver);\n\t}\n\n\t/**\n\t * Emit device status update event with parsed status data.\n\t *\n\t * @param deviceName The device name.\n\t * @param ip The device IP address.\n\t * @param messageObject The parsed message object containing device status.\n\t */\n\tprivate emitDeviceStatusUpdate(deviceName: string, ip: string, messageObject: any): void {\n\t\tconst deviceData = messageObject.msg.data;\n\t\tconst colorString = `#${componentToHex(deviceData.color.r)}${componentToHex(deviceData.color.g)}${componentToHex(deviceData.color.b)}`;\n\n\t\tthis.emit('deviceStatusUpdate', {\n\t\t\tdeviceName: deviceName,\n\t\t\tip: ip,\n\t\t\tstatus: {\n\t\t\t\tonOff: deviceData.onOff === 1,\n\t\t\t\tbrightness: deviceData.brightness,\n\t\t\t\tcolor: colorString,\n\t\t\t\tcolorTemInKelvin: deviceData.colorTemInKelvin,\n\t\t\t},\n\t\t} as DeviceStatusEvent);\n\t}\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAKA,YAAuB;AACvB,yBAA6B;AAE7B,qBAAyC;AACzC,0BAAiC;AAiD1B,MAAM,qBAAqB,gCAAa;AAAA,EACtC;AAAA,EACA;AAAA,EACA,UAAoC,CAAC;AAAA,EACrC,gBAA0B,CAAC;AAAA,EAC3B;AAAA,EACA;AAAA,EAER,OAAgB,aAAa;AAAA,EAC7B,OAAgB,iBAAiB;AAAA,EACjC,OAAgB,eAAe;AAAA,EAC/B,OAAgB,SAAS;AAAA,EACzB,OAAgB,cAAc,EAAE,KAAK,EAAE,KAAK,QAAQ,MAAM,EAAE,eAAe,WAAW,EAAE,EAAE;AAAA,EAC1F,OAAgB,uBAAuB,EAAE,KAAK,EAAE,KAAK,aAAa,MAAM,CAAC,EAAE,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAO7E,YAAY,SAA8B;AACzC,UAAM;AACN,SAAK,UAAU;AACf,SAAK,SAAS,MAAM,aAAa,EAAE,MAAM,OAAO,CAAC;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA,EAKO,QAAc;AACpB,SAAK,OAAO,GAAG,WAAW,KAAK,aAAa,KAAK,IAAI,CAAC;AACtD,SAAK,OAAO,GAAG,SAAS,CAAC,UAAU;AAzFrC;AA0FG,iBAAK,QAAQ,WAAb,mBAAqB,MAAM,uBAAuB,MAAM,OAAO;AAAA,IAChE,CAAC;AACD,SAAK,OAAO;AAAA,MACX,EAAE,SAAS,KAAK,QAAQ,WAAW,MAAM,aAAa,WAAW;AAAA,MACjE,KAAK,YAAY,KAAK,IAAI;AAAA,IAC3B;AAAA,EACD;AAAA,EAEQ,cAAoB;AAlG7B;AAmGE,SAAK,OAAO,aAAa,IAAI;AAC7B,SAAK,OAAO,gBAAgB,GAAG;AAC/B,SAAK,OAAO,sBAAsB,KAAK,QAAQ,SAAS;AACxD,SAAK,OAAO,cAAc,aAAa,MAAM;AAC7C,eAAK,QAAQ,WAAb,mBAAqB;AAAA,MACpB,oBAAqB,KAAK,OAAO,QAAQ,EAAU,OAAO,IAAK,KAAK,OAAO,QAAQ,EAAU,IAAI;AAAA;AAIlG,QAAI,KAAK,QAAQ,qBAAqB,KAAK,QAAQ,kBAAkB,SAAS,GAAG;AAChF,WAAK,iBAAiB,KAAK,QAAQ,iBAAiB;AACpD,iBAAK,QAAQ,WAAb,mBAAqB;AAAA,QACpB,SAAS,KAAK,QAAQ,kBAAkB,MAAM;AAAA;AAAA,IAEhD;AAGA,QAAI,CAAC,KAAK,QAAQ,sBAAsB;AACvC,WAAK,iBAAiB,YAAY,MAAM,KAAK,SAAS,GAAG,KAAK,QAAQ,iBAAiB,GAAI;AAC3F,iBAAK,QAAQ,WAAb,mBAAqB,KAAK,wCAAwC,KAAK,QAAQ,cAAc;AAAA,IAC9F,OAAO;AACN,iBAAK,QAAQ,WAAb,mBAAqB,KAAK;AAAA,IAC3B;AAEA,SAAK,kBAAkB;AAAA,MACtB,MAAM,KAAK,kBAAkB;AAAA,MAC7B,KAAK,QAAQ,8BAA8B;AAAA,IAC5C;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,aAAa,SAAiB,QAAgC;AAvIvE;AAwIE,UAAM,gBAAgB,KAAK,MAAM,QAAQ,SAAS,CAAC;AACnD,YAAQ,cAAc,IAAI,KAAK;AAAA,MAC9B,KAAK,QAAQ;AACZ,YAAI,cAAc,IAAI,KAAK,QAAQ;AAClC,gBAAM,aAAa,cAAc,IAAI,KAAK,OAAO;AAAA,aAChD,UAAK,QAAQ,mBAAb,YAA+B;AAAA,YAC/B;AAAA,UACD;AACA,eAAK,QAAQ,OAAO,OAAO,IAAI;AAC/B,eAAK,KAAK,oBAAoB;AAAA,YAC7B,IAAI,OAAO;AAAA,YACX;AAAA,UACD,CAAyB;AAAA,QAC1B;AACA;AAAA,MACD;AAAA,MACA,KAAK,aAAa;AACjB,cAAM,gBAAgB,KAAK,QAAQ,OAAO,OAAO;AACjD,YAAI,eAAe;AAClB,cAAI,KAAK,QAAQ,mBAAmB,CAAC,KAAK,cAAc,SAAS,OAAO,QAAQ,SAAS,CAAC,GAAG;AAC5F,uBAAK,QAAQ,WAAb,mBAAqB,KAAK,+BAA+B,KAAK,UAAU,aAAa,CAAC;AACtF,iBAAK,cAAc,KAAK,OAAO,QAAQ,SAAS,CAAC;AAAA,UAClD;AACA,eAAK,uBAAuB,eAAe,OAAO,SAAS,aAAa;AAAA,QACzE;AACA;AAAA,MACD;AAAA,MACA,SAAS;AACR,mBAAK,QAAQ,WAAb,mBAAqB,MAAM,iBAAiB,OAAO,OAAO,IAAI,OAAO,IAAI,MAAM,QAAQ,SAAS,CAAC;AAAA,MAClG;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA,EAKO,oBAA0B;AAChC,eAAW,MAAM,KAAK,SAAS;AAC9B,WAAK,oBAAoB,EAAE;AAAA,IAC5B;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOO,oBAAoB,UAAwB;AAClD,UAAM,4BAA4B,OAAO,KAAK,KAAK,UAAU,aAAa,oBAAoB,CAAC;AAC/F,SAAK,OAAO;AAAA,MACX;AAAA,MACA;AAAA,MACA,0BAA0B;AAAA,MAC1B,aAAa;AAAA,MACb;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA,EAKO,WAAiB;AArMzB;AAsME,eAAK,QAAQ,WAAb,mBAAqB,MAAM;AAC3B,UAAM,oBAAoB,OAAO,KAAK,KAAK,UAAU,aAAa,WAAW,CAAC;AAC9E,SAAK,OAAO;AAAA,MACX;AAAA,MACA;AAAA,MACA,kBAAkB;AAAA,MAClB,aAAa;AAAA,MACb,aAAa;AAAA,IACd;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQO,iBAAiB,aAA6B;AAvNtD;AAwNE,eAAW,MAAM,aAAa;AAC7B,UAAI,CAAC,MAAM,GAAG,KAAK,EAAE,WAAW,GAAG;AAClC;AAAA,MACD;AACA,YAAM,YAAY,GAAG,KAAK;AAG1B,UAAI,KAAC,sCAAiB,SAAS,GAAG;AACjC,mBAAK,QAAQ,WAAb,mBAAqB,MAAM,+BAA+B,SAAS;AACnE;AAAA,MACD;AAGA,YAAM,aAAa,UAAU,UAAU,QAAQ,OAAO,GAAG,CAAC;AAC1D,WAAK,QAAQ,SAAS,IAAI;AAC1B,iBAAK,QAAQ,WAAb,mBAAqB,KAAK,wBAAwB,UAAU,OAAO,SAAS;AAG5E,WAAK,KAAK,oBAAoB;AAAA,QAC7B,IAAI;AAAA,QACJ;AAAA,MACD,CAAyB;AAGzB,WAAK,oBAAoB,SAAS;AAAA,IACnC;AAAA,EACD;AAAA;AAAA;AAAA;AAAA,EAKO,OAAa;AACnB,QAAI,KAAK,gBAAgB;AACxB,oBAAc,KAAK,cAAc;AAAA,IAClC;AACA,QAAI,KAAK,iBAAiB;AACzB,oBAAc,KAAK,eAAe;AAAA,IACnC;AACA,SAAK,OAAO,MAAM;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA,EAKO,aAAuC;AAC7C,WAAO,KAAK;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASO,kBAAkB,IAAY,OAAuB,UAAwB;AA/QrF;AAgRE,UAAM,WAAW,GAAG,MAAM,GAAG,EAAE,CAAC;AAChC,YAAQ,UAAU;AAAA,MACjB,KAAK;AACJ,aAAK,gBAAgB,UAAU,CAAC,CAAC,MAAM,GAAG;AAC1C;AAAA,MACD,KAAK;AACJ,aAAK,sBAAsB,UAAU,OAAO,MAAM,GAAG,CAAC;AACtD;AAAA,MACD,KAAK;AACJ,aAAK,qBAAqB,UAAU,OAAO,MAAM,GAAG,CAAC;AACrD;AAAA,MACD,KAAK,SAAS;AACb,cAAM,cAAa,WAAM,QAAN,mBAAW;AAC9B,YAAI,YAAY;AACf,eAAK,iBAAiB,UAAU,UAAU;AAAA,QAC3C;AACA;AAAA,MACD;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOO,gBAAgB,UAAkB,OAAsB;AAC9D,UAAM,cAAc,EAAE,KAAK,EAAE,KAAK,QAAQ,MAAM,EAAE,OAAO,QAAQ,IAAI,EAAE,EAAE,EAAE;AAC3E,UAAM,oBAAoB,OAAO,KAAK,KAAK,UAAU,WAAW,CAAC;AACjE,SAAK,OAAO,KAAK,mBAAmB,GAAG,kBAAkB,QAAQ,aAAa,cAAc,QAAQ;AAAA,EACrG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQO,sBAAsB,UAAkB,OAAqB;AACnE,UAAM,oBAAoB,EAAE,KAAK,EAAE,KAAK,cAAc,MAAM,EAAE,MAAM,EAAE,EAAE;AACxE,UAAM,0BAA0B,OAAO,KAAK,KAAK,UAAU,iBAAiB,CAAC;AAC7E,SAAK,OAAO;AAAA,MACX;AAAA,MACA;AAAA,MACA,wBAAwB;AAAA,MACxB,aAAa;AAAA,MACb;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQO,qBAAqB,UAAkB,QAAsB;AACnE,UAAM,yBAAyB,OAAO;AAAA,MACrC,KAAK,UAAU;AAAA,QACd,KAAK;AAAA,UACJ,KAAK;AAAA,UACL,MAAM,EAAE,OAAO,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,EAAE,GAAG,kBAAkB,OAAO;AAAA,QAC/D;AAAA,MACD,CAAC;AAAA,IACF;AACA,SAAK,OAAO,KAAK,wBAAwB,GAAG,uBAAuB,QAAQ,aAAa,cAAc,QAAQ;AAAA,EAC/G;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQO,iBAAiB,UAAkB,UAAwB;AACjE,UAAM,UAAM,yBAAS,QAAQ;AAC7B,UAAM,eAAe,EAAE,KAAK,EAAE,KAAK,WAAW,MAAM,EAAE,OAAO,IAAI,EAAE,EAAE;AACrE,UAAM,qBAAqB,OAAO,KAAK,KAAK,UAAU,YAAY,CAAC;AACnE,SAAK,OAAO,KAAK,oBAAoB,GAAG,mBAAmB,QAAQ,aAAa,cAAc,QAAQ;AAAA,EACvG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,uBAAuB,YAAoB,IAAY,eAA0B;AACxF,UAAM,aAAa,cAAc,IAAI;AACrC,UAAM,cAAc,QAAI,+BAAe,WAAW,MAAM,CAAC,CAAC,OAAG,+BAAe,WAAW,MAAM,CAAC,CAAC,OAAG,+BAAe,WAAW,MAAM,CAAC,CAAC;AAEpI,SAAK,KAAK,sBAAsB;AAAA,MAC/B;AAAA,MACA;AAAA,MACA,QAAQ;AAAA,QACP,OAAO,WAAW,UAAU;AAAA,QAC5B,YAAY,WAAW;AAAA,QACvB,OAAO;AAAA,QACP,kBAAkB,WAAW;AAAA,MAC9B;AAAA,IACD,CAAsB;AAAA,EACvB;AACD;",
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\t/** The IP address of the discovered device. */\n\tip: string;\n\t/** The sanitized device name. */\n\tdeviceName: string;\n\t/** The device model. */\n\tdeviceModel: string;\n}\n\n/**\n * Device status update event data.\n */\nexport interface DeviceStatusEvent {\n\t/** The sanitized device name. */\n\tdeviceName: string;\n\t/** The IP address of the device. */\n\tip: string;\n\t/** The current status of the device. */\n\tstatus: {\n\t\t/** Current on/off state. */\n\t\tonOff: boolean;\n\t\t/** Current brightness level. */\n\t\tbrightness: number;\n\t\t/** Current color as hex string. */\n\t\tcolor: string;\n\t\t/** Current color temperature in Kelvin. */\n\t\tcolorTemInKelvin: number;\n\t};\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\tprivate socket: dgram.Socket;\n\tprivate options: GoveeServiceOptions;\n\tprivate devices: { [ip: string]: string } = {};\n\tprivate loggedDevices: string[] = [];\n\tprivate searchInterval?: NodeJS.Timeout;\n\tprivate refreshInterval?: NodeJS.Timeout;\n\n\tstatic readonly LOCAL_PORT = 4002;\n\tstatic readonly SEND_SCAN_PORT = 4001;\n\tstatic readonly CONTROL_PORT = 4003;\n\tstatic readonly M_CAST = '239.255.255.250';\n\tstatic readonly scanMessage = { msg: { cmd: 'scan', data: { account_topic: 'reserved' } } };\n\tstatic readonly requestStatusMessage = { msg: { cmd: 'devStatus', data: {} } };\n\n\t/**\n\t * Create a new GoveeService instance.\n\t *\n\t * @param options Configuration options for the service.\n\t */\n\tconstructor(options: GoveeServiceOptions) {\n\t\tsuper();\n\t\tthis.options = options;\n\t\tthis.socket = dgram.createSocket({ type: 'udp4' });\n\t}\n\n\t/**\n\t * Bind UDP socket and start device search/refresh intervals.\n\t */\n\tpublic start(): void {\n\t\tthis.socket.on('message', this.onUdpMessage.bind(this));\n\t\tthis.socket.on('error', (error) => {\n\t\t\tthis.options.logger?.error(`server bind error : ${error.message}`);\n\t\t});\n\t\tthis.socket.bind(\n\t\t\t{ address: this.options.interface, port: GoveeService.LOCAL_PORT },\n\t\t\tthis.serverBound.bind(this),\n\t\t);\n\t}\n\n\tprivate serverBound(): void {\n\t\tthis.socket.setBroadcast(true);\n\t\tthis.socket.setMulticastTTL(128);\n\t\tthis.socket.setMulticastInterface(this.options.interface);\n\t\tthis.socket.addMembership(GoveeService.M_CAST);\n\t\tthis.options.logger?.debug(\n\t\t\t`UDP listening on ${(this.socket.address() as any).address}:${(this.socket.address() as any).port}`,\n\t\t);\n\n\t\t// Add manual IP addresses if provided\n\t\tif (this.options.manualIpAddresses && this.options.manualIpAddresses.length > 0) {\n\t\t\tthis.addManualDevices(this.options.manualIpAddresses);\n\t\t\tthis.options.logger?.info(\n\t\t\t\t`Added ${this.options.manualIpAddresses.length} manual device(s) from configuration.`,\n\t\t\t);\n\t\t}\n\n\t\t// Start auto-discovery if not disabled\n\t\tif (!this.options.disableAutoDiscovery) {\n\t\t\tthis.searchInterval = setInterval(() => this.sendScan(), this.options.searchInterval * 1000);\n\t\t\tthis.options.logger?.debug(`Auto-discovery started with interval ${this.options.searchInterval} seconds.`);\n\t\t} else {\n\t\t\tthis.options.logger?.debug('Auto-discovery is disabled. Only manual devices will be used.');\n\t\t}\n\n\t\tthis.refreshInterval = setInterval(\n\t\t\t() => this.refreshAllDevices(),\n\t\t\tthis.options.deviceStatusRefreshInterval * 1000,\n\t\t);\n\n\t\t// Emit serviceStarted event after successful startup\n\t\tthis.emit('serviceStarted');\n\t}\n\n\t/**\n\t * Handle incoming UDP messages.\n\t *\n\t * @param message The message buffer.\n\t * @param remote The sender info.\n\t */\n\tprivate onUdpMessage(message: Buffer, remote: dgram.RemoteInfo): void {\n\t\tconst messageObject = JSON.parse(message.toString());\n\t\tswitch (messageObject.msg.cmd) {\n\t\t\tcase 'scan': {\n\t\t\t\tif (messageObject.msg.data.device) {\n\t\t\t\t\tconst deviceName = messageObject.msg.data.device.replace(\n\t\t\t\t\t\tthis.options.forbiddenChars ?? /[^a-zA-Z0-9_-]/g,\n\t\t\t\t\t\t'_',\n\t\t\t\t\t);\n\t\t\t\t\tthis.devices[remote.address] = deviceName;\n\t\t\t\t\tif (this.options.extendedLogging) {\n\t\t\t\t\t\tthis.options.logger?.info(\n\t\t\t\t\t\t\t`Discovered device: ${deviceName} at ${remote.address} (model: ${messageObject.msg.data.sku})`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tthis.emit('deviceDiscovered', {\n\t\t\t\t\t\tip: remote.address,\n\t\t\t\t\t\tdeviceName: deviceName,\n\t\t\t\t\t\tdeviceModel: messageObject.msg.data.sku,\n\t\t\t\t\t} as DeviceDiscoveryEvent);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'devStatus': {\n\t\t\t\tconst sendingDevice = this.devices[remote.address];\n\t\t\t\tif (sendingDevice) {\n\t\t\t\t\tif (this.options.extendedLogging && !this.loggedDevices.includes(remote.address.toString())) {\n\t\t\t\t\t\tthis.options.logger?.info(`device status message data: ${JSON.stringify(messageObject)}`);\n\t\t\t\t\t\tthis.loggedDevices.push(remote.address.toString());\n\t\t\t\t\t}\n\t\t\t\t\tthis.emitDeviceStatusUpdate(sendingDevice, remote.address, messageObject);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault: {\n\t\t\t\tthis.options.logger?.debug(`message from: ${remote.address}:${remote.port} - ${message.toString()}`);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Send device status request to all known devices.\n\t */\n\tpublic refreshAllDevices(): void {\n\t\tfor (const ip in this.devices) {\n\t\t\tthis.requestDeviceStatus(ip);\n\t\t}\n\t}\n\n\t/**\n\t * Send device status request to a specific device.\n\t *\n\t * @param receiver The IP address or hostname of the device.\n\t */\n\tpublic requestDeviceStatus(receiver: string): void {\n\t\tconst requestDeviceStatusBuffer = Buffer.from(JSON.stringify(GoveeService.requestStatusMessage));\n\t\tthis.socket.send(\n\t\t\trequestDeviceStatusBuffer,\n\t\t\t0,\n\t\t\trequestDeviceStatusBuffer.length,\n\t\t\tGoveeService.CONTROL_PORT,\n\t\t\treceiver,\n\t\t);\n\t}\n\n\t/**\n\t * Send scan message to the UDP multicast address.\n\t */\n\tpublic sendScan(): void {\n\t\tif (this.options.extendedLogging) {\n\t\t\tthis.options.logger?.debug('sending scan message');\n\t\t}\n\t\tconst scanMessageBuffer = Buffer.from(JSON.stringify(GoveeService.scanMessage));\n\t\tthis.socket.send(\n\t\t\tscanMessageBuffer,\n\t\t\t0,\n\t\t\tscanMessageBuffer.length,\n\t\t\tGoveeService.SEND_SCAN_PORT,\n\t\t\tGoveeService.M_CAST,\n\t\t);\n\t}\n\n\t/**\n\t * Add manual devices by IP address without discovery.\n\t * These devices will be added to the device list and can be controlled.\n\t *\n\t * @param ipAddresses Array of IP addresses to add as manual devices.\n\t */\n\tpublic addManualDevices(ipAddresses: string[]): void {\n\t\tfor (const ip of ipAddresses) {\n\t\t\tif (!ip || ip.trim().length === 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst trimmedIp = ip.trim();\n\n\t\t\t// Validate IP address format\n\t\t\tif (!isValidIpAddress(trimmedIp)) {\n\t\t\t\tthis.options.logger?.error(`Invalid IP address format: \"${trimmedIp}\" - skipping this entry`);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Generate a device name based on the IP address\n\t\t\tconst deviceName = `Manual_${trimmedIp.replace(/\\./g, '_')}`;\n\t\t\tthis.devices[trimmedIp] = deviceName;\n\t\t\tthis.options.logger?.info(`Added manual device: ${deviceName} at ${trimmedIp}`);\n\n\t\t\t// Emit discovery event for manual device\n\t\t\tthis.emit('deviceDiscovered', {\n\t\t\t\tip: trimmedIp,\n\t\t\t\tdeviceName: deviceName,\n\t\t\t} as DeviceDiscoveryEvent);\n\n\t\t\t// Request initial status\n\t\t\tthis.requestDeviceStatus(trimmedIp);\n\t\t}\n\t}\n\n\t/**\n\t * Stop all intervals and close the socket.\n\t */\n\tpublic stop(): void {\n\t\tthis.options.logger?.debug('Stopping GoveeService and closing UDP socket.');\n\t\tif (this.searchInterval) {\n\t\t\tclearInterval(this.searchInterval);\n\t\t}\n\t\tif (this.refreshInterval) {\n\t\t\tclearInterval(this.refreshInterval);\n\t\t}\n\t\tthis.socket.close();\n\t}\n\n\t/**\n\t * Get all known devices.\n\t */\n\tpublic getDevices(): { [ip: string]: string } {\n\t\treturn this.devices;\n\t}\n\n\t/**\n\t * Handles a state change and sends the appropriate command to the device.\n\t *\n\t * @param id The state ID\n\t * @param state The new state object\n\t * @param receiver The device IP as string\n\t */\n\tpublic handleStateChange(id: string, state: ioBroker.State, receiver: string): void {\n\t\tconst stateKey = id.split('.')[4];\n\t\tswitch (stateKey) {\n\t\t\tcase 'onOff':\n\t\t\t\tthis.sendTurnCommand(receiver, !!state.val);\n\t\t\t\tbreak;\n\t\t\tcase 'brightness':\n\t\t\t\tthis.sendBrightnessCommand(receiver, Number(state.val));\n\t\t\t\tbreak;\n\t\t\tcase 'colorTemInKelvin':\n\t\t\t\tthis.sendColorTempCommand(receiver, Number(state.val));\n\t\t\t\tbreak;\n\t\t\tcase 'color': {\n\t\t\t\tconst colorValue = state.val?.toString();\n\t\t\t\tif (colorValue) {\n\t\t\t\t\tthis.sendColorCommand(receiver, colorValue);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\t/**\n\t * Send a turn on/off command to a device.\n\t *\n\t * @param receiver IP address or hostname\n\t * @param value true for on, false for off\n\t */\n\tpublic sendTurnCommand(receiver: string, value: boolean): void {\n\t\tconst turnMessage = { msg: { cmd: 'turn', data: { value: value ? 1 : 0 } } };\n\t\tconst turnMessageBuffer = Buffer.from(JSON.stringify(turnMessage));\n\t\tthis.socket.send(turnMessageBuffer, 0, turnMessageBuffer.length, GoveeService.CONTROL_PORT, receiver);\n\t}\n\n\t/**\n\t * Send a brightness command to a device.\n\t *\n\t * @param receiver IP address or hostname\n\t * @param value Brightness value\n\t */\n\tpublic sendBrightnessCommand(receiver: string, value: number): void {\n\t\tconst brightnessMessage = { msg: { cmd: 'brightness', data: { value } } };\n\t\tconst brightnessMessageBuffer = Buffer.from(JSON.stringify(brightnessMessage));\n\t\tthis.socket.send(\n\t\t\tbrightnessMessageBuffer,\n\t\t\t0,\n\t\t\tbrightnessMessageBuffer.length,\n\t\t\tGoveeService.CONTROL_PORT,\n\t\t\treceiver,\n\t\t);\n\t}\n\n\t/**\n\t * Send a color temperature command to a device.\n\t *\n\t * @param receiver IP address or hostname\n\t * @param kelvin Color temperature in Kelvin\n\t */\n\tpublic sendColorTempCommand(receiver: string, kelvin: number): void {\n\t\tconst colorTempMessageBuffer = Buffer.from(\n\t\t\tJSON.stringify({\n\t\t\t\tmsg: {\n\t\t\t\t\tcmd: 'colorwc',\n\t\t\t\t\tdata: { color: { r: 0, g: 0, b: 0 }, colorTemInKelvin: kelvin },\n\t\t\t\t},\n\t\t\t}),\n\t\t);\n\t\tthis.socket.send(colorTempMessageBuffer, 0, colorTempMessageBuffer.length, GoveeService.CONTROL_PORT, receiver);\n\t}\n\n\t/**\n\t * Send a color command to a device.\n\t *\n\t * @param receiver IP address or hostname\n\t * @param hexColor Color as hex string (e.g. #FFAABB)\n\t */\n\tpublic sendColorCommand(receiver: string, hexColor: string): void {\n\t\tconst rgb = hexToRgb(hexColor);\n\t\tconst colorMessage = { msg: { cmd: 'colorwc', data: { color: rgb } } };\n\t\tconst colorMessageBuffer = Buffer.from(JSON.stringify(colorMessage));\n\t\tthis.socket.send(colorMessageBuffer, 0, colorMessageBuffer.length, GoveeService.CONTROL_PORT, receiver);\n\t}\n\n\t/**\n\t * Emit device status update event with parsed status data.\n\t *\n\t * @param deviceName The device name.\n\t * @param ip The device IP address.\n\t * @param messageObject The parsed message object containing device status.\n\t */\n\tprivate emitDeviceStatusUpdate(deviceName: string, ip: string, messageObject: any): void {\n\t\tconst deviceData = messageObject.msg.data;\n\t\tconst colorString = `#${componentToHex(deviceData.color.r)}${componentToHex(deviceData.color.g)}${componentToHex(deviceData.color.b)}`;\n\n\t\tthis.emit('deviceStatusUpdate', {\n\t\t\tdeviceName: deviceName,\n\t\t\tip: ip,\n\t\t\tstatus: {\n\t\t\t\tonOff: deviceData.onOff === 1,\n\t\t\t\tbrightness: deviceData.brightness,\n\t\t\t\tcolor: colorString,\n\t\t\t\tcolorTemInKelvin: deviceData.colorTemInKelvin,\n\t\t\t},\n\t\t} as DeviceStatusEvent);\n\t}\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/build/main.js CHANGED
@@ -98,6 +98,9 @@ class GoveeLocal extends utils.Adapter {
98
98
  this.goveeService.on("deviceStatusUpdate", (data) => {
99
99
  void this.handleDeviceStatusUpdate(data);
100
100
  });
101
+ this.goveeService.on("serviceStarted", () => {
102
+ void this.setState("info.connection", { val: true, ack: true });
103
+ });
101
104
  this.goveeService.start();
102
105
  if (this.config.extendedLogging) {
103
106
  this.log.debug("running with extended logging");
@@ -166,11 +169,12 @@ class GoveeLocal extends utils.Adapter {
166
169
  * @param event The device discovery event data.
167
170
  */
168
171
  async handleDeviceDiscovered(event) {
169
- const { ip, deviceName } = event;
172
+ const { ip, deviceName, deviceModel } = event;
170
173
  await this.setObjectNotExistsAsync(deviceName, {
171
- type: "folder",
174
+ type: "device",
172
175
  common: {
173
- name: deviceName
176
+ name: deviceModel,
177
+ role: "group"
174
178
  },
175
179
  native: {}
176
180
  });
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\t/** Instance of GoveeService for device communication */\n\tprivate goveeService!: GoveeService;\n\t/**\n\t * Adapter constructor. Registers lifecycle event handlers.\n\t *\n\t * @param options Optional adapter options to override defaults.\n\t */\n\tpublic constructor(options: Partial<utils.AdapterOptions> = {}) {\n\t\tsuper({\n\t\t\t...options,\n\t\t\tname: 'govee-local',\n\t\t});\n\t\t// Register event handlers for adapter lifecycle\n\t\tthis.on('ready', this.onReady.bind(this));\n\t\tthis.on('stateChange', this.onStateChange.bind(this));\n\t\t// Uncomment if you want to handle object changes or messages:\n\t\t// this.on('objectChange', this.onObjectChange.bind(this));\n\t\t// this.on('message', this.onMessage.bind(this));\n\t\tthis.on('unload', this.onUnload.bind(this));\n\t}\n\n\t/**\n\t * Called when databases are connected and adapter received configuration.\n\t * Initializes GoveeService and sets up event listeners for device events.\n\t */\n\tprivate async onReady(): Promise<void> {\n\t\t// Create connection indicator state if it doesn't exist\n\t\tvoid this.setObjectNotExists('info.connection', {\n\t\t\ttype: 'state',\n\t\t\tcommon: {\n\t\t\t\tname: 'Device discovery running',\n\t\t\t\ttype: 'boolean',\n\t\t\t\trole: 'indicator.connected',\n\t\t\t\tread: true,\n\t\t\t\twrite: false,\n\t\t\t},\n\t\t\tnative: {},\n\t\t});\n\n\t\t// Create state for discovered devices\n\t\tvoid this.setObjectNotExists('info.discoveredDevices', {\n\t\t\ttype: 'state',\n\t\t\tcommon: {\n\t\t\t\tname: 'List of discovered devices',\n\t\t\t\ttype: 'string',\n\t\t\t\trole: 'json',\n\t\t\t\tread: true,\n\t\t\t\twrite: false,\n\t\t\t\tdesc: 'JSON object with IP addresses and device names of all discovered devices',\n\t\t\t},\n\t\t\tnative: {},\n\t\t});\n\n\t\t// Initialize GoveeService with adapter config and logger\n\t\tthis.goveeService = new GoveeService({\n\t\t\tinterface: this.config.interface,\n\t\t\tsearchInterval: this.config.searchInterval,\n\t\t\tdeviceStatusRefreshInterval: this.config.deviceStatusRefreshInterval,\n\t\t\textendedLogging: this.config.extendedLogging,\n\t\t\tforbiddenChars: /[^a-zA-Z0-9_-]/g,\n\t\t\tmanualIpAddresses: this.config.manualIpTable,\n\t\t\tdisableAutoDiscovery: this.config.disableAutoDiscovery,\n\t\t\tlogger: {\n\t\t\t\tdebug: (msg) => this.log.debug(msg),\n\t\t\t\tinfo: (msg) => this.log.info(msg),\n\t\t\t\terror: (msg) => this.log.error(msg),\n\t\t\t},\n\t\t});\n\n\t\t// Listen for device discovery and status update events\n\t\tthis.goveeService.on('deviceDiscovered', (data) => {\n\t\t\tvoid this.handleDeviceDiscovered(data);\n\t\t});\n\n\t\tthis.goveeService.on('deviceStatusUpdate', (data) => {\n\t\t\tvoid this.handleDeviceStatusUpdate(data);\n\t\t});\n\t\t// Start device discovery and status polling\n\t\tthis.goveeService.start();\n\n\t\tif (this.config.extendedLogging) {\n\t\t\tthis.log.debug('running with extended logging');\n\t\t}\n\n\t\t// Subscribe to all device status state changes\n\t\tvoid this.subscribeStates('*.devStatus.*');\n\t\treturn Promise.resolve();\n\t}\n\n\t/**\n\t * Called if a subscribed state changes (e.g. user toggles a switch in ioBroker UI).\n\t * Forwards the change to the GoveeService for device communication.\n\t *\n\t * @param id The ID of the changed state.\n\t * @param state The new state object or null/undefined.\n\t */\n\tprivate async onStateChange(id: string, state: ioBroker.State | null | undefined): Promise<void> {\n\t\tif (state && !state.ack) {\n\t\t\t// Extract device name from state ID and get its IP address\n\t\t\tconst ipOfDevice = await this.getStateAsync(`${id.split('.')[2]}.deviceInfo.ip`);\n\t\t\tconst receiver = ipOfDevice?.val?.toString();\n\t\t\tif (typeof receiver === 'string') {\n\t\t\t\t// Forward the state change to the GoveeService\n\t\t\t\tthis.goveeService.handleStateChange(id, state, receiver);\n\t\t\t} else {\n\t\t\t\tthis.log.error('device not found or IP is not a string');\n\t\t\t}\n\t\t}\n\t\treturn Promise.resolve();\n\t}\n\t/**\n\t * Called when the adapter shuts down. Cleans up resources and stops services.\n\t *\n\t * @param callback Callback function after unload process.\n\t */\n\tprivate onUnload(callback: () => void): void {\n\t\ttry {\n\t\t\tif (this.goveeService) {\n\t\t\t\tthis.goveeService.removeAllListeners();\n\t\t\t\tthis.goveeService.stop();\n\t\t\t}\n\t\t\t// Set connection state to false\n\t\t\tvoid this.updateStateAsync('info.connection', false);\n\t\t\tcallback();\n\t\t} catch (e: any) {\n\t\t\tthis.log.error(e.message);\n\t\t\tcallback();\n\t\t}\n\t}\n\n\t/**\n\t * Updates a state only if the value has changed.\n\t *\n\t * @param fullName Full object path\n\t * @param state New value\n\t * @param acknowledged Whether the value is acknowledged (default: true)\n\t */\n\tprivate async updateStateAsync(fullName: string, state: any, acknowledged = true): Promise<void> {\n\t\tconst currentState = await this.getStateAsync(fullName);\n\t\tif (currentState?.val !== state) {\n\t\t\tvoid this.setState(fullName, {\n\t\t\t\tval: state,\n\t\t\t\tack: acknowledged,\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Handles device discovery event from GoveeService.\n\t * Creates all necessary objects and states for the new device.\n\t *\n\t * @param event The device discovery event data.\n\t */\n\tprivate async handleDeviceDiscovered(event: DeviceDiscoveryEvent): Promise<void> {\n\t\tconst { ip, deviceName } = event;\n\n\t\t// Create device folder\n\t\tawait this.setObjectNotExistsAsync(deviceName, {\n\t\t\ttype: 'folder',\n\t\t\tcommon: {\n\t\t\t\tname: deviceName,\n\t\t\t},\n\t\t\tnative: {},\n\t\t});\n\n\t\t// Create deviceInfo folder\n\t\tawait this.setObjectNotExistsAsync(`${deviceName}.deviceInfo`, {\n\t\t\ttype: 'folder',\n\t\t\tcommon: {\n\t\t\t\tname: 'Device Info',\n\t\t\t},\n\t\t\tnative: {},\n\t\t});\n\n\t\t// Create IP address state\n\t\tawait this.setObjectNotExistsAsync(`${deviceName}.deviceInfo.ip`, {\n\t\t\ttype: 'state',\n\t\t\tcommon: {\n\t\t\t\tname: 'IP address of the Lamp',\n\t\t\t\ttype: 'string',\n\t\t\t\trole: 'info.ip',\n\t\t\t\tread: true,\n\t\t\t\twrite: false,\n\t\t\t},\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.updateStateAsync(`${deviceName}.deviceInfo.ip`, ip);\n\n\t\t// Create devStatus folder\n\t\tawait this.setObjectNotExistsAsync(`${deviceName}.devStatus`, {\n\t\t\ttype: 'folder',\n\t\t\tcommon: {\n\t\t\t\tname: 'Device Status',\n\t\t\t},\n\t\t\tnative: {},\n\t\t});\n\n\t\tthis.log.info(`Device discovered: ${deviceName} at ${ip}`);\n\n\t\t// Update the list of discovered devices\n\t\tawait this.updateDiscoveredDevicesList();\n\t}\n\n\t/**\n\t * Updates the info.discoveredDevices state with the current list of all discovered devices.\n\t */\n\tprivate async updateDiscoveredDevicesList(): Promise<void> {\n\t\tconst devices = this.goveeService.getDevices();\n\t\tawait this.setStateAsync('info.discoveredDevices', {\n\t\t\tval: JSON.stringify(devices, null, 2),\n\t\t\tack: true,\n\t\t});\n\t}\n\n\t/**\n\t * Handles device status update event from GoveeService.\n\t * Creates and updates all relevant states for the device.\n\t *\n\t * @param event The device status update event data.\n\t */\n\tprivate async handleDeviceStatusUpdate(event: DeviceStatusEvent): Promise<void> {\n\t\tconst { deviceName, status } = event;\n\n\t\t// Create and update onOff state\n\t\tawait this.setObjectNotExistsAsync(`${deviceName}.devStatus.onOff`, {\n\t\t\ttype: 'state',\n\t\t\tcommon: {\n\t\t\t\tname: 'On / Off state of the lamp',\n\t\t\t\ttype: 'boolean',\n\t\t\t\trole: 'switch',\n\t\t\t\tread: true,\n\t\t\t\twrite: true,\n\t\t\t},\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.updateStateAsync(`${deviceName}.devStatus.onOff`, status.onOff);\n\n\t\t// Create and update brightness state\n\t\tawait this.setObjectNotExistsAsync(`${deviceName}.devStatus.brightness`, {\n\t\t\ttype: 'state',\n\t\t\tcommon: {\n\t\t\t\tname: 'Brightness of the light',\n\t\t\t\ttype: 'number',\n\t\t\t\trole: 'level.dimmer',\n\t\t\t\tread: true,\n\t\t\t\twrite: true,\n\t\t\t},\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.updateStateAsync(`${deviceName}.devStatus.brightness`, status.brightness);\n\n\t\t// Create and update color state\n\t\tawait this.setObjectNotExistsAsync(`${deviceName}.devStatus.color`, {\n\t\t\ttype: 'state',\n\t\t\tcommon: {\n\t\t\t\tname: 'Current showing color of the lamp',\n\t\t\t\ttype: 'string',\n\t\t\t\trole: 'level.color.rgb',\n\t\t\t\tread: true,\n\t\t\t\twrite: true,\n\t\t\t},\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.updateStateAsync(`${deviceName}.devStatus.color`, status.color);\n\n\t\t// Create and update color temperature state\n\t\tawait this.setObjectNotExistsAsync(`${deviceName}.devStatus.colorTemInKelvin`, {\n\t\t\ttype: 'state',\n\t\t\tcommon: {\n\t\t\t\tname: 'If staying in white light, the color temperature',\n\t\t\t\ttype: 'number',\n\t\t\t\trole: 'level.color.temperature',\n\t\t\t\tread: true,\n\t\t\t\twrite: true,\n\t\t\t},\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.updateStateAsync(`${deviceName}.devStatus.colorTemInKelvin`, status.colorTemInKelvin);\n\t}\n}\n\n// Export factory function for ioBroker or start instance directly\nif (require.main !== module) {\n\t// Export factory function for ioBroker, also available as ES6 export\n\tmodule.exports = (options: Partial<utils.AdapterOptions> | undefined) => new GoveeLocal(options);\n} else {\n\t// Otherwise start the instance directly\n\t(() => 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';\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\t/** Instance of GoveeService for device communication */\n\tprivate goveeService!: GoveeService;\n\t/**\n\t * Adapter constructor. Registers lifecycle event handlers.\n\t *\n\t * @param options Optional adapter options to override defaults.\n\t */\n\tpublic constructor(options: Partial<utils.AdapterOptions> = {}) {\n\t\tsuper({\n\t\t\t...options,\n\t\t\tname: 'govee-local',\n\t\t});\n\t\t// Register event handlers for adapter lifecycle\n\t\tthis.on('ready', this.onReady.bind(this));\n\t\tthis.on('stateChange', this.onStateChange.bind(this));\n\t\t// Uncomment if you want to handle object changes or messages:\n\t\t// this.on('objectChange', this.onObjectChange.bind(this));\n\t\t// this.on('message', this.onMessage.bind(this));\n\t\tthis.on('unload', this.onUnload.bind(this));\n\t}\n\n\t/**\n\t * Called when databases are connected and adapter received configuration.\n\t * Initializes GoveeService and sets up event listeners for device events.\n\t */\n\tprivate async onReady(): Promise<void> {\n\t\t// Create connection indicator state if it doesn't exist\n\t\tvoid this.setObjectNotExists('info.connection', {\n\t\t\ttype: 'state',\n\t\t\tcommon: {\n\t\t\t\tname: 'Device discovery running',\n\t\t\t\ttype: 'boolean',\n\t\t\t\trole: 'indicator.connected',\n\t\t\t\tread: true,\n\t\t\t\twrite: false,\n\t\t\t},\n\t\t\tnative: {},\n\t\t});\n\n\t\t// Create state for discovered devices\n\t\tvoid this.setObjectNotExists('info.discoveredDevices', {\n\t\t\ttype: 'state',\n\t\t\tcommon: {\n\t\t\t\tname: 'List of discovered devices',\n\t\t\t\ttype: 'string',\n\t\t\t\trole: 'json',\n\t\t\t\tread: true,\n\t\t\t\twrite: false,\n\t\t\t\tdesc: 'JSON object with IP addresses and device names of all discovered devices',\n\t\t\t},\n\t\t\tnative: {},\n\t\t});\n\n\t\t// Initialize GoveeService with adapter config and logger\n\t\tthis.goveeService = new GoveeService({\n\t\t\tinterface: this.config.interface,\n\t\t\tsearchInterval: this.config.searchInterval,\n\t\t\tdeviceStatusRefreshInterval: this.config.deviceStatusRefreshInterval,\n\t\t\textendedLogging: this.config.extendedLogging,\n\t\t\tforbiddenChars: /[^a-zA-Z0-9_-]/g,\n\t\t\tmanualIpAddresses: this.config.manualIpTable,\n\t\t\tdisableAutoDiscovery: this.config.disableAutoDiscovery,\n\t\t\tlogger: {\n\t\t\t\tdebug: (msg) => this.log.debug(msg),\n\t\t\t\tinfo: (msg) => this.log.info(msg),\n\t\t\t\terror: (msg) => this.log.error(msg),\n\t\t\t},\n\t\t});\n\n\t\t// Listen for device discovery and status update events\n\t\tthis.goveeService.on('deviceDiscovered', (data) => {\n\t\t\tvoid this.handleDeviceDiscovered(data);\n\t\t});\n\n\t\tthis.goveeService.on('deviceStatusUpdate', (data) => {\n\t\t\tvoid this.handleDeviceStatusUpdate(data);\n\t\t});\n\n\t\t// Listen for serviceStarted event to set connection state to true\n\t\tthis.goveeService.on('serviceStarted', () => {\n\t\t\tvoid this.setState('info.connection', { val: true, ack: true });\n\t\t});\n\n\t\t// Start device discovery and status polling\n\t\tthis.goveeService.start();\n\n\t\tif (this.config.extendedLogging) {\n\t\t\tthis.log.debug('running with extended logging');\n\t\t}\n\n\t\t// Subscribe to all device status state changes\n\t\tvoid this.subscribeStates('*.devStatus.*');\n\t\treturn Promise.resolve();\n\t}\n\n\t/**\n\t * Called if a subscribed state changes (e.g. user toggles a switch in ioBroker UI).\n\t * Forwards the change to the GoveeService for device communication.\n\t *\n\t * @param id The ID of the changed state.\n\t * @param state The new state object or null/undefined.\n\t */\n\tprivate async onStateChange(id: string, state: ioBroker.State | null | undefined): Promise<void> {\n\t\tif (state && !state.ack) {\n\t\t\t// Extract device name from state ID and get its IP address\n\t\t\tconst ipOfDevice = await this.getStateAsync(`${id.split('.')[2]}.deviceInfo.ip`);\n\t\t\tconst receiver = ipOfDevice?.val?.toString();\n\t\t\tif (typeof receiver === 'string') {\n\t\t\t\t// Forward the state change to the GoveeService\n\t\t\t\tthis.goveeService.handleStateChange(id, state, receiver);\n\t\t\t} else {\n\t\t\t\tthis.log.error('device not found or IP is not a string');\n\t\t\t}\n\t\t}\n\t\treturn Promise.resolve();\n\t}\n\t/**\n\t * Called when the adapter shuts down. Cleans up resources and stops services.\n\t *\n\t * @param callback Callback function after unload process.\n\t */\n\tprivate onUnload(callback: () => void): void {\n\t\ttry {\n\t\t\tif (this.goveeService) {\n\t\t\t\tthis.goveeService.removeAllListeners();\n\t\t\t\tthis.goveeService.stop();\n\t\t\t}\n\t\t\t// Set connection state to false\n\t\t\tvoid this.updateStateAsync('info.connection', false);\n\t\t\tcallback();\n\t\t} catch (e: any) {\n\t\t\tthis.log.error(e.message);\n\t\t\tcallback();\n\t\t}\n\t}\n\n\t/**\n\t * Updates a state only if the value has changed.\n\t *\n\t * @param fullName Full object path\n\t * @param state New value\n\t * @param acknowledged Whether the value is acknowledged (default: true)\n\t */\n\tprivate async updateStateAsync(fullName: string, state: any, acknowledged = true): Promise<void> {\n\t\tconst currentState = await this.getStateAsync(fullName);\n\t\tif (currentState?.val !== state) {\n\t\t\tvoid this.setState(fullName, {\n\t\t\t\tval: state,\n\t\t\t\tack: acknowledged,\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Handles device discovery event from GoveeService.\n\t * Creates all necessary objects and states for the new device.\n\t *\n\t * @param event The device discovery event data.\n\t */\n\tprivate async handleDeviceDiscovered(event: DeviceDiscoveryEvent): Promise<void> {\n\t\tconst { ip, deviceName, deviceModel } = event;\n\n\t\t// Create device folder\n\t\tawait this.setObjectNotExistsAsync(deviceName, {\n\t\t\ttype: 'device',\n\t\t\tcommon: {\n\t\t\t\tname: deviceModel,\n\t\t\t\trole: 'group',\n\t\t\t},\n\t\t\tnative: {},\n\t\t});\n\n\t\t// Create deviceInfo folder\n\t\tawait this.setObjectNotExistsAsync(`${deviceName}.deviceInfo`, {\n\t\t\ttype: 'folder',\n\t\t\tcommon: {\n\t\t\t\tname: 'Device Info',\n\t\t\t},\n\t\t\tnative: {},\n\t\t});\n\n\t\t// Create IP address state\n\t\tawait this.setObjectNotExistsAsync(`${deviceName}.deviceInfo.ip`, {\n\t\t\ttype: 'state',\n\t\t\tcommon: {\n\t\t\t\tname: 'IP address of the Lamp',\n\t\t\t\ttype: 'string',\n\t\t\t\trole: 'info.ip',\n\t\t\t\tread: true,\n\t\t\t\twrite: false,\n\t\t\t},\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.updateStateAsync(`${deviceName}.deviceInfo.ip`, ip);\n\n\t\t// Create devStatus folder\n\t\tawait this.setObjectNotExistsAsync(`${deviceName}.devStatus`, {\n\t\t\ttype: 'folder',\n\t\t\tcommon: {\n\t\t\t\tname: 'Device Status',\n\t\t\t},\n\t\t\tnative: {},\n\t\t});\n\n\t\tthis.log.info(`Device discovered: ${deviceName} at ${ip}`);\n\n\t\t// Update the list of discovered devices\n\t\tawait this.updateDiscoveredDevicesList();\n\t}\n\n\t/**\n\t * Updates the info.discoveredDevices state with the current list of all discovered devices.\n\t */\n\tprivate async updateDiscoveredDevicesList(): Promise<void> {\n\t\tconst devices = this.goveeService.getDevices();\n\t\tawait this.setStateAsync('info.discoveredDevices', {\n\t\t\tval: JSON.stringify(devices, null, 2),\n\t\t\tack: true,\n\t\t});\n\t}\n\n\t/**\n\t * Handles device status update event from GoveeService.\n\t * Creates and updates all relevant states for the device.\n\t *\n\t * @param event The device status update event data.\n\t */\n\tprivate async handleDeviceStatusUpdate(event: DeviceStatusEvent): Promise<void> {\n\t\tconst { deviceName, status } = event;\n\n\t\t// Create and update onOff state\n\t\tawait this.setObjectNotExistsAsync(`${deviceName}.devStatus.onOff`, {\n\t\t\ttype: 'state',\n\t\t\tcommon: {\n\t\t\t\tname: 'On / Off state of the lamp',\n\t\t\t\ttype: 'boolean',\n\t\t\t\trole: 'switch',\n\t\t\t\tread: true,\n\t\t\t\twrite: true,\n\t\t\t},\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.updateStateAsync(`${deviceName}.devStatus.onOff`, status.onOff);\n\n\t\t// Create and update brightness state\n\t\tawait this.setObjectNotExistsAsync(`${deviceName}.devStatus.brightness`, {\n\t\t\ttype: 'state',\n\t\t\tcommon: {\n\t\t\t\tname: 'Brightness of the light',\n\t\t\t\ttype: 'number',\n\t\t\t\trole: 'level.dimmer',\n\t\t\t\tread: true,\n\t\t\t\twrite: true,\n\t\t\t},\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.updateStateAsync(`${deviceName}.devStatus.brightness`, status.brightness);\n\n\t\t// Create and update color state\n\t\tawait this.setObjectNotExistsAsync(`${deviceName}.devStatus.color`, {\n\t\t\ttype: 'state',\n\t\t\tcommon: {\n\t\t\t\tname: 'Current showing color of the lamp',\n\t\t\t\ttype: 'string',\n\t\t\t\trole: 'level.color.rgb',\n\t\t\t\tread: true,\n\t\t\t\twrite: true,\n\t\t\t},\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.updateStateAsync(`${deviceName}.devStatus.color`, status.color);\n\n\t\t// Create and update color temperature state\n\t\tawait this.setObjectNotExistsAsync(`${deviceName}.devStatus.colorTemInKelvin`, {\n\t\t\ttype: 'state',\n\t\t\tcommon: {\n\t\t\t\tname: 'If staying in white light, the color temperature',\n\t\t\t\ttype: 'number',\n\t\t\t\trole: 'level.color.temperature',\n\t\t\t\tread: true,\n\t\t\t\twrite: true,\n\t\t\t},\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.updateStateAsync(`${deviceName}.devStatus.colorTemInKelvin`, status.colorTemInKelvin);\n\t}\n}\n\n// Export factory function for ioBroker or start instance directly\nif (require.main !== module) {\n\t// Export factory function for ioBroker, also available as ES6 export\n\tmodule.exports = (options: Partial<utils.AdapterOptions> | undefined) => new GoveeLocal(options);\n} else {\n\t// Otherwise start the instance directly\n\t(() => new GoveeLocal())();\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/io-package.json CHANGED
@@ -1,8 +1,34 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "govee-local",
4
- "version": "0.4.0",
4
+ "version": "0.4.2",
5
5
  "news": {
6
+ "0.4.2": {
7
+ "en": "update dependencies\nfix device name / model identifier in the objects",
8
+ "de": "aktualisierung der abhängigkeiten\ngerätename / modellkennung in den objekten fixieren",
9
+ "ru": "обновление зависимостей\nфиксировать имя устройства / идентификатор модели в объектах",
10
+ "pt": "atualizar dependências\ncorrigir o nome do dispositivo / identificador do modelo nos objetos",
11
+ "nl": "afhankelijkheden bijwerken\nfix apparaatnaam / modelidentificatie in de objecten",
12
+ "fr": "mettre à jour les dépendances\ncorrection du nom du périphérique / de l'identificateur du modèle dans les objets",
13
+ "it": "aggiornamento dipendenze\nfix nome del dispositivo / modello identificatore negli oggetti",
14
+ "es": "dependencias de actualización\nfijar el nombre del dispositivo / identificador modelo en los objetos",
15
+ "pl": "aktualizacji zależności\nnaprawić nazwę urządzenia / identyfikator modelu w obiektach",
16
+ "uk": "оновлення залежності\nвиправити назву пристрою / ідентифікатор моделі в об'єктах",
17
+ "zh-cn": "更新依赖关系\n对象中的固定设备名称/ 模型标识符"
18
+ },
19
+ "0.4.1": {
20
+ "en": "fix logging\nfix connection state",
21
+ "de": "fehlerbehebung\nbefestigungszustand",
22
+ "ru": "фиксировать лесозаготовки\nфиксировать состояние соединения",
23
+ "pt": "corrigir o registo\ncorrigir o estado da ligação",
24
+ "nl": "loggen herstellen\nfix verbindingstoestand",
25
+ "fr": "correction de l'enregistrement\nfixer l'état de connexion",
26
+ "it": "correggere la registrazione\ncorrezione dello stato di connessione",
27
+ "es": "corrección de registro\nestado de conexión",
28
+ "pl": "naprawić logowanie\nstan połączenia",
29
+ "uk": "фіксувати забір\nфіксувати стан з'єднання",
30
+ "zh-cn": "固定记录\n固定连接状态"
31
+ },
6
32
  "0.4.0": {
7
33
  "en": "Updated translations for datapoint descriptions in all supported languages\nRefreshed and modernized the admin UI: clearer sections, manual IP address list, Windows hint, and option to disable auto-discovery\nPossibility to manually add devices via IP-Address and Option to disable auto discovery\nSave auto discovered devices in info datapoint and show in admin UI",
8
34
  "de": "Aktualisierte Übersetzungen für Datapoint-Beschreibungen in allen unterstützten Sprachen\nErfrischt und modernisiert den Admin UI: klarere Abschnitte, manuelle IP-Adressenliste, Windows-Hinweis und Option, Auto-Entdeckung deaktivieren\nMöglichkeit, manuell Geräte über IP-Adresse und Option hinzuzufügen, um die automatische Entdeckung zu deaktivieren\nSpeichern Sie Auto entdeckte Geräte in Info-Datenpunkt und zeigen Sie in admin UI",
@@ -67,32 +93,6 @@
67
93
  "pl": "aktualizacji zależności",
68
94
  "uk": "оновлення залежності",
69
95
  "zh-cn": "更新依赖关系"
70
- },
71
- "0.2.7": {
72
- "en": "update core dependencies and min required node version to 18",
73
- "de": "aktualisieren von kernabhängigkeiten und min benötigte knotenversion bis 18",
74
- "ru": "обновление основных зависимостей и мин требуется версия узла до 18",
75
- "pt": "atualizar as dependências do núcleo e a versão mínima exigida do nó para 18",
76
- "nl": "update core afhankelijkheden en min vereist node versie naar 18",
77
- "fr": "mettre à jour les dépendances de base et la version de nœud min requise à 18",
78
- "it": "aggiornare le dipendenze del nucleo e la versione del nodo richiesto min a 18",
79
- "es": "actualización de las dependencias básicas y la versión de los nodos necesarios min a 18",
80
- "pl": "aktualizacji zależności rdzenia i min wymagana wersja węzła do 18",
81
- "uk": "оновлення основних залежностей і обов'язкової версії вершини до 18",
82
- "zh-cn": "将核心依赖和分钟需要的节点版本更新到 18"
83
- },
84
- "0.2.6": {
85
- "en": "fix brightness change also changing white tmperature",
86
- "de": "helligkeitsänderung auch ändern weiße tmperatur",
87
- "ru": "исправить яркость изменения также изменение белое tmperature",
88
- "pt": "corrigir a mudança de brilho também mudando a tintura branca",
89
- "nl": "fix helderheid verandering ook het veranderen van witte tmperatuur",
90
- "fr": "fixer le changement de luminosité aussi changer la morphologie blanche",
91
- "it": "correzione cambiamento di luminosità anche cambiando la serratura bianca",
92
- "es": "arreglar el cambio de brillo también cambiar la tmperatura blanca",
93
- "pl": "naprawić zmianę jasności również zmieniając biały tmperature",
94
- "uk": "зафіксувати яскравість зміни також змінюють білу мпературу",
95
- "zh-cn": "固定亮度改变, 也改变白色温度"
96
96
  }
97
97
  },
98
98
  "titleLang": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.govee-local",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Control Govee devices via local access (no cloud)",
5
5
  "author": {
6
6
  "name": "Børge Grunicke",
@@ -26,34 +26,24 @@
26
26
  },
27
27
  "devDependencies": {
28
28
  "@alcalzone/release-script": "^5.0.0",
29
- "@alcalzone/release-script-plugin-iobroker": "^3.7.2",
29
+ "@alcalzone/release-script-plugin-iobroker": "^4.0.0",
30
30
  "@alcalzone/release-script-plugin-license": "^4.0.0",
31
31
  "@alcalzone/release-script-plugin-manual-review": "^4.0.0",
32
- "@iobroker/adapter-dev": "^1.4.0",
33
- "@iobroker/eslint-config": "^2.1.0",
32
+ "@iobroker/adapter-dev": "^1.5.0",
33
+ "@iobroker/eslint-config": "^2.2.0",
34
34
  "@iobroker/testing": "^5.1.1",
35
35
  "@tsconfig/node14": "^14.1.5",
36
36
  "@tsconfig/node18": "^18.2.4",
37
37
  "@tsconfig/node20": "^20.1.6",
38
- "@types/chai": "^4.1.1",
39
- "@types/chai-as-promised": "^8.0.2",
40
- "@types/mocha": "^10.0.10",
41
- "@types/node": "^24.3.0",
38
+ "@types/node": "^24.10.0",
42
39
  "@types/proxyquire": "^1.3.31",
43
- "@types/sinon": "^17.0.4",
44
- "@types/sinon-chai": "^3.2.12",
45
- "chai": "^6.2.0",
46
- "chai-as-promised": "^8.0.2",
47
- "eslint": "^9.34.0",
48
- "mocha": "^11.7.4",
49
- "prettier": "^3.3.3",
40
+ "eslint": "^9.39.1",
41
+ "prettier": "^3.6.2",
50
42
  "proxyquire": "^2.1.3",
51
- "rimraf": "^6.0.1",
52
- "sinon": "^21.0.0",
53
- "sinon-chai": "^4.0.1",
43
+ "rimraf": "^6.1.0",
54
44
  "source-map-support": "^0.5.21",
55
45
  "ts-node": "^10.9.2",
56
- "typescript": "^5.6.3"
46
+ "typescript": "^5.9.3"
57
47
  },
58
48
  "main": "build/main.js",
59
49
  "files": [