iobroker.govee-local 0.2.8 → 0.3.1

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,78 +24,95 @@ 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.3.1 (2025-10-19)
28
+
29
+ - updated release script run configuration
30
+
31
+ ### 0.3.0 (2025-10-19)
32
+
33
+ - Refactored: Moved all device logic to a dedicated GoveeService class, modularized utilities
34
+ - Improved test coverage
35
+ - Dependencies updated
36
+
37
+ ### 0.2.9 (2025-08-22)
38
+
39
+ - update dependencies
40
+ - add support for node 24
41
+ - update to ESlint 9
42
+
27
43
  ### 0.2.8 (2025-03-28)
28
44
 
29
- - update dependencies
45
+ - update dependencies
46
+ - avoid repeated overriding of states
30
47
 
31
48
  ### 0.2.7 (2024-08-18)
32
49
 
33
- - update core dependencies and min required node version to 18
50
+ - update core dependencies and min required node version to 18
34
51
 
35
52
  ### 0.2.6 (2024-02-06)
36
53
 
37
- - fix brightness change also changing white temperature
54
+ - fix brightness change also changing white temperature
38
55
 
39
56
  ### 0.2.5 (2024-01-13)
40
57
 
41
- - create only one socket, as the second seems not to be necessary
42
- - refactoring the code for better structure
43
- - possibility to choose listen interface in settings
58
+ - create only one socket, as the second seems not to be necessary
59
+ - refactoring the code for better structure
60
+ - possibility to choose listen interface in settings
44
61
 
45
62
  ### 0.2.4 (2024-01-05)
46
63
 
47
- - fix access
64
+ - fix access
48
65
 
49
66
  ### 0.2.2 (2024-01-05)
50
67
 
51
- - fix color temperature message
68
+ - fix color temperature message
52
69
 
53
70
  ### 0.2.1 (2023-12-24)
54
71
 
55
- - repair onOff / all other actions Fixes: [#65](https://github.com/boergegrunicke/ioBroker.govee-local/issues/65)
56
- - fix log spamming because of wildcard
72
+ - repair onOff / all other actions Fixes: [#65](https://github.com/boergegrunicke/ioBroker.govee-local/issues/65)
73
+ - fix log spamming because of wildcard
57
74
 
58
75
  ### 0.2.0 (2023-12-17)
59
76
 
60
- - support controlling the color
61
- - extended logging mode
77
+ - support controlling the color
78
+ - extended logging mode
62
79
 
63
80
  ### 0.1.2 (2023-09-06)
64
81
 
65
- - change icon path and resolution
82
+ - change icon path and resolution
66
83
 
67
84
  ### 0.1.1 (2023-08-21)
68
85
 
69
- - fix image
86
+ - fix image
70
87
 
71
88
  ### 0.1.0 (2023-08-09)
72
89
 
73
- - make search intervals configurable
74
- - clear all timeouts, when adapter is stopped
75
- - replace forbidden characters in names
76
- - update translations
77
- - update dependencies
90
+ - make search intervals configurable
91
+ - clear all timeouts, when adapter is stopped
92
+ - replace forbidden characters in names
93
+ - update translations
94
+ - update dependencies
78
95
 
79
96
  ### 0.0.6 (2023-05-18)
80
97
 
81
- - update dependencies
98
+ - update dependencies
82
99
 
83
100
  ### 0.0.5 (2023-04-02)
84
101
 
85
- - make pipeline run
102
+ - make pipeline run
86
103
 
87
104
  ### 0.0.4 (2023-04-02)
88
105
 
89
- - make device status refresh independent from device search interval
106
+ - make device status refresh independent from device search interval
90
107
 
91
108
  ### 0.0.3 (2023-04-01)
92
109
 
93
- - update dependencies
110
+ - update dependencies
94
111
 
95
112
  ### 0.0.2
96
113
 
97
- - frequently searching for devices and requesting their specific state
98
- - on / off state, brightness and and color temperature can be controlled
114
+ - frequently searching for devices and requesting their specific state
115
+ - on / off state, brightness and and color temperature can be controlled
99
116
 
100
117
  ## License
101
118
 
@@ -0,0 +1,297 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var goveeService_exports = {};
30
+ __export(goveeService_exports, {
31
+ GoveeService: () => GoveeService
32
+ });
33
+ module.exports = __toCommonJS(goveeService_exports);
34
+ var dgram = __toESM(require("node:dgram"));
35
+ var import_node_events = require("node:events");
36
+ var import_hexTool = require("./tools/hexTool");
37
+ class GoveeService extends import_node_events.EventEmitter {
38
+ socket;
39
+ options;
40
+ devices = {};
41
+ loggedDevices = [];
42
+ searchInterval;
43
+ refreshInterval;
44
+ static LOCAL_PORT = 4002;
45
+ static SEND_SCAN_PORT = 4001;
46
+ static CONTROL_PORT = 4003;
47
+ static M_CAST = "239.255.255.250";
48
+ static scanMessage = { msg: { cmd: "scan", data: { account_topic: "reserved" } } };
49
+ static requestStatusMessage = { msg: { cmd: "devStatus", data: {} } };
50
+ /**
51
+ * Create a new GoveeService instance.
52
+ *
53
+ * @param options Configuration options for the service.
54
+ */
55
+ constructor(options) {
56
+ super();
57
+ this.options = options;
58
+ this.socket = dgram.createSocket({ type: "udp4" });
59
+ }
60
+ /**
61
+ * Bind UDP socket and start device search/refresh intervals.
62
+ */
63
+ start() {
64
+ this.socket.on("message", this.onUdpMessage.bind(this));
65
+ this.socket.on("error", (error) => {
66
+ var _a;
67
+ (_a = this.options.logger) == null ? void 0 : _a.error(`server bind error : ${error.message}`);
68
+ });
69
+ this.socket.bind(
70
+ { address: this.options.interface, port: GoveeService.LOCAL_PORT },
71
+ this.serverBound.bind(this)
72
+ );
73
+ }
74
+ serverBound() {
75
+ var _a;
76
+ this.socket.setBroadcast(true);
77
+ this.socket.setMulticastTTL(128);
78
+ this.socket.setMulticastInterface(this.options.interface);
79
+ this.socket.addMembership(GoveeService.M_CAST);
80
+ (_a = this.options.logger) == null ? void 0 : _a.debug(
81
+ `UDP listening on ${this.socket.address().address}:${this.socket.address().port}`
82
+ );
83
+ this.searchInterval = setInterval(() => this.sendScan(), this.options.searchInterval * 1e3);
84
+ this.refreshInterval = setInterval(
85
+ () => this.refreshAllDevices(),
86
+ this.options.deviceStatusRefreshInterval * 1e3
87
+ );
88
+ }
89
+ /**
90
+ * Handle incoming UDP messages.
91
+ *
92
+ * @param message The message buffer.
93
+ * @param remote The sender info.
94
+ */
95
+ onUdpMessage(message, remote) {
96
+ var _a, _b, _c;
97
+ const messageObject = JSON.parse(message.toString());
98
+ switch (messageObject.msg.cmd) {
99
+ case "scan": {
100
+ if (messageObject.msg.data.device) {
101
+ const deviceName = messageObject.msg.data.device.replace(
102
+ (_a = this.options.forbiddenChars) != null ? _a : /[^a-zA-Z0-9_-]/g,
103
+ "_"
104
+ );
105
+ this.devices[remote.address] = deviceName;
106
+ this.emit("deviceDiscovered", {
107
+ ip: remote.address,
108
+ deviceName
109
+ });
110
+ }
111
+ break;
112
+ }
113
+ case "devStatus": {
114
+ const sendingDevice = this.devices[remote.address];
115
+ if (sendingDevice) {
116
+ if (this.options.extendedLogging && !this.loggedDevices.includes(remote.address.toString())) {
117
+ (_b = this.options.logger) == null ? void 0 : _b.info(`device status message data: ${JSON.stringify(messageObject)}`);
118
+ this.loggedDevices.push(remote.address.toString());
119
+ }
120
+ this.emitDeviceStatusUpdate(sendingDevice, remote.address, messageObject);
121
+ }
122
+ break;
123
+ }
124
+ default: {
125
+ (_c = this.options.logger) == null ? void 0 : _c.debug(`message from: ${remote.address}:${remote.port} - ${message.toString()}`);
126
+ }
127
+ }
128
+ }
129
+ /**
130
+ * Send device status request to all known devices.
131
+ */
132
+ refreshAllDevices() {
133
+ for (const ip in this.devices) {
134
+ this.requestDeviceStatus(ip);
135
+ }
136
+ }
137
+ /**
138
+ * Send device status request to a specific device.
139
+ *
140
+ * @param receiver The IP address or hostname of the device.
141
+ */
142
+ requestDeviceStatus(receiver) {
143
+ const requestDeviceStatusBuffer = Buffer.from(JSON.stringify(GoveeService.requestStatusMessage));
144
+ this.socket.send(
145
+ requestDeviceStatusBuffer,
146
+ 0,
147
+ requestDeviceStatusBuffer.length,
148
+ GoveeService.CONTROL_PORT,
149
+ receiver
150
+ );
151
+ }
152
+ /**
153
+ * Send scan message to the UDP multicast address.
154
+ */
155
+ sendScan() {
156
+ var _a;
157
+ (_a = this.options.logger) == null ? void 0 : _a.debug("sending scan message");
158
+ const scanMessageBuffer = Buffer.from(JSON.stringify(GoveeService.scanMessage));
159
+ this.socket.send(
160
+ scanMessageBuffer,
161
+ 0,
162
+ scanMessageBuffer.length,
163
+ GoveeService.SEND_SCAN_PORT,
164
+ GoveeService.M_CAST
165
+ );
166
+ }
167
+ /**
168
+ * Stop all intervals and close the socket.
169
+ */
170
+ stop() {
171
+ if (this.searchInterval) {
172
+ clearInterval(this.searchInterval);
173
+ }
174
+ if (this.refreshInterval) {
175
+ clearInterval(this.refreshInterval);
176
+ }
177
+ this.socket.close();
178
+ }
179
+ /**
180
+ * Get all known devices.
181
+ */
182
+ getDevices() {
183
+ return this.devices;
184
+ }
185
+ /**
186
+ * Handles a state change and sends the appropriate command to the device.
187
+ *
188
+ * @param id The state ID
189
+ * @param state The new state object
190
+ * @param receiver The device IP as string
191
+ */
192
+ handleStateChange(id, state, receiver) {
193
+ var _a;
194
+ const stateKey = id.split(".")[4];
195
+ switch (stateKey) {
196
+ case "onOff":
197
+ this.sendTurnCommand(receiver, !!state.val);
198
+ break;
199
+ case "brightness":
200
+ this.sendBrightnessCommand(receiver, Number(state.val));
201
+ break;
202
+ case "colorTemInKelvin":
203
+ this.sendColorTempCommand(receiver, Number(state.val));
204
+ break;
205
+ case "color": {
206
+ const colorValue = (_a = state.val) == null ? void 0 : _a.toString();
207
+ if (colorValue) {
208
+ this.sendColorCommand(receiver, colorValue);
209
+ }
210
+ break;
211
+ }
212
+ }
213
+ }
214
+ /**
215
+ * Send a turn on/off command to a device.
216
+ *
217
+ * @param receiver IP address or hostname
218
+ * @param value true for on, false for off
219
+ */
220
+ sendTurnCommand(receiver, value) {
221
+ const turnMessage = { msg: { cmd: "turn", data: { value: value ? 1 : 0 } } };
222
+ const turnMessageBuffer = Buffer.from(JSON.stringify(turnMessage));
223
+ this.socket.send(turnMessageBuffer, 0, turnMessageBuffer.length, GoveeService.CONTROL_PORT, receiver);
224
+ }
225
+ /**
226
+ * Send a brightness command to a device.
227
+ *
228
+ * @param receiver IP address or hostname
229
+ * @param value Brightness value
230
+ */
231
+ sendBrightnessCommand(receiver, value) {
232
+ const brightnessMessage = { msg: { cmd: "brightness", data: { value } } };
233
+ const brightnessMessageBuffer = Buffer.from(JSON.stringify(brightnessMessage));
234
+ this.socket.send(
235
+ brightnessMessageBuffer,
236
+ 0,
237
+ brightnessMessageBuffer.length,
238
+ GoveeService.CONTROL_PORT,
239
+ receiver
240
+ );
241
+ }
242
+ /**
243
+ * Send a color temperature command to a device.
244
+ *
245
+ * @param receiver IP address or hostname
246
+ * @param kelvin Color temperature in Kelvin
247
+ */
248
+ sendColorTempCommand(receiver, kelvin) {
249
+ const colorTempMessageBuffer = Buffer.from(
250
+ JSON.stringify({
251
+ msg: {
252
+ cmd: "colorwc",
253
+ data: { color: { r: 0, g: 0, b: 0 }, colorTemInKelvin: kelvin }
254
+ }
255
+ })
256
+ );
257
+ this.socket.send(colorTempMessageBuffer, 0, colorTempMessageBuffer.length, GoveeService.CONTROL_PORT, receiver);
258
+ }
259
+ /**
260
+ * Send a color command to a device.
261
+ *
262
+ * @param receiver IP address or hostname
263
+ * @param hexColor Color as hex string (e.g. #FFAABB)
264
+ */
265
+ sendColorCommand(receiver, hexColor) {
266
+ const rgb = (0, import_hexTool.hexToRgb)(hexColor);
267
+ const colorMessage = { msg: { cmd: "colorwc", data: { color: rgb } } };
268
+ const colorMessageBuffer = Buffer.from(JSON.stringify(colorMessage));
269
+ this.socket.send(colorMessageBuffer, 0, colorMessageBuffer.length, GoveeService.CONTROL_PORT, receiver);
270
+ }
271
+ /**
272
+ * Emit device status update event with parsed status data.
273
+ *
274
+ * @param deviceName The device name.
275
+ * @param ip The device IP address.
276
+ * @param messageObject The parsed message object containing device status.
277
+ */
278
+ emitDeviceStatusUpdate(deviceName, ip, messageObject) {
279
+ const deviceData = messageObject.msg.data;
280
+ const colorString = `#${(0, import_hexTool.componentToHex)(deviceData.color.r)}${(0, import_hexTool.componentToHex)(deviceData.color.g)}${(0, import_hexTool.componentToHex)(deviceData.color.b)}`;
281
+ this.emit("deviceStatusUpdate", {
282
+ deviceName,
283
+ ip,
284
+ status: {
285
+ onOff: deviceData.onOff === 1,
286
+ brightness: deviceData.brightness,
287
+ color: colorString,
288
+ colorTemInKelvin: deviceData.colorTemInKelvin
289
+ }
290
+ });
291
+ }
292
+ }
293
+ // Annotate the CommonJS export names for ESM import in node:
294
+ 0 && (module.exports = {
295
+ GoveeService
296
+ });
297
+ //# sourceMappingURL=goveeService.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 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';\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\t\tthis.searchInterval = setInterval(() => this.sendScan(), this.options.searchInterval * 1000);\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 * 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": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,17 @@
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 __copyProps = (to, from, except, desc) => {
7
+ if (from && typeof from === "object" || typeof from === "function") {
8
+ for (let key of __getOwnPropNames(from))
9
+ if (!__hasOwnProp.call(to, key) && key !== except)
10
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
11
+ }
12
+ return to;
13
+ };
14
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
15
+ var goveeServiceOptions_exports = {};
16
+ module.exports = __toCommonJS(goveeServiceOptions_exports);
17
+ //# sourceMappingURL=goveeServiceOptions.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/lib/goveeServiceOptions.ts"],
4
+ "sourcesContent": ["/**\n * Configuration options for the GoveeService.\n */\nexport interface GoveeServiceOptions {\n\t/** Network interface to bind UDP socket to. */\n\tinterface: string;\n\t/** Interval in seconds for device search. */\n\tsearchInterval: number;\n\t/** Interval in seconds for device status refresh. */\n\tdeviceStatusRefreshInterval: number;\n\t/** Enable extended debug/info logging. */\n\textendedLogging?: boolean;\n\t/** RegExp for forbidden characters in device names. */\n\tforbiddenChars?: RegExp;\n\t/** Logger object with debug/info/error methods. */\n\tlogger?: { debug: (msg: string) => void; info: (msg: string) => void; error: (msg: string) => void };\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;AAAA;AAAA;",
6
+ "names": []
7
+ }
@@ -0,0 +1,46 @@
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 datapointTools_exports = {};
20
+ __export(datapointTools_exports, {
21
+ getDatapointDescription: () => getDatapointDescription
22
+ });
23
+ module.exports = __toCommonJS(datapointTools_exports);
24
+ function getDatapointDescription(name) {
25
+ switch (name) {
26
+ case "model":
27
+ return "Specific model of the Lamp";
28
+ case "ip":
29
+ return "IP address of the Lamp";
30
+ case "bleVersionHard":
31
+ return "Bluetooth Low Energy Hardware Version";
32
+ case "bleVersionSoft":
33
+ return "Bluetooth Low Energy Software Version";
34
+ case "wifiVersionHard":
35
+ return "WiFi Hardware Version";
36
+ case "wifiVersionSoft":
37
+ return "WiFi Software Version";
38
+ default:
39
+ return "";
40
+ }
41
+ }
42
+ // Annotate the CommonJS export names for ESM import in node:
43
+ 0 && (module.exports = {
44
+ getDatapointDescription
45
+ });
46
+ //# sourceMappingURL=datapointTools.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/lib/tools/datapointTools.ts"],
4
+ "sourcesContent": ["/**\n * Returns the description for device information datapoints.\n * This helps to keep the main adapter class clean and centralizes datapoint metadata.\n *\n * @param name The name of the parameter retrieved from the device\n * @returns The description to be set to the datapoint\n */\nexport function getDatapointDescription(name: string): string {\n\tswitch (name) {\n\t\tcase 'model':\n\t\t\treturn 'Specific model of the Lamp';\n\t\tcase 'ip':\n\t\t\treturn 'IP address of the Lamp';\n\t\tcase 'bleVersionHard':\n\t\t\treturn 'Bluetooth Low Energy Hardware Version';\n\t\tcase 'bleVersionSoft':\n\t\t\treturn 'Bluetooth Low Energy Software Version';\n\t\tcase 'wifiVersionHard':\n\t\t\treturn 'WiFi Hardware Version';\n\t\tcase 'wifiVersionSoft':\n\t\t\treturn 'WiFi Software Version';\n\t\tdefault:\n\t\t\treturn '';\n\t}\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAOO,SAAS,wBAAwB,MAAsB;AAC7D,UAAQ,MAAM;AAAA,IACb,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO;AAAA,IACR;AACC,aAAO;AAAA,EACT;AACD;",
6
+ "names": []
7
+ }
@@ -23,17 +23,19 @@ __export(hexTool_exports, {
23
23
  });
24
24
  module.exports = __toCommonJS(hexTool_exports);
25
25
  function componentToHex(c) {
26
- const hex = c.toString(16);
27
- return hex.length == 1 ? "0" + hex : hex;
26
+ const hex = c.toString(16).toUpperCase();
27
+ return hex.padStart(2, "0");
28
28
  }
29
- function hexToRgb(hexString) {
30
- if (!/^#[0-9a-fA-F]{6}$/i.test(hexString)) {
31
- throw new Error("Invalid hex string");
29
+ function hexToRgb(hex) {
30
+ hex = hex.replace(/^#/, "");
31
+ if (hex.length === 3) {
32
+ hex = hex.split("").map((x) => x + x).join("");
32
33
  }
34
+ const num = parseInt(hex, 16);
33
35
  return {
34
- r: parseInt(hexString.slice(1, 3), 16),
35
- g: parseInt(hexString.slice(3, 5), 16),
36
- b: parseInt(hexString.slice(5, 7), 16)
36
+ r: num >> 16 & 255,
37
+ g: num >> 8 & 255,
38
+ b: num & 255
37
39
  };
38
40
  }
39
41
  // Annotate the CommonJS export names for ESM import in node:
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/lib/tools/hexTool.ts"],
4
+ "sourcesContent": ["/**\n * Converts a number (0-255) to a two-digit hexadecimal string.\n *\n * @param c The number to convert.\n * @returns Hex string (e.g. '0A', 'FF').\n */\nexport function componentToHex(c: number): string {\n\tconst hex = c.toString(16).toUpperCase();\n\treturn hex.padStart(2, '0');\n}\n\n/**\n * Converts a hex color string (e.g. '#FFAABB') to an RGB object.\n *\n * @param hex The hex color string.\n * @returns An object with r, g, b properties.\n */\nexport function hexToRgb(hex: string): { r: number; g: number; b: number } {\n\t// Remove leading # if present\n\thex = hex.replace(/^#/, '');\n\tif (hex.length === 3) {\n\t\thex = hex\n\t\t\t.split('')\n\t\t\t.map((x) => x + x)\n\t\t\t.join('');\n\t}\n\tconst num = parseInt(hex, 16);\n\treturn {\n\t\tr: (num >> 16) & 255,\n\t\tg: (num >> 8) & 255,\n\t\tb: num & 255,\n\t};\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAMO,SAAS,eAAe,GAAmB;AACjD,QAAM,MAAM,EAAE,SAAS,EAAE,EAAE,YAAY;AACvC,SAAO,IAAI,SAAS,GAAG,GAAG;AAC3B;AAQO,SAAS,SAAS,KAAkD;AAE1E,QAAM,IAAI,QAAQ,MAAM,EAAE;AAC1B,MAAI,IAAI,WAAW,GAAG;AACrB,UAAM,IACJ,MAAM,EAAE,EACR,IAAI,CAAC,MAAM,IAAI,CAAC,EAChB,KAAK,EAAE;AAAA,EACV;AACA,QAAM,MAAM,SAAS,KAAK,EAAE;AAC5B,SAAO;AAAA,IACN,GAAI,OAAO,KAAM;AAAA,IACjB,GAAI,OAAO,IAAK;AAAA,IAChB,GAAG,MAAM;AAAA,EACV;AACD;",
6
+ "names": []
7
+ }