@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
@@ -1,359 +0,0 @@
1
- import {
2
- base64ToHex,
3
- getTwoItemPosition,
4
- hexToTwoItems,
5
- parseError,
6
- } from '../utils/functions.js'
7
- import platformLang from '../utils/lang-en.js'
8
-
9
- export default class {
10
- constructor(platform, accessory) {
11
- // Set up variables from the platform
12
- this.cusChar = platform.cusChar
13
- this.hapChar = platform.api.hap.Characteristic
14
- this.hapErr = platform.api.hap.HapStatusError
15
- this.hapServ = platform.api.hap.Service
16
- this.platform = platform
17
-
18
- // Set up variables from the accessory
19
- this.accessory = accessory
20
-
21
- this.speed2Value = (speed) => {
22
- if (speed <= 33) {
23
- return 1
24
- }
25
- if (speed <= 66) {
26
- return 2
27
- }
28
- return 3
29
- }
30
-
31
- this.value2Speed = {
32
- 1: 33,
33
- 2: 66,
34
- 3: 99,
35
- }
36
-
37
- // Speed codes
38
- this.value2Code = {
39
- 1: 'MwUBAQAAAAAAAAAAAAAAAAAAADY=', // sleep
40
- 2: 'MwUBAgAAAAAAAAAAAAAAAAAAADU=', // low
41
- 3: 'MwUBAwAAAAAAAAAAAAAAAAAAADQ=', // high
42
- }
43
-
44
- // Lock codes
45
- this.lock2Code = {
46
- on: 'MxABAAAAAAAAAAAAAAAAAAAAACI=',
47
- off: 'MxAAAAAAAAAAAAAAAAAAAAAAACM=',
48
- }
49
-
50
- // Display codes
51
- this.display2Code = {
52
- on: 'MxYBAAAAAAAAAAAAAAAAAAAAACQ=',
53
- off: 'MxYAAAAAAAAAAAAAAAAAAAAAACU=',
54
- }
55
-
56
- // Add the purifier service if it doesn't already exist
57
- this.service = this.accessory.getService(this.hapServ.AirPurifier)
58
- || this.accessory.addService(this.hapServ.AirPurifier)
59
-
60
- // Add the set handler to the switch on/off characteristic
61
- this.service.getCharacteristic(this.hapChar.Active).onSet(async (value) => {
62
- await this.internalStateUpdate(value)
63
- })
64
- this.cacheState = this.service.getCharacteristic(this.hapChar.Active).value === 1 ? 'on' : 'off'
65
-
66
- // Add options to the purifier target state characteristic
67
- this.service
68
- .getCharacteristic(this.hapChar.TargetAirPurifierState)
69
- .setProps({
70
- validValues: [0],
71
- })
72
- .updateValue(0)
73
-
74
- // Add the set handler to the fan rotation speed characteristic
75
- this.service
76
- .getCharacteristic(this.hapChar.RotationSpeed)
77
- .setProps({
78
- minStep: 33,
79
- validValues: [0, 33, 66, 99],
80
- })
81
- .onSet(async value => this.internalSpeedUpdate(value))
82
- this.cacheSpeed = this.service.getCharacteristic(this.hapChar.RotationSpeed).value || 33
83
-
84
- // Add the set handler to the lock controls characteristic
85
- this.service.getCharacteristic(this.hapChar.LockPhysicalControls).onSet(async (value) => {
86
- await this.internalLockUpdate(value)
87
- })
88
- this.cacheLock = this.service.getCharacteristic(this.hapChar.LockPhysicalControls).value === 1 ? 'on' : 'off'
89
-
90
- // Add display light Eve characteristic if it doesn't exist already
91
- if (!this.service.testCharacteristic(this.cusChar.DisplayLight)) {
92
- this.service.addCharacteristic(this.cusChar.DisplayLight)
93
- }
94
-
95
- // Add the set handler to the custom display light characteristic
96
- this.service.getCharacteristic(this.cusChar.DisplayLight).onSet(async (value) => {
97
- await this.internalDisplayLightUpdate(value)
98
- })
99
- this.cacheDisplay = this.service.getCharacteristic(this.cusChar.DisplayLight).value
100
- ? 'on'
101
- : 'off'
102
-
103
- // Output the customised options to the log
104
- const opts = JSON.stringify({})
105
- platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
106
- }
107
-
108
- async internalStateUpdate(value) {
109
- try {
110
- const newValue = value === 1 ? 'on' : 'off'
111
-
112
- // Don't continue if the new value is the same as before
113
- if (this.cacheState === newValue) {
114
- return
115
- }
116
-
117
- // Send the request to the platform sender function
118
- await this.platform.sendDeviceUpdate(this.accessory, {
119
- cmd: 'statePuri',
120
- value: value ? 1 : 0,
121
- })
122
-
123
- // Update the current state characteristic
124
- this.service.updateCharacteristic(this.hapChar.CurrentAirPurifierState, value === 1 ? 2 : 0)
125
-
126
- // Cache the new state and log if appropriate
127
- this.cacheState = newValue
128
- this.accessory.log(`${platformLang.curState} [${newValue}]`)
129
- } catch (err) {
130
- // Catch any errors during the process
131
- this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
132
-
133
- // Throw a 'no response' error and set a timeout to revert this after 2 seconds
134
- setTimeout(() => {
135
- this.service.updateCharacteristic(this.hapChar.Active, this.cacheState === 'on' ? 1 : 0)
136
- }, 2000)
137
- throw new this.hapErr(-70402)
138
- }
139
- }
140
-
141
- async internalSpeedUpdate(value) {
142
- try {
143
- // Don't continue if the speed is 0
144
- if (value === 0) {
145
- return
146
- }
147
-
148
- // Get the single Govee value {1, 2, 3}
149
- const newValue = this.speed2Value(value)
150
- const newSpeed = this.value2Speed[newValue]
151
-
152
- // Don't continue if the speed value won't have effect
153
- if (newSpeed === this.cacheSpeed) {
154
- return
155
- }
156
-
157
- // Get the scene code for this value
158
- const newCode = this.value2Code[newValue]
159
-
160
- // Send the request to the platform sender function
161
- await this.platform.sendDeviceUpdate(this.accessory, {
162
- cmd: 'ptReal',
163
- value: newCode,
164
- openApi: this.accessory.context.openApiCapabilities?.workMode
165
- ? { instance: 'workMode', capabilityType: 'devices.capabilities.work_mode', value: { workMode: 1, modeValue: newValue } }
166
- : undefined,
167
- })
168
-
169
- // Cache the new state and log if appropriate
170
- this.cacheSpeed = newSpeed
171
- this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
172
- this.accessory.log(`${platformLang.curSpeed} [${this.cacheSpeed}%]`)
173
- } catch (err) {
174
- // Catch any errors during the process
175
- this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
176
-
177
- // Throw a 'no response' error and set a timeout to revert this after 2 seconds
178
- setTimeout(() => {
179
- this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
180
- }, 2000)
181
- throw new this.hapErr(-70402)
182
- }
183
- }
184
-
185
- async internalLockUpdate(value) {
186
- try {
187
- const newValue = value === 1 ? 'on' : 'off'
188
-
189
- // Don't continue if the new value is the same as before
190
- if (this.cacheLock === newValue) {
191
- return
192
- }
193
-
194
- // Send the request to the platform sender function
195
- await this.platform.sendDeviceUpdate(this.accessory, {
196
- cmd: 'ptReal',
197
- value: this.lock2Code[newValue],
198
- openApi: this.accessory.context.openApiCapabilities?.lockToggle
199
- ? { instance: 'lockToggle', capabilityType: 'devices.capabilities.toggle', value: newValue === 'on' ? 1 : 0 }
200
- : undefined,
201
- })
202
-
203
- // Cache the new state and log if appropriate
204
- this.cacheLock = newValue
205
- this.accessory.log(`${platformLang.curLock} [${newValue}]`)
206
- } catch (err) {
207
- // Catch any errors during the process
208
- this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
209
-
210
- // Throw a 'no response' error and set a timeout to revert this after 2 seconds
211
- setTimeout(() => {
212
- this.service.updateCharacteristic(
213
- this.hapChar.LockPhysicalControls,
214
- this.cacheLock === 'on' ? 1 : 0,
215
- )
216
- }, 2000)
217
- throw new this.hapErr(-70402)
218
- }
219
- }
220
-
221
- async internalDisplayLightUpdate(value) {
222
- try {
223
- const newValue = value ? 'on' : 'off'
224
-
225
- // Don't continue if the new value is the same as before
226
- if (this.cacheDisplay === newValue) {
227
- return
228
- }
229
-
230
- // Send the request to the platform sender function
231
- await this.platform.sendDeviceUpdate(this.accessory, {
232
- cmd: 'ptReal',
233
- value: this.display2Code[newValue],
234
- openApi: this.accessory.context.openApiCapabilities?.displayToggle
235
- ? { instance: 'displayToggle', capabilityType: 'devices.capabilities.toggle', value: newValue === 'on' ? 1 : 0 }
236
- : undefined,
237
- })
238
-
239
- // Cache the new state and log if appropriate
240
- this.cacheDisplay = newValue
241
- this.accessory.log(`${platformLang.curDisplay} [${newValue}]`)
242
- } catch (err) {
243
- // Catch any errors during the process
244
- this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
245
-
246
- // Throw a 'no response' error and set a timeout to revert this after 2 seconds
247
- setTimeout(() => {
248
- this.service.updateCharacteristic(this.cusChar.DisplayLight, this.cacheDisplay === 'on')
249
- }, 2000)
250
- throw new this.hapErr(-70402)
251
- }
252
- }
253
-
254
- externalUpdate(params) {
255
- // Check for an ON/OFF change
256
- if (params.state && params.state !== this.cacheState) {
257
- this.cacheState = params.state
258
- this.service.updateCharacteristic(this.hapChar.Active, this.cacheState === 'on')
259
- this.service.updateCharacteristic(this.hapChar.CurrentAirPurifierState, this.cacheState === 'on' ? 2 : 0)
260
-
261
- // Log the change
262
- this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
263
- }
264
-
265
- // Handle OpenAPI workMode (speed/mode)
266
- if (params.workMode) {
267
- const speed = params.workMode.modeValue
268
- if (speed >= 1 && speed <= 3) {
269
- const newSpeed = this.value2Speed[speed]
270
- if (newSpeed && newSpeed !== this.cacheSpeed) {
271
- this.cacheSpeed = newSpeed
272
- this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
273
- this.accessory.log(`${platformLang.curSpeed} [${this.cacheSpeed}%]`)
274
- }
275
- }
276
- }
277
-
278
- // Handle OpenAPI toggles
279
- if (params.toggles) {
280
- if (params.toggles.lockToggle !== undefined) {
281
- const newLock = params.toggles.lockToggle ? 'on' : 'off'
282
- if (newLock !== this.cacheLock) {
283
- this.cacheLock = newLock
284
- this.service.updateCharacteristic(this.hapChar.LockPhysicalControls, this.cacheLock === 'on' ? 1 : 0)
285
- this.accessory.log(`${platformLang.curLock} [${this.cacheLock}]`)
286
- }
287
- }
288
- if (params.toggles.displayToggle !== undefined) {
289
- const newDisplay = params.toggles.displayToggle ? 'on' : 'off'
290
- if (newDisplay !== this.cacheDisplay) {
291
- this.cacheDisplay = newDisplay
292
- this.service.updateCharacteristic(this.cusChar.DisplayLight, this.cacheDisplay === 'on')
293
- this.accessory.log(`${platformLang.curDisplay} [${this.cacheDisplay}]`)
294
- }
295
- }
296
- }
297
-
298
- // Check for some other scene/mode change
299
- (params.commands || []).forEach((command) => {
300
- const hexString = base64ToHex(command)
301
- const hexParts = hexToTwoItems(hexString)
302
-
303
- // Return now if not a device query update code
304
- if (getTwoItemPosition(hexParts, 1) !== 'aa') {
305
- return
306
- }
307
-
308
- const deviceFunction = `${getTwoItemPosition(hexParts, 2)}${getTwoItemPosition(hexParts, 3)}`
309
-
310
- switch (deviceFunction) {
311
- case '0501': {
312
- // Manual speed
313
- const newSpeedRaw = getTwoItemPosition(hexParts, 4)
314
- const newSpeedValue = Number.parseInt(newSpeedRaw, 16)
315
-
316
- if ([1, 2, 3].includes(newSpeedValue)) {
317
- const newSpeed = this.value2Speed[newSpeedValue]
318
-
319
- if (newSpeed !== this.cacheSpeed) {
320
- this.cacheSpeedRaw = newSpeedRaw
321
- this.cacheSpeed = newSpeed
322
- this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
323
- this.accessory.log(`${platformLang.curSpeed} [${this.cacheSpeed}%]`)
324
- }
325
- }
326
- break
327
- }
328
- case '1000':
329
- case '1001': { // lock
330
- const newLock = deviceFunction === '1001' ? 'on' : 'off'
331
- if (newLock !== this.cacheLock) {
332
- this.cacheLock = newLock
333
- this.service.updateCharacteristic(this.hapChar.LockPhysicalControls, this.cacheLock === 'on' ? 1 : 0)
334
- this.accessory.log(`${platformLang.curLock} [${this.cacheLock}]`)
335
- }
336
- break
337
- }
338
- case '1600':
339
- case '1601': { // display light
340
- const newDisplay = deviceFunction === '1601' ? 'on' : 'off'
341
- if (newDisplay === 'on') {
342
- this.accessory.context.cacheDisplayCode = hexString
343
- }
344
- if (newDisplay !== this.cacheDisplay) {
345
- this.cacheDisplay = newDisplay
346
- this.service.updateCharacteristic(this.cusChar.DisplayLight, this.cacheDisplay === 'on')
347
-
348
- // Log the change
349
- this.accessory.log(`${platformLang.curDisplay} [${this.cacheDisplay}]`)
350
- }
351
- break
352
- }
353
- default:
354
- this.accessory.logDebugWarn(`${platformLang.newScene}: [${command}] [${hexString}]`)
355
- break
356
- }
357
- })
358
- }
359
- }
@@ -1,22 +0,0 @@
1
- import platformLang from '../utils/lang-en.js'
2
-
3
- export default class {
4
- constructor(platform, accessory) {
5
- // Set up variables from the platform
6
- this.hapChar = platform.api.hap.Characteristic
7
- this.hapErr = platform.api.hap.HapStatusError
8
- this.hapServ = platform.api.hap.Service
9
- this.platform = platform
10
-
11
- // Set up variables from the accessory
12
- this.accessory = accessory
13
-
14
- // Output the customised options to the log
15
- const opts = JSON.stringify({})
16
- platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
17
- }
18
-
19
- externalUpdate(params) {
20
- this.accessory.logWarn(`${platformLang.newScene}: [${params.scene}]`)
21
- }
22
- }
@@ -1,44 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
-
3
- import { BLE_STAGE_TIMEOUTS, BLE_UPDATE_TIMEOUT } from './ble-protocol.js'
4
-
5
- /**
6
- * The bug these guard against (#1328): the BLE connect timeout and the platform
7
- * queue timeout were both a flat 10000ms. The queue therefore killed a task at
8
- * the exact moment the connect gave up, and any light that connected slowly had
9
- * nothing left of the budget for discovering services and writing its command.
10
- */
11
- describe('bLE timeout budget', () => {
12
- it('leaves room for every stage of an update', () => {
13
- const stages = BLE_STAGE_TIMEOUTS.powerOn
14
- + BLE_STAGE_TIMEOUTS.connect
15
- + BLE_STAGE_TIMEOUTS.untimedStages
16
- + BLE_STAGE_TIMEOUTS.write
17
- + BLE_STAGE_TIMEOUTS.settle
18
-
19
- expect(BLE_UPDATE_TIMEOUT).toBeGreaterThan(stages)
20
- })
21
-
22
- it('is not simply the connect timeout, which is what caused #1328', () => {
23
- expect(BLE_UPDATE_TIMEOUT).not.toBe(BLE_STAGE_TIMEOUTS.connect)
24
- })
25
-
26
- it('still has time left after the slowest possible connect', () => {
27
- // A connect that only just succeeds must leave enough behind to discover
28
- // services and send the command - otherwise a slow device is
29
- // indistinguishable from an unreachable one.
30
- const afterSlowestConnect = BLE_UPDATE_TIMEOUT
31
- - BLE_STAGE_TIMEOUTS.powerOn
32
- - BLE_STAGE_TIMEOUTS.connect
33
-
34
- expect(afterSlowestConnect).toBeGreaterThanOrEqual(
35
- BLE_STAGE_TIMEOUTS.untimedStages + BLE_STAGE_TIMEOUTS.write + BLE_STAGE_TIMEOUTS.settle,
36
- )
37
- })
38
-
39
- it('keeps every stage timeout positive', () => {
40
- Object.entries(BLE_STAGE_TIMEOUTS).forEach(([stage, ms]) => {
41
- expect(ms, stage).toBeGreaterThan(0)
42
- })
43
- })
44
- })