homebridge-gree-ac 2.2.2-beta.4 → 2.2.2-beta.6
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/CHANGELOG.md +9 -4
- package/LICENSE +175 -175
- package/README.md +511 -500
- package/config.schema.json +520 -495
- package/dist/commands.d.ts +35 -160
- package/dist/commands.js +160 -162
- package/dist/commands.js.map +1 -1
- package/dist/crypto.d.ts +10 -11
- package/dist/crypto.js +28 -33
- package/dist/crypto.js.map +1 -1
- package/dist/index.d.ts +6 -7
- package/dist/index.js +8 -6
- package/dist/index.js.map +1 -1
- package/dist/platform.d.ts +47 -43
- package/dist/platform.js +585 -533
- package/dist/platform.js.map +1 -1
- package/dist/platformAccessory.d.ts +108 -100
- package/dist/platformAccessory.js +1763 -1688
- package/dist/platformAccessory.js.map +1 -1
- package/dist/settings.d.ts +72 -112
- package/dist/settings.js +97 -103
- package/dist/settings.js.map +1 -1
- package/dist/tsAccessory.d.ts +32 -34
- package/dist/tsAccessory.js +69 -71
- package/dist/tsAccessory.js.map +1 -1
- package/dist/version.d.ts +1 -2
- package/dist/version.js +2 -5
- package/dist/version.js.map +1 -1
- package/greedevice.jpg +0 -0
- package/greedevinfo.jpg +0 -0
- package/greemac.jpg +0 -0
- package/ha_fan.jpg +0 -0
- package/ha_settings.jpg +0 -0
- package/package.json +56 -52
- package/uiconfig.jpg +0 -0
- package/uiconfigcustdef.jpg +0 -0
- package/uiconfigmin.jpg +0 -0
- package/dist/commands.d.ts.map +0 -1
- package/dist/crypto.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/platform.d.ts.map +0 -1
- package/dist/platformAccessory.d.ts.map +0 -1
- package/dist/settings.d.ts.map +0 -1
- package/dist/tsAccessory.d.ts.map +0 -1
- package/dist/version.d.ts.map +0 -1
package/dist/platform.js
CHANGED
|
@@ -1,534 +1,586 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if (
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
if (
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
//
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
this.
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
});
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
1
|
+
import dgram from 'dgram';
|
|
2
|
+
import crypto from './crypto.js';
|
|
3
|
+
import { networkInterfaces } from 'os';
|
|
4
|
+
import { readFileSync } from 'fs';
|
|
5
|
+
import { GreeAirConditioner } from './platformAccessory.js';
|
|
6
|
+
import { PLATFORM_NAME, PLUGIN_NAME, UDP_SCAN_PORT, DEFAULT_DEVICE_CONFIG, MODIFY_VERTICAL_SWING_POSITION, ENCRYPTION_VERSION, TS_TYPE, DEF_SCAN_INTERVAL, TEMPERATURE_LIMITS, TEMPERATURE_STEPS } from './settings.js';
|
|
7
|
+
import commands from './commands.js';
|
|
8
|
+
import { version } from './version.js';
|
|
9
|
+
/**
|
|
10
|
+
* HomebridgePlatform
|
|
11
|
+
* This class is the main constructor for your plugin, this is where you should
|
|
12
|
+
* parse the user config and discover/register accessories with Homebridge.
|
|
13
|
+
*/
|
|
14
|
+
export class GreeACPlatform {
|
|
15
|
+
log;
|
|
16
|
+
config;
|
|
17
|
+
api;
|
|
18
|
+
Service;
|
|
19
|
+
Characteristic;
|
|
20
|
+
// this is used to track restored cached accessories
|
|
21
|
+
devices;
|
|
22
|
+
processedDevices;
|
|
23
|
+
skippedDevices;
|
|
24
|
+
warningShown;
|
|
25
|
+
socket;
|
|
26
|
+
pluginAddresses = {};
|
|
27
|
+
ports = [];
|
|
28
|
+
tempUnit;
|
|
29
|
+
// This is only required when using Custom Services and Characteristics not support by HomeKit
|
|
30
|
+
//public readonly CustomServices: any;
|
|
31
|
+
//public readonly CustomCharacteristics: any;
|
|
32
|
+
constructor(log, config, api) {
|
|
33
|
+
this.log = log;
|
|
34
|
+
this.config = config;
|
|
35
|
+
this.api = api;
|
|
36
|
+
this.Service = api.hap.Service;
|
|
37
|
+
this.Characteristic = api.hap.Characteristic;
|
|
38
|
+
// This is only required when using Custom Services and Characteristics not support by HomeKit
|
|
39
|
+
//this.CustomServices = new EveHomeKitTypes(this.api).Services;
|
|
40
|
+
//this.CustomCharacteristics = new EveHomeKitTypes(this.api).Characteristics;
|
|
41
|
+
this.devices = {};
|
|
42
|
+
this.processedDevices = {};
|
|
43
|
+
this.skippedDevices = {};
|
|
44
|
+
this.warningShown = {};
|
|
45
|
+
// get temperature unit from Homebridge UI config
|
|
46
|
+
const configPath = this.api.user.configPath();
|
|
47
|
+
const cfg = JSON.parse(readFileSync(configPath).toString());
|
|
48
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
49
|
+
const configPlatform = cfg?.platforms?.find((item) => item.platform === 'config') || {};
|
|
50
|
+
this.tempUnit = configPlatform?.tempUnits || 'f';
|
|
51
|
+
if (!['f', 'c'].includes(this.tempUnit)) {
|
|
52
|
+
this.tempUnit = 'f';
|
|
53
|
+
}
|
|
54
|
+
this.log.debug(`Temperature display unit is ${this.tempUnit === 'f' ? 'Fahrenheit (°F)' : 'Celsius (°C)'}`);
|
|
55
|
+
// log auto detection parameter
|
|
56
|
+
if (this.config.disableAutoDetection === true) {
|
|
57
|
+
this.log.debug('Auto detection disabled');
|
|
58
|
+
}
|
|
59
|
+
// network initialization
|
|
60
|
+
this.pluginAddresses = this.getNetworkAddresses(cfg?.bridge?.bind);
|
|
61
|
+
if (Object.entries(this.pluginAddresses).length > 0) {
|
|
62
|
+
this.log.debug('Device detection address list {(address : netmask) pairs}:', this.pluginAddresses);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
this.log.error('Error: Homebridge host has no IPv4 address');
|
|
66
|
+
}
|
|
67
|
+
// if no IPv4 address found we create socket for IPv6
|
|
68
|
+
this.socket = dgram.createSocket({ type: (Object.entries(this.pluginAddresses).length > 0) ? 'udp4' : 'udp6', reuseAddr: true });
|
|
69
|
+
this.socket.on('error', (err) => {
|
|
70
|
+
this.log.error('Network - Error:', err.message);
|
|
71
|
+
});
|
|
72
|
+
this.socket.on('close', () => {
|
|
73
|
+
this.log.debug('Network - Connection closed');
|
|
74
|
+
});
|
|
75
|
+
this.log.debug('Finished initializing platform');
|
|
76
|
+
// When this event is fired it means Homebridge has restored all cached accessories from disk.
|
|
77
|
+
// Dynamic Platform plugins should only register new accessories after this event was fired,
|
|
78
|
+
// in order to ensure they weren't added to homebridge already. This event can also be used
|
|
79
|
+
// to start discovery of new accessories.
|
|
80
|
+
this.api.on('didFinishLaunching', () => {
|
|
81
|
+
log.debug('Executing didFinishLaunching callback');
|
|
82
|
+
if (Object.entries(this.pluginAddresses).length === 0) {
|
|
83
|
+
this.socket.close();
|
|
84
|
+
this.log.error('Network - Error: No IPv4 host address found');
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
this.socket.on('message', this.handleMessage);
|
|
88
|
+
// run the method to discover / register your devices as accessories
|
|
89
|
+
this.discoverDevices();
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* This function is invoked when homebridge restores cached accessories from disk at startup.
|
|
95
|
+
* It should be used to set up event handlers for characteristics and update respective values.
|
|
96
|
+
*/
|
|
97
|
+
configureAccessory(accessory) {
|
|
98
|
+
this.log.debug('Loading accessory from cache:', accessory.displayName, JSON.stringify(accessory.context.device));
|
|
99
|
+
// add the restored accessory to the accessories cache, so we can track if it has already been registered
|
|
100
|
+
if (accessory.context?.device?.mac) {
|
|
101
|
+
if (!accessory.context.deviceType || accessory.context.deviceType === 'HeaterCooler') {
|
|
102
|
+
accessory.bound = false;
|
|
103
|
+
}
|
|
104
|
+
accessory.registered = true;
|
|
105
|
+
this.devices[accessory.context.device.mac] = accessory;
|
|
106
|
+
}
|
|
107
|
+
// clean all invalid accessories found in cache
|
|
108
|
+
if (!accessory.context) {
|
|
109
|
+
this.log.debug('Invalid accessory found in cache - deleting:', accessory.displayName, accessory.UUID);
|
|
110
|
+
this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
|
|
111
|
+
}
|
|
112
|
+
// remove deprecated properties from cached accessory
|
|
113
|
+
if (accessory.context?.bound !== undefined) {
|
|
114
|
+
delete accessory.context.bound;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Accessories must only be registered once, previously created accessories
|
|
119
|
+
* must not be registered again to prevent "duplicate UUID" errors.
|
|
120
|
+
*/
|
|
121
|
+
bindCallback() {
|
|
122
|
+
this.log.success(`${PLATFORM_NAME} (${PLUGIN_NAME}) v%s is running on UDP port %d`, version, this.socket.address().port);
|
|
123
|
+
this.ports.push(this.socket.address().port);
|
|
124
|
+
this.socket.setBroadcast(true);
|
|
125
|
+
this.sendScan();
|
|
126
|
+
setInterval(() => {
|
|
127
|
+
this.sendScan();
|
|
128
|
+
}, (this.config.scanInterval || DEF_SCAN_INTERVAL) * 1000); // scanInterval in seconds (default = 60 sec)
|
|
129
|
+
}
|
|
130
|
+
discoverDevices() {
|
|
131
|
+
if (this.config.port === undefined || (this.config.port !== undefined && typeof this.config.port === 'number' &&
|
|
132
|
+
this.config.port === this.config.port && this.config.port >= 1025 && this.config.port <= 65535)) {
|
|
133
|
+
this.socket.bind(this.config.port, undefined, () => this.bindCallback());
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
this.log.error('Error: Port is misconfigured (Valid port values: 1025~65535 or leave port empty to auto select)');
|
|
137
|
+
this.socket.close();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
handleMessage = (msg, rinfo) => {
|
|
141
|
+
this.log.debug('handleMessage -> %s', msg.toString());
|
|
142
|
+
try {
|
|
143
|
+
let message;
|
|
144
|
+
try {
|
|
145
|
+
message = JSON.parse(msg.toString());
|
|
146
|
+
}
|
|
147
|
+
catch (e) {
|
|
148
|
+
this.log.debug('handleMessage - unknown message from %s - BASE64 encoded message: %s', rinfo.address, Buffer.from(msg).toString('base64'));
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (message.i !== 1 || message.t !== 'pack') {
|
|
152
|
+
this.log.debug('handleMessage - unknown response from %s: %j', rinfo.address, message);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
let pack, encryptionVersion;
|
|
156
|
+
if (message.tag === undefined) {
|
|
157
|
+
this.log.debug('handleMessage -> Encryption version: 1');
|
|
158
|
+
pack = crypto.decrypt_v1(message.pack);
|
|
159
|
+
encryptionVersion = 1;
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
this.log.debug('handleMessage -> Encryption version: 2');
|
|
163
|
+
pack = crypto.decrypt_v2(message.pack, message.tag);
|
|
164
|
+
encryptionVersion = 2;
|
|
165
|
+
}
|
|
166
|
+
this.log.debug('handleMessage - Package -> %j', pack);
|
|
167
|
+
if (encryptionVersion === 1 && pack.t === 'dev' && pack.ver && !pack.ver.toString().startsWith('V1.')) {
|
|
168
|
+
// some devices respond to scan command with V1 encryption but binding requires V2 encryption
|
|
169
|
+
// we set encryption to V2 if device version is not V1.x
|
|
170
|
+
encryptionVersion = 2;
|
|
171
|
+
}
|
|
172
|
+
if (pack.t === 'dev') {
|
|
173
|
+
if (this.config.disableAutoDetection !== true || this.config.devices?.find((item) => item.mac === pack.mac) !==
|
|
174
|
+
undefined) {
|
|
175
|
+
this.registerDevice({
|
|
176
|
+
...pack,
|
|
177
|
+
address: rinfo.address,
|
|
178
|
+
port: rinfo.port,
|
|
179
|
+
encryptionVersion,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
if (this.config.disableAutoDetection === true && this.config.devices?.find((item) => item.mac === pack.mac) ===
|
|
184
|
+
undefined) {
|
|
185
|
+
if (this.skippedDevices[pack.mac] !== true) {
|
|
186
|
+
this.log.debug(`Accessory ${pack.mac} skipped`);
|
|
187
|
+
this.skippedDevices[pack.mac] = true;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
this.log.debug('handleMessage - unknown package from %s: %j', rinfo.address.toString(), pack);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
const msg = err.message;
|
|
198
|
+
this.log.error('handleMessage (%s) - Error: %s', rinfo.address.toString(), msg);
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
202
|
+
registerDevice = (deviceInfo) => {
|
|
203
|
+
this.log.debug('registerDevice - deviceInfo:', JSON.stringify(deviceInfo));
|
|
204
|
+
const devcfg = this.config.devices?.find((item) => item.mac === deviceInfo.mac) || { mac: deviceInfo.mac };
|
|
205
|
+
if (!devcfg.disabled && deviceInfo.subCnt !== undefined) {
|
|
206
|
+
// this is a bridge and bridge is enabled
|
|
207
|
+
if (!this.skippedDevices[deviceInfo.mac]) {
|
|
208
|
+
this.log.warn(`Accessory ${deviceInfo.mac} (${devcfg?.name ?? (deviceInfo.name || deviceInfo.mac)}) is a bridge.` +
|
|
209
|
+
' Bridge accessories and devices attached to a bridge are not supported. If you are ready to help plugin development then' +
|
|
210
|
+
'you can create an issue and post detailed debug log about your environment.');
|
|
211
|
+
// skip bridge, only subdevices are AC units
|
|
212
|
+
this.skippedDevices[deviceInfo.mac] = true;
|
|
213
|
+
if (!deviceInfo.subCnt || deviceInfo.subCnt <= 0) {
|
|
214
|
+
this.log.warn(`Warning: No device is attached to bridge '${devcfg?.name ?? (deviceInfo.name || deviceInfo.mac)}'`);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
// read subdevice parameters from configuration
|
|
218
|
+
const subDevices = this.config.devices?.filter((cfg) => cfg.mac?.endsWith(`@${deviceInfo.mac}`) &&
|
|
219
|
+
cfg.mac.length > deviceInfo.mac.length + 1) || [];
|
|
220
|
+
// register subdevices
|
|
221
|
+
subDevices.forEach((cfg) => {
|
|
222
|
+
const subDeviceInfo = { ...deviceInfo };
|
|
223
|
+
subDeviceInfo.mac = cfg.mac;
|
|
224
|
+
subDeviceInfo.uid = cfg.mac?.substring(0, cfg.mac?.indexOf('@'));
|
|
225
|
+
if (subDeviceInfo.subCnt !== undefined) {
|
|
226
|
+
delete subDeviceInfo.subCnt;
|
|
227
|
+
}
|
|
228
|
+
if (subDeviceInfo.name) {
|
|
229
|
+
subDeviceInfo.name = `${subDeviceInfo.uid}@${subDeviceInfo.name}`;
|
|
230
|
+
}
|
|
231
|
+
this.log.debug('registerDevice - sub device:', subDeviceInfo.mac);
|
|
232
|
+
this.registerDevice(subDeviceInfo);
|
|
233
|
+
});
|
|
234
|
+
// try to register all subdevices -- this part may be removed
|
|
235
|
+
for (let i = 1; i <= deviceInfo.subCnt; i++) {
|
|
236
|
+
const subDeviceInfo = { ...deviceInfo };
|
|
237
|
+
subDeviceInfo.mac = `${i.toString()}@${deviceInfo.mac}`;
|
|
238
|
+
subDeviceInfo.uid = i;
|
|
239
|
+
if (subDeviceInfo.subCnt !== undefined) {
|
|
240
|
+
delete subDeviceInfo.subCnt;
|
|
241
|
+
}
|
|
242
|
+
if (subDeviceInfo.name) {
|
|
243
|
+
subDeviceInfo.name = `${subDeviceInfo.uid.toString()}@${subDeviceInfo.name}`;
|
|
244
|
+
}
|
|
245
|
+
this.log.debug('registerDevice - sub device:', subDeviceInfo.mac);
|
|
246
|
+
this.registerDevice(subDeviceInfo);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
this.log.debug('registerDevice - already processed:', devcfg?.name ?? (deviceInfo.name || deviceInfo.mac), deviceInfo.mac);
|
|
251
|
+
}
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
const deviceConfig = {
|
|
255
|
+
// parameters read from config
|
|
256
|
+
...devcfg,
|
|
257
|
+
// fix incorrect values read from config but do not add any value if parameter is missing
|
|
258
|
+
...((devcfg.speedSteps && devcfg.speedSteps !== 3 && devcfg.speedSteps !== 5) || devcfg.speedSteps === 0 ?
|
|
259
|
+
{ speedSteps: DEFAULT_DEVICE_CONFIG.speedSteps } : {}),
|
|
260
|
+
...(devcfg.temperatureSensor && Object.values(TS_TYPE).includes(devcfg.temperatureSensor.toLowerCase()) ?
|
|
261
|
+
{ temperatureSensor: devcfg.temperatureSensor.toLowerCase() }
|
|
262
|
+
: (devcfg.temperatureSensor ? { temperatureSensor: DEFAULT_DEVICE_CONFIG.temperatureSensor } : {})),
|
|
263
|
+
...(devcfg.minimumTargetTemperature &&
|
|
264
|
+
(devcfg.minimumTargetTemperature < Math.min(TEMPERATURE_LIMITS.coolingMinimum, TEMPERATURE_LIMITS.heatingMinimum) ||
|
|
265
|
+
devcfg.minimumTargetTemperature > Math.max(TEMPERATURE_LIMITS.coolingMaximum, TEMPERATURE_LIMITS.heatingMaximum)) ?
|
|
266
|
+
{ minimumTargetTemperature: DEFAULT_DEVICE_CONFIG.minimumTargetTemperature } : {}),
|
|
267
|
+
...(devcfg.maximumTargetTemperature &&
|
|
268
|
+
(devcfg.maximumTargetTemperature < Math.min(TEMPERATURE_LIMITS.coolingMinimum, TEMPERATURE_LIMITS.heatingMinimum) ||
|
|
269
|
+
devcfg.maximumTargetTemperature > Math.max(TEMPERATURE_LIMITS.coolingMaximum, TEMPERATURE_LIMITS.heatingMaximum)) ?
|
|
270
|
+
{ maximumTargetTemperature: DEFAULT_DEVICE_CONFIG.maximumTargetTemperature } : {}),
|
|
271
|
+
...(devcfg.defaultVerticalSwing && ![commands.swingVertical.value.default, commands.swingVertical.value.fixedHighest,
|
|
272
|
+
commands.swingVertical.value.fixedHigher, commands.swingVertical.value.fixedMiddle, commands.swingVertical.value.fixedLower,
|
|
273
|
+
commands.swingVertical.value.fixedLowest].includes(devcfg.defaultVerticalSwing) ?
|
|
274
|
+
{ defaultVerticalSwing: DEFAULT_DEVICE_CONFIG.defaultVerticalSwing } : {}),
|
|
275
|
+
...(devcfg.defaultFanVerticalSwing && ![commands.swingVertical.value.default, commands.swingVertical.value.fixedHighest,
|
|
276
|
+
commands.swingVertical.value.fixedHigher, commands.swingVertical.value.fixedMiddle, commands.swingVertical.value.fixedLower,
|
|
277
|
+
commands.swingVertical.value.fixedLowest].includes(devcfg.defaultFanVerticalSwing) ?
|
|
278
|
+
{ defaultFanVerticalSwing: DEFAULT_DEVICE_CONFIG.defaultFanVerticalSwing } : {}),
|
|
279
|
+
// overrideDefaultVerticalSwing remains here for compatibility reasons
|
|
280
|
+
...(devcfg.overrideDefaultVerticalSwing &&
|
|
281
|
+
!Object.values(MODIFY_VERTICAL_SWING_POSITION).includes(devcfg.overrideDefaultVerticalSwing) ?
|
|
282
|
+
{ overrideDefaultVerticalSwing: DEFAULT_DEVICE_CONFIG.modifyVerticalSwingPosition } : {}),
|
|
283
|
+
...(devcfg.modifyVerticalSwingPosition &&
|
|
284
|
+
!Object.values(MODIFY_VERTICAL_SWING_POSITION).includes(devcfg.modifyVerticalSwingPosition) ?
|
|
285
|
+
{ modifyVerticalSwingPosition: DEFAULT_DEVICE_CONFIG.modifyVerticalSwingPosition } : {}),
|
|
286
|
+
...(devcfg.encryptionVersion && !Object.values(ENCRYPTION_VERSION).includes(devcfg.encryptionVersion) ?
|
|
287
|
+
{ encryptionVersion: DEFAULT_DEVICE_CONFIG.encryptionVersion } : {}),
|
|
288
|
+
};
|
|
289
|
+
// assign customized default to missing parameters
|
|
290
|
+
Object.entries(this.config.devices?.find((item) => item.mac?.toLowerCase() === 'default' &&
|
|
291
|
+
!item?.disabled) || {})
|
|
292
|
+
.forEach(([key, value]) => {
|
|
293
|
+
if (!['mac', 'name', 'ip', 'port', 'disabled'].includes(key) && deviceConfig[key] === undefined) {
|
|
294
|
+
deviceConfig[key] = value;
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
// try to assign temperatureStepSize from Homebridge UI if missing in configuration
|
|
298
|
+
if (deviceConfig.temperatureStepSize === undefined && this.tempUnit === 'c') {
|
|
299
|
+
deviceConfig.temperatureStepSize = TEMPERATURE_STEPS.celsius;
|
|
300
|
+
}
|
|
301
|
+
if (deviceConfig.temperatureStepSize === undefined && this.tempUnit === 'f') {
|
|
302
|
+
deviceConfig.temperatureStepSize = TEMPERATURE_STEPS.fahrenheit;
|
|
303
|
+
}
|
|
304
|
+
if (deviceConfig.temperatureStepSize !== undefined && !Object.values(TEMPERATURE_STEPS).includes(deviceConfig.temperatureStepSize)) {
|
|
305
|
+
this.log.warn(`Warning: Invalid temperature step size detected: ${deviceConfig.temperatureStepSize} ->`, `Accessory ${deviceInfo.mac} is using default value (0.5) instead of the configured one`);
|
|
306
|
+
delete deviceConfig.temperatureStepSize;
|
|
307
|
+
}
|
|
308
|
+
// assign plugin default to missing parameters
|
|
309
|
+
Object.entries(DEFAULT_DEVICE_CONFIG).forEach(([key, value]) => {
|
|
310
|
+
if (deviceConfig[key] === undefined) {
|
|
311
|
+
deviceConfig[key] = value;
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
// check parameters and fix incorrect values (some of them are repeated checks because default device may also be incorrect)
|
|
315
|
+
if ((deviceConfig.speedSteps && deviceConfig.speedSteps !== 3 && deviceConfig.speedSteps !== 5) || deviceConfig.speedSteps === 0) {
|
|
316
|
+
deviceConfig.speedSteps = DEFAULT_DEVICE_CONFIG.speedSteps;
|
|
317
|
+
}
|
|
318
|
+
if (deviceConfig.temperatureSensor && Object.values(TS_TYPE).includes(deviceConfig.temperatureSensor.toLowerCase())) {
|
|
319
|
+
deviceConfig.temperatureSensor = deviceConfig.temperatureSensor.toLowerCase();
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
deviceConfig.temperatureSensor = DEFAULT_DEVICE_CONFIG.temperatureSensor;
|
|
323
|
+
}
|
|
324
|
+
if (deviceConfig.minimumTargetTemperature &&
|
|
325
|
+
(deviceConfig.minimumTargetTemperature < Math.min(TEMPERATURE_LIMITS.coolingMinimum, TEMPERATURE_LIMITS.heatingMinimum) ||
|
|
326
|
+
deviceConfig.minimumTargetTemperature > Math.max(TEMPERATURE_LIMITS.coolingMaximum, TEMPERATURE_LIMITS.heatingMaximum))) {
|
|
327
|
+
deviceConfig.minimumTargetTemperature = DEFAULT_DEVICE_CONFIG.minimumTargetTemperature;
|
|
328
|
+
}
|
|
329
|
+
if (deviceConfig.maximumTargetTemperature &&
|
|
330
|
+
(deviceConfig.maximumTargetTemperature < Math.min(TEMPERATURE_LIMITS.coolingMinimum, TEMPERATURE_LIMITS.heatingMinimum) ||
|
|
331
|
+
deviceConfig.maximumTargetTemperature > Math.max(TEMPERATURE_LIMITS.coolingMaximum, TEMPERATURE_LIMITS.heatingMaximum))) {
|
|
332
|
+
deviceConfig.maximumTargetTemperature = DEFAULT_DEVICE_CONFIG.maximumTargetTemperature;
|
|
333
|
+
}
|
|
334
|
+
if (deviceConfig.minimumTargetTemperature && deviceConfig.maximumTargetTemperature &&
|
|
335
|
+
deviceConfig.minimumTargetTemperature > deviceConfig.maximumTargetTemperature) {
|
|
336
|
+
deviceConfig.minimumTargetTemperature =
|
|
337
|
+
Math.min(DEFAULT_DEVICE_CONFIG.minimumTargetTemperature, DEFAULT_DEVICE_CONFIG.maximumTargetTemperature);
|
|
338
|
+
deviceConfig.maximumTargetTemperature =
|
|
339
|
+
Math.max(DEFAULT_DEVICE_CONFIG.minimumTargetTemperature, DEFAULT_DEVICE_CONFIG.maximumTargetTemperature);
|
|
340
|
+
this.log.warn('Warning: Invalid minimum and maximum target temperature values detected ->', `Accessory ${deviceInfo.mac} is using default values instead of the configured ones`);
|
|
341
|
+
}
|
|
342
|
+
if (deviceConfig.defaultVerticalSwing && ![commands.swingVertical.value.default, commands.swingVertical.value.fixedHighest,
|
|
343
|
+
commands.swingVertical.value.fixedHigher, commands.swingVertical.value.fixedMiddle, commands.swingVertical.value.fixedLower,
|
|
344
|
+
commands.swingVertical.value.fixedLowest].includes(deviceConfig.defaultVerticalSwing)) {
|
|
345
|
+
deviceConfig.defaultVerticalSwing = DEFAULT_DEVICE_CONFIG.defaultVerticalSwing;
|
|
346
|
+
this.log.warn('Warning: Invalid vertical position detected ->', `Accessory ${deviceInfo.mac} is using default value instead of the configured one`);
|
|
347
|
+
}
|
|
348
|
+
if (deviceConfig.defaultFanVerticalSwing && ![commands.swingVertical.value.default, commands.swingVertical.value.fixedHighest,
|
|
349
|
+
commands.swingVertical.value.fixedHigher, commands.swingVertical.value.fixedMiddle, commands.swingVertical.value.fixedLower,
|
|
350
|
+
commands.swingVertical.value.fixedLowest].includes(deviceConfig.defaultFanVerticalSwing)) {
|
|
351
|
+
deviceConfig.defaultFanVerticalSwing = DEFAULT_DEVICE_CONFIG.defaultFanVerticalSwing;
|
|
352
|
+
this.log.warn('Warning: Invalid vertical fan position detected ->', `Accessory ${deviceInfo.mac} is using default value instead of the configured one`);
|
|
353
|
+
}
|
|
354
|
+
// overrideDefaultVerticalSwing remains here for compatibility reasons
|
|
355
|
+
if (deviceConfig.overrideDefaultVerticalSwing &&
|
|
356
|
+
!Object.values(MODIFY_VERTICAL_SWING_POSITION).includes(deviceConfig.overrideDefaultVerticalSwing)) {
|
|
357
|
+
deviceConfig.overrideDefaultVerticalSwing = DEFAULT_DEVICE_CONFIG.modifyVerticalSwingPosition;
|
|
358
|
+
}
|
|
359
|
+
if (deviceConfig.modifyVerticalSwingPosition &&
|
|
360
|
+
!Object.values(MODIFY_VERTICAL_SWING_POSITION).includes(deviceConfig.modifyVerticalSwingPosition)) {
|
|
361
|
+
deviceConfig.modifyVerticalSwingPosition = DEFAULT_DEVICE_CONFIG.modifyVerticalSwingPosition;
|
|
362
|
+
}
|
|
363
|
+
if (deviceConfig.encryptionVersion && !Object.values(ENCRYPTION_VERSION).includes(deviceConfig.encryptionVersion)) {
|
|
364
|
+
deviceConfig.encryptionVersion = DEFAULT_DEVICE_CONFIG.encryptionVersion;
|
|
365
|
+
}
|
|
366
|
+
if (deviceConfig.port !== undefined && (typeof deviceConfig.port !== 'number' || deviceConfig.port !== deviceConfig.port ||
|
|
367
|
+
(typeof deviceConfig.port === 'number' && (deviceConfig.port < 1025 || deviceConfig.port > 65535)))) {
|
|
368
|
+
this.log.warn('Warning: Port is misconfigured (Valid port values: 1025~65535 or leave port empty to auto select) - ' +
|
|
369
|
+
`Accessory ${deviceInfo.mac} listening port overridden: ${deviceConfig.port} -> auto`);
|
|
370
|
+
delete deviceConfig.port;
|
|
371
|
+
}
|
|
372
|
+
// replace deprecated overrideDefaultVerticalSwing with new modifyVerticalSwingPosition
|
|
373
|
+
if (deviceConfig.overrideDefaultVerticalSwing !== undefined && deviceConfig.modifyVerticalSwingPosition === undefined) {
|
|
374
|
+
// found deprecated but missing new
|
|
375
|
+
if (!this.warningShown[`${deviceInfo.mac}_overrideDefaultVerticalSwing`]) {
|
|
376
|
+
this.log.warn('Deprecated configuration parameter found: overrideDefaultVerticalSwing - ' +
|
|
377
|
+
`Accessory ${deviceInfo.mac} parameter value: ${deviceConfig.overrideDefaultVerticalSwing} -> use modifyVerticalSwingPosition`);
|
|
378
|
+
this.warningShown[`${deviceInfo.mac}_overrideDefaultVerticalSwing`] = true;
|
|
379
|
+
}
|
|
380
|
+
deviceConfig.modifyVerticalSwingPosition = deviceConfig.overrideDefaultVerticalSwing;
|
|
381
|
+
delete deviceConfig.overrideDefaultVerticalSwing;
|
|
382
|
+
}
|
|
383
|
+
else if (deviceConfig.overrideDefaultVerticalSwing !== undefined && deviceConfig.modifyVerticalSwingPosition !== undefined) {
|
|
384
|
+
// found both deprecated and new -> keep new only
|
|
385
|
+
if (!this.warningShown[`${deviceInfo.mac}_overrideDefaultVerticalSwing`]) {
|
|
386
|
+
this.log.warn('Deprecated configuration parameter found: overrideDefaultVerticalSwing - ' +
|
|
387
|
+
`Accessory ${deviceInfo.mac} parameter value: ${deviceConfig.overrideDefaultVerticalSwing} -> ignoring`);
|
|
388
|
+
this.warningShown[`${deviceInfo.mac}_overrideDefaultVerticalSwing`] = true;
|
|
389
|
+
}
|
|
390
|
+
delete deviceConfig.overrideDefaultVerticalSwing;
|
|
391
|
+
}
|
|
392
|
+
// ignore invalid silentTimeRange
|
|
393
|
+
if (deviceConfig.silentTimeRange) {
|
|
394
|
+
const match = deviceConfig.silentTimeRange.match(/^(0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]-(((0[0-9]|1[0-9]|2[0-3]):[0-5][0-9])|24:00)$/);
|
|
395
|
+
if (!match || (match && deviceConfig.silentTimeRange !== match[0])) {
|
|
396
|
+
// invalid parameter value (not in HH:MM-HH:MM format)
|
|
397
|
+
if (!this.warningShown[`${deviceInfo.mac}_silentTimeRange`]) {
|
|
398
|
+
this.log.warn('Invalid configuration parameter value found: silentTimeRange - ' +
|
|
399
|
+
`Accessory ${deviceInfo.mac} parameter value: ${deviceConfig.silentTimeRange} -> ignoring`);
|
|
400
|
+
this.warningShown[`${deviceInfo.mac}_silentTimeRange`] = true;
|
|
401
|
+
}
|
|
402
|
+
delete deviceConfig.silentTimeRange;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
// force encryption version if set in config
|
|
406
|
+
if (deviceConfig.encryptionVersion !== ENCRYPTION_VERSION.auto) {
|
|
407
|
+
deviceInfo.encryptionVersion = deviceConfig.encryptionVersion;
|
|
408
|
+
this.log.debug(`Accessory ${deviceInfo.mac} encryption version forced:`, deviceInfo.encryptionVersion);
|
|
409
|
+
}
|
|
410
|
+
let accessory = this.devices[deviceInfo.mac];
|
|
411
|
+
let accessory_ts = this.devices[deviceInfo.mac + '_ts'];
|
|
412
|
+
if (deviceConfig?.disabled || !/^[a-f0-9]{12}$/.test(deviceConfig?.mac.substring(deviceConfig?.mac.indexOf('@') + 1))) {
|
|
413
|
+
if (!devcfg || Object.keys(devcfg).length === 0) {
|
|
414
|
+
this.log.debug('14 DEBUG:', deviceConfig);
|
|
415
|
+
}
|
|
416
|
+
//do not skip unconfigured devices
|
|
417
|
+
if (!this.skippedDevices[deviceInfo.mac]) {
|
|
418
|
+
this.log.info(`Accessory ${deviceInfo.mac} skipped`);
|
|
419
|
+
this.skippedDevices[deviceInfo.mac] = true;
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
this.log.debug(`Accessory ${deviceInfo.mac} skipped`);
|
|
423
|
+
}
|
|
424
|
+
if (accessory) {
|
|
425
|
+
delete this.devices[accessory.context.device.mac];
|
|
426
|
+
this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
|
|
427
|
+
this.log.debug(`registerDevice - unregister (${devcfg.mac === undefined ? 'not configured' : 'disabled'}):`, accessory.displayName, accessory.UUID);
|
|
428
|
+
accessory = undefined;
|
|
429
|
+
}
|
|
430
|
+
if (accessory_ts) {
|
|
431
|
+
delete this.devices[accessory_ts.context.device.mac];
|
|
432
|
+
this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory_ts]);
|
|
433
|
+
this.log.debug(`registerDevice - unregister (${devcfg.mac === undefined ? 'not configured' : 'disabled'}):`, accessory_ts.displayName, accessory_ts.UUID);
|
|
434
|
+
accessory_ts = undefined;
|
|
435
|
+
}
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
// check device address change
|
|
439
|
+
if (accessory && deviceInfo.address !== accessory.context.device.address) {
|
|
440
|
+
this.log.info(`Device [${accessory.displayName} - ${accessory.context.device.mac}] address has changed: %s -> %s`, accessory.context.device.address, deviceInfo.address);
|
|
441
|
+
accessory.context.device.address = deviceInfo.address;
|
|
442
|
+
}
|
|
443
|
+
if (accessory_ts && deviceInfo.address !== accessory_ts.context.device.address) {
|
|
444
|
+
accessory_ts.context.device.address = deviceInfo.address;
|
|
445
|
+
}
|
|
446
|
+
if (accessory && this.processedDevices[accessory.UUID]) {
|
|
447
|
+
// already initalized
|
|
448
|
+
this.log.debug('registerDevice - already processed:', accessory.displayName, accessory.context.device.mac, accessory.UUID);
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
// create heatercooler accessory if not loaded from cache
|
|
452
|
+
const deviceName = deviceConfig?.name ?? (deviceInfo.name || deviceInfo.mac);
|
|
453
|
+
if (!accessory) {
|
|
454
|
+
this.log.debug(`Creating new accessory ${deviceInfo.mac} with name ${deviceName} ...`);
|
|
455
|
+
const uuid = this.api.hap.uuid.generate(deviceInfo.mac);
|
|
456
|
+
accessory = new this.api.platformAccessory(deviceName, uuid, 21 /* Categories.AIR_CONDITIONER */);
|
|
457
|
+
accessory.bound = false;
|
|
458
|
+
accessory.registered = false;
|
|
459
|
+
this.devices[deviceInfo.mac] = accessory;
|
|
460
|
+
}
|
|
461
|
+
// create temperaturesensor accessory if configured as separate and not loaded from cache
|
|
462
|
+
const tsDeviceName = 'Temperature Sensor ' + (deviceConfig?.name ?? (deviceInfo.name || deviceInfo.mac));
|
|
463
|
+
if (!accessory_ts && deviceConfig.temperatureSensor === TS_TYPE.separate) {
|
|
464
|
+
this.log.debug(`Creating new accessory ${deviceInfo.mac}_ts with name ${tsDeviceName} ...`);
|
|
465
|
+
const uuid = this.api.hap.uuid.generate(deviceInfo.mac + '_ts');
|
|
466
|
+
accessory_ts = new this.api.platformAccessory(tsDeviceName, uuid, 10 /* Categories.SENSOR */);
|
|
467
|
+
accessory_ts.registered = false;
|
|
468
|
+
this.devices[deviceInfo.mac + '_ts'] = accessory_ts;
|
|
469
|
+
}
|
|
470
|
+
// unregister temperaturesensor accessory if configuration has changed from separate to any other
|
|
471
|
+
if (accessory_ts && deviceConfig.temperatureSensor !== TS_TYPE.separate) {
|
|
472
|
+
this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory_ts]);
|
|
473
|
+
delete this.devices[deviceInfo.mac + '_ts'];
|
|
474
|
+
this.log.debug('registerDevice - unregister:', accessory_ts.displayName, accessory_ts.UUID);
|
|
475
|
+
accessory_ts = undefined;
|
|
476
|
+
}
|
|
477
|
+
if (accessory_ts && deviceConfig.temperatureSensor === TS_TYPE.separate) {
|
|
478
|
+
// mark temperature sensor device as initialized
|
|
479
|
+
accessory_ts.context.device = { ...deviceInfo };
|
|
480
|
+
accessory_ts.context.device.mac = deviceInfo.mac + '_ts';
|
|
481
|
+
accessory_ts.context.deviceType = 'TemperatureSensor';
|
|
482
|
+
if (deviceConfig.model) {
|
|
483
|
+
accessory_ts.context.device.model = deviceConfig.model;
|
|
484
|
+
}
|
|
485
|
+
this.processedDevices[accessory_ts.UUID] = true;
|
|
486
|
+
this.log.debug(`registerDevice - ${accessory_ts.context.deviceType} created:`, accessory_ts.displayName, accessory_ts.context.device.mac, accessory_ts.UUID);
|
|
487
|
+
// do not load temperature sensor accessory here (it will be loaded from heatercooler accessory)
|
|
488
|
+
}
|
|
489
|
+
if (accessory) {
|
|
490
|
+
// mark heatercooler device as processed
|
|
491
|
+
accessory.context.device = deviceInfo;
|
|
492
|
+
accessory.context.deviceType = 'HeaterCooler';
|
|
493
|
+
this.processedDevices[accessory.UUID] = true;
|
|
494
|
+
this.log.debug(`registerDevice - ${accessory.context.deviceType} created:`, accessory.displayName, accessory.context.device.mac, accessory.UUID);
|
|
495
|
+
// load heatercooler accessory
|
|
496
|
+
new GreeAirConditioner(this, accessory, deviceConfig, accessory_ts?.context.device.mac);
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
sendScan() {
|
|
500
|
+
const message = Buffer.from(JSON.stringify({ t: 'scan' }));
|
|
501
|
+
Object.entries(this.pluginAddresses).forEach((value) => {
|
|
502
|
+
const addr = value[0];
|
|
503
|
+
this.socket.send(message, 0, message.length, UDP_SCAN_PORT, addr, (error) => {
|
|
504
|
+
if (this.pluginAddresses[addr] === '255.255.255.255') {
|
|
505
|
+
this.log.debug(`Scanning for device (unicast) '${message}' ${addr}:${UDP_SCAN_PORT}`);
|
|
506
|
+
}
|
|
507
|
+
else {
|
|
508
|
+
this.log.debug(`Scanning for devices (broadcast) '${message}' ${addr}:${UDP_SCAN_PORT}`);
|
|
509
|
+
}
|
|
510
|
+
if (error) {
|
|
511
|
+
this.log.error('Device scan - Error:', error.message);
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
517
|
+
getNetworkAddresses(bindInterfaces) {
|
|
518
|
+
this.log.debug('Checking network interfaces');
|
|
519
|
+
const pluginAddresses = {};
|
|
520
|
+
let allInterfaces;
|
|
521
|
+
if (bindInterfaces !== null && bindInterfaces !== undefined && bindInterfaces.length > 0) {
|
|
522
|
+
this.log.debug('Homebridge bound to:', bindInterfaces);
|
|
523
|
+
const filteredEntries = Object.entries(networkInterfaces()).filter(([key]) => {
|
|
524
|
+
return bindInterfaces.includes(key);
|
|
525
|
+
});
|
|
526
|
+
allInterfaces = Object.fromEntries(filteredEntries);
|
|
527
|
+
}
|
|
528
|
+
else {
|
|
529
|
+
allInterfaces = networkInterfaces();
|
|
530
|
+
}
|
|
531
|
+
for (const name of Object.keys(allInterfaces)) {
|
|
532
|
+
const nets = allInterfaces[name];
|
|
533
|
+
if (nets) {
|
|
534
|
+
for (const iface of nets) {
|
|
535
|
+
// Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses
|
|
536
|
+
const familyV4Value = typeof iface.family === 'string' ? 'IPv4' : 4;
|
|
537
|
+
if (iface.family === familyV4Value && !iface.internal) {
|
|
538
|
+
const addrParts = iface.address.split('.');
|
|
539
|
+
const netmaskParts = iface.netmask.split('.');
|
|
540
|
+
const broadcast = addrParts.map((e, i) => ((~Number(netmaskParts[i]) & 0xFF) | Number(e)).toString()).join('.');
|
|
541
|
+
this.log.debug('Interface: \'%s\' Address: %s Netmask: %s Broadcast: %s', name, iface.address, iface.netmask, broadcast);
|
|
542
|
+
if (pluginAddresses[broadcast] === undefined) {
|
|
543
|
+
pluginAddresses[broadcast] = iface.netmask;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
// Add IPs from configuration but only if at least one host address found (add only for valid mac addresses)
|
|
550
|
+
if (Object.keys(pluginAddresses).length > 0) {
|
|
551
|
+
const devcfgs = this.config.devices?.filter((item) => item.ip && !item.disabled && /^[a-f0-9]{12}$/.test(item.mac || '')) || [];
|
|
552
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
553
|
+
devcfgs.forEach((value) => {
|
|
554
|
+
const ip = value.ip;
|
|
555
|
+
const ipv4Pattern = /^(25[0-5]|2[0-4]\d|1\d{2}|\d{1,2})(\.(25[0-5]|2[0-4]\d|1\d{2}|\d{1,2})){3}$/;
|
|
556
|
+
if (ipv4Pattern.test(ip)) {
|
|
557
|
+
this.log.debug('Found AC Unit address in configuration:', ip);
|
|
558
|
+
const addrParts = ip.split('.');
|
|
559
|
+
const addresses = {};
|
|
560
|
+
Object.keys(pluginAddresses).forEach((addr) => {
|
|
561
|
+
const netmaskParts = pluginAddresses[addr].split('.');
|
|
562
|
+
const broadcast = addrParts.map((e, i) => ((~Number(netmaskParts[i]) & 0xFF) | Number(e)).toString()).join('.');
|
|
563
|
+
if (addr === broadcast) {
|
|
564
|
+
addresses[ip] = true;
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
const skipAddress = Object.keys(addresses).find((addr) => addr === ip);
|
|
568
|
+
if (skipAddress === undefined) {
|
|
569
|
+
pluginAddresses[ip] = '255.255.255.255';
|
|
570
|
+
}
|
|
571
|
+
else {
|
|
572
|
+
this.log.debug('AC Unit (%s) is already on broadcast list - skipping', skipAddress);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
else {
|
|
576
|
+
this.log.warn('Warning: Invalid IP address found in configuration: %s - skipping', ip);
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
return pluginAddresses;
|
|
581
|
+
}
|
|
582
|
+
getAccessory(mac) {
|
|
583
|
+
return this.devices[mac];
|
|
584
|
+
}
|
|
585
|
+
}
|
|
534
586
|
//# sourceMappingURL=platform.js.map
|