@homebridge-plugins/homebridge-ewelink 12.18.1 → 12.18.2-beta.0

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 (87) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/config.schema.json +98 -0
  3. package/lib/connection/api.js +1 -4
  4. package/lib/connection/http.js +8 -8
  5. package/lib/connection/lan.js +3 -10
  6. package/lib/connection/ws.js +8 -25
  7. package/lib/device/air-conditioner.js +1 -1
  8. package/lib/device/curtain.js +1 -1
  9. package/lib/device/diffuser.js +1 -1
  10. package/lib/device/fan-gear.js +1 -1
  11. package/lib/device/fan.js +1 -1
  12. package/lib/device/group.js +1 -1
  13. package/lib/device/humidifier.js +1 -1
  14. package/lib/device/light-cct.js +20 -4
  15. package/lib/device/light-dimmer.js +1 -1
  16. package/lib/device/light-rgb-cct.js +52 -10
  17. package/lib/device/light-rgb.js +21 -5
  18. package/lib/device/motor.js +1 -1
  19. package/lib/device/outlet-multi.js +1 -1
  20. package/lib/device/outlet-single-inched.js +1 -1
  21. package/lib/device/outlet-single.js +1 -1
  22. package/lib/device/panel.js +1 -1
  23. package/lib/device/rf-bridge.js +1 -1
  24. package/lib/device/rf-button.js +1 -1
  25. package/lib/device/rf-sensor.js +1 -1
  26. package/lib/device/sensor-air-quality.js +1 -1
  27. package/lib/device/sensor-ambient.js +1 -1
  28. package/lib/device/sensor-contact.js +1 -1
  29. package/lib/device/sensor-temp-humi.js +1 -1
  30. package/lib/device/simulation/blind.js +1 -1
  31. package/lib/device/simulation/cooler.js +1 -1
  32. package/lib/device/simulation/door.js +1 -1
  33. package/lib/device/simulation/doorbell.js +1 -1
  34. package/lib/device/simulation/garage-eachen.js +1 -1
  35. package/lib/device/simulation/garage-four.js +1 -1
  36. package/lib/device/simulation/garage-one.js +1 -1
  37. package/lib/device/simulation/garage-two.js +1 -1
  38. package/lib/device/simulation/gate-one.js +1 -1
  39. package/lib/device/simulation/heater.js +1 -1
  40. package/lib/device/simulation/light-fan.js +1 -1
  41. package/lib/device/simulation/lock-eachen.js +1 -1
  42. package/lib/device/simulation/lock-one.js +1 -1
  43. package/lib/device/simulation/p-button.js +1 -1
  44. package/lib/device/simulation/purifier.js +1 -1
  45. package/lib/device/simulation/rf-blind.js +1 -1
  46. package/lib/device/simulation/rf-door.js +1 -1
  47. package/lib/device/simulation/rf-window.js +1 -1
  48. package/lib/device/simulation/sensor-leak.js +1 -1
  49. package/lib/device/simulation/sensor-visible.js +1 -1
  50. package/lib/device/simulation/sensor.js +1 -1
  51. package/lib/device/simulation/switch-valve.js +1 -1
  52. package/lib/device/simulation/tap-one.js +1 -1
  53. package/lib/device/simulation/tap-two.js +1 -1
  54. package/lib/device/simulation/th-cooler.js +1 -1
  55. package/lib/device/simulation/th-dehumidifier.js +1 -1
  56. package/lib/device/simulation/th-heater.js +1 -1
  57. package/lib/device/simulation/th-humidifier.js +1 -1
  58. package/lib/device/simulation/th-thermostat.js +1 -1
  59. package/lib/device/simulation/tv.js +1 -1
  60. package/lib/device/simulation/valve-four.js +1 -1
  61. package/lib/device/simulation/valve-one.js +1 -1
  62. package/lib/device/simulation/valve-two.js +1 -1
  63. package/lib/device/simulation/window.js +1 -1
  64. package/lib/device/switch-man.js +1 -1
  65. package/lib/device/switch-mate.js +1 -1
  66. package/lib/device/switch-multi.js +1 -1
  67. package/lib/device/switch-single-inched.js +1 -1
  68. package/lib/device/switch-single.js +1 -1
  69. package/lib/device/thermostat.js +1 -1
  70. package/lib/device/virtual.js +1 -1
  71. package/lib/device/zigbee/light-cct.js +20 -4
  72. package/lib/device/zigbee/light-dimmer.js +1 -1
  73. package/lib/device/zigbee/light-rgb-cct.js +34 -5
  74. package/lib/device/zigbee/motor.js +1 -1
  75. package/lib/device/zigbee/sensor-ambient.js +1 -1
  76. package/lib/device/zigbee/sensor-contact.js +1 -1
  77. package/lib/device/zigbee/sensor-leak.js +1 -1
  78. package/lib/device/zigbee/sensor-motion.js +1 -1
  79. package/lib/device/zigbee/sensor-occupancy.js +1 -1
  80. package/lib/device/zigbee/sensor-smoke.js +1 -1
  81. package/lib/device/zigbee/switch-stateless.js +1 -1
  82. package/lib/device/zigbee/thermostat.js +1 -1
  83. package/lib/node-dns-sd/lib/dns-sd.js +12 -2
  84. package/lib/platform.js +46 -30
  85. package/lib/utils/lang-en.js +1 -1
  86. package/package.json +1 -1
  87. package/lib/utils/functions.test.js +0 -77
package/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  All notable changes to `@homebridge-plugins/homebridge-ewelink` will be documented in this file.
4
4
 
5
+ ## v12.18.2 (Pending Release)
6
+
7
+ ### Changed
8
+
9
+ - chore: exclude test files and the test config from the published package
10
+ - fix: restore debug logging when the plugin runs in a child bridge
11
+ - fix: only turn adaptive lighting off when a colour is genuinely chosen elsewhere
12
+ - fix: send a colour change when only the saturation moves, and mend the al cleanup
13
+ - fix: follow the saturation on the b1 bulb, in both directions
14
+ - fix: explain a port clash on the built-in api instead of crashing the bridge
15
+ - fix: survive a network change on the lan discovery socket instead of crashing the bridge
16
+ - fix: set a garage's sensor up after the garage, so it gets its open and closed feedback
17
+ - fix: make the per-device logging override settable, and drop the debug option that never was
18
+
5
19
  ## v12.18.1 (2026-08-02)
6
20
 
7
21
  ### Changed
@@ -154,6 +154,19 @@
154
154
  "minLength": 10,
155
155
  "maxLength": 10
156
156
  },
157
+ "overrideLogging": {
158
+ "type": "string",
159
+ "title": "Device Logging Override",
160
+ "description": "Override the plugin-wide logging setting for this device only.",
161
+ "oneOf": [
162
+ { "title": "Standard", "enum": ["standard"] },
163
+ { "title": "Debug", "enum": ["debug"] },
164
+ { "title": "Disable", "enum": ["disable"] }
165
+ ],
166
+ "condition": {
167
+ "functionBody": "return (model.singleDevices && model.singleDevices[arrayIndices] && model.singleDevices[arrayIndices].deviceId && model.singleDevices[arrayIndices].deviceId.length === 10);"
168
+ }
169
+ },
157
170
  "ignoreDevice": {
158
171
  "type": "boolean",
159
172
  "title": "Hide From HomeKit",
@@ -486,6 +499,19 @@
486
499
  "minLength": 10,
487
500
  "maxLength": 10
488
501
  },
502
+ "overrideLogging": {
503
+ "type": "string",
504
+ "title": "Device Logging Override",
505
+ "description": "Override the plugin-wide logging setting for this device only.",
506
+ "oneOf": [
507
+ { "title": "Standard", "enum": ["standard"] },
508
+ { "title": "Debug", "enum": ["debug"] },
509
+ { "title": "Disable", "enum": ["disable"] }
510
+ ],
511
+ "condition": {
512
+ "functionBody": "return (model.multiDevices && model.multiDevices[arrayIndices] && model.multiDevices[arrayIndices].deviceId && model.multiDevices[arrayIndices].deviceId.length === 10);"
513
+ }
514
+ },
489
515
  "ignoreDevice": {
490
516
  "type": "boolean",
491
517
  "title": "Hide From HomeKit",
@@ -804,6 +830,19 @@
804
830
  "minLength": 10,
805
831
  "maxLength": 10
806
832
  },
833
+ "overrideLogging": {
834
+ "type": "string",
835
+ "title": "Device Logging Override",
836
+ "description": "Override the plugin-wide logging setting for this device only.",
837
+ "oneOf": [
838
+ { "title": "Standard", "enum": ["standard"] },
839
+ { "title": "Debug", "enum": ["debug"] },
840
+ { "title": "Disable", "enum": ["disable"] }
841
+ ],
842
+ "condition": {
843
+ "functionBody": "return (model.lightDevices && model.lightDevices[arrayIndices] && model.lightDevices[arrayIndices].deviceId && model.lightDevices[arrayIndices].deviceId.length === 10);"
844
+ }
845
+ },
807
846
  "ignoreDevice": {
808
847
  "type": "boolean",
809
848
  "title": "Hide From HomeKit",
@@ -942,6 +981,19 @@
942
981
  "minLength": 10,
943
982
  "maxLength": 10
944
983
  },
984
+ "overrideLogging": {
985
+ "type": "string",
986
+ "title": "Device Logging Override",
987
+ "description": "Override the plugin-wide logging setting for this device only.",
988
+ "oneOf": [
989
+ { "title": "Standard", "enum": ["standard"] },
990
+ { "title": "Debug", "enum": ["debug"] },
991
+ { "title": "Disable", "enum": ["disable"] }
992
+ ],
993
+ "condition": {
994
+ "functionBody": "return (model.thDevices && model.thDevices[arrayIndices] && model.thDevices[arrayIndices].deviceId && model.thDevices[arrayIndices].deviceId.length === 10);"
995
+ }
996
+ },
945
997
  "ignoreDevice": {
946
998
  "type": "boolean",
947
999
  "title": "Hide From HomeKit",
@@ -1119,6 +1171,19 @@
1119
1171
  "minLength": 10,
1120
1172
  "maxLength": 10
1121
1173
  },
1174
+ "overrideLogging": {
1175
+ "type": "string",
1176
+ "title": "Device Logging Override",
1177
+ "description": "Override the plugin-wide logging setting for this device only.",
1178
+ "oneOf": [
1179
+ { "title": "Standard", "enum": ["standard"] },
1180
+ { "title": "Debug", "enum": ["debug"] },
1181
+ { "title": "Disable", "enum": ["disable"] }
1182
+ ],
1183
+ "condition": {
1184
+ "functionBody": "return (model.fanDevices && model.fanDevices[arrayIndices] && model.fanDevices[arrayIndices].deviceId && model.fanDevices[arrayIndices].deviceId.length === 10);"
1185
+ }
1186
+ },
1122
1187
  "ignoreDevice": {
1123
1188
  "type": "boolean",
1124
1189
  "title": "Hide From HomeKit",
@@ -1179,6 +1244,19 @@
1179
1244
  "minLength": 10,
1180
1245
  "maxLength": 10
1181
1246
  },
1247
+ "overrideLogging": {
1248
+ "type": "string",
1249
+ "title": "Device Logging Override",
1250
+ "description": "Override the plugin-wide logging setting for this device only.",
1251
+ "oneOf": [
1252
+ { "title": "Standard", "enum": ["standard"] },
1253
+ { "title": "Debug", "enum": ["debug"] },
1254
+ { "title": "Disable", "enum": ["disable"] }
1255
+ ],
1256
+ "condition": {
1257
+ "functionBody": "return (model.sensorDevices && model.sensorDevices[arrayIndices] && model.sensorDevices[arrayIndices].deviceId && model.sensorDevices[arrayIndices].deviceId.length === 10);"
1258
+ }
1259
+ },
1182
1260
  "ignoreDevice": {
1183
1261
  "type": "boolean",
1184
1262
  "title": "Hide From HomeKit",
@@ -1331,6 +1409,19 @@
1331
1409
  "minLength": 10,
1332
1410
  "maxLength": 10
1333
1411
  },
1412
+ "overrideLogging": {
1413
+ "type": "string",
1414
+ "title": "Device Logging Override",
1415
+ "description": "Override the plugin-wide logging setting for this device only.",
1416
+ "oneOf": [
1417
+ { "title": "Standard", "enum": ["standard"] },
1418
+ { "title": "Debug", "enum": ["debug"] },
1419
+ { "title": "Disable", "enum": ["disable"] }
1420
+ ],
1421
+ "condition": {
1422
+ "functionBody": "return (model.rfDevices && model.rfDevices[arrayIndices] && model.rfDevices[arrayIndices].deviceId && model.rfDevices[arrayIndices].deviceId.length === 10);"
1423
+ }
1424
+ },
1334
1425
  "ignoreDevice": {
1335
1426
  "type": "boolean",
1336
1427
  "title": "Hide From HomeKit",
@@ -1562,6 +1653,7 @@
1562
1653
  "singleDevices[].label",
1563
1654
  "singleDevices[].deviceId",
1564
1655
  "singleDevices[].ignoreDevice",
1656
+ "singleDevices[].overrideLogging",
1565
1657
  "singleDevices[].deviceModel",
1566
1658
  "singleDevices[].showAs",
1567
1659
  "singleDevices[].showAsMotor",
@@ -1595,6 +1687,7 @@
1595
1687
  "multiDevices[].label",
1596
1688
  "multiDevices[].deviceId",
1597
1689
  "multiDevices[].ignoreDevice",
1690
+ "multiDevices[].overrideLogging",
1598
1691
  "multiDevices[].deviceModel",
1599
1692
  "multiDevices[].showAs",
1600
1693
  "multiDevices[].showAsMotor",
@@ -1627,6 +1720,7 @@
1627
1720
  "lightDevices[].label",
1628
1721
  "lightDevices[].deviceId",
1629
1722
  "lightDevices[].ignoreDevice",
1723
+ "lightDevices[].overrideLogging",
1630
1724
  "lightDevices[].deviceModel",
1631
1725
  "lightDevices[].showAs",
1632
1726
  "lightDevices[].brightnessStep",
@@ -1651,6 +1745,7 @@
1651
1745
  "thDevices[].label",
1652
1746
  "thDevices[].deviceId",
1653
1747
  "thDevices[].ignoreDevice",
1748
+ "thDevices[].overrideLogging",
1654
1749
  "thDevices[].deviceModel",
1655
1750
  "thDevices[].showAs",
1656
1751
  "thDevices[].showHeatCool",
@@ -1681,6 +1776,7 @@
1681
1776
  "fanDevices[].label",
1682
1777
  "fanDevices[].deviceId",
1683
1778
  "fanDevices[].ignoreDevice",
1779
+ "fanDevices[].overrideLogging",
1684
1780
  "fanDevices[].deviceModel",
1685
1781
  "fanDevices[].hideLight",
1686
1782
  "fanDevices[].ipAddress"
@@ -1702,6 +1798,7 @@
1702
1798
  "sensorDevices[].label",
1703
1799
  "sensorDevices[].deviceId",
1704
1800
  "sensorDevices[].ignoreDevice",
1801
+ "sensorDevices[].overrideLogging",
1705
1802
  "sensorDevices[].deviceModel",
1706
1803
  "sensorDevices[].showAs",
1707
1804
  "sensorDevices[].lowBattThreshold",
@@ -1730,6 +1827,7 @@
1730
1827
  "rfDevices[].label",
1731
1828
  "rfDevices[].deviceId",
1732
1829
  "rfDevices[].ignoreDevice",
1830
+ "rfDevices[].overrideLogging",
1733
1831
  "rfDevices[].resetOnStartup",
1734
1832
  "rfDevices[].ipAddress"
1735
1833
  ]
@@ -4,7 +4,6 @@ export default class {
4
4
  constructor(platform, devicesInHB) {
5
5
  // Set up variables from the platform
6
6
  this.config = platform.config
7
- this.debug = platform.config.debug
8
7
  this.devicesInHB = devicesInHB
9
8
  this.hapChar = platform.api.hap.Characteristic
10
9
  this.hapServ = platform.api.hap.Service
@@ -215,9 +214,7 @@ export default class {
215
214
 
216
215
  async action(req) {
217
216
  // Log the request if appropriate
218
- if (this.debug) {
219
- this.log('API request [%s].', req.url)
220
- }
217
+ this.log.debug('API request [%s].', req.url)
221
218
 
222
219
  // Authenticate the request
223
220
  if (
@@ -12,7 +12,6 @@ export default class {
12
12
  this.appId = platform.config.appId || platformConsts.appId
13
13
  this.appSecret = platform.config.appSecret || platformConsts.appSecret
14
14
  this.countryCode = platform.config.countryCode
15
- this.debug = platform.config.debug
16
15
  this.homeList = []
17
16
  this.httpHost = platform.config.httpHost
18
17
  this.ignoredDevices = platform.ignoredDevices
@@ -69,9 +68,7 @@ export default class {
69
68
  }
70
69
 
71
70
  // Log the data depending on the debug setting
72
- if (this.debug) {
73
- this.log('%s.', this.lang.sendLogin)
74
- }
71
+ this.log.debug('%s.', this.lang.sendLogin)
75
72
 
76
73
  // Set up the request signature
77
74
  const dataToSign = createHmac('sha256', this.appSecret)
@@ -110,9 +107,7 @@ export default class {
110
107
  }
111
108
 
112
109
  // Log the new http host if appropriate
113
- if (this.debug) {
114
- this.log('%s [%s].', this.lang.newRegionRec, this.httpHost)
115
- }
110
+ this.log.debug('%s [%s].', this.lang.newRegionRec, this.httpHost)
116
111
 
117
112
  // Retry the login with the new http host
118
113
  return await this.login()
@@ -261,7 +256,12 @@ export default class {
261
256
  }
262
257
  // Separate the sensors as these need to be set up last
263
258
  const isObstructSwitch = this.obstructSwitches[d.itemData.deviceid]
264
- const isSensorSwitch = this.sensorSwitches[d.itemData.deviceid]
259
+ // sensorSwitches is a list of device ids, so it has to be searched, not
260
+ // indexed. Indexing it always gave undefined, so a non-DW2 sensor was
261
+ // never moved to the end of the list and could be set up before the
262
+ // garage that needs it - which fell back to a plain contact sensor until
263
+ // the next restart.
264
+ const isSensorSwitch = this.sensorSwitches.includes(d.itemData.deviceid)
265
265
  if (
266
266
  platformConsts.devices.garageSensors.includes(d.itemData.extra.uiid)
267
267
  || isObstructSwitch
@@ -18,7 +18,6 @@ const emitter = new events()
18
18
  export default class {
19
19
  constructor(platform) {
20
20
  // Set up variables from the platform
21
- this.debug = platform.config.debug
22
21
  this.ipOverride = platform.ipOverride
23
22
  this.lang = platform.lang
24
23
  this.log = platform.log
@@ -40,13 +39,9 @@ export default class {
40
39
  })
41
40
 
42
41
  // Perform an initial discovery of devices on the local network
43
- if (this.debug) {
44
- this.log('%s.', this.lang.lanStarting)
45
- }
42
+ this.log.debug('%s.', this.lang.lanStarting)
46
43
  const res = await dnsSd.discover({ name: '_ewelink._tcp.local' })
47
- if (this.debug) {
48
- this.log('%s.', this.lang.lanStarted)
49
- }
44
+ this.log.debug('%s.', this.lang.lanStarted)
50
45
 
51
46
  // Update the device map for each device found on the local network
52
47
  res.forEach((device) => {
@@ -342,8 +337,6 @@ export default class {
342
337
  async closeConnection() {
343
338
  // This is called when Homebridge is shutdown
344
339
  await dnsSd.stopMonitoring()
345
- if (this.debug) {
346
- this.log('%s.', this.lang.stoppedLAN)
347
- }
340
+ this.log.debug('%s.', this.lang.stoppedLAN)
348
341
  }
349
342
  }
@@ -15,7 +15,6 @@ export default class {
15
15
  // Set up variables from the platform
16
16
  this.appId = platform.config.appId || platformConsts.appId
17
17
  this.appSecret = platform.config.appSecret || platformConsts.appSecret
18
- this.debug = platform.config.debug
19
18
  this.lang = platform.lang
20
19
  this.log = platform.log
21
20
 
@@ -57,9 +56,7 @@ export default class {
57
56
  }
58
57
 
59
58
  // Log the received host if appropriate
60
- if (this.debug) {
61
- this.log('%s [%s].', this.lang.wsHostRec, body.domain)
62
- }
59
+ this.log.debug('%s [%s].', this.lang.wsHostRec, body.domain)
63
60
 
64
61
  // Return the received web socket host
65
62
  return body.domain
@@ -130,9 +127,7 @@ export default class {
130
127
  }
131
128
 
132
129
  // Log the web socket login if appropriate
133
- if (this.debug) {
134
- this.log('%s.', this.lang.wsLogin)
135
- }
130
+ this.log.debug('%s.', this.lang.wsLogin)
136
131
 
137
132
  // Attempt to authenticate the web socket connection
138
133
  try {
@@ -153,17 +148,13 @@ export default class {
153
148
  this.wsClient.send('ping')
154
149
  } catch (err) {
155
150
  // Catch errors sending ping and show in debug mode
156
- if (this.debug) {
157
- this.log.warn('%s %s.', this.lang.wsPingError, parseError(err))
158
- }
151
+ this.log.debugWarn('%s %s.', this.lang.wsPingError, parseError(err))
159
152
  }
160
153
  }, (res.config.hbInterval + 7) * 1000)
161
154
 
162
155
  // Login was successful and log if appropriate
163
156
  this.authRetries = 0
164
- if (this.debug) {
165
- this.log('%s.', this.lang.wsLoginSuccess)
166
- }
157
+ this.log.debug('%s.', this.lang.wsLoginSuccess)
167
158
  } else if (res.error === 406) {
168
159
  // Token invalidated (e.g. concurrent session), re-authenticate with backoff
169
160
  this.log.warn(this.lang.wsLogin406)
@@ -285,9 +276,7 @@ export default class {
285
276
  return
286
277
  }
287
278
 
288
- if (this.debug) {
289
- this.log.warn('%s [%s].', this.lang.wsReconnectError, e)
290
- }
279
+ this.log.debugWarn('%s [%s].', this.lang.wsReconnectError, e)
291
280
 
292
281
  // Reconnect with exponential backoff
293
282
  this.reconnectAttempts = (this.reconnectAttempts || 0) + 1
@@ -298,9 +287,7 @@ export default class {
298
287
 
299
288
  // Add a listener for when the web socket throws an error
300
289
  this.wsClient.onError.addListener(async (e) => {
301
- if (this.debug) {
302
- this.log.warn('%s [%s].', this.lang.wsReconnectClose, e)
303
- }
290
+ this.log.debugWarn('%s [%s].', this.lang.wsReconnectClose, e)
304
291
 
305
292
  this.reconnectAttempts = (this.reconnectAttempts || 0) + 1
306
293
  const delay = Math.min(5000 * 2 ** (this.reconnectAttempts - 1), 300000)
@@ -316,9 +303,7 @@ export default class {
316
303
  } catch (err) {
317
304
  const errToShow = parseError(err)
318
305
 
319
- if (this.debug) {
320
- this.log('%s: %s.', this.lang.wsLoginErrRecon, errToShow)
321
- }
306
+ this.log.debug('%s: %s.', this.lang.wsLoginErrRecon, errToShow)
322
307
 
323
308
  this.reconnectAttempts = (this.reconnectAttempts || 0) + 1
324
309
  const delay = Math.min(5000 * 2 ** (this.reconnectAttempts - 1), 300000)
@@ -444,9 +429,7 @@ export default class {
444
429
  await this.wsClient.close()
445
430
 
446
431
  // Log if appropriate
447
- if (this.debug) {
448
- this.log('%s.', this.lang.stoppedWS)
449
- }
432
+ this.log.debug('%s.', this.lang.stoppedWS)
450
433
  }
451
434
  }
452
435
  }
@@ -39,7 +39,7 @@ export default class {
39
39
  break
40
40
  default:
41
41
  this.enableLogging = !platform.config.disableDeviceLogging
42
- this.enableDebugLogging = platform.config.debug
42
+ this.enableDebugLogging = false
43
43
  break
44
44
  }
45
45
 
@@ -38,7 +38,7 @@ export default class {
38
38
  break
39
39
  default:
40
40
  this.enableLogging = !platform.config.disableDeviceLogging
41
- this.enableDebugLogging = platform.config.debug
41
+ this.enableDebugLogging = false
42
42
  break
43
43
  }
44
44
 
@@ -37,7 +37,7 @@ export default class {
37
37
  break
38
38
  default:
39
39
  this.enableLogging = !platform.config.disableDeviceLogging
40
- this.enableDebugLogging = platform.config.debug
40
+ this.enableDebugLogging = false
41
41
  break
42
42
  }
43
43
 
@@ -43,7 +43,7 @@ export default class {
43
43
  break
44
44
  default:
45
45
  this.enableLogging = !platform.config.disableDeviceLogging
46
- this.enableDebugLogging = platform.config.debug
46
+ this.enableDebugLogging = false
47
47
  break
48
48
  }
49
49
 
package/lib/device/fan.js CHANGED
@@ -37,7 +37,7 @@ export default class {
37
37
  break
38
38
  default:
39
39
  this.enableLogging = !platform.config.disableDeviceLogging
40
- this.enableDebugLogging = platform.config.debug
40
+ this.enableDebugLogging = false
41
41
  break
42
42
  }
43
43
 
@@ -17,7 +17,7 @@ export default class {
17
17
 
18
18
  // Set the correct logging variables for this accessory
19
19
  this.enableLogging = !platform.config.disableDeviceLogging
20
- this.enableDebugLogging = platform.config.debug
20
+ this.enableDebugLogging = false
21
21
 
22
22
  if (platformConsts.devices.switchSCM.includes(this.accessory.context.groupUIID)) {
23
23
  this.isSCM = true
@@ -36,7 +36,7 @@ export default class {
36
36
  break
37
37
  default:
38
38
  this.enableLogging = !platform.config.disableDeviceLogging
39
- this.enableDebugLogging = platform.config.debug
39
+ this.enableDebugLogging = false
40
40
  break
41
41
  }
42
42
 
@@ -43,7 +43,7 @@ export default class {
43
43
  break
44
44
  default:
45
45
  this.enableLogging = !platform.config.disableDeviceLogging
46
- this.enableDebugLogging = platform.config.debug
46
+ this.enableDebugLogging = false
47
47
  break
48
48
  }
49
49
 
@@ -67,10 +67,22 @@ export default class {
67
67
  this.service = this.accessory.getService(this.hapServ.Lightbulb)
68
68
  || this.accessory.addService(this.hapServ.Lightbulb)
69
69
 
70
- // If adaptive lighting has just been disabled then remove and re-add service to hide AL icon
70
+ // Adaptive lighting has just been switched off, so take away the three
71
+ // characteristics that make Home offer it. Only these need to go: the whole
72
+ // Lightbulb service used to be thrown away and rebuilt here, which does hide
73
+ // the icon but presents Home with what looks like a different light, and an
74
+ // owner can lose that light's room and its automations as a result.
71
75
  if (this.alShift === -1 && this.accessory.context.adaptiveLighting) {
72
- this.accessory.removeService(this.service)
73
- this.service = this.accessory.addService(this.hapServ.Lightbulb)
76
+ [
77
+ 'SupportedCharacteristicValueTransitionConfiguration',
78
+ 'CharacteristicValueTransitionControl',
79
+ 'CharacteristicValueActiveTransitionCount',
80
+ ].forEach((charName) => {
81
+ const char = this.hapChar[charName]
82
+ if (char && this.service.testCharacteristic(char)) {
83
+ this.service.removeCharacteristic(this.service.getCharacteristic(char))
84
+ }
85
+ })
74
86
  this.accessory.context.adaptiveLighting = false
75
87
  }
76
88
 
@@ -99,6 +111,10 @@ export default class {
99
111
  customTemperatureAdjustment: this.alShift,
100
112
  })
101
113
  this.accessory.configureController(this.accessory.alController)
114
+ // Without this the cleanup that removes the adaptive lighting
115
+ // characteristics can never run, because it is guarded on this flag - so
116
+ // switching adaptive lighting off left Home still offering it.
117
+ this.accessory.context.adaptiveLighting = true
102
118
  }
103
119
 
104
120
  // Add the get handlers only if the user hasn't disabled the disableNoResponse setting
@@ -42,7 +42,7 @@ export default class {
42
42
  break
43
43
  default:
44
44
  this.enableLogging = !platform.config.disableDeviceLogging
45
- this.enableDebugLogging = platform.config.debug
45
+ this.enableDebugLogging = false
46
46
  break
47
47
  }
48
48
 
@@ -49,7 +49,7 @@ export default class {
49
49
  break
50
50
  default:
51
51
  this.enableLogging = !platform.config.disableDeviceLogging
52
- this.enableDebugLogging = platform.config.debug
52
+ this.enableDebugLogging = false
53
53
  break
54
54
  }
55
55
 
@@ -62,10 +62,22 @@ export default class {
62
62
  this.service = this.accessory.getService(this.hapServ.Lightbulb)
63
63
  || this.accessory.addService(this.hapServ.Lightbulb)
64
64
 
65
- // If adaptive lighting has just been disabled then remove and re-add service to hide AL icon
65
+ // Adaptive lighting has just been switched off, so take away the three
66
+ // characteristics that make Home offer it. Only these need to go: the whole
67
+ // Lightbulb service used to be thrown away and rebuilt here, which does hide
68
+ // the icon but presents Home with what looks like a different light, and an
69
+ // owner can lose that light's room and its automations as a result.
66
70
  if (this.alShift === -1 && this.accessory.context.adaptiveLighting) {
67
- this.accessory.removeService(this.service)
68
- this.service = this.accessory.addService(this.hapServ.Lightbulb)
71
+ [
72
+ 'SupportedCharacteristicValueTransitionConfiguration',
73
+ 'CharacteristicValueTransitionControl',
74
+ 'CharacteristicValueActiveTransitionCount',
75
+ ].forEach((charName) => {
76
+ const char = this.hapChar[charName]
77
+ if (char && this.service.testCharacteristic(char)) {
78
+ this.service.removeCharacteristic(this.service.getCharacteristic(char))
79
+ }
80
+ })
69
81
  this.accessory.context.adaptiveLighting = false
70
82
  }
71
83
 
@@ -84,6 +96,15 @@ export default class {
84
96
  this.service
85
97
  .getCharacteristic(this.hapChar.Hue)
86
98
  .onSet(async value => this.internalColourUpdate(value))
99
+
100
+ // Saturation needs a handler of its own. HomeKit writes hue and saturation
101
+ // separately, so with only hue listened for, dragging saturation alone -
102
+ // the same colour made paler or richer - reached nothing and the light
103
+ // never changed. Both handlers share one update, and the debounce inside
104
+ // it collapses a hue-and-saturation pair into a single command.
105
+ this.service
106
+ .getCharacteristic(this.hapChar.Saturation)
107
+ .onSet(async () => this.internalColourUpdate(this.service.getCharacteristic(this.hapChar.Hue).value))
87
108
  this.cacheSat = this.service.getCharacteristic(this.hapChar.Saturation).value
88
109
 
89
110
  // Add the set handler to the lightbulb colour temperature characteristic
@@ -110,6 +131,10 @@ export default class {
110
131
  customTemperatureAdjustment: this.alShift,
111
132
  })
112
133
  this.accessory.configureController(this.accessory.alController)
134
+ // Without this the cleanup that removes the adaptive lighting
135
+ // characteristics can never run, because it is guarded on this flag - so
136
+ // switching adaptive lighting off left Home still offering it.
137
+ this.accessory.context.adaptiveLighting = true
113
138
  }
114
139
 
115
140
  /*
@@ -311,7 +336,11 @@ export default class {
311
336
 
312
337
  async internalColourUpdate(value) {
313
338
  try {
314
- if (this.cacheHue === value) {
339
+ // Saturation is part of "has the colour changed": comparing hue alone
340
+ // meant a change of saturation at the same hue looked like no change at
341
+ // all, and nothing was sent to the light.
342
+ const currentSat = this.service.getCharacteristic(this.hapChar.Saturation).value
343
+ if (this.cacheHue === value && this.cacheSat === currentSat) {
315
344
  return
316
345
  }
317
346
  const updateKey = generateRandomString(5)
@@ -374,7 +403,11 @@ export default class {
374
403
  return
375
404
  }
376
405
  await this.platform.sendDeviceUpdate(this.accessory, params)
377
- this.cacheHue = value;
406
+ this.cacheHue = value
407
+ // Remember the saturation that was sent, or the check at the top of this
408
+ // method compares against a value that never moves and a second change of
409
+ // saturation looks like no change at all
410
+ this.cacheSat = sat;
378
411
  [this.cacheR, this.cacheG, this.cacheB] = rgb
379
412
  this.cacheMired = 0
380
413
  this.cacheCT = 0
@@ -597,9 +630,18 @@ export default class {
597
630
  || params.colorG !== this.cacheG
598
631
  || params.colorB !== this.cacheB
599
632
  ) {
600
- const rgbDiff = Math.abs(params.colorR - this.cacheR)
601
- + Math.abs(params.colorG - this.cacheG)
602
- + Math.abs(params.colorB - this.cacheB)
633
+ // A light repeating our own command back can land a unit or two
634
+ // off. Beyond that, somebody set this colour themselves, outside
635
+ // HomeKit - which nothing else notices, so adaptive lighting has
636
+ // to be stopped here or it moves the light off that colour within
637
+ // minutes. This used to add the three channels together and want
638
+ // a total of 50 out of a possible 765, so a deliberate but subtle
639
+ // choice was read as noise and ignored.
640
+ const rgbDiff = Math.max(
641
+ Math.abs(params.colorR - this.cacheR),
642
+ Math.abs(params.colorG - this.cacheG),
643
+ Math.abs(params.colorB - this.cacheB),
644
+ )
603
645
  this.cacheR = params.colorR
604
646
  this.cacheG = params.colorG
605
647
  this.cacheB = params.colorB
@@ -620,7 +662,7 @@ export default class {
620
662
  if (
621
663
  this.accessory.alController
622
664
  && this.accessory.alController.isAdaptiveLightingActive()
623
- && rgbDiff > 50
665
+ && rgbDiff > 5
624
666
  ) {
625
667
  this.accessory.alController.disableAdaptiveLighting()
626
668
  if (this.enableLogging) {