@homebridge-plugins/homebridge-govee 11.32.2-beta.2 → 11.32.2-beta.21

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.
Files changed (73) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/config.schema.json +1 -1
  3. package/lib/connection/ble.js +26 -1
  4. package/lib/connection/openapi.js +18 -0
  5. package/lib/device/appliance-basic.js +377 -0
  6. package/lib/device/dehumidifier-H7150.js +9 -3
  7. package/lib/device/diffuser-H7161.js +7 -1
  8. package/lib/device/fan-H1370.js +7 -1
  9. package/lib/device/fan-H7100.js +36 -4
  10. package/lib/device/fan-H7105.js +7 -1
  11. package/lib/device/fan-H7107.js +7 -1
  12. package/lib/device/heater1a.js +52 -12
  13. package/lib/device/heater1b.js +14 -3
  14. package/lib/device/heater2.js +7 -1
  15. package/lib/device/humidifier-H7140.js +7 -1
  16. package/lib/device/humidifier-H7141.js +7 -1
  17. package/lib/device/humidifier-H7142.js +7 -1
  18. package/lib/device/humidifier-H7143.js +7 -1
  19. package/lib/device/humidifier-H7160.js +7 -1
  20. package/lib/device/ice-maker-H7172.js +19 -7
  21. package/lib/device/index.js +44 -51
  22. package/lib/device/kettle.js +7 -1
  23. package/lib/device/outlet-single.js +7 -1
  24. package/lib/device/purifier-H7120.js +14 -25
  25. package/lib/device/purifier-H7122.js +7 -1
  26. package/lib/device/purifier-H7123.js +9 -3
  27. package/lib/device/purifier-H7126.js +7 -1
  28. package/lib/device/{sensor-button.js → sensor-basic.js} +14 -2
  29. package/lib/device/sensor-presence.js +7 -1
  30. package/lib/device/sensor-thermo4.js +13 -2
  31. package/lib/device/switch-double.js +34 -15
  32. package/lib/device/switch-triple.js +36 -17
  33. package/lib/device/template.js +15 -2
  34. package/lib/platform.js +83 -799
  35. package/lib/utils/capabilities.js +220 -0
  36. package/lib/utils/command-builder.js +6 -4
  37. package/lib/utils/config-parser.js +280 -0
  38. package/lib/utils/constants.js +120 -8
  39. package/lib/utils/decode.js +4 -1
  40. package/lib/utils/device-capabilities.js +77 -3
  41. package/lib/utils/device-context.js +179 -0
  42. package/lib/utils/device-merge.js +251 -0
  43. package/lib/utils/dispatch.js +159 -0
  44. package/lib/utils/functions.js +9 -0
  45. package/lib/utils/lang-en.js +0 -2
  46. package/lib/utils/report-unknown.js +156 -0
  47. package/package.json +1 -1
  48. package/lib/device/dehumidifier-H7151.js +0 -189
  49. package/lib/device/dehumidifier-H7152.js +0 -180
  50. package/lib/device/diffuser-H7162.js +0 -123
  51. package/lib/device/fan-H1310.js +0 -284
  52. package/lib/device/fan-H7101.js +0 -340
  53. package/lib/device/fan-H7102.js +0 -284
  54. package/lib/device/fan-H7106.js +0 -284
  55. package/lib/device/fan-H7111.js +0 -345
  56. package/lib/device/humidifier-H7145.js +0 -187
  57. package/lib/device/humidifier-H7147.js +0 -187
  58. package/lib/device/humidifier-H7148.js +0 -187
  59. package/lib/device/humidifier-H7149.js +0 -187
  60. package/lib/device/humidifier-H714E.js +0 -187
  61. package/lib/device/ice-maker-H717D.js +0 -218
  62. package/lib/device/ice-maker-H8120.js +0 -218
  63. package/lib/device/outlet-double.js +0 -188
  64. package/lib/device/outlet-triple.js +0 -160
  65. package/lib/device/purifier-H7121.js +0 -406
  66. package/lib/device/purifier-H7124.js +0 -504
  67. package/lib/device/purifier-H7127.js +0 -359
  68. package/lib/device/purifier-H7128.js +0 -359
  69. package/lib/device/purifier-H7129.js +0 -359
  70. package/lib/device/purifier-H712C.js +0 -359
  71. package/lib/device/sensor-contact.js +0 -22
  72. package/lib/utils/ble-timeouts.test.js +0 -44
  73. package/lib/utils/functions.test.js +0 -292
@@ -129,17 +129,56 @@ export default {
129
129
 
130
130
  models: {
131
131
  rgb: [
132
+ 'B7080', // 4 pcs Rock Lights
133
+ 'B7081', // 8 pcs Rock Lights
134
+ 'B7082', // 6 pcs Rock Lights
135
+ 'H1161', // Govee Sync
136
+ 'H1163', // Music Sync Box 2
137
+ 'H1168', // Sync Box
138
+ 'H1232', // Ceiling Light
132
139
  'H1250', // 18-Inch Ceiling Light Pro, https://github.com/homebridge-plugins/homebridge-govee/issues/1333
140
+ 'H1252', // Ceiling Light
133
141
  'H1270', // Ceiling Light Ultra
142
+ 'H12D0', // Govee Ceiling Light
134
143
  'H1401',
144
+ 'H14A1', // Smart LED Bulb
135
145
  'H14C0', // https://github.com/homebridge-plugins/homebridge-govee/issues/1301
136
146
  'H14C1', // https://github.com/homebridge-plugins/homebridge-govee/issues/1319
147
+ 'H14C2', // Edison Bulb
137
148
  'H1630',
138
149
  'H16B0', // https://github.com/homebridge-plugins/homebridge-govee/issues/1278
139
150
  'H16C0', // https://github.com/homebridge-plugins/homebridge-govee/issues/1286
140
151
  'H1741', // https://github.com/homebridge-plugins/homebridge-govee/issues/1278
152
+ 'H1771', // Table Lamp
153
+ 'H1811', // Projector Light
154
+ 'H1A42', // Strip Light
155
+ 'H1A43', // Strip Light
156
+ 'H1A44', // Strip Light
157
+ 'H1A45', // Strip Light
158
+ 'H1AA2', // Strip Light
159
+ 'H1AA5', // Strip Light
160
+ 'H1AB1', // Strip Light
161
+ 'H1AB2', // Strip Light
162
+ 'H1AB3', // Strip Light
163
+ 'H1B6A', // Strip Light
164
+ 'H2800', // Monitor Light Bar
165
+ 'H2A40', // TV Backlight 3
166
+ 'H2A41', // TV Backlight 3
141
167
  'H3001', // bluetooth-only solar string lights, https://github.com/homebridge-plugins/homebridge-govee/issues/1328
168
+ 'H30D0', // Bulb String Lights
169
+ 'H30D1', // Bulb String Lights
142
170
  'H3200', // https://github.com/homebridge-plugins/homebridge-govee/issues/1292
171
+ 'H3351', // Wall Floodlight
172
+ 'H3401', // Outdoor Deck Lights
173
+ 'H3500', // RGBIC Path Lights
174
+ 'H3501', // RGBIC Path Lights
175
+ 'H3510', // Cylinder Pathway Light
176
+ 'H3511', // Cylinder Pathway Light
177
+ 'H3751', // Wall Light Pro
178
+ 'H3860', // Projector Light
179
+ 'H3A51', // Permanent Lights 2 Pro
180
+ 'H3A52', // Permanent Lights 2 Pro
181
+ 'H3A53', // Permanent Lights 2 Pro
143
182
  'H6001',
144
183
  'H6002',
145
184
  'H6003',
@@ -219,6 +258,7 @@ export default {
219
258
  'H6091',
220
259
  'H6092',
221
260
  'H6093',
261
+ 'H6094', // Star Light Projector
222
262
  'H6095',
223
263
  'H6097',
224
264
  'H6098',
@@ -236,14 +276,18 @@ export default {
236
276
  'H6101',
237
277
  'H6102',
238
278
  'H6104',
279
+ 'H6105', // TV Backlight
239
280
  'H6107',
240
281
  'H6109',
241
282
  'H610A',
242
283
  'H610B',
243
284
  'H6110',
285
+ 'H6113', // Car Light
244
286
  'H6114',
245
287
  'H6116',
246
288
  'H6117',
289
+ 'H6118', // Car Led Lights
290
+ 'H6119', // Car Led Lights
247
291
  'H611A',
248
292
  'H611B',
249
293
  'H611C',
@@ -294,9 +338,12 @@ export default {
294
338
  'H6160',
295
339
  'H6161',
296
340
  'H6163',
341
+ 'H6165', // Rock Light
342
+ 'H6166', // Rock Light
297
343
  'H6167',
298
344
  'H6168',
299
345
  'H6169',
346
+ 'H616B', // RGB Strip Lights
300
347
  'H616C',
301
348
  'H616D',
302
349
  'H616E',
@@ -312,14 +359,19 @@ export default {
312
359
  'H617C',
313
360
  'H617E',
314
361
  'H617F',
362
+ 'H617G', // RGBIC LED Strip Light
315
363
  'H6181',
316
364
  'H6182',
365
+ 'H6184', // Car Underglow Lights
317
366
  'H6185',
318
367
  'H6188',
319
368
  'H618A',
320
369
  'H618C',
321
370
  'H618E',
322
371
  'H618F',
372
+ 'H618G', // RGBIC LED Strip Light
373
+ 'H6192', // Motorcycle LED Lights
374
+ 'H6194', // Motorcycle LED Lights
323
375
  'H6195',
324
376
  'H6196',
325
377
  'H6198',
@@ -350,6 +402,7 @@ export default {
350
402
  'H61C2',
351
403
  'H61C3',
352
404
  'H61C5',
405
+ 'H61D2', // Neon Rope Light
353
406
  'H61D3',
354
407
  'H61D5',
355
408
  'H61D6',
@@ -360,9 +413,13 @@ export default {
360
413
  'H61F2',
361
414
  'H61F5',
362
415
  'H61F6',
416
+ 'H6350', // Net Lights
417
+ 'H6351', // Net Lights
363
418
  'H6601',
364
419
  'H6602',
420
+ 'H6603', // AI Sync Box Kit
365
421
  'H6604',
422
+ 'H6608', // Gaming Light Kit G1
366
423
  'H6609',
367
424
  'H6630',
368
425
  'H6631',
@@ -370,16 +427,25 @@ export default {
370
427
  'H6641',
371
428
  'H6671',
372
429
  'H6672',
430
+ 'H6690', // TV Backlight 3 Lite
373
431
  'H66A0',
374
432
  'H66A1',
375
433
  'H6800',
376
434
  'H6810',
377
435
  'H6811',
436
+ 'H6821', // Meteor Shower Lights
378
437
  'H6840',
379
438
  'H6841',
439
+ 'H6842', // Cone Tree Lights
440
+ 'H6843', // Cone Tree Lights
441
+ 'H6850', // Govee Ball Lights
442
+ 'H6860', // C9 String Lights
443
+ 'H6861', // C9 String Lights
444
+ 'H6870', // RGBWIC String Lights
380
445
  'H6871',
381
446
  'H7001',
382
447
  'H7002',
448
+ 'H7004', // Plant Grow Lights
383
449
  'H7005',
384
450
  'H7006',
385
451
  'H7007',
@@ -413,8 +479,10 @@ export default {
413
479
  'H7039',
414
480
  'H703A',
415
481
  'H703B',
482
+ 'H7040', // Camping lantern
416
483
  'H7041',
417
484
  'H7042',
485
+ 'H7045', // String Lights
418
486
  'H7046', // https://github.com/homebridge-plugins/homebridge-govee/issues/1297
419
487
  'H7050',
420
488
  'H7051',
@@ -443,11 +511,15 @@ export default {
443
511
  'H706B',
444
512
  'H706C',
445
513
  'H7070',
514
+ 'H7071', // Decoration Projector
515
+ 'H7072', // Lamp Post Lights
446
516
  'H7073',
447
517
  'H7075',
448
518
  'H7076',
449
519
  'H7078',
450
520
  'H707A',
521
+ 'H707B', // Permanent Lights Prism
522
+ 'H707C', // Permanent Lights Prism
451
523
  'H7086',
452
524
  'H7087',
453
525
  'H7090',
@@ -463,6 +535,7 @@ export default {
463
535
  'H70B4',
464
536
  'H70B5',
465
537
  'H70B6',
538
+ 'H70B8', // Lightwall
466
539
  'H70BC',
467
540
  'H70C1',
468
541
  'H70C2',
@@ -474,26 +547,49 @@ export default {
474
547
  'H70D1',
475
548
  'H70D2',
476
549
  'H70D3',
550
+ 'H7308', // Copper Wire Light
551
+ 'H7309', // Copper Wire Light
552
+ 'H7310', // Copper Wire Light
553
+ 'H7311', // Copper Wire Light
554
+ 'H7312', // Copper Wire Light
555
+ 'H7313', // Copper Wire Light
556
+ 'H7315', // LED Curtain Lights
557
+ 'H7317', // RGB Copper Wire Lights
558
+ 'H7318', // RGB Copper Wire Lights
559
+ 'H800B', // Govee Smart Bulb
477
560
  'H8015',
561
+ 'H801A', // Downlight
478
562
  'H801B',
479
563
  'H801C',
480
564
  'H801D', // https://github.com/homebridge-plugins/homebridge-govee/issues/1320
481
565
  'H8022',
482
566
  'H8025',
567
+ 'H8026', // LED Bulb String Lights
483
568
  'H802A', // https://github.com/homebridge-plugins/homebridge-govee/issues/1295
569
+ 'H8048', // Gaming Light Bars
570
+ 'H8057', // Flood Lights
484
571
  'H805A',
485
572
  'H805B',
486
573
  'H805C',
574
+ 'H805D', // Permanent Lights Elite
487
575
  'H8066',
576
+ 'H8067', // Outdoor Spotlights
577
+ 'H8069', // Mini Panel Lights
488
578
  'H806A',
579
+ 'H806C', // Permanent Lights Pro
489
580
  'H8072',
490
581
  'H8076',
582
+ 'H807C', // Floor Lamp 2
491
583
  'H808A',
584
+ 'H8098', // RGBIC TV Backlight 3S
492
585
  'H80A1',
493
586
  'H80A4',
587
+ 'H80B5', // Strip Light
494
588
  'H80C4',
495
589
  'H80C5',
590
+ 'H80D1', // Icicle Lights
496
591
  'H8604',
592
+ 'H8630', // Gaming Pixel Light
497
593
  'H8811',
498
594
  'H8840',
499
595
  'H8841',
@@ -508,14 +604,27 @@ export default {
508
604
  'R1630',
509
605
  'R16D0',
510
606
  'R1741',
607
+ 'R1810', // Star Light Projector
608
+ 'R2A80', // TV Backlight 3 Pro
609
+ 'R2A81', // TV Backlight 3 Pro
610
+ 'R3A51', // Permanent Lights
611
+ 'R60B3', // Uplighter Floor Lamp
612
+ 'R617G', // RGBIC LED Strip Light
613
+ 'R618D', // RGBIC LED Strip Light
614
+ 'R6842', // Cone Tree Lights
615
+ 'R6861', // C9 String Lights
616
+ 'R6871', // RGBWIC String Lights
617
+ 'R707A', // Permanent Lights Prism
618
+ 'R707B', // Permanent Lights Prism
619
+ 'R707C', // Permanent Lights Prism
511
620
  ],
512
621
  // H5901 is a water timer, controlled only through the OpenAPI - it has no
513
622
  // usable AWS, BLE or LAN path, so it needs an API key configured. Set
514
623
  // showAs to 'valve' or 'tap' for a more fitting HomeKit tile (#1324)
515
- switchSingle: ['H5001', 'H5080', 'H5081', 'H5083', 'H5086', 'H5901', 'H7014'],
624
+ switchSingle: ['H5001', 'H5080', 'H5081', 'H5083', 'H5085', 'H5086', 'H5901', 'H7014'],
516
625
  switchDouble: ['H5082', 'H5089'],
517
626
  switchTriple: ['H5160'],
518
- sensorLeak: ['H5054', 'H5058', 'H5059'],
627
+ sensorLeak: ['H5054', 'H5058', 'H5059', 'H5830'],
519
628
  sensorThermo: [
520
629
  'B5178',
521
630
  'H5051',
@@ -536,6 +645,7 @@ export default {
536
645
  'H5108',
537
646
  'H5109', // https://github.com/homebridge-plugins/homebridge-govee/issues/1099
538
647
  'H5110', // https://github.com/homebridge-plugins/homebridge-govee/issues/1217
648
+ 'H5111', // same broadcast format as the H5101 family
539
649
  'H5112', // https://github.com/homebridge-plugins/homebridge-govee/issues/1197
540
650
  'H5171', // https://github.com/homebridge-plugins/homebridge-govee/issues/1243
541
651
  'H5174',
@@ -543,19 +653,21 @@ export default {
543
653
  'H5179',
544
654
  'H5183',
545
655
  'H5190',
656
+ 'H5220', // same broadcast format as the H5101 family
546
657
  'H5310', // https://github.com/homebridge-plugins/homebridge-govee/issues/1293
658
+ 'R5112', // the refreshed H5112
547
659
  ],
548
660
  sensorThermo4: ['H5198'],
549
661
  sensorMonitor: ['H5106'],
550
662
  sensorCO2: ['H5140'], // CO2 + temp + humidity monitor, AWS opcode 0x0A — closes #1179
551
- fan: ['H1310', 'H1370', 'H7100', 'H7101', 'H7102', 'H7105', 'H7106', 'H7107', 'H7111', 'R1310'],
552
- heater1: ['H7130', 'H713A', 'H713B', 'H713C'],
663
+ fan: ['H1310', 'H1370', 'H7100', 'H7101', 'H7102', 'H7103', 'H7105', 'H7106', 'H7107', 'H7111', 'H7112', 'R1310'],
664
+ heater1: ['H7130', 'H713A', 'H713B', 'H713C', 'H7137', 'H713E'],
553
665
  heater2: ['H7131', 'H7132', 'H7133', 'H7134', 'H7135'],
554
666
  dehumidifier: ['H7150', 'H7151', 'H7152'],
555
- humidifier: ['H7140', 'H7141', 'H7142', 'H7143', 'H7145', 'H7147', 'H7148', 'H7149', 'H714E', 'H7160'],
667
+ humidifier: ['H7140', 'H7141', 'H7142', 'H7143', 'H7145', 'H7146', 'H7147', 'H7148', 'H7149', 'H714E', 'H7160'],
556
668
  purifier: ['H7120', 'H7121', 'H7122', 'H7123', 'H7124', 'H7126', 'H7127', 'H7128', 'H7129', 'H712C'],
557
669
  diffuser: ['H7161', 'H7162'],
558
- iceMaker: ['H7172', 'H717D', 'H8120'],
670
+ iceMaker: ['H7172', 'H717D', 'H8102', 'H8120', 'H8121', 'H8122', 'H8131'],
559
671
  sensorButton: ['H5122'],
560
672
  sensorContact: ['H5123'],
561
673
  sensorPresence: ['H5127'],
@@ -567,8 +679,8 @@ export default {
567
679
  'H5024', // https://github.com/homebridge-plugins/homebridge-govee/issues/835
568
680
  'H5042', // https://github.com/homebridge-plugins/homebridge-govee/issues/849
569
681
  'H5043', // https://github.com/homebridge-plugins/homebridge-govee/issues/558
570
- 'H5085', // https://github.com/homebridge-plugins/homebridge-govee/issues/951
571
- 'H5111', // https://github.com/homebridge-plugins/homebridge-govee/issues/1117
682
+ // https://github.com/homebridge-plugins/homebridge-govee/issues/951
683
+ // https://github.com/homebridge-plugins/homebridge-govee/issues/1117
572
684
  'H5121', // https://github.com/homebridge-plugins/homebridge-govee/issues/913
573
685
  'H5124', // https://github.com/homebridge-plugins/homebridge-govee/issues/1119
574
686
  'H5126', // https://github.com/homebridge-plugins/homebridge-govee/issues/910
@@ -84,7 +84,10 @@ export function decodeH5179Values(streamUpdate) {
84
84
  throw new Error(`H5179 stream too short: ${streamUpdate.length} chars (need 22)`)
85
85
  }
86
86
 
87
- const temp_lsb = streamUpdate.substring(14, 16) + streamUpdate.substring(16, 18)
87
+ // Temperature and humidity are each a little-endian pair, so the high byte is
88
+ // read before the low one. They must not share a byte: the temperature pair
89
+ // is bytes 6 and 7, the humidity pair bytes 8 and 9, and the battery byte 10.
90
+ const temp_lsb = streamUpdate.substring(14, 16) + streamUpdate.substring(12, 14)
88
91
  const hum_lsb = streamUpdate.substring(18, 20) + streamUpdate.substring(16, 18)
89
92
 
90
93
  const tempInC = twos_complement(Number.parseInt(temp_lsb, 16)) / 100
@@ -11,7 +11,6 @@ const modelOverrides = {
11
11
  H6005: { bleColorCmd: [COLOR_SUB.RGB_ALT] },
12
12
  H6052: { bleColorCmd: [COLOR_SUB.RGB_ALT], bleBrightnessScale: 0x64 },
13
13
  H6058: { bleColorCmd: [COLOR_SUB.RGB_ALT], bleBrightnessScale: 0x64 },
14
- H6102: { bleColorCmd: [COLOR_SUB.RGB_ALT], bleBrightnessScale: 0x64 },
15
14
  H613B: { bleColorCmd: [COLOR_SUB.RGB_ALT], bleBrightnessScale: 0x64 },
16
15
  H613D: { bleColorCmd: [COLOR_SUB.RGB_ALT], bleBrightnessScale: 0x64 },
17
16
 
@@ -28,6 +27,26 @@ const modelOverrides = {
28
27
  // segment-based extended format, as seen in its own TTR rule data (#1290)
29
28
  H617E: { bleColorCmd: COLOR_SUB.RGB_EXTENDED, bleColorCmdSuffix: [0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x7F], bleBrightnessScale: 0x64 },
30
29
 
30
+ // The H6102 is the same RGBIC generation as the H617A above. It was on the
31
+ // 0x0D command, which it accepts but silently ignores - on, off and
32
+ // brightness worked while colour did nothing. Which colour command it wants
33
+ // depends on its firmware, so the choice is made in firmwareOverrides below
34
+ // and only the brightness scale is fixed here (#1332)
35
+ H6102: { bleBrightnessScale: 0x64 },
36
+
37
+ // The H1310 ceiling fan shares the H7100's handler, but its oscillation
38
+ // status message has never been confirmed to mean the same thing, so reading
39
+ // it back stays off. While it is off, an owner's fan reporting one produces a
40
+ // line in the log saying so - which is the evidence needed to turn this on
41
+ H1310: { readsSwingStatus: false },
42
+ R1310: { readsSwingStatus: false },
43
+
44
+ // Later ice makers number their ice sizes the natural way round. The older
45
+ // ones run the other way, small being the highest byte, so the two families
46
+ // share a handler and differ only here
47
+ H8121: { iceSizeAscending: true },
48
+ H8122: { iceSizeAscending: true },
49
+
31
50
  // AWS outlet uses 17/16 for on/off instead of 1/0
32
51
  H5080: { awsPowerOn: 17, awsPowerOff: 16 },
33
52
  H5083: { awsPowerOn: 17, awsPowerOff: 16 },
@@ -48,6 +67,44 @@ const modelOverrides = {
48
67
  H6022: { awsBrightnessNoScale: true },
49
68
  }
50
69
 
70
+ /**
71
+ * Overrides that depend on the device's firmware version as well as its model.
72
+ *
73
+ * Each entry gives a `minVersion` and the capabilities to apply from that
74
+ * version upwards. Versions are the dotted `versionSoft` string reported by the
75
+ * device, compared part by part, so "1.03.01" sorts above "1.02.99". A device
76
+ * whose firmware is unknown keeps the plain model defaults, which is the safer
77
+ * of the two branches.
78
+ */
79
+ const firmwareOverrides = {
80
+ // Older H6102 firmware takes the standard 0x02 colour command. From 1.03.01
81
+ // the strip moves to the newer extended format, the same one the H617A and
82
+ // H617E need (#1332)
83
+ H6102: [
84
+ {
85
+ minVersion: '1.03.01',
86
+ caps: { bleColorCmd: COLOR_SUB.RGB_EXTENDED, bleColorCmdSuffix: [0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x7F] },
87
+ },
88
+ ],
89
+ }
90
+
91
+ /**
92
+ * Compare two dotted version strings. Returns a positive number if `a` is
93
+ * newer than `b`, negative if older, and zero if they match. Missing parts
94
+ * count as zero, so "1.03" and "1.03.00" are equal.
95
+ */
96
+ export function compareVersions(a, b) {
97
+ const aParts = String(a).split('.')
98
+ const bParts = String(b).split('.')
99
+ for (let i = 0; i < Math.max(aParts.length, bParts.length); i += 1) {
100
+ const diff = (Number.parseInt(aParts[i], 10) || 0) - (Number.parseInt(bParts[i], 10) || 0)
101
+ if (diff !== 0) {
102
+ return diff
103
+ }
104
+ }
105
+ return 0
106
+ }
107
+
51
108
  const defaults = {
52
109
  bleColorCmd: [COLOR_SUB.RGB_DEFAULT],
53
110
  bleColorCmdSuffix: [],
@@ -57,9 +114,26 @@ const defaults = {
57
114
  bleWriteUuid: UUID.WRITE_DEFAULT,
58
115
  awsStatusCmdVersion: 2,
59
116
  awsBrightnessNoScale: false,
117
+ readsSwingStatus: true,
118
+ iceSizeAscending: false,
60
119
  }
61
120
 
62
- export function getDeviceCapabilities(model) {
121
+ export function getDeviceCapabilities(model, firmware) {
63
122
  const overrides = modelOverrides[model] || {}
64
- return { ...defaults, ...overrides }
123
+
124
+ // Layer on any firmware-gated capabilities, oldest matching rule first so a
125
+ // newer rule wins. Skipped entirely when the device has not told us its
126
+ // firmware version.
127
+ const firmwareCaps = {}
128
+ if (firmware) {
129
+ const rules = [...(firmwareOverrides[model] || [])]
130
+ .sort((a, b) => compareVersions(a.minVersion, b.minVersion))
131
+ rules.forEach((rule) => {
132
+ if (compareVersions(firmware, rule.minVersion) >= 0) {
133
+ Object.assign(firmwareCaps, rule.caps)
134
+ }
135
+ })
136
+ }
137
+
138
+ return { ...defaults, ...overrides, ...firmwareCaps }
65
139
  }
@@ -0,0 +1,179 @@
1
+ import platformConsts from './constants.js'
2
+ import { getDeviceCapabilities } from './device-capabilities.js'
3
+ import { hasProperty } from './functions.js'
4
+
5
+ /**
6
+ * Fills in everything a handler needs to know about a device before it is
7
+ * built: which connections are available, what firmware it reports, the limits
8
+ * it works within, and what Govee's api says it can do.
9
+ *
10
+ * Lifted out of the platform unchanged. It is data assembly rather than
11
+ * anything to do with Homebridge, and it was making the one method that sets a
12
+ * device up twice as long as it needed to be.
13
+ *
14
+ * @param {object} platform the platform, for logging and settings
15
+ * @param {object} accessory the accessory being set up
16
+ * @param {object} device the device as Govee describes it
17
+ * @param {object} deviceConf this device's settings
18
+ * @param {object} joins where to record the connections this device can use -
19
+ * `awsDevices`, `awsDevicesToPoll`, and whether this kind of device needs its
20
+ * status asking for over AWS
21
+ */
22
+ export function applyDeviceContext(platform, accessory, device, deviceConf, joins) {
23
+ const { awsDevices, awsDevicesToPoll, doAWSPolling } = joins
24
+ // Add the temperatureSource config to the context if exists
25
+ if (deviceConf.temperatureSource) {
26
+ accessory.context.temperatureSource = deviceConf.temperatureSource
27
+ }
28
+
29
+ // Add the openApiTempUnit config to the context if set to something other than auto
30
+ if (deviceConf.openApiTempUnit && deviceConf.openApiTempUnit !== 'auto') {
31
+ accessory.context.openApiTempUnit = deviceConf.openApiTempUnit
32
+ } else {
33
+ delete accessory.context.openApiTempUnit
34
+ }
35
+
36
+ // Get a supported command list if provided, with their options
37
+ if (device.supportCmds && Array.isArray(device.supportCmds)) {
38
+ accessory.context.supportedCmds = device.supportCmds
39
+ accessory.context.supportedCmdsOpts = {}
40
+
41
+ device.supportCmds.forEach((cmd) => {
42
+ if (device?.properties?.[cmd]) {
43
+ accessory.context.supportedCmdsOpts[cmd] = device.properties[cmd]
44
+ }
45
+ })
46
+ }
47
+
48
+ // Add some initial context information which is changed later
49
+ accessory.context.hasAwsControl = false
50
+ accessory.context.useAwsControl = false
51
+ accessory.context.hasOpenApiControl = false
52
+ accessory.context.useOpenApiControl = false
53
+ accessory.context.hasBleControl = false
54
+ accessory.context.useBleControl = false
55
+ accessory.context.hasLanControl = device.isLanDevice
56
+ accessory.context.useLanControl = accessory.context.hasLanControl
57
+ accessory.context.firmware = false
58
+ accessory.context.hardware = false
59
+ accessory.context.image = false
60
+
61
+ // Overrides for when a custom IP is provided, for a light which is not BLE only
62
+ if (
63
+ deviceConf.customIPAddress
64
+ && accessory.context.hasLanControl
65
+ && accessory.context.hasAwsControl
66
+ && platformConsts.models.rgb.includes(device.model)
67
+ ) {
68
+ accessory.context.hasLanControl = true
69
+ accessory.context.useLanControl = true
70
+ }
71
+
72
+ // If the device is LAN-only, then sync the display name with the label in the configuration
73
+ if (device.isLanOnly) {
74
+ accessory.displayName = device.deviceName
75
+ }
76
+
77
+ // See if we have extra HTTP client info for this device
78
+ if (device.httpInfo) {
79
+ // Save the hardware and firmware versions
80
+ accessory.context.firmware = device.httpInfo.versionSoft
81
+ accessory.context.hardware = device.httpInfo.versionHard
82
+
83
+ // It's possible to show a nice little icon of the device in the Homebridge UI
84
+ if (device.httpInfo.deviceExt && device.httpInfo.deviceExt.extResources) {
85
+ try {
86
+ const parsed = JSON.parse(device.httpInfo.deviceExt.extResources)
87
+ if (parsed && parsed.skuUrl) {
88
+ accessory.context.image = parsed.skuUrl
89
+ }
90
+ } catch {
91
+ // Ignore malformed extResources
92
+ }
93
+ }
94
+
95
+ // HTTP info lets us see if AWS/BLE connection methods are available
96
+ if (device.httpInfo.deviceExt && device.httpInfo.deviceExt.deviceSettings) {
97
+ let parsed
98
+ try {
99
+ parsed = JSON.parse(device.httpInfo.deviceExt.deviceSettings)
100
+ } catch {
101
+ platform.log.debugWarn('[%s] failed to parse deviceSettings, skipping AWS/BLE setup.', accessory.displayName)
102
+ }
103
+
104
+ // Check to see if AWS is possible
105
+ if (parsed) {
106
+ if (parsed.topic) {
107
+ accessory.context.hasAwsControl = true
108
+ accessory.context.awsTopic = parsed.topic
109
+
110
+ if (platform.awsClient) {
111
+ accessory.context.useAwsControl = true
112
+ accessory.context.awsBrightnessNoScale = deviceConf.awsBrightnessNoScale
113
+ accessory.context.awsColourMode = deviceConf.awsColourMode || platformConsts.defaultValues.awsColourMode
114
+ awsDevices.push(device.device)
115
+
116
+ // Certain models need AWS polling
117
+ if (doAWSPolling) {
118
+ awsDevicesToPoll.push(device.device)
119
+ }
120
+ }
121
+ }
122
+
123
+ // Check to see if BLE is possible
124
+ if (parsed.bleName) {
125
+ const providedBle = parsed.address ? parsed.address.toLowerCase() : device.device.substring(6).toLowerCase()
126
+ accessory.context.hasBleControl = !!parsed.bleName
127
+ accessory.context.bleAddress = deviceConf.customAddress
128
+ ? deviceConf.customAddress.toLowerCase()
129
+ : providedBle
130
+ accessory.context.bleName = parsed.bleName
131
+ accessory.context.bleWriteUuid = getDeviceCapabilities(accessory.context.gvModel).bleWriteUuid
132
+ if (platform.bleClient) {
133
+ accessory.context.useBleControl = true
134
+ }
135
+ }
136
+
137
+ // Gateway subdevices (e.g. leak sensors paired to an H5044) include
138
+ // their gateway id and slot number, which we need to match real-time
139
+ // leak events relayed by the gateway over AWS (#1276)
140
+ if (hasProperty(parsed, 'sno') && parsed.gatewayInfo?.device) {
141
+ accessory.context.gatewaySno = parsed.sno
142
+ accessory.context.gatewayDevice = parsed.gatewayInfo.device
143
+ }
144
+
145
+ // Get a min and max temperature/humidity range to show in the homebridge-ui
146
+ if (hasProperty(parsed, 'temCali')) {
147
+ accessory.context.minTemp = parsed.temMin / 100
148
+ accessory.context.maxTemp = parsed.temMax / 100
149
+ accessory.context.offTemp = parsed.temCali
150
+ }
151
+ if (hasProperty(parsed, 'humCali')) {
152
+ accessory.context.minHumi = parsed.humMin / 100
153
+ accessory.context.maxHumi = parsed.humMax / 100
154
+ accessory.context.offHumi = parsed.humCali
155
+ }
156
+ }
157
+ }
158
+ }
159
+
160
+ if (device.openApiInfo) {
161
+ accessory.context.hasOpenApiControl = true
162
+ accessory.context.useOpenApiControl = !!platform.openApiClient
163
+ accessory.context.openApiCapabilities = device.openApiInfo.byInstance || {}
164
+
165
+ // Log what Govee says this device can do. Working out whether a model
166
+ // exposes something like a night light otherwise means reverse
167
+ // engineering its status messages, when the answer is right here
168
+ const instances = Object.entries(accessory.context.openApiCapabilities)
169
+ .map(([instance, capability]) => `${instance} (${capability?.type ?? 'unknown type'})`)
170
+ platform.log.debug(
171
+ '[%s] openapi category [%s] capabilities [%s].',
172
+ accessory.displayName,
173
+ device.openApiInfo.category || 'unknown',
174
+ instances.join(', ') || 'none',
175
+ )
176
+ }
177
+
178
+ // Create the instance for this device type
179
+ }