iobroker.govee-local 0.2.9 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -26
- package/build/lib/goveeService.js +297 -0
- package/build/lib/goveeService.js.map +7 -0
- package/build/lib/goveeServiceOptions.js +17 -0
- package/build/lib/goveeServiceOptions.js.map +7 -0
- package/build/lib/tools/datapointTools.js +46 -0
- package/build/lib/tools/datapointTools.js.map +7 -0
- package/build/{tools → lib/tools}/hexTool.js +10 -8
- package/build/lib/tools/hexTool.js.map +7 -0
- package/build/main.js +143 -269
- package/build/main.js.map +2 -2
- package/io-package.json +29 -29
- package/package.json +10 -10
- package/build/tools/hexTool.js.map +0 -7
package/README.md
CHANGED
|
@@ -24,6 +24,16 @@ For **Windows** Users: Please go to the settings and make sure you choose the co
|
|
|
24
24
|
Placeholder for the next version (at the beginning of the line):
|
|
25
25
|
### **WORK IN PROGRESS**
|
|
26
26
|
-->
|
|
27
|
+
### 0.3.1 (2025-10-19)
|
|
28
|
+
|
|
29
|
+
- updated release script run configuration
|
|
30
|
+
|
|
31
|
+
### 0.3.0 (2025-10-19)
|
|
32
|
+
|
|
33
|
+
- Refactored: Moved all device logic to a dedicated GoveeService class, modularized utilities
|
|
34
|
+
- Improved test coverage
|
|
35
|
+
- Dependencies updated
|
|
36
|
+
|
|
27
37
|
### 0.2.9 (2025-08-22)
|
|
28
38
|
|
|
29
39
|
- update dependencies
|
|
@@ -32,77 +42,77 @@ For **Windows** Users: Please go to the settings and make sure you choose the co
|
|
|
32
42
|
|
|
33
43
|
### 0.2.8 (2025-03-28)
|
|
34
44
|
|
|
35
|
-
-
|
|
36
|
-
-
|
|
45
|
+
- update dependencies
|
|
46
|
+
- avoid repeated overriding of states
|
|
37
47
|
|
|
38
48
|
### 0.2.7 (2024-08-18)
|
|
39
49
|
|
|
40
|
-
-
|
|
50
|
+
- update core dependencies and min required node version to 18
|
|
41
51
|
|
|
42
52
|
### 0.2.6 (2024-02-06)
|
|
43
53
|
|
|
44
|
-
-
|
|
54
|
+
- fix brightness change also changing white temperature
|
|
45
55
|
|
|
46
56
|
### 0.2.5 (2024-01-13)
|
|
47
57
|
|
|
48
|
-
-
|
|
49
|
-
-
|
|
50
|
-
-
|
|
58
|
+
- create only one socket, as the second seems not to be necessary
|
|
59
|
+
- refactoring the code for better structure
|
|
60
|
+
- possibility to choose listen interface in settings
|
|
51
61
|
|
|
52
62
|
### 0.2.4 (2024-01-05)
|
|
53
63
|
|
|
54
|
-
-
|
|
64
|
+
- fix access
|
|
55
65
|
|
|
56
66
|
### 0.2.2 (2024-01-05)
|
|
57
67
|
|
|
58
|
-
-
|
|
68
|
+
- fix color temperature message
|
|
59
69
|
|
|
60
70
|
### 0.2.1 (2023-12-24)
|
|
61
71
|
|
|
62
|
-
-
|
|
63
|
-
-
|
|
72
|
+
- repair onOff / all other actions Fixes: [#65](https://github.com/boergegrunicke/ioBroker.govee-local/issues/65)
|
|
73
|
+
- fix log spamming because of wildcard
|
|
64
74
|
|
|
65
75
|
### 0.2.0 (2023-12-17)
|
|
66
76
|
|
|
67
|
-
-
|
|
68
|
-
-
|
|
77
|
+
- support controlling the color
|
|
78
|
+
- extended logging mode
|
|
69
79
|
|
|
70
80
|
### 0.1.2 (2023-09-06)
|
|
71
81
|
|
|
72
|
-
-
|
|
82
|
+
- change icon path and resolution
|
|
73
83
|
|
|
74
84
|
### 0.1.1 (2023-08-21)
|
|
75
85
|
|
|
76
|
-
-
|
|
86
|
+
- fix image
|
|
77
87
|
|
|
78
88
|
### 0.1.0 (2023-08-09)
|
|
79
89
|
|
|
80
|
-
-
|
|
81
|
-
-
|
|
82
|
-
-
|
|
83
|
-
-
|
|
84
|
-
-
|
|
90
|
+
- make search intervals configurable
|
|
91
|
+
- clear all timeouts, when adapter is stopped
|
|
92
|
+
- replace forbidden characters in names
|
|
93
|
+
- update translations
|
|
94
|
+
- update dependencies
|
|
85
95
|
|
|
86
96
|
### 0.0.6 (2023-05-18)
|
|
87
97
|
|
|
88
|
-
-
|
|
98
|
+
- update dependencies
|
|
89
99
|
|
|
90
100
|
### 0.0.5 (2023-04-02)
|
|
91
101
|
|
|
92
|
-
-
|
|
102
|
+
- make pipeline run
|
|
93
103
|
|
|
94
104
|
### 0.0.4 (2023-04-02)
|
|
95
105
|
|
|
96
|
-
-
|
|
106
|
+
- make device status refresh independent from device search interval
|
|
97
107
|
|
|
98
108
|
### 0.0.3 (2023-04-01)
|
|
99
109
|
|
|
100
|
-
-
|
|
110
|
+
- update dependencies
|
|
101
111
|
|
|
102
112
|
### 0.0.2
|
|
103
113
|
|
|
104
|
-
-
|
|
105
|
-
-
|
|
114
|
+
- frequently searching for devices and requesting their specific state
|
|
115
|
+
- on / off state, brightness and and color temperature can be controlled
|
|
106
116
|
|
|
107
117
|
## License
|
|
108
118
|
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var goveeService_exports = {};
|
|
30
|
+
__export(goveeService_exports, {
|
|
31
|
+
GoveeService: () => GoveeService
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(goveeService_exports);
|
|
34
|
+
var dgram = __toESM(require("node:dgram"));
|
|
35
|
+
var import_node_events = require("node:events");
|
|
36
|
+
var import_hexTool = require("./tools/hexTool");
|
|
37
|
+
class GoveeService extends import_node_events.EventEmitter {
|
|
38
|
+
socket;
|
|
39
|
+
options;
|
|
40
|
+
devices = {};
|
|
41
|
+
loggedDevices = [];
|
|
42
|
+
searchInterval;
|
|
43
|
+
refreshInterval;
|
|
44
|
+
static LOCAL_PORT = 4002;
|
|
45
|
+
static SEND_SCAN_PORT = 4001;
|
|
46
|
+
static CONTROL_PORT = 4003;
|
|
47
|
+
static M_CAST = "239.255.255.250";
|
|
48
|
+
static scanMessage = { msg: { cmd: "scan", data: { account_topic: "reserved" } } };
|
|
49
|
+
static requestStatusMessage = { msg: { cmd: "devStatus", data: {} } };
|
|
50
|
+
/**
|
|
51
|
+
* Create a new GoveeService instance.
|
|
52
|
+
*
|
|
53
|
+
* @param options Configuration options for the service.
|
|
54
|
+
*/
|
|
55
|
+
constructor(options) {
|
|
56
|
+
super();
|
|
57
|
+
this.options = options;
|
|
58
|
+
this.socket = dgram.createSocket({ type: "udp4" });
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Bind UDP socket and start device search/refresh intervals.
|
|
62
|
+
*/
|
|
63
|
+
start() {
|
|
64
|
+
this.socket.on("message", this.onUdpMessage.bind(this));
|
|
65
|
+
this.socket.on("error", (error) => {
|
|
66
|
+
var _a;
|
|
67
|
+
(_a = this.options.logger) == null ? void 0 : _a.error(`server bind error : ${error.message}`);
|
|
68
|
+
});
|
|
69
|
+
this.socket.bind(
|
|
70
|
+
{ address: this.options.interface, port: GoveeService.LOCAL_PORT },
|
|
71
|
+
this.serverBound.bind(this)
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
serverBound() {
|
|
75
|
+
var _a;
|
|
76
|
+
this.socket.setBroadcast(true);
|
|
77
|
+
this.socket.setMulticastTTL(128);
|
|
78
|
+
this.socket.setMulticastInterface(this.options.interface);
|
|
79
|
+
this.socket.addMembership(GoveeService.M_CAST);
|
|
80
|
+
(_a = this.options.logger) == null ? void 0 : _a.debug(
|
|
81
|
+
`UDP listening on ${this.socket.address().address}:${this.socket.address().port}`
|
|
82
|
+
);
|
|
83
|
+
this.searchInterval = setInterval(() => this.sendScan(), this.options.searchInterval * 1e3);
|
|
84
|
+
this.refreshInterval = setInterval(
|
|
85
|
+
() => this.refreshAllDevices(),
|
|
86
|
+
this.options.deviceStatusRefreshInterval * 1e3
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Handle incoming UDP messages.
|
|
91
|
+
*
|
|
92
|
+
* @param message The message buffer.
|
|
93
|
+
* @param remote The sender info.
|
|
94
|
+
*/
|
|
95
|
+
onUdpMessage(message, remote) {
|
|
96
|
+
var _a, _b, _c;
|
|
97
|
+
const messageObject = JSON.parse(message.toString());
|
|
98
|
+
switch (messageObject.msg.cmd) {
|
|
99
|
+
case "scan": {
|
|
100
|
+
if (messageObject.msg.data.device) {
|
|
101
|
+
const deviceName = messageObject.msg.data.device.replace(
|
|
102
|
+
(_a = this.options.forbiddenChars) != null ? _a : /[^a-zA-Z0-9_-]/g,
|
|
103
|
+
"_"
|
|
104
|
+
);
|
|
105
|
+
this.devices[remote.address] = deviceName;
|
|
106
|
+
this.emit("deviceDiscovered", {
|
|
107
|
+
ip: remote.address,
|
|
108
|
+
deviceName
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
case "devStatus": {
|
|
114
|
+
const sendingDevice = this.devices[remote.address];
|
|
115
|
+
if (sendingDevice) {
|
|
116
|
+
if (this.options.extendedLogging && !this.loggedDevices.includes(remote.address.toString())) {
|
|
117
|
+
(_b = this.options.logger) == null ? void 0 : _b.info(`device status message data: ${JSON.stringify(messageObject)}`);
|
|
118
|
+
this.loggedDevices.push(remote.address.toString());
|
|
119
|
+
}
|
|
120
|
+
this.emitDeviceStatusUpdate(sendingDevice, remote.address, messageObject);
|
|
121
|
+
}
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
default: {
|
|
125
|
+
(_c = this.options.logger) == null ? void 0 : _c.debug(`message from: ${remote.address}:${remote.port} - ${message.toString()}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Send device status request to all known devices.
|
|
131
|
+
*/
|
|
132
|
+
refreshAllDevices() {
|
|
133
|
+
for (const ip in this.devices) {
|
|
134
|
+
this.requestDeviceStatus(ip);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Send device status request to a specific device.
|
|
139
|
+
*
|
|
140
|
+
* @param receiver The IP address or hostname of the device.
|
|
141
|
+
*/
|
|
142
|
+
requestDeviceStatus(receiver) {
|
|
143
|
+
const requestDeviceStatusBuffer = Buffer.from(JSON.stringify(GoveeService.requestStatusMessage));
|
|
144
|
+
this.socket.send(
|
|
145
|
+
requestDeviceStatusBuffer,
|
|
146
|
+
0,
|
|
147
|
+
requestDeviceStatusBuffer.length,
|
|
148
|
+
GoveeService.CONTROL_PORT,
|
|
149
|
+
receiver
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Send scan message to the UDP multicast address.
|
|
154
|
+
*/
|
|
155
|
+
sendScan() {
|
|
156
|
+
var _a;
|
|
157
|
+
(_a = this.options.logger) == null ? void 0 : _a.debug("sending scan message");
|
|
158
|
+
const scanMessageBuffer = Buffer.from(JSON.stringify(GoveeService.scanMessage));
|
|
159
|
+
this.socket.send(
|
|
160
|
+
scanMessageBuffer,
|
|
161
|
+
0,
|
|
162
|
+
scanMessageBuffer.length,
|
|
163
|
+
GoveeService.SEND_SCAN_PORT,
|
|
164
|
+
GoveeService.M_CAST
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Stop all intervals and close the socket.
|
|
169
|
+
*/
|
|
170
|
+
stop() {
|
|
171
|
+
if (this.searchInterval) {
|
|
172
|
+
clearInterval(this.searchInterval);
|
|
173
|
+
}
|
|
174
|
+
if (this.refreshInterval) {
|
|
175
|
+
clearInterval(this.refreshInterval);
|
|
176
|
+
}
|
|
177
|
+
this.socket.close();
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Get all known devices.
|
|
181
|
+
*/
|
|
182
|
+
getDevices() {
|
|
183
|
+
return this.devices;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Handles a state change and sends the appropriate command to the device.
|
|
187
|
+
*
|
|
188
|
+
* @param id The state ID
|
|
189
|
+
* @param state The new state object
|
|
190
|
+
* @param receiver The device IP as string
|
|
191
|
+
*/
|
|
192
|
+
handleStateChange(id, state, receiver) {
|
|
193
|
+
var _a;
|
|
194
|
+
const stateKey = id.split(".")[4];
|
|
195
|
+
switch (stateKey) {
|
|
196
|
+
case "onOff":
|
|
197
|
+
this.sendTurnCommand(receiver, !!state.val);
|
|
198
|
+
break;
|
|
199
|
+
case "brightness":
|
|
200
|
+
this.sendBrightnessCommand(receiver, Number(state.val));
|
|
201
|
+
break;
|
|
202
|
+
case "colorTemInKelvin":
|
|
203
|
+
this.sendColorTempCommand(receiver, Number(state.val));
|
|
204
|
+
break;
|
|
205
|
+
case "color": {
|
|
206
|
+
const colorValue = (_a = state.val) == null ? void 0 : _a.toString();
|
|
207
|
+
if (colorValue) {
|
|
208
|
+
this.sendColorCommand(receiver, colorValue);
|
|
209
|
+
}
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Send a turn on/off command to a device.
|
|
216
|
+
*
|
|
217
|
+
* @param receiver IP address or hostname
|
|
218
|
+
* @param value true for on, false for off
|
|
219
|
+
*/
|
|
220
|
+
sendTurnCommand(receiver, value) {
|
|
221
|
+
const turnMessage = { msg: { cmd: "turn", data: { value: value ? 1 : 0 } } };
|
|
222
|
+
const turnMessageBuffer = Buffer.from(JSON.stringify(turnMessage));
|
|
223
|
+
this.socket.send(turnMessageBuffer, 0, turnMessageBuffer.length, GoveeService.CONTROL_PORT, receiver);
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Send a brightness command to a device.
|
|
227
|
+
*
|
|
228
|
+
* @param receiver IP address or hostname
|
|
229
|
+
* @param value Brightness value
|
|
230
|
+
*/
|
|
231
|
+
sendBrightnessCommand(receiver, value) {
|
|
232
|
+
const brightnessMessage = { msg: { cmd: "brightness", data: { value } } };
|
|
233
|
+
const brightnessMessageBuffer = Buffer.from(JSON.stringify(brightnessMessage));
|
|
234
|
+
this.socket.send(
|
|
235
|
+
brightnessMessageBuffer,
|
|
236
|
+
0,
|
|
237
|
+
brightnessMessageBuffer.length,
|
|
238
|
+
GoveeService.CONTROL_PORT,
|
|
239
|
+
receiver
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Send a color temperature command to a device.
|
|
244
|
+
*
|
|
245
|
+
* @param receiver IP address or hostname
|
|
246
|
+
* @param kelvin Color temperature in Kelvin
|
|
247
|
+
*/
|
|
248
|
+
sendColorTempCommand(receiver, kelvin) {
|
|
249
|
+
const colorTempMessageBuffer = Buffer.from(
|
|
250
|
+
JSON.stringify({
|
|
251
|
+
msg: {
|
|
252
|
+
cmd: "colorwc",
|
|
253
|
+
data: { color: { r: 0, g: 0, b: 0 }, colorTemInKelvin: kelvin }
|
|
254
|
+
}
|
|
255
|
+
})
|
|
256
|
+
);
|
|
257
|
+
this.socket.send(colorTempMessageBuffer, 0, colorTempMessageBuffer.length, GoveeService.CONTROL_PORT, receiver);
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Send a color command to a device.
|
|
261
|
+
*
|
|
262
|
+
* @param receiver IP address or hostname
|
|
263
|
+
* @param hexColor Color as hex string (e.g. #FFAABB)
|
|
264
|
+
*/
|
|
265
|
+
sendColorCommand(receiver, hexColor) {
|
|
266
|
+
const rgb = (0, import_hexTool.hexToRgb)(hexColor);
|
|
267
|
+
const colorMessage = { msg: { cmd: "colorwc", data: { color: rgb } } };
|
|
268
|
+
const colorMessageBuffer = Buffer.from(JSON.stringify(colorMessage));
|
|
269
|
+
this.socket.send(colorMessageBuffer, 0, colorMessageBuffer.length, GoveeService.CONTROL_PORT, receiver);
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Emit device status update event with parsed status data.
|
|
273
|
+
*
|
|
274
|
+
* @param deviceName The device name.
|
|
275
|
+
* @param ip The device IP address.
|
|
276
|
+
* @param messageObject The parsed message object containing device status.
|
|
277
|
+
*/
|
|
278
|
+
emitDeviceStatusUpdate(deviceName, ip, messageObject) {
|
|
279
|
+
const deviceData = messageObject.msg.data;
|
|
280
|
+
const colorString = `#${(0, import_hexTool.componentToHex)(deviceData.color.r)}${(0, import_hexTool.componentToHex)(deviceData.color.g)}${(0, import_hexTool.componentToHex)(deviceData.color.b)}`;
|
|
281
|
+
this.emit("deviceStatusUpdate", {
|
|
282
|
+
deviceName,
|
|
283
|
+
ip,
|
|
284
|
+
status: {
|
|
285
|
+
onOff: deviceData.onOff === 1,
|
|
286
|
+
brightness: deviceData.brightness,
|
|
287
|
+
color: colorString,
|
|
288
|
+
colorTemInKelvin: deviceData.colorTemInKelvin
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
294
|
+
0 && (module.exports = {
|
|
295
|
+
GoveeService
|
|
296
|
+
});
|
|
297
|
+
//# sourceMappingURL=goveeService.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/lib/goveeService.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * GoveeService handles all business logic for device discovery, status updates, and UDP communication.\n * This class is independent from ioBroker.Adapter and can be tested separately.\n */\n\nimport * as dgram from 'node:dgram';\nimport { EventEmitter } from 'node:events';\nimport type { GoveeServiceOptions } from './goveeServiceOptions';\nimport { componentToHex, hexToRgb } from './tools/hexTool';\n\n/**\n * Device discovery event data.\n */\nexport interface DeviceDiscoveryEvent {\n\t/** The IP address of the discovered device. */\n\tip: string;\n\t/** The sanitized device name. */\n\tdeviceName: string;\n}\n\n/**\n * Device status update event data.\n */\nexport interface DeviceStatusEvent {\n\t/** The sanitized device name. */\n\tdeviceName: string;\n\t/** The IP address of the device. */\n\tip: string;\n\t/** The current status of the device. */\n\tstatus: {\n\t\t/** Current on/off state. */\n\t\tonOff: boolean;\n\t\t/** Current brightness level. */\n\t\tbrightness: number;\n\t\t/** Current color as hex string. */\n\t\tcolor: string;\n\t\t/** Current color temperature in Kelvin. */\n\t\tcolorTemInKelvin: number;\n\t};\n}\n\n/**\n * GoveeService handles all business logic for device discovery, status updates, and UDP communication.\n * This class is independent from ioBroker.Adapter and can be tested separately.\n *\n * Events:\n * - 'deviceDiscovered': Emitted when a new device is found\n * - 'deviceStatusUpdate': Emitted when device status is updated\n *\n * Usage:\n * const service = new GoveeService(options);\n * service.on('deviceDiscovered', (data) => { ... });\n * service.on('deviceStatusUpdate', (data) => { ... });\n * service.start();\n * // ...\n * service.stop();\n */\nexport class GoveeService extends EventEmitter {\n\tprivate socket: dgram.Socket;\n\tprivate options: GoveeServiceOptions;\n\tprivate devices: { [ip: string]: string } = {};\n\tprivate loggedDevices: string[] = [];\n\tprivate searchInterval?: NodeJS.Timeout;\n\tprivate refreshInterval?: NodeJS.Timeout;\n\n\tstatic readonly LOCAL_PORT = 4002;\n\tstatic readonly SEND_SCAN_PORT = 4001;\n\tstatic readonly CONTROL_PORT = 4003;\n\tstatic readonly M_CAST = '239.255.255.250';\n\tstatic readonly scanMessage = { msg: { cmd: 'scan', data: { account_topic: 'reserved' } } };\n\tstatic readonly requestStatusMessage = { msg: { cmd: 'devStatus', data: {} } };\n\n\t/**\n\t * Create a new GoveeService instance.\n\t *\n\t * @param options Configuration options for the service.\n\t */\n\tconstructor(options: GoveeServiceOptions) {\n\t\tsuper();\n\t\tthis.options = options;\n\t\tthis.socket = dgram.createSocket({ type: 'udp4' });\n\t}\n\n\t/**\n\t * Bind UDP socket and start device search/refresh intervals.\n\t */\n\tpublic start(): void {\n\t\tthis.socket.on('message', this.onUdpMessage.bind(this));\n\t\tthis.socket.on('error', (error) => {\n\t\t\tthis.options.logger?.error(`server bind error : ${error.message}`);\n\t\t});\n\t\tthis.socket.bind(\n\t\t\t{ address: this.options.interface, port: GoveeService.LOCAL_PORT },\n\t\t\tthis.serverBound.bind(this),\n\t\t);\n\t}\n\n\tprivate serverBound(): void {\n\t\tthis.socket.setBroadcast(true);\n\t\tthis.socket.setMulticastTTL(128);\n\t\tthis.socket.setMulticastInterface(this.options.interface);\n\t\tthis.socket.addMembership(GoveeService.M_CAST);\n\t\tthis.options.logger?.debug(\n\t\t\t`UDP listening on ${(this.socket.address() as any).address}:${(this.socket.address() as any).port}`,\n\t\t);\n\t\tthis.searchInterval = setInterval(() => this.sendScan(), this.options.searchInterval * 1000);\n\t\tthis.refreshInterval = setInterval(\n\t\t\t() => this.refreshAllDevices(),\n\t\t\tthis.options.deviceStatusRefreshInterval * 1000,\n\t\t);\n\t}\n\n\t/**\n\t * Handle incoming UDP messages.\n\t *\n\t * @param message The message buffer.\n\t * @param remote The sender info.\n\t */\n\tprivate onUdpMessage(message: Buffer, remote: dgram.RemoteInfo): void {\n\t\tconst messageObject = JSON.parse(message.toString());\n\t\tswitch (messageObject.msg.cmd) {\n\t\t\tcase 'scan': {\n\t\t\t\tif (messageObject.msg.data.device) {\n\t\t\t\t\tconst deviceName = messageObject.msg.data.device.replace(\n\t\t\t\t\t\tthis.options.forbiddenChars ?? /[^a-zA-Z0-9_-]/g,\n\t\t\t\t\t\t'_',\n\t\t\t\t\t);\n\t\t\t\t\tthis.devices[remote.address] = deviceName;\n\t\t\t\t\tthis.emit('deviceDiscovered', {\n\t\t\t\t\t\tip: remote.address,\n\t\t\t\t\t\tdeviceName: deviceName,\n\t\t\t\t\t} as DeviceDiscoveryEvent);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'devStatus': {\n\t\t\t\tconst sendingDevice = this.devices[remote.address];\n\t\t\t\tif (sendingDevice) {\n\t\t\t\t\tif (this.options.extendedLogging && !this.loggedDevices.includes(remote.address.toString())) {\n\t\t\t\t\t\tthis.options.logger?.info(`device status message data: ${JSON.stringify(messageObject)}`);\n\t\t\t\t\t\tthis.loggedDevices.push(remote.address.toString());\n\t\t\t\t\t}\n\t\t\t\t\tthis.emitDeviceStatusUpdate(sendingDevice, remote.address, messageObject);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault: {\n\t\t\t\tthis.options.logger?.debug(`message from: ${remote.address}:${remote.port} - ${message.toString()}`);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Send device status request to all known devices.\n\t */\n\tpublic refreshAllDevices(): void {\n\t\tfor (const ip in this.devices) {\n\t\t\tthis.requestDeviceStatus(ip);\n\t\t}\n\t}\n\n\t/**\n\t * Send device status request to a specific device.\n\t *\n\t * @param receiver The IP address or hostname of the device.\n\t */\n\tpublic requestDeviceStatus(receiver: string): void {\n\t\tconst requestDeviceStatusBuffer = Buffer.from(JSON.stringify(GoveeService.requestStatusMessage));\n\t\tthis.socket.send(\n\t\t\trequestDeviceStatusBuffer,\n\t\t\t0,\n\t\t\trequestDeviceStatusBuffer.length,\n\t\t\tGoveeService.CONTROL_PORT,\n\t\t\treceiver,\n\t\t);\n\t}\n\n\t/**\n\t * Send scan message to the UDP multicast address.\n\t */\n\tpublic sendScan(): void {\n\t\tthis.options.logger?.debug('sending scan message');\n\t\tconst scanMessageBuffer = Buffer.from(JSON.stringify(GoveeService.scanMessage));\n\t\tthis.socket.send(\n\t\t\tscanMessageBuffer,\n\t\t\t0,\n\t\t\tscanMessageBuffer.length,\n\t\t\tGoveeService.SEND_SCAN_PORT,\n\t\t\tGoveeService.M_CAST,\n\t\t);\n\t}\n\n\t/**\n\t * Stop all intervals and close the socket.\n\t */\n\tpublic stop(): void {\n\t\tif (this.searchInterval) {\n\t\t\tclearInterval(this.searchInterval);\n\t\t}\n\t\tif (this.refreshInterval) {\n\t\t\tclearInterval(this.refreshInterval);\n\t\t}\n\t\tthis.socket.close();\n\t}\n\n\t/**\n\t * Get all known devices.\n\t */\n\tpublic getDevices(): { [ip: string]: string } {\n\t\treturn this.devices;\n\t}\n\n\t/**\n\t * Handles a state change and sends the appropriate command to the device.\n\t *\n\t * @param id The state ID\n\t * @param state The new state object\n\t * @param receiver The device IP as string\n\t */\n\tpublic handleStateChange(id: string, state: ioBroker.State, receiver: string): void {\n\t\tconst stateKey = id.split('.')[4];\n\t\tswitch (stateKey) {\n\t\t\tcase 'onOff':\n\t\t\t\tthis.sendTurnCommand(receiver, !!state.val);\n\t\t\t\tbreak;\n\t\t\tcase 'brightness':\n\t\t\t\tthis.sendBrightnessCommand(receiver, Number(state.val));\n\t\t\t\tbreak;\n\t\t\tcase 'colorTemInKelvin':\n\t\t\t\tthis.sendColorTempCommand(receiver, Number(state.val));\n\t\t\t\tbreak;\n\t\t\tcase 'color': {\n\t\t\t\tconst colorValue = state.val?.toString();\n\t\t\t\tif (colorValue) {\n\t\t\t\t\tthis.sendColorCommand(receiver, colorValue);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\t/**\n\t * Send a turn on/off command to a device.\n\t *\n\t * @param receiver IP address or hostname\n\t * @param value true for on, false for off\n\t */\n\tpublic sendTurnCommand(receiver: string, value: boolean): void {\n\t\tconst turnMessage = { msg: { cmd: 'turn', data: { value: value ? 1 : 0 } } };\n\t\tconst turnMessageBuffer = Buffer.from(JSON.stringify(turnMessage));\n\t\tthis.socket.send(turnMessageBuffer, 0, turnMessageBuffer.length, GoveeService.CONTROL_PORT, receiver);\n\t}\n\n\t/**\n\t * Send a brightness command to a device.\n\t *\n\t * @param receiver IP address or hostname\n\t * @param value Brightness value\n\t */\n\tpublic sendBrightnessCommand(receiver: string, value: number): void {\n\t\tconst brightnessMessage = { msg: { cmd: 'brightness', data: { value } } };\n\t\tconst brightnessMessageBuffer = Buffer.from(JSON.stringify(brightnessMessage));\n\t\tthis.socket.send(\n\t\t\tbrightnessMessageBuffer,\n\t\t\t0,\n\t\t\tbrightnessMessageBuffer.length,\n\t\t\tGoveeService.CONTROL_PORT,\n\t\t\treceiver,\n\t\t);\n\t}\n\n\t/**\n\t * Send a color temperature command to a device.\n\t *\n\t * @param receiver IP address or hostname\n\t * @param kelvin Color temperature in Kelvin\n\t */\n\tpublic sendColorTempCommand(receiver: string, kelvin: number): void {\n\t\tconst colorTempMessageBuffer = Buffer.from(\n\t\t\tJSON.stringify({\n\t\t\t\tmsg: {\n\t\t\t\t\tcmd: 'colorwc',\n\t\t\t\t\tdata: { color: { r: 0, g: 0, b: 0 }, colorTemInKelvin: kelvin },\n\t\t\t\t},\n\t\t\t}),\n\t\t);\n\t\tthis.socket.send(colorTempMessageBuffer, 0, colorTempMessageBuffer.length, GoveeService.CONTROL_PORT, receiver);\n\t}\n\n\t/**\n\t * Send a color command to a device.\n\t *\n\t * @param receiver IP address or hostname\n\t * @param hexColor Color as hex string (e.g. #FFAABB)\n\t */\n\tpublic sendColorCommand(receiver: string, hexColor: string): void {\n\t\tconst rgb = hexToRgb(hexColor);\n\t\tconst colorMessage = { msg: { cmd: 'colorwc', data: { color: rgb } } };\n\t\tconst colorMessageBuffer = Buffer.from(JSON.stringify(colorMessage));\n\t\tthis.socket.send(colorMessageBuffer, 0, colorMessageBuffer.length, GoveeService.CONTROL_PORT, receiver);\n\t}\n\n\t/**\n\t * Emit device status update event with parsed status data.\n\t *\n\t * @param deviceName The device name.\n\t * @param ip The device IP address.\n\t * @param messageObject The parsed message object containing device status.\n\t */\n\tprivate emitDeviceStatusUpdate(deviceName: string, ip: string, messageObject: any): void {\n\t\tconst deviceData = messageObject.msg.data;\n\t\tconst colorString = `#${componentToHex(deviceData.color.r)}${componentToHex(deviceData.color.g)}${componentToHex(deviceData.color.b)}`;\n\n\t\tthis.emit('deviceStatusUpdate', {\n\t\t\tdeviceName: deviceName,\n\t\t\tip: ip,\n\t\t\tstatus: {\n\t\t\t\tonOff: deviceData.onOff === 1,\n\t\t\t\tbrightness: deviceData.brightness,\n\t\t\t\tcolor: colorString,\n\t\t\t\tcolorTemInKelvin: deviceData.colorTemInKelvin,\n\t\t\t},\n\t\t} as DeviceStatusEvent);\n\t}\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __copyProps = (to, from, except, desc) => {
|
|
7
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
8
|
+
for (let key of __getOwnPropNames(from))
|
|
9
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
10
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
11
|
+
}
|
|
12
|
+
return to;
|
|
13
|
+
};
|
|
14
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
15
|
+
var goveeServiceOptions_exports = {};
|
|
16
|
+
module.exports = __toCommonJS(goveeServiceOptions_exports);
|
|
17
|
+
//# sourceMappingURL=goveeServiceOptions.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/lib/goveeServiceOptions.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Configuration options for the GoveeService.\n */\nexport interface GoveeServiceOptions {\n\t/** Network interface to bind UDP socket to. */\n\tinterface: string;\n\t/** Interval in seconds for device search. */\n\tsearchInterval: number;\n\t/** Interval in seconds for device status refresh. */\n\tdeviceStatusRefreshInterval: number;\n\t/** Enable extended debug/info logging. */\n\textendedLogging?: boolean;\n\t/** RegExp for forbidden characters in device names. */\n\tforbiddenChars?: RegExp;\n\t/** Logger object with debug/info/error methods. */\n\tlogger?: { debug: (msg: string) => void; info: (msg: string) => void; error: (msg: string) => void };\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;AAAA;AAAA;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var datapointTools_exports = {};
|
|
20
|
+
__export(datapointTools_exports, {
|
|
21
|
+
getDatapointDescription: () => getDatapointDescription
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(datapointTools_exports);
|
|
24
|
+
function getDatapointDescription(name) {
|
|
25
|
+
switch (name) {
|
|
26
|
+
case "model":
|
|
27
|
+
return "Specific model of the Lamp";
|
|
28
|
+
case "ip":
|
|
29
|
+
return "IP address of the Lamp";
|
|
30
|
+
case "bleVersionHard":
|
|
31
|
+
return "Bluetooth Low Energy Hardware Version";
|
|
32
|
+
case "bleVersionSoft":
|
|
33
|
+
return "Bluetooth Low Energy Software Version";
|
|
34
|
+
case "wifiVersionHard":
|
|
35
|
+
return "WiFi Hardware Version";
|
|
36
|
+
case "wifiVersionSoft":
|
|
37
|
+
return "WiFi Software Version";
|
|
38
|
+
default:
|
|
39
|
+
return "";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
43
|
+
0 && (module.exports = {
|
|
44
|
+
getDatapointDescription
|
|
45
|
+
});
|
|
46
|
+
//# sourceMappingURL=datapointTools.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/lib/tools/datapointTools.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Returns the description for device information datapoints.\n * This helps to keep the main adapter class clean and centralizes datapoint metadata.\n *\n * @param name The name of the parameter retrieved from the device\n * @returns The description to be set to the datapoint\n */\nexport function getDatapointDescription(name: string): string {\n\tswitch (name) {\n\t\tcase 'model':\n\t\t\treturn 'Specific model of the Lamp';\n\t\tcase 'ip':\n\t\t\treturn 'IP address of the Lamp';\n\t\tcase 'bleVersionHard':\n\t\t\treturn 'Bluetooth Low Energy Hardware Version';\n\t\tcase 'bleVersionSoft':\n\t\t\treturn 'Bluetooth Low Energy Software Version';\n\t\tcase 'wifiVersionHard':\n\t\t\treturn 'WiFi Hardware Version';\n\t\tcase 'wifiVersionSoft':\n\t\t\treturn 'WiFi Software Version';\n\t\tdefault:\n\t\t\treturn '';\n\t}\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAOO,SAAS,wBAAwB,MAAsB;AAC7D,UAAQ,MAAM;AAAA,IACb,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO;AAAA,IACR;AACC,aAAO;AAAA,EACT;AACD;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -23,17 +23,19 @@ __export(hexTool_exports, {
|
|
|
23
23
|
});
|
|
24
24
|
module.exports = __toCommonJS(hexTool_exports);
|
|
25
25
|
function componentToHex(c) {
|
|
26
|
-
const hex = c.toString(16);
|
|
27
|
-
return hex.
|
|
26
|
+
const hex = c.toString(16).toUpperCase();
|
|
27
|
+
return hex.padStart(2, "0");
|
|
28
28
|
}
|
|
29
|
-
function hexToRgb(
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
function hexToRgb(hex) {
|
|
30
|
+
hex = hex.replace(/^#/, "");
|
|
31
|
+
if (hex.length === 3) {
|
|
32
|
+
hex = hex.split("").map((x) => x + x).join("");
|
|
32
33
|
}
|
|
34
|
+
const num = parseInt(hex, 16);
|
|
33
35
|
return {
|
|
34
|
-
r:
|
|
35
|
-
g:
|
|
36
|
-
b:
|
|
36
|
+
r: num >> 16 & 255,
|
|
37
|
+
g: num >> 8 & 255,
|
|
38
|
+
b: num & 255
|
|
37
39
|
};
|
|
38
40
|
}
|
|
39
41
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/lib/tools/hexTool.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Converts a number (0-255) to a two-digit hexadecimal string.\n *\n * @param c The number to convert.\n * @returns Hex string (e.g. '0A', 'FF').\n */\nexport function componentToHex(c: number): string {\n\tconst hex = c.toString(16).toUpperCase();\n\treturn hex.padStart(2, '0');\n}\n\n/**\n * Converts a hex color string (e.g. '#FFAABB') to an RGB object.\n *\n * @param hex The hex color string.\n * @returns An object with r, g, b properties.\n */\nexport function hexToRgb(hex: string): { r: number; g: number; b: number } {\n\t// Remove leading # if present\n\thex = hex.replace(/^#/, '');\n\tif (hex.length === 3) {\n\t\thex = hex\n\t\t\t.split('')\n\t\t\t.map((x) => x + x)\n\t\t\t.join('');\n\t}\n\tconst num = parseInt(hex, 16);\n\treturn {\n\t\tr: (num >> 16) & 255,\n\t\tg: (num >> 8) & 255,\n\t\tb: num & 255,\n\t};\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAMO,SAAS,eAAe,GAAmB;AACjD,QAAM,MAAM,EAAE,SAAS,EAAE,EAAE,YAAY;AACvC,SAAO,IAAI,SAAS,GAAG,GAAG;AAC3B;AAQO,SAAS,SAAS,KAAkD;AAE1E,QAAM,IAAI,QAAQ,MAAM,EAAE;AAC1B,MAAI,IAAI,WAAW,GAAG;AACrB,UAAM,IACJ,MAAM,EAAE,EACR,IAAI,CAAC,MAAM,IAAI,CAAC,EAChB,KAAK,EAAE;AAAA,EACV;AACA,QAAM,MAAM,SAAS,KAAK,EAAE;AAC5B,SAAO;AAAA,IACN,GAAI,OAAO,KAAM;AAAA,IACjB,GAAI,OAAO,IAAK;AAAA,IAChB,GAAG,MAAM;AAAA,EACV;AACD;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/build/main.js
CHANGED
|
@@ -28,25 +28,18 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
28
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
29
|
var main_exports = {};
|
|
30
30
|
__export(main_exports, {
|
|
31
|
-
GoveeLocal: () => GoveeLocal
|
|
32
|
-
getDatapointDescription: () => getDatapointDescription
|
|
31
|
+
GoveeLocal: () => GoveeLocal
|
|
33
32
|
});
|
|
34
33
|
module.exports = __toCommonJS(main_exports);
|
|
35
34
|
var utils = __toESM(require("@iobroker/adapter-core"));
|
|
36
|
-
var
|
|
37
|
-
var import_hexTool = require("./tools/hexTool");
|
|
38
|
-
const LOCAL_PORT = 4002;
|
|
39
|
-
const SEND_SCAN_PORT = 4001;
|
|
40
|
-
const CONTROL_PORT = 4003;
|
|
41
|
-
const M_CAST = "239.255.255.250";
|
|
42
|
-
const socket = dgram.createSocket({ type: "udp4" });
|
|
43
|
-
const scanMessage = { msg: { cmd: "scan", data: { account_topic: "reserved" } } };
|
|
44
|
-
const requestStatusMessage = { msg: { cmd: "devStatus", data: {} } };
|
|
45
|
-
let searchInterval;
|
|
46
|
-
let refreshInterval;
|
|
47
|
-
const devices = {};
|
|
48
|
-
const loggedDevices = [];
|
|
35
|
+
var import_goveeService = require("./lib/goveeService");
|
|
49
36
|
class GoveeLocal extends utils.Adapter {
|
|
37
|
+
goveeService;
|
|
38
|
+
/**
|
|
39
|
+
* Constructor of the adapter class.
|
|
40
|
+
*
|
|
41
|
+
* @param options Optional adapter options to override defaults.
|
|
42
|
+
*/
|
|
50
43
|
constructor(options = {}) {
|
|
51
44
|
super({
|
|
52
45
|
...options,
|
|
@@ -71,193 +64,61 @@ class GoveeLocal extends utils.Adapter {
|
|
|
71
64
|
},
|
|
72
65
|
native: {}
|
|
73
66
|
});
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
this.
|
|
77
|
-
|
|
67
|
+
this.goveeService = new import_goveeService.GoveeService({
|
|
68
|
+
interface: this.config.interface,
|
|
69
|
+
searchInterval: this.config.searchInterval,
|
|
70
|
+
deviceStatusRefreshInterval: this.config.deviceStatusRefreshInterval,
|
|
71
|
+
extendedLogging: this.config.extendedLogging,
|
|
72
|
+
forbiddenChars: /[^a-zA-Z0-9_-]/g,
|
|
73
|
+
logger: {
|
|
74
|
+
debug: (msg) => this.log.debug(msg),
|
|
75
|
+
info: (msg) => this.log.info(msg),
|
|
76
|
+
error: (msg) => this.log.error(msg)
|
|
77
|
+
}
|
|
78
78
|
});
|
|
79
|
+
this.goveeService.on("deviceDiscovered", (data) => {
|
|
80
|
+
void this.handleDeviceDiscovered(data);
|
|
81
|
+
});
|
|
82
|
+
this.goveeService.on("deviceStatusUpdate", (data) => {
|
|
83
|
+
void this.handleDeviceStatusUpdate(data);
|
|
84
|
+
});
|
|
85
|
+
this.goveeService.start();
|
|
79
86
|
if (this.config.extendedLogging) {
|
|
80
87
|
this.log.debug("running with extended logging");
|
|
81
88
|
}
|
|
82
|
-
void socket.bind({ address: this.config.interface, port: LOCAL_PORT }, this.serverBound.bind(this));
|
|
83
89
|
void this.subscribeStates("*.devStatus.*");
|
|
84
90
|
return Promise.resolve();
|
|
85
91
|
}
|
|
86
92
|
/**
|
|
87
|
-
*
|
|
88
|
-
* configure multicast membership
|
|
89
|
-
* start periodic scan for devices
|
|
90
|
-
*/
|
|
91
|
-
async serverBound() {
|
|
92
|
-
socket.setBroadcast(true);
|
|
93
|
-
socket.setMulticastTTL(128);
|
|
94
|
-
socket.setMulticastInterface(this.config.interface);
|
|
95
|
-
socket.addMembership(M_CAST);
|
|
96
|
-
void this.setStateChanged("info.connection", { val: true, ack: true });
|
|
97
|
-
this.log.debug(`UDP listening on ${socket.address().address}:${socket.address().port}`);
|
|
98
|
-
const deviceSearchInterval = this.setInterval(this.sendScan.bind(this), this.config.searchInterval * 1e3);
|
|
99
|
-
if (deviceSearchInterval) {
|
|
100
|
-
searchInterval = deviceSearchInterval;
|
|
101
|
-
}
|
|
102
|
-
const deviceRefreshInterval = this.setInterval(
|
|
103
|
-
this.refreshAllDevices.bind(this),
|
|
104
|
-
this.config.deviceStatusRefreshInterval * 1e3
|
|
105
|
-
);
|
|
106
|
-
if (deviceRefreshInterval) {
|
|
107
|
-
refreshInterval = deviceRefreshInterval;
|
|
108
|
-
}
|
|
109
|
-
return Promise.resolve();
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* handle icoming messages on the udp socket
|
|
93
|
+
* Is called if a subscribed state changes
|
|
113
94
|
*
|
|
114
|
-
* @param
|
|
115
|
-
* @param
|
|
95
|
+
* @param id The ID of the changed state.
|
|
96
|
+
* @param state The new state object or null/undefined.
|
|
116
97
|
*/
|
|
117
|
-
async
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
type: "device",
|
|
127
|
-
common: {
|
|
128
|
-
name: messageObject.msg.data.sku,
|
|
129
|
-
role: "group"
|
|
130
|
-
},
|
|
131
|
-
native: {}
|
|
132
|
-
});
|
|
133
|
-
void this.setObjectNotExists(`${deviceName}.deviceInfo.${key}`, {
|
|
134
|
-
type: "state",
|
|
135
|
-
common: {
|
|
136
|
-
name: getDatapointDescription(key),
|
|
137
|
-
type: "string",
|
|
138
|
-
role: "state",
|
|
139
|
-
read: true,
|
|
140
|
-
write: false
|
|
141
|
-
},
|
|
142
|
-
native: {}
|
|
143
|
-
});
|
|
144
|
-
void this.updateStateAsync(`${deviceName}.deviceInfo.${key}`, messageObject.msg.data[key]);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
break;
|
|
148
|
-
}
|
|
149
|
-
case "devStatus": {
|
|
150
|
-
const sendingDevice = devices[remote.address];
|
|
151
|
-
if (sendingDevice) {
|
|
152
|
-
if (this.config.extendedLogging && !loggedDevices.includes(remote.address.toString())) {
|
|
153
|
-
this.log.info(`deivce status message data: ${JSON.stringify(messageObject)}`);
|
|
154
|
-
loggedDevices.push(remote.address.toString());
|
|
155
|
-
}
|
|
156
|
-
const devStatusMessageObject = JSON.parse(message.toString());
|
|
157
|
-
void this.setObjectNotExists(`${sendingDevice}.devStatus.onOff`, {
|
|
158
|
-
type: "state",
|
|
159
|
-
common: {
|
|
160
|
-
name: "On / Off state of the lamp",
|
|
161
|
-
type: "boolean",
|
|
162
|
-
role: "switch",
|
|
163
|
-
read: true,
|
|
164
|
-
write: true
|
|
165
|
-
},
|
|
166
|
-
native: {}
|
|
167
|
-
});
|
|
168
|
-
void this.updateStateAsync(
|
|
169
|
-
`${sendingDevice}.devStatus.onOff`,
|
|
170
|
-
devStatusMessageObject.msg.data.onOff == 1
|
|
171
|
-
);
|
|
172
|
-
void this.setObjectNotExists(`${sendingDevice}.devStatus.brightness`, {
|
|
173
|
-
type: "state",
|
|
174
|
-
common: {
|
|
175
|
-
name: "Brightness of the light",
|
|
176
|
-
type: "number",
|
|
177
|
-
role: "level.dimmer",
|
|
178
|
-
read: true,
|
|
179
|
-
write: true
|
|
180
|
-
},
|
|
181
|
-
native: {}
|
|
182
|
-
});
|
|
183
|
-
void this.updateStateAsync(
|
|
184
|
-
`${sendingDevice}.devStatus.brightness`,
|
|
185
|
-
devStatusMessageObject.msg.data.brightness
|
|
186
|
-
);
|
|
187
|
-
void this.setObjectNotExists(`${sendingDevice}.devStatus.color`, {
|
|
188
|
-
type: "state",
|
|
189
|
-
common: {
|
|
190
|
-
name: "Current showing color of the lamp",
|
|
191
|
-
type: "string",
|
|
192
|
-
role: "level.color.rgb",
|
|
193
|
-
read: true,
|
|
194
|
-
write: true
|
|
195
|
-
},
|
|
196
|
-
native: {}
|
|
197
|
-
});
|
|
198
|
-
const colorString = `#${(0, import_hexTool.componentToHex)(devStatusMessageObject.msg.data.color.r)}${(0, import_hexTool.componentToHex)(devStatusMessageObject.msg.data.color.g)}${(0, import_hexTool.componentToHex)(devStatusMessageObject.msg.data.color.b)}`;
|
|
199
|
-
void this.setState(`${sendingDevice}.devStatus.color`, {
|
|
200
|
-
val: colorString,
|
|
201
|
-
ack: true
|
|
202
|
-
});
|
|
203
|
-
void this.updateStateAsync(`${sendingDevice}.devStatus.color`, colorString);
|
|
204
|
-
void this.setObjectNotExists(`${sendingDevice}.devStatus.colorTemInKelvin`, {
|
|
205
|
-
type: "state",
|
|
206
|
-
common: {
|
|
207
|
-
name: "If staying in white light, the color temperature",
|
|
208
|
-
type: "number",
|
|
209
|
-
role: "level.color.temperature",
|
|
210
|
-
read: true,
|
|
211
|
-
write: true
|
|
212
|
-
},
|
|
213
|
-
native: {}
|
|
214
|
-
});
|
|
215
|
-
void this.updateStateAsync(
|
|
216
|
-
`${sendingDevice}.devStatus.colorTemInKelvin`,
|
|
217
|
-
devStatusMessageObject.msg.data.colorTemInKelvin
|
|
218
|
-
);
|
|
219
|
-
}
|
|
220
|
-
break;
|
|
221
|
-
}
|
|
222
|
-
default: {
|
|
223
|
-
this.log.error(`message from: ${remote.address}:${remote.port} - ${message.toString()}`);
|
|
98
|
+
async onStateChange(id, state) {
|
|
99
|
+
var _a;
|
|
100
|
+
if (state && !state.ack) {
|
|
101
|
+
const ipOfDevice = await this.getStateAsync(`${id.split(".")[2]}.deviceInfo.ip`);
|
|
102
|
+
const receiver = (_a = ipOfDevice == null ? void 0 : ipOfDevice.val) == null ? void 0 : _a.toString();
|
|
103
|
+
if (typeof receiver === "string") {
|
|
104
|
+
this.goveeService.handleStateChange(id, state, receiver);
|
|
105
|
+
} else {
|
|
106
|
+
this.log.error("device not found or IP is not a string");
|
|
224
107
|
}
|
|
225
108
|
}
|
|
226
109
|
return Promise.resolve();
|
|
227
110
|
}
|
|
228
|
-
async refreshAllDevices() {
|
|
229
|
-
for (const ip in devices) {
|
|
230
|
-
this.requestDeviceStatus(ip);
|
|
231
|
-
}
|
|
232
|
-
return Promise.resolve();
|
|
233
|
-
}
|
|
234
111
|
/**
|
|
235
|
-
*
|
|
112
|
+
* Called when adapter shuts down - callback must be called under any circumstances!
|
|
236
113
|
*
|
|
237
|
-
* @param
|
|
238
|
-
*/
|
|
239
|
-
requestDeviceStatus(receiver) {
|
|
240
|
-
const requestDeviceStatusBuffer = Buffer.from(JSON.stringify(requestStatusMessage));
|
|
241
|
-
void socket.send(requestDeviceStatusBuffer, 0, requestDeviceStatusBuffer.length, CONTROL_PORT, receiver);
|
|
242
|
-
}
|
|
243
|
-
/**
|
|
244
|
-
* send the scan message to the udp multicast address
|
|
245
|
-
*/
|
|
246
|
-
async sendScan() {
|
|
247
|
-
const scanMessageBuffer = Buffer.from(JSON.stringify(scanMessage));
|
|
248
|
-
void socket.send(scanMessageBuffer, 0, scanMessageBuffer.length, SEND_SCAN_PORT, M_CAST);
|
|
249
|
-
return Promise.resolve();
|
|
250
|
-
}
|
|
251
|
-
/**
|
|
252
|
-
* Is called when adapter shuts down - callback has to be called under any circumstances!
|
|
253
|
-
*
|
|
254
|
-
* @param callback Callback-Funktion, die nach Abschluss des Unload-Prozesses aufgerufen wird.
|
|
114
|
+
* @param callback Callback function after unload process.
|
|
255
115
|
*/
|
|
256
116
|
onUnload(callback) {
|
|
257
117
|
try {
|
|
258
|
-
this.
|
|
259
|
-
|
|
260
|
-
|
|
118
|
+
if (this.goveeService) {
|
|
119
|
+
this.goveeService.removeAllListeners();
|
|
120
|
+
this.goveeService.stop();
|
|
121
|
+
}
|
|
261
122
|
void this.updateStateAsync("info.connection", false);
|
|
262
123
|
callback();
|
|
263
124
|
} catch (e) {
|
|
@@ -265,72 +126,6 @@ class GoveeLocal extends utils.Adapter {
|
|
|
265
126
|
callback();
|
|
266
127
|
}
|
|
267
128
|
}
|
|
268
|
-
/**
|
|
269
|
-
* Is called if a subscribed state changes
|
|
270
|
-
*
|
|
271
|
-
* @param id Die ID des geänderten States.
|
|
272
|
-
* @param state Das neue State-Objekt oder null/undefined.
|
|
273
|
-
*/
|
|
274
|
-
async onStateChange(id, state) {
|
|
275
|
-
var _a, _b;
|
|
276
|
-
if (state && !state.ack) {
|
|
277
|
-
const ipOfDevice = await this.getStateAsync(`${id.split(".")[2]}.deviceInfo.ip`);
|
|
278
|
-
if (ipOfDevice) {
|
|
279
|
-
const receiver = (_a = ipOfDevice.val) == null ? void 0 : _a.toString();
|
|
280
|
-
switch (id.split(".")[4]) {
|
|
281
|
-
case "onOff": {
|
|
282
|
-
const turnMessage = { msg: { cmd: "turn", data: { value: state.val ? 1 : 0 } } };
|
|
283
|
-
const turnMessageBuffer = Buffer.from(JSON.stringify(turnMessage));
|
|
284
|
-
void socket.send(turnMessageBuffer, 0, turnMessageBuffer.length, CONTROL_PORT, receiver);
|
|
285
|
-
break;
|
|
286
|
-
}
|
|
287
|
-
case "brightness": {
|
|
288
|
-
const brightnessMessage = { msg: { cmd: "brightness", data: { value: state.val } } };
|
|
289
|
-
const brightnessMessageBuffer = Buffer.from(JSON.stringify(brightnessMessage));
|
|
290
|
-
void socket.send(
|
|
291
|
-
brightnessMessageBuffer,
|
|
292
|
-
0,
|
|
293
|
-
brightnessMessageBuffer.length,
|
|
294
|
-
CONTROL_PORT,
|
|
295
|
-
receiver
|
|
296
|
-
);
|
|
297
|
-
break;
|
|
298
|
-
}
|
|
299
|
-
case "colorTemInKelvin": {
|
|
300
|
-
const colorTempMessageBuffer = Buffer.from(
|
|
301
|
-
JSON.stringify({
|
|
302
|
-
msg: {
|
|
303
|
-
cmd: "colorwc",
|
|
304
|
-
data: { color: { r: "0", g: "0", b: "0" }, colorTemInKelvin: state.val }
|
|
305
|
-
}
|
|
306
|
-
})
|
|
307
|
-
);
|
|
308
|
-
void socket.send(
|
|
309
|
-
colorTempMessageBuffer,
|
|
310
|
-
0,
|
|
311
|
-
colorTempMessageBuffer.length,
|
|
312
|
-
CONTROL_PORT,
|
|
313
|
-
receiver
|
|
314
|
-
);
|
|
315
|
-
break;
|
|
316
|
-
}
|
|
317
|
-
case "color": {
|
|
318
|
-
const colorValue = (_b = state.val) == null ? void 0 : _b.toString();
|
|
319
|
-
if (colorValue) {
|
|
320
|
-
const rgb = (0, import_hexTool.hexToRgb)(colorValue);
|
|
321
|
-
const colorMessage = { msg: { cmd: "colorwc", data: { color: rgb } } };
|
|
322
|
-
const colorMessageBuffer = Buffer.from(JSON.stringify(colorMessage));
|
|
323
|
-
void socket.send(colorMessageBuffer, 0, colorMessageBuffer.length, CONTROL_PORT, receiver);
|
|
324
|
-
}
|
|
325
|
-
break;
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
} else {
|
|
329
|
-
this.log.error("device not found");
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
return Promise.resolve();
|
|
333
|
-
}
|
|
334
129
|
async updateStateAsync(fullName, state, acknowledged = true) {
|
|
335
130
|
const currentState = await this.getStateAsync(fullName);
|
|
336
131
|
if (currentState != state) {
|
|
@@ -340,33 +135,112 @@ class GoveeLocal extends utils.Adapter {
|
|
|
340
135
|
});
|
|
341
136
|
}
|
|
342
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* Handle device discovery event.
|
|
140
|
+
*
|
|
141
|
+
* @param event The device discovery event data.
|
|
142
|
+
*/
|
|
143
|
+
async handleDeviceDiscovered(event) {
|
|
144
|
+
const { ip, deviceName } = event;
|
|
145
|
+
await this.setObjectNotExistsAsync(deviceName, {
|
|
146
|
+
type: "folder",
|
|
147
|
+
common: {
|
|
148
|
+
name: deviceName
|
|
149
|
+
},
|
|
150
|
+
native: {}
|
|
151
|
+
});
|
|
152
|
+
await this.setObjectNotExistsAsync(`${deviceName}.deviceInfo`, {
|
|
153
|
+
type: "folder",
|
|
154
|
+
common: {
|
|
155
|
+
name: "Device Info"
|
|
156
|
+
},
|
|
157
|
+
native: {}
|
|
158
|
+
});
|
|
159
|
+
await this.setObjectNotExistsAsync(`${deviceName}.deviceInfo.ip`, {
|
|
160
|
+
type: "state",
|
|
161
|
+
common: {
|
|
162
|
+
name: "IP address of the Lamp",
|
|
163
|
+
type: "string",
|
|
164
|
+
role: "info.ip",
|
|
165
|
+
read: true,
|
|
166
|
+
write: false
|
|
167
|
+
},
|
|
168
|
+
native: {}
|
|
169
|
+
});
|
|
170
|
+
await this.updateStateAsync(`${deviceName}.deviceInfo.ip`, ip);
|
|
171
|
+
await this.setObjectNotExistsAsync(`${deviceName}.devStatus`, {
|
|
172
|
+
type: "folder",
|
|
173
|
+
common: {
|
|
174
|
+
name: "Device Status"
|
|
175
|
+
},
|
|
176
|
+
native: {}
|
|
177
|
+
});
|
|
178
|
+
this.log.info(`Device discovered: ${deviceName} at ${ip}`);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Handle device status update event.
|
|
182
|
+
*
|
|
183
|
+
* @param event The device status update event data.
|
|
184
|
+
*/
|
|
185
|
+
async handleDeviceStatusUpdate(event) {
|
|
186
|
+
const { deviceName, status } = event;
|
|
187
|
+
await this.setObjectNotExistsAsync(`${deviceName}.devStatus.onOff`, {
|
|
188
|
+
type: "state",
|
|
189
|
+
common: {
|
|
190
|
+
name: "On / Off state of the lamp",
|
|
191
|
+
type: "boolean",
|
|
192
|
+
role: "switch",
|
|
193
|
+
read: true,
|
|
194
|
+
write: true
|
|
195
|
+
},
|
|
196
|
+
native: {}
|
|
197
|
+
});
|
|
198
|
+
await this.updateStateAsync(`${deviceName}.devStatus.onOff`, status.onOff);
|
|
199
|
+
await this.setObjectNotExistsAsync(`${deviceName}.devStatus.brightness`, {
|
|
200
|
+
type: "state",
|
|
201
|
+
common: {
|
|
202
|
+
name: "Brightness of the light",
|
|
203
|
+
type: "number",
|
|
204
|
+
role: "level.dimmer",
|
|
205
|
+
read: true,
|
|
206
|
+
write: true
|
|
207
|
+
},
|
|
208
|
+
native: {}
|
|
209
|
+
});
|
|
210
|
+
await this.updateStateAsync(`${deviceName}.devStatus.brightness`, status.brightness);
|
|
211
|
+
await this.setObjectNotExistsAsync(`${deviceName}.devStatus.color`, {
|
|
212
|
+
type: "state",
|
|
213
|
+
common: {
|
|
214
|
+
name: "Current showing color of the lamp",
|
|
215
|
+
type: "string",
|
|
216
|
+
role: "level.color.rgb",
|
|
217
|
+
read: true,
|
|
218
|
+
write: true
|
|
219
|
+
},
|
|
220
|
+
native: {}
|
|
221
|
+
});
|
|
222
|
+
await this.updateStateAsync(`${deviceName}.devStatus.color`, status.color);
|
|
223
|
+
await this.setObjectNotExistsAsync(`${deviceName}.devStatus.colorTemInKelvin`, {
|
|
224
|
+
type: "state",
|
|
225
|
+
common: {
|
|
226
|
+
name: "If staying in white light, the color temperature",
|
|
227
|
+
type: "number",
|
|
228
|
+
role: "level.color.temperature",
|
|
229
|
+
read: true,
|
|
230
|
+
write: true
|
|
231
|
+
},
|
|
232
|
+
native: {}
|
|
233
|
+
});
|
|
234
|
+
await this.updateStateAsync(`${deviceName}.devStatus.colorTemInKelvin`, status.colorTemInKelvin);
|
|
235
|
+
}
|
|
343
236
|
}
|
|
344
237
|
if (require.main !== module) {
|
|
345
238
|
module.exports = (options) => new GoveeLocal(options);
|
|
346
239
|
} else {
|
|
347
240
|
(() => new GoveeLocal())();
|
|
348
241
|
}
|
|
349
|
-
function getDatapointDescription(name) {
|
|
350
|
-
switch (name) {
|
|
351
|
-
case "model":
|
|
352
|
-
return "Specific model of the Lamp";
|
|
353
|
-
case "ip":
|
|
354
|
-
return "IP address of the Lamp";
|
|
355
|
-
case "bleVersionHard":
|
|
356
|
-
return "Bluetooth Low Energy Hardware Version";
|
|
357
|
-
case "bleVersionSoft":
|
|
358
|
-
return "Bluetooth Low Energy Software Version";
|
|
359
|
-
case "wifiVersionHard":
|
|
360
|
-
return "WiFi Hardware Version";
|
|
361
|
-
case "wifiVersionSoft":
|
|
362
|
-
return "WiFi Software Version";
|
|
363
|
-
default:
|
|
364
|
-
return "";
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
242
|
// Annotate the CommonJS export names for ESM import in node:
|
|
368
243
|
0 && (module.exports = {
|
|
369
|
-
GoveeLocal
|
|
370
|
-
getDatapointDescription
|
|
244
|
+
GoveeLocal
|
|
371
245
|
});
|
|
372
246
|
//# sourceMappingURL=main.js.map
|
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\n// The adapter-core module gives you access to the core ioBroker functions\n// you need to create an adapter\nimport * as utils from '@iobroker/adapter-core';\nimport * as dgram from 'node:dgram';\nimport { componentToHex, hexToRgb } from './tools/hexTool';\n\n// Load your modules here, e.g.:\n\nconst LOCAL_PORT = 4002;\nconst SEND_SCAN_PORT = 4001;\nconst CONTROL_PORT = 4003;\nconst M_CAST = '239.255.255.250';\n\nconst socket = dgram.createSocket({ type: 'udp4' });\n\nconst scanMessage = { msg: { cmd: 'scan', data: { account_topic: 'reserved' } } };\nconst requestStatusMessage = { msg: { cmd: 'devStatus', data: {} } };\n\nlet searchInterval: ioBroker.Interval;\nlet refreshInterval: ioBroker.Interval;\n\nconst devices: { [ip: string]: string } = {};\n\nconst loggedDevices = [] as string[];\n\nexport class GoveeLocal extends utils.Adapter {\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\tthis.on('ready', this.onReady.bind(this));\n\t\tthis.on('stateChange', this.onStateChange.bind(this));\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 * Is called when databases are connected and adapter received configuration.\n\t */\n\tprivate async onReady(): Promise<void> {\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\t\tsocket.on('message', this.onUdpMessage.bind(this));\n\t\tsocket.on('error', (error) => {\n\t\t\tthis.log.error(`server bind error : ${error.message}`);\n\t\t\tvoid this.setStateChanged('info.connection', { val: false, ack: true });\n\t\t});\n\n\t\tif (this.config.extendedLogging) {\n\t\t\tthis.log.debug('running with extended logging');\n\t\t}\n\n\t\tvoid socket.bind({ address: this.config.interface, port: LOCAL_PORT }, this.serverBound.bind(this));\n\t\tvoid this.subscribeStates('*.devStatus.*');\n\t\treturn Promise.resolve();\n\t}\n\n\t/**\n\t * handles when udp socket is up\n\t * configure multicast membership\n\t * start periodic scan for devices\n\t */\n\tprivate async serverBound(): Promise<void> {\n\t\tsocket.setBroadcast(true);\n\t\tsocket.setMulticastTTL(128);\n\t\tsocket.setMulticastInterface(this.config.interface);\n\t\tsocket.addMembership(M_CAST);\n\t\tvoid this.setStateChanged('info.connection', { val: true, ack: true });\n\t\tthis.log.debug(`UDP listening on ${(socket.address() as any).address}:${(socket.address() as any).port}`);\n\n\t\tconst deviceSearchInterval = this.setInterval(this.sendScan.bind(this), this.config.searchInterval * 1000);\n\t\tif (deviceSearchInterval) {\n\t\t\tsearchInterval = deviceSearchInterval;\n\t\t}\n\n\t\tconst deviceRefreshInterval = this.setInterval(\n\t\t\tthis.refreshAllDevices.bind(this),\n\t\t\tthis.config.deviceStatusRefreshInterval * 1000,\n\t\t);\n\t\tif (deviceRefreshInterval) {\n\t\t\trefreshInterval = deviceRefreshInterval;\n\t\t}\n\t\treturn Promise.resolve();\n\t}\n\n\t/**\n\t * handle icoming messages on the udp socket\n\t *\n\t * @param message the message itself\n\t * @param remote the sender of the message\n\t */\n\tprivate async onUdpMessage(message: Buffer, remote: dgram.RemoteInfo): Promise<void> {\n\t\tconst messageObject = JSON.parse(message.toString());\n\t\tswitch (messageObject.msg.cmd) {\n\t\t\tcase 'scan': {\n\t\t\t\tfor (const key of Object.keys(messageObject.msg.data)) {\n\t\t\t\t\tif (key != 'device') {\n\t\t\t\t\t\tconst deviceName = messageObject.msg.data.device.replace(this.FORBIDDEN_CHARS, '_');\n\t\t\t\t\t\tdevices[remote.address] = deviceName;\n\t\t\t\t\t\tvoid this.setObjectNotExists(deviceName, {\n\t\t\t\t\t\t\ttype: 'device',\n\t\t\t\t\t\t\tcommon: {\n\t\t\t\t\t\t\t\tname: messageObject.msg.data.sku,\n\t\t\t\t\t\t\t\trole: 'group',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tnative: {},\n\t\t\t\t\t\t});\n\t\t\t\t\t\tvoid this.setObjectNotExists(`${deviceName}.deviceInfo.${key}`, {\n\t\t\t\t\t\t\ttype: 'state',\n\t\t\t\t\t\t\tcommon: {\n\t\t\t\t\t\t\t\tname: getDatapointDescription(key),\n\t\t\t\t\t\t\t\ttype: 'string',\n\t\t\t\t\t\t\t\trole: 'state',\n\t\t\t\t\t\t\t\tread: true,\n\t\t\t\t\t\t\t\twrite: false,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tnative: {},\n\t\t\t\t\t\t});\n\t\t\t\t\t\tvoid this.updateStateAsync(`${deviceName}.deviceInfo.${key}`, messageObject.msg.data[key]);\n\t\t\t\t\t}\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 = devices[remote.address];\n\t\t\t\tif (sendingDevice) {\n\t\t\t\t\tif (this.config.extendedLogging && !loggedDevices.includes(remote.address.toString())) {\n\t\t\t\t\t\tthis.log.info(`deivce status message data: ${JSON.stringify(messageObject)}`);\n\t\t\t\t\t\tloggedDevices.push(remote.address.toString());\n\t\t\t\t\t}\n\t\t\t\t\tconst devStatusMessageObject = JSON.parse(message.toString());\n\t\t\t\t\tvoid this.setObjectNotExists(`${sendingDevice}.devStatus.onOff`, {\n\t\t\t\t\t\ttype: 'state',\n\t\t\t\t\t\tcommon: {\n\t\t\t\t\t\t\tname: 'On / Off state of the lamp',\n\t\t\t\t\t\t\ttype: 'boolean',\n\t\t\t\t\t\t\trole: 'switch',\n\t\t\t\t\t\t\tread: true,\n\t\t\t\t\t\t\twrite: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tnative: {},\n\t\t\t\t\t});\n\t\t\t\t\tvoid this.updateStateAsync(\n\t\t\t\t\t\t`${sendingDevice}.devStatus.onOff`,\n\t\t\t\t\t\tdevStatusMessageObject.msg.data.onOff == 1,\n\t\t\t\t\t);\n\t\t\t\t\tvoid this.setObjectNotExists(`${sendingDevice}.devStatus.brightness`, {\n\t\t\t\t\t\ttype: 'state',\n\t\t\t\t\t\tcommon: {\n\t\t\t\t\t\t\tname: 'Brightness of the light',\n\t\t\t\t\t\t\ttype: 'number',\n\t\t\t\t\t\t\trole: 'level.dimmer',\n\t\t\t\t\t\t\tread: true,\n\t\t\t\t\t\t\twrite: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tnative: {},\n\t\t\t\t\t});\n\t\t\t\t\tvoid this.updateStateAsync(\n\t\t\t\t\t\t`${sendingDevice}.devStatus.brightness`,\n\t\t\t\t\t\tdevStatusMessageObject.msg.data.brightness,\n\t\t\t\t\t);\n\t\t\t\t\tvoid this.setObjectNotExists(`${sendingDevice}.devStatus.color`, {\n\t\t\t\t\t\ttype: 'state',\n\t\t\t\t\t\tcommon: {\n\t\t\t\t\t\t\tname: 'Current showing color of the lamp',\n\t\t\t\t\t\t\ttype: 'string',\n\t\t\t\t\t\t\trole: 'level.color.rgb',\n\t\t\t\t\t\t\tread: true,\n\t\t\t\t\t\t\twrite: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tnative: {},\n\t\t\t\t\t});\n\t\t\t\t\tconst colorString = `#${componentToHex(devStatusMessageObject.msg.data.color.r)}${componentToHex(devStatusMessageObject.msg.data.color.g)}${componentToHex(devStatusMessageObject.msg.data.color.b)}`;\n\t\t\t\t\tvoid this.setState(`${sendingDevice}.devStatus.color`, {\n\t\t\t\t\t\tval: colorString,\n\t\t\t\t\t\tack: true,\n\t\t\t\t\t});\n\t\t\t\t\tvoid this.updateStateAsync(`${sendingDevice}.devStatus.color`, colorString);\n\t\t\t\t\tvoid this.setObjectNotExists(`${sendingDevice}.devStatus.colorTemInKelvin`, {\n\t\t\t\t\t\ttype: 'state',\n\t\t\t\t\t\tcommon: {\n\t\t\t\t\t\t\tname: 'If staying in white light, the color temperature',\n\t\t\t\t\t\t\ttype: 'number',\n\t\t\t\t\t\t\trole: 'level.color.temperature',\n\t\t\t\t\t\t\tread: true,\n\t\t\t\t\t\t\twrite: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tnative: {},\n\t\t\t\t\t});\n\t\t\t\t\tvoid this.updateStateAsync(\n\t\t\t\t\t\t`${sendingDevice}.devStatus.colorTemInKelvin`,\n\t\t\t\t\t\tdevStatusMessageObject.msg.data.colorTemInKelvin,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault: {\n\t\t\t\tthis.log.error(`message from: ${remote.address}:${remote.port} - ${message.toString()}`);\n\t\t\t}\n\t\t}\n\t\treturn Promise.resolve();\n\t}\n\n\tprivate async refreshAllDevices(): Promise<void> {\n\t\tfor (const ip in devices) {\n\t\t\tthis.requestDeviceStatus(ip);\n\t\t}\n\t\treturn Promise.resolve();\n\t}\n\n\t/**\n\t * sends the device status request to one specific device\n\t *\n\t * @param receiver the ip ( / hsotname ) of the device that should be queried\n\t */\n\tprivate requestDeviceStatus(receiver: string): void {\n\t\tconst requestDeviceStatusBuffer = Buffer.from(JSON.stringify(requestStatusMessage));\n\t\tvoid socket.send(requestDeviceStatusBuffer, 0, requestDeviceStatusBuffer.length, CONTROL_PORT, receiver);\n\t}\n\n\t/**\n\t * send the scan message to the udp multicast address\n\t */\n\tprivate async sendScan(): Promise<void> {\n\t\tconst scanMessageBuffer = Buffer.from(JSON.stringify(scanMessage));\n\t\tvoid socket.send(scanMessageBuffer, 0, scanMessageBuffer.length, SEND_SCAN_PORT, M_CAST);\n\t\treturn Promise.resolve();\n\t}\n\n\t/**\n\t * Is called when adapter shuts down - callback has to be called under any circumstances!\n\t *\n\t * @param callback Callback-Funktion, die nach Abschluss des Unload-Prozesses aufgerufen wird.\n\t */\n\tprivate onUnload(callback: () => void): void {\n\t\ttry {\n\t\t\tthis.clearInterval(searchInterval);\n\t\t\tthis.clearInterval(refreshInterval);\n\t\t\tsocket.close();\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 * Is called if a subscribed state changes\n\t *\n\t * @param id Die ID des ge\u00E4nderten States.\n\t * @param state Das neue State-Objekt oder 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\tconst ipOfDevice = await this.getStateAsync(`${id.split('.')[2]}.deviceInfo.ip`);\n\t\t\tif (ipOfDevice) {\n\t\t\t\tconst receiver = ipOfDevice.val?.toString();\n\t\t\t\tswitch (id.split('.')[4]) {\n\t\t\t\t\tcase 'onOff': {\n\t\t\t\t\t\tconst turnMessage = { msg: { cmd: 'turn', data: { value: state.val ? 1 : 0 } } };\n\t\t\t\t\t\tconst turnMessageBuffer = Buffer.from(JSON.stringify(turnMessage));\n\t\t\t\t\t\tvoid socket.send(turnMessageBuffer, 0, turnMessageBuffer.length, CONTROL_PORT, receiver);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase 'brightness': {\n\t\t\t\t\t\tconst brightnessMessage = { msg: { cmd: 'brightness', data: { value: state.val } } };\n\t\t\t\t\t\tconst brightnessMessageBuffer = Buffer.from(JSON.stringify(brightnessMessage));\n\t\t\t\t\t\tvoid socket.send(\n\t\t\t\t\t\t\tbrightnessMessageBuffer,\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\tbrightnessMessageBuffer.length,\n\t\t\t\t\t\t\tCONTROL_PORT,\n\t\t\t\t\t\t\treceiver,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase 'colorTemInKelvin': {\n\t\t\t\t\t\tconst colorTempMessageBuffer = Buffer.from(\n\t\t\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\t\t\tmsg: {\n\t\t\t\t\t\t\t\t\tcmd: 'colorwc',\n\t\t\t\t\t\t\t\t\tdata: { color: { r: '0', g: '0', b: '0' }, colorTemInKelvin: state.val },\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tvoid socket.send(\n\t\t\t\t\t\t\tcolorTempMessageBuffer,\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\tcolorTempMessageBuffer.length,\n\t\t\t\t\t\t\tCONTROL_PORT,\n\t\t\t\t\t\t\treceiver,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase 'color': {\n\t\t\t\t\t\tconst colorValue = state.val?.toString();\n\t\t\t\t\t\tif (colorValue) {\n\t\t\t\t\t\t\tconst rgb = hexToRgb(colorValue);\n\t\t\t\t\t\t\tconst colorMessage = { msg: { cmd: 'colorwc', data: { color: rgb } } };\n\t\t\t\t\t\t\tconst colorMessageBuffer = Buffer.from(JSON.stringify(colorMessage));\n\t\t\t\t\t\t\tvoid socket.send(colorMessageBuffer, 0, colorMessageBuffer.length, CONTROL_PORT, receiver);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tthis.log.error('device not found');\n\t\t\t}\n\t\t}\n\t\treturn Promise.resolve();\n\t}\n\n\tprivate async updateStateAsync(fullName: string, state: any, acknowledged = true): Promise<void> {\n\t\tconst currentState = await this.getStateAsync(fullName);\n\t\tif (currentState != 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\nif (require.main !== module) {\n\t// Exportiere eine Factory-Funktion f\u00FCr ioBroker, aber die Klasse ist jetzt auch als ES6-Export verf\u00FCgbar\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\n/**\n * This method returns the description for device information datapoints\n * to unbloat the upper methods.\n * tanslations would be great here\n *\n * @param name the name of the parameter retrieved from the device\n * @returns the description, that should be set to the datapoint\n */\nexport function getDatapointDescription(name: string): string {\n\tswitch (name) {\n\t\tcase 'model':\n\t\t\treturn 'Specific model of the Lamp';\n\t\tcase 'ip':\n\t\t\treturn 'IP address of the Lamp';\n\t\tcase 'bleVersionHard':\n\t\t\treturn 'Bluetooth Low Energy Hardware Version';\n\t\tcase 'bleVersionSoft':\n\t\t\treturn 'Bluetooth Low Energy Software Version';\n\t\tcase 'wifiVersionHard':\n\t\t\treturn 'WiFi Hardware Version';\n\t\tcase 'wifiVersionSoft':\n\t\t\treturn 'WiFi Software Version';\n\t\tdefault:\n\t\t\treturn '';\n\t}\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;
|
|
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 * The adapter class.\n */\nexport class GoveeLocal extends utils.Adapter {\n\tprivate goveeService!: GoveeService;\n\t/**\n\t * Constructor of the adapter class.\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\tthis.on('ready', this.onReady.bind(this));\n\t\tthis.on('stateChange', this.onStateChange.bind(this));\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 * Is called when databases are connected and adapter received configuration.\n\t */\n\tprivate async onReady(): Promise<void> {\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\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\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// Set up event listeners\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\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\tvoid this.subscribeStates('*.devStatus.*');\n\t\treturn Promise.resolve();\n\t}\n\n\t/**\n\t * Is called if a subscribed state changes\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\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\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 adapter shuts down - callback must be called under any circumstances!\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\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\tprivate async updateStateAsync(fullName: string, state: any, acknowledged = true): Promise<void> {\n\t\tconst currentState = await this.getStateAsync(fullName);\n\t\tif (currentState != 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 * Handle device discovery event.\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\t}\n\n\t/**\n\t * Handle device status update event.\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\nif (require.main !== module) {\n\t// Exportiere eine Factory-Funktion f\u00FCr ioBroker, aber die Klasse ist jetzt auch als ES6-Export verf\u00FCgbar\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
|
+
"version": "0.3.1",
|
|
5
5
|
"news": {
|
|
6
|
+
"0.3.1": {
|
|
7
|
+
"en": "updated release script run configuration",
|
|
8
|
+
"de": "aktualisierte versionsskriptlaufkonfiguration",
|
|
9
|
+
"ru": "обновленная конфигурация для запуска сценария",
|
|
10
|
+
"pt": "configuração de execução do script de versão atualizada",
|
|
11
|
+
"nl": "bijgewerkte versie script-run configuratie",
|
|
12
|
+
"fr": "mise à jour de la configuration du script de publication",
|
|
13
|
+
"it": "configurazione dell'esecuzione dello script di rilascio aggiornata",
|
|
14
|
+
"es": "configuración actualizada script script script run",
|
|
15
|
+
"pl": "zaktualizowana konfiguracja uruchamiania skryptu wydania",
|
|
16
|
+
"uk": "оновлена конфігурація запуску сценарію релізу",
|
|
17
|
+
"zh-cn": "更新发布脚本运行配置"
|
|
18
|
+
},
|
|
19
|
+
"0.3.0": {
|
|
20
|
+
"en": "Refactored: Moved all device logic to a dedicated GoveeService class, modularized utilities\nImproved test coverage\nDependencies updated",
|
|
21
|
+
"de": "Hergestellt: Alle Gerätelogik auf eine dedizierte GoveeService-Klasse verschoben, modularisierte Versorgungsprogramme\nVerbesserte Testabdeckung\nAusgaben aktualisiert",
|
|
22
|
+
"ru": "Рефакторированный: Перенес всю логику устройства в выделенный класс GoveeService, модульные утилиты\nУлучшенный охват испытаниями\nОбновленные зависимости",
|
|
23
|
+
"pt": "Refactorado: Moveu toda a lógica do dispositivo para uma classe dedicada do GoveeService, utilitários modularizados\nMelhor cobertura de teste\nDependências atualizadas",
|
|
24
|
+
"nl": "Refactored: Verplaatste alle apparaat logica naar een speciale GoveeService klasse, modulaire utilities\nVerbeterde testdekking\nAfhankelijkheden bijgewerkt",
|
|
25
|
+
"fr": "Reformulé : Déplace toute la logique de l'appareil vers une classe GoveeService dédiée, utilitaires modulaires\nAmélioration de la couverture des tests\nDépendances actualisées",
|
|
26
|
+
"it": "Refactored: Spostato tutta la logica del dispositivo a una classe GoveeService dedicata, utilità modularizzate\nCopertura di test migliorata\nDipendenze aggiornate",
|
|
27
|
+
"es": "Refactored: Movedo toda la lógica del dispositivo a una clase dedicada GoveeService, utilidades modulares\nMejora de la cobertura de pruebas\nActualización de las dependencias",
|
|
28
|
+
"pl": "Zmienione: Przesunął całą logikę urządzenia do specjalnej klasy GoveeService, modularyzowane narzędzia\nLepszy zakres badań\nAktualizacja zależności",
|
|
29
|
+
"uk": "Рефактор: Переміщено всі логіку пристрою для виділеного класу GoveeService, модульовані утиліти\nПокращене покриття тесту\nЗалежність оновлено",
|
|
30
|
+
"zh-cn": "已重构 : 将所有设备逻辑移至专用的 GoveeService 类,模块化的公用事业\n提高测试覆盖率\n更新的依赖关系"
|
|
31
|
+
},
|
|
6
32
|
"0.2.9": {
|
|
7
33
|
"en": "update dependencies\nadd support for node 24\nupdate to ESlint 9",
|
|
8
34
|
"de": "aktualisierung der abhängigkeiten\nunterstützung für knoten 24 hinzufügen\nupdate auf ESlint 9",
|
|
@@ -67,32 +93,6 @@
|
|
|
67
93
|
"pl": "utworzyć tylko jedno gniazdo, ponieważ drugie wydaje się nie być konieczne\nrefaktoring kodu dla lepszej struktury\nmożliwość wyboru interfejsu słuchania w ustawieniach",
|
|
68
94
|
"uk": "створити тільки одну розетку, так як другий здається не потрібно\nрефакторинг коду для кращої структури\nможливість вибору інтерфейсу прослуховування в налаштуваннях",
|
|
69
95
|
"zh-cn": "只创建一个套接字, 因为第二个似乎没有必要\n为更好的结构重构代码\n在设置中选择监听界面的可能性"
|
|
70
|
-
},
|
|
71
|
-
"0.2.4": {
|
|
72
|
-
"en": "fix access",
|
|
73
|
-
"de": "zugriff",
|
|
74
|
-
"ru": "исправить доступ",
|
|
75
|
-
"pt": "corrigir o acesso",
|
|
76
|
-
"nl": "fix toegang",
|
|
77
|
-
"fr": "fixer l'accès",
|
|
78
|
-
"it": "fissare l'accesso",
|
|
79
|
-
"es": "acceso fijo",
|
|
80
|
-
"pl": "naprawić dostęp",
|
|
81
|
-
"uk": "фіксувати доступ",
|
|
82
|
-
"zh-cn": "固定访问"
|
|
83
|
-
},
|
|
84
|
-
"0.2.1": {
|
|
85
|
-
"en": "repair onOff / all other actions Fixes: #65\nfix log spamming because of wildcard",
|
|
86
|
-
"de": "reparatur onOff / alle anderen Aktionen Behebungen: #65\nprotokoll spamming wegen wildcard",
|
|
87
|
-
"ru": "ремонт onOff / все другие действия Исправления: #65\nисправить спамирование лога из-за дикой карты",
|
|
88
|
-
"pt": "reparação emOff / todas as outras ações Correções: #65 #\ncorrigir spam de log por causa do wildcard",
|
|
89
|
-
"nl": "vertaling: Vertaling: 65\nvertaling:",
|
|
90
|
-
"fr": "réparation onOff / toutes les autres actions Fixes: #65\ncorriger l'éblouissement du journal à cause de wildcard",
|
|
91
|
-
"it": "riparazione onOff / tutte le altre azioni Fissi: #65\nsistemare il log spamming a causa di jolly",
|
|
92
|
-
"es": "reparación onOff / todas las demás acciones Arreglos: #65\nfijar registro de spam debido a wildcard",
|
|
93
|
-
"pl": "remontowanie Off / wszystkie inne działania Fiksy #65\nusunąć z powodu dzikiej karty",
|
|
94
|
-
"uk": "ремонт наОфф / всі інші дії Виправлення: #65\nфіксація спамерування колод через дикийкарт",
|
|
95
|
-
"zh-cn": "b. 修理效率/所有其他行动 固定: #65\nfix 由于野生素卡的投机"
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
98
|
"titleLang": {
|
|
@@ -150,12 +150,12 @@
|
|
|
150
150
|
},
|
|
151
151
|
"dependencies": [
|
|
152
152
|
{
|
|
153
|
-
"js-controller": ">=
|
|
153
|
+
"js-controller": ">=6.0.11"
|
|
154
154
|
}
|
|
155
155
|
],
|
|
156
156
|
"globalDependencies": [
|
|
157
157
|
{
|
|
158
|
-
"admin": ">=7.
|
|
158
|
+
"admin": ">=7.6.17"
|
|
159
159
|
}
|
|
160
160
|
]
|
|
161
161
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.govee-local",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Control Govee devices via local access (no cloud)",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Børge Grunicke",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"node": ">= 20.0"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@iobroker/adapter-core": "^3.2
|
|
25
|
+
"@iobroker/adapter-core": "^3.3.2"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@alcalzone/release-script": "^3.8.0",
|
|
@@ -30,27 +30,27 @@
|
|
|
30
30
|
"@alcalzone/release-script-plugin-license": "^3.7.0",
|
|
31
31
|
"@alcalzone/release-script-plugin-manual-review": "^3.7.0",
|
|
32
32
|
"@iobroker/adapter-dev": "^1.4.0",
|
|
33
|
-
"@iobroker/eslint-config": "^2.0
|
|
34
|
-
"@iobroker/testing": "^5.
|
|
35
|
-
"@tsconfig/node14": "^14.1.
|
|
33
|
+
"@iobroker/eslint-config": "^2.1.0",
|
|
34
|
+
"@iobroker/testing": "^5.1.1",
|
|
35
|
+
"@tsconfig/node14": "^14.1.5",
|
|
36
36
|
"@tsconfig/node18": "^18.2.4",
|
|
37
37
|
"@tsconfig/node20": "^20.1.6",
|
|
38
38
|
"@types/chai": "^4.1.1",
|
|
39
39
|
"@types/chai-as-promised": "^8.0.2",
|
|
40
40
|
"@types/mocha": "^10.0.10",
|
|
41
|
-
"@types/node": "^24.0
|
|
41
|
+
"@types/node": "^24.3.0",
|
|
42
42
|
"@types/proxyquire": "^1.3.31",
|
|
43
43
|
"@types/sinon": "^17.0.4",
|
|
44
44
|
"@types/sinon-chai": "^3.2.12",
|
|
45
|
-
"chai": "^
|
|
46
|
-
"chai-as-promised": "^8.0.
|
|
47
|
-
"eslint": "^9.
|
|
45
|
+
"chai": "^6.0.1",
|
|
46
|
+
"chai-as-promised": "^8.0.2",
|
|
47
|
+
"eslint": "^9.34.0",
|
|
48
48
|
"mocha": "^11.7.1",
|
|
49
49
|
"prettier": "^3.3.3",
|
|
50
50
|
"proxyquire": "^2.1.3",
|
|
51
51
|
"rimraf": "^6.0.1",
|
|
52
52
|
"sinon": "^21.0.0",
|
|
53
|
-
"sinon-chai": "^
|
|
53
|
+
"sinon-chai": "^4.0.1",
|
|
54
54
|
"source-map-support": "^0.5.21",
|
|
55
55
|
"ts-node": "^10.9.2",
|
|
56
56
|
"typescript": "^5.6.3"
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/tools/hexTool.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Konvertiert eine Zahl (<255) in einen zweistelligen Hex-String f\u00FCr Farbwerte.\n *\n * @param c Die Zahl, die konvertiert werden soll (0-255)\n * @returns Der Hex-Wert als String\n */\nexport function componentToHex(c: number): string {\n\tconst hex = c.toString(16);\n\treturn hex.length == 1 ? `0${hex}` : hex;\n}\n\n/**\n * Wandelt einen Hex-Farbstring (#RRGGBB) in ein Color-Objekt um.\n *\n * @param hexString Hex-Farbwert als String (Format: #RRGGBB)\n * @returns Ein Objekt mit den Farbwerten r, g und b\n */\nexport function hexToRgb(hexString: string): Color {\n\tif (!/^#[0-9a-fA-F]{6}$/i.test(hexString)) {\n\t\tthrow new Error('Invalid hex string');\n\t}\n\treturn {\n\t\tr: parseInt(hexString.slice(1, 3), 16),\n\t\tg: parseInt(hexString.slice(3, 5), 16),\n\t\tb: parseInt(hexString.slice(5, 7), 16),\n\t};\n}\n\n/**\n * Farbobjekt mit Werten f\u00FCr Rot, Gr\u00FCn und Blau\n */\ntype Color = {\n\tr: number;\n\tg: number;\n\tb: number;\n};\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAMO,SAAS,eAAe,GAAmB;AACjD,QAAM,MAAM,EAAE,SAAS,EAAE;AACzB,SAAO,IAAI,UAAU,IAAI,IAAI,GAAG,KAAK;AACtC;AAQO,SAAS,SAAS,WAA0B;AAClD,MAAI,CAAC,qBAAqB,KAAK,SAAS,GAAG;AAC1C,UAAM,IAAI,MAAM,oBAAoB;AAAA,EACrC;AACA,SAAO;AAAA,IACN,GAAG,SAAS,UAAU,MAAM,GAAG,CAAC,GAAG,EAAE;AAAA,IACrC,GAAG,SAAS,UAAU,MAAM,GAAG,CAAC,GAAG,EAAE;AAAA,IACrC,GAAG,SAAS,UAAU,MAAM,GAAG,CAAC,GAAG,EAAE;AAAA,EACtC;AACD;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|