iobroker.govee-local 0.4.1 → 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 +5 -4
- package/admin/jsonConfig.json +23 -3
- package/build/lib/goveeService.js +10 -4
- package/build/lib/goveeService.js.map +2 -2
- package/build/main.js +4 -3
- package/build/main.js.map +2 -2
- package/io-package.json +14 -14
- package/package.json +8 -18
package/README.md
CHANGED
|
@@ -24,6 +24,11 @@ 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.2 (2025-11-08)
|
|
28
|
+
|
|
29
|
+
- update dependencies
|
|
30
|
+
- fix device name / model identifier in the objects
|
|
31
|
+
|
|
27
32
|
### 0.4.1 (2025-11-07)
|
|
28
33
|
|
|
29
34
|
- fix logging
|
|
@@ -38,10 +43,6 @@ For **Windows** Users: Please go to the settings and make sure you choose the co
|
|
|
38
43
|
|
|
39
44
|
### 0.3.1 (2025-10-19)
|
|
40
45
|
|
|
41
|
-
- updated release script run configuration
|
|
42
|
-
|
|
43
|
-
### 0.3.0 (2025-10-19)
|
|
44
|
-
|
|
45
46
|
- Refactored: Moved all device logic to a dedicated GoveeService class, modularized utilities
|
|
46
47
|
- Improved test coverage
|
|
47
48
|
- Dependencies updated
|
package/admin/jsonConfig.json
CHANGED
|
@@ -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",
|
|
@@ -106,7 +106,7 @@ class GoveeService extends import_node_events.EventEmitter {
|
|
|
106
106
|
* @param remote The sender info.
|
|
107
107
|
*/
|
|
108
108
|
onUdpMessage(message, remote) {
|
|
109
|
-
var _a, _b, _c;
|
|
109
|
+
var _a, _b, _c, _d;
|
|
110
110
|
const messageObject = JSON.parse(message.toString());
|
|
111
111
|
switch (messageObject.msg.cmd) {
|
|
112
112
|
case "scan": {
|
|
@@ -116,9 +116,15 @@ class GoveeService extends import_node_events.EventEmitter {
|
|
|
116
116
|
"_"
|
|
117
117
|
);
|
|
118
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
|
+
}
|
|
119
124
|
this.emit("deviceDiscovered", {
|
|
120
125
|
ip: remote.address,
|
|
121
|
-
deviceName
|
|
126
|
+
deviceName,
|
|
127
|
+
deviceModel: messageObject.msg.data.sku
|
|
122
128
|
});
|
|
123
129
|
}
|
|
124
130
|
break;
|
|
@@ -127,7 +133,7 @@ class GoveeService extends import_node_events.EventEmitter {
|
|
|
127
133
|
const sendingDevice = this.devices[remote.address];
|
|
128
134
|
if (sendingDevice) {
|
|
129
135
|
if (this.options.extendedLogging && !this.loggedDevices.includes(remote.address.toString())) {
|
|
130
|
-
(
|
|
136
|
+
(_c = this.options.logger) == null ? void 0 : _c.info(`device status message data: ${JSON.stringify(messageObject)}`);
|
|
131
137
|
this.loggedDevices.push(remote.address.toString());
|
|
132
138
|
}
|
|
133
139
|
this.emitDeviceStatusUpdate(sendingDevice, remote.address, messageObject);
|
|
@@ -135,7 +141,7 @@ class GoveeService extends import_node_events.EventEmitter {
|
|
|
135
141
|
break;
|
|
136
142
|
}
|
|
137
143
|
default: {
|
|
138
|
-
(
|
|
144
|
+
(_d = this.options.logger) == null ? void 0 : _d.debug(`message from: ${remote.address}:${remote.port} - ${message.toString()}`);
|
|
139
145
|
}
|
|
140
146
|
}
|
|
141
147
|
}
|
|
@@ -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// 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\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\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": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAKA,YAAuB;AACvB,yBAA6B;AAE7B,qBAAyC;AACzC,0BAAiC;
|
|
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
|
@@ -169,11 +169,12 @@ class GoveeLocal extends utils.Adapter {
|
|
|
169
169
|
* @param event The device discovery event data.
|
|
170
170
|
*/
|
|
171
171
|
async handleDeviceDiscovered(event) {
|
|
172
|
-
const { ip, deviceName } = event;
|
|
172
|
+
const { ip, deviceName, deviceModel } = event;
|
|
173
173
|
await this.setObjectNotExistsAsync(deviceName, {
|
|
174
|
-
type: "
|
|
174
|
+
type: "device",
|
|
175
175
|
common: {
|
|
176
|
-
name:
|
|
176
|
+
name: deviceModel,
|
|
177
|
+
role: "group"
|
|
177
178
|
},
|
|
178
179
|
native: {}
|
|
179
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\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 } = 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,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "govee-local",
|
|
4
|
-
"version": "0.4.
|
|
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
|
+
},
|
|
6
19
|
"0.4.1": {
|
|
7
20
|
"en": "fix logging\nfix connection state",
|
|
8
21
|
"de": "fehlerbehebung\nbefestigungszustand",
|
|
@@ -80,19 +93,6 @@
|
|
|
80
93
|
"pl": "aktualizacji zależności",
|
|
81
94
|
"uk": "оновлення залежності",
|
|
82
95
|
"zh-cn": "更新依赖关系"
|
|
83
|
-
},
|
|
84
|
-
"0.2.7": {
|
|
85
|
-
"en": "update core dependencies and min required node version to 18",
|
|
86
|
-
"de": "aktualisieren von kernabhängigkeiten und min benötigte knotenversion bis 18",
|
|
87
|
-
"ru": "обновление основных зависимостей и мин требуется версия узла до 18",
|
|
88
|
-
"pt": "atualizar as dependências do núcleo e a versão mínima exigida do nó para 18",
|
|
89
|
-
"nl": "update core afhankelijkheden en min vereist node versie naar 18",
|
|
90
|
-
"fr": "mettre à jour les dépendances de base et la version de nœud min requise à 18",
|
|
91
|
-
"it": "aggiornare le dipendenze del nucleo e la versione del nodo richiesto min a 18",
|
|
92
|
-
"es": "actualización de las dependencias básicas y la versión de los nodos necesarios min a 18",
|
|
93
|
-
"pl": "aktualizacji zależności rdzenia i min wymagana wersja węzła do 18",
|
|
94
|
-
"uk": "оновлення основних залежностей і обов'язкової версії вершини до 18",
|
|
95
|
-
"zh-cn": "将核心依赖和分钟需要的节点版本更新到 18"
|
|
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.
|
|
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",
|
|
@@ -29,31 +29,21 @@
|
|
|
29
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.
|
|
33
|
-
"@iobroker/eslint-config": "^2.
|
|
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/
|
|
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
|
-
"
|
|
44
|
-
"
|
|
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
|
|
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.
|
|
46
|
+
"typescript": "^5.9.3"
|
|
57
47
|
},
|
|
58
48
|
"main": "build/main.js",
|
|
59
49
|
"files": [
|