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 +46 -40
- package/admin/i18n/de/translations.json +19 -19
- package/admin/jsonConfig.json +23 -3
- package/build/lib/goveeService.js +18 -7
- package/build/lib/goveeService.js.map +2 -2
- package/build/main.js +7 -3
- package/build/main.js.map +2 -2
- package/io-package.json +27 -27
- package/package.json +9 -19
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.
|
|
27
|
+
### 0.4.2 (2025-11-08)
|
|
28
28
|
|
|
29
|
-
-
|
|
30
|
-
-
|
|
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.
|
|
32
|
+
### 0.4.1 (2025-11-07)
|
|
33
|
+
|
|
34
|
+
- fix logging
|
|
35
|
+
- fix connection state
|
|
35
36
|
|
|
36
|
-
|
|
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.
|
|
44
|
+
### 0.3.1 (2025-10-19)
|
|
39
45
|
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
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
|
-
-
|
|
47
|
-
-
|
|
48
|
-
-
|
|
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
|
-
-
|
|
53
|
-
-
|
|
58
|
+
- update dependencies
|
|
59
|
+
- avoid repeated overriding of states
|
|
54
60
|
|
|
55
61
|
### 0.2.7 (2024-08-18)
|
|
56
62
|
|
|
57
|
-
-
|
|
63
|
+
- update core dependencies and min required node version to 18
|
|
58
64
|
|
|
59
65
|
### 0.2.6 (2024-02-06)
|
|
60
66
|
|
|
61
|
-
-
|
|
67
|
+
- fix brightness change also changing white temperature
|
|
62
68
|
|
|
63
69
|
### 0.2.5 (2024-01-13)
|
|
64
70
|
|
|
65
|
-
-
|
|
66
|
-
-
|
|
67
|
-
-
|
|
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
|
-
-
|
|
77
|
+
- fix access
|
|
72
78
|
|
|
73
79
|
### 0.2.2 (2024-01-05)
|
|
74
80
|
|
|
75
|
-
-
|
|
81
|
+
- fix color temperature message
|
|
76
82
|
|
|
77
83
|
### 0.2.1 (2023-12-24)
|
|
78
84
|
|
|
79
|
-
-
|
|
80
|
-
-
|
|
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
|
-
-
|
|
85
|
-
-
|
|
90
|
+
- support controlling the color
|
|
91
|
+
- extended logging mode
|
|
86
92
|
|
|
87
93
|
### 0.1.2 (2023-09-06)
|
|
88
94
|
|
|
89
|
-
-
|
|
95
|
+
- change icon path and resolution
|
|
90
96
|
|
|
91
97
|
### 0.1.1 (2023-08-21)
|
|
92
98
|
|
|
93
|
-
-
|
|
99
|
+
- fix image
|
|
94
100
|
|
|
95
101
|
### 0.1.0 (2023-08-09)
|
|
96
102
|
|
|
97
|
-
-
|
|
98
|
-
-
|
|
99
|
-
-
|
|
100
|
-
-
|
|
101
|
-
-
|
|
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
|
-
-
|
|
111
|
+
- update dependencies
|
|
106
112
|
|
|
107
113
|
### 0.0.5 (2023-04-02)
|
|
108
114
|
|
|
109
|
-
-
|
|
115
|
+
- make pipeline run
|
|
110
116
|
|
|
111
117
|
### 0.0.4 (2023-04-02)
|
|
112
118
|
|
|
113
|
-
-
|
|
119
|
+
- make device status refresh independent from device search interval
|
|
114
120
|
|
|
115
121
|
### 0.0.3 (2023-04-01)
|
|
116
122
|
|
|
117
|
-
-
|
|
123
|
+
- update dependencies
|
|
118
124
|
|
|
119
125
|
### 0.0.2
|
|
120
126
|
|
|
121
|
-
-
|
|
122
|
-
-
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
}
|
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",
|
|
@@ -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.
|
|
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.
|
|
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
|
-
(
|
|
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
|
-
(
|
|
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
|
-
(
|
|
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;
|
|
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: "
|
|
174
|
+
type: "device",
|
|
172
175
|
common: {
|
|
173
|
-
name:
|
|
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": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAIA,YAAuB;AACvB,0BAAgF;AAMzE,MAAM,mBAAmB,MAAM,QAAQ;AAAA;AAAA,EAErC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMD,YAAY,UAAyC,CAAC,GAAG;AAC/D,UAAM;AAAA,MACL,GAAG;AAAA,MACH,MAAM;AAAA,IACP,CAAC;AAED,SAAK,GAAG,SAAS,KAAK,QAAQ,KAAK,IAAI,CAAC;AACxC,SAAK,GAAG,eAAe,KAAK,cAAc,KAAK,IAAI,CAAC;AAIpD,SAAK,GAAG,UAAU,KAAK,SAAS,KAAK,IAAI,CAAC;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,UAAyB;AAEtC,SAAK,KAAK,mBAAmB,mBAAmB;AAAA,MAC/C,MAAM;AAAA,MACN,QAAQ;AAAA,QACP,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,QACN,OAAO;AAAA,MACR;AAAA,MACA,QAAQ,CAAC;AAAA,IACV,CAAC;AAGD,SAAK,KAAK,mBAAmB,0BAA0B;AAAA,MACtD,MAAM;AAAA,MACN,QAAQ;AAAA,QACP,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,QACN,OAAO;AAAA,QACP,MAAM;AAAA,MACP;AAAA,MACA,QAAQ,CAAC;AAAA,IACV,CAAC;AAGD,SAAK,eAAe,IAAI,iCAAa;AAAA,MACpC,WAAW,KAAK,OAAO;AAAA,MACvB,gBAAgB,KAAK,OAAO;AAAA,MAC5B,6BAA6B,KAAK,OAAO;AAAA,MACzC,iBAAiB,KAAK,OAAO;AAAA,MAC7B,gBAAgB;AAAA,MAChB,mBAAmB,KAAK,OAAO;AAAA,MAC/B,sBAAsB,KAAK,OAAO;AAAA,MAClC,QAAQ;AAAA,QACP,OAAO,CAAC,QAAQ,KAAK,IAAI,MAAM,GAAG;AAAA,QAClC,MAAM,CAAC,QAAQ,KAAK,IAAI,KAAK,GAAG;AAAA,QAChC,OAAO,CAAC,QAAQ,KAAK,IAAI,MAAM,GAAG;AAAA,MACnC;AAAA,IACD,CAAC;AAGD,SAAK,aAAa,GAAG,oBAAoB,CAAC,SAAS;AAClD,WAAK,KAAK,uBAAuB,IAAI;AAAA,IACtC,CAAC;AAED,SAAK,aAAa,GAAG,sBAAsB,CAAC,SAAS;AACpD,WAAK,KAAK,yBAAyB,IAAI;AAAA,IACxC,CAAC;
|
|
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.
|
|
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.
|
|
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": "^
|
|
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": [
|