@homebridge-plugins/homebridge-govee 10.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/CHANGELOG.md +1937 -0
  2. package/LICENSE +21 -0
  3. package/README.md +72 -0
  4. package/config.schema.json +1727 -0
  5. package/eslint.config.js +49 -0
  6. package/lib/connection/aws.js +174 -0
  7. package/lib/connection/ble.js +208 -0
  8. package/lib/connection/cert/AmazonRootCA1.pem +20 -0
  9. package/lib/connection/http.js +240 -0
  10. package/lib/connection/lan.js +284 -0
  11. package/lib/device/cooler-single.js +300 -0
  12. package/lib/device/dehumidifier-H7150.js +182 -0
  13. package/lib/device/dehumidifier-H7151.js +157 -0
  14. package/lib/device/diffuser-H7161.js +117 -0
  15. package/lib/device/diffuser-H7162.js +117 -0
  16. package/lib/device/fan-H7100.js +274 -0
  17. package/lib/device/fan-H7101.js +330 -0
  18. package/lib/device/fan-H7102.js +274 -0
  19. package/lib/device/fan-H7105.js +503 -0
  20. package/lib/device/fan-H7106.js +274 -0
  21. package/lib/device/fan-H7111.js +335 -0
  22. package/lib/device/heater-single.js +300 -0
  23. package/lib/device/heater1a.js +353 -0
  24. package/lib/device/heater1b.js +616 -0
  25. package/lib/device/heater2.js +838 -0
  26. package/lib/device/humidifier-H7140.js +224 -0
  27. package/lib/device/humidifier-H7141.js +257 -0
  28. package/lib/device/humidifier-H7142.js +522 -0
  29. package/lib/device/humidifier-H7143.js +157 -0
  30. package/lib/device/humidifier-H7148.js +157 -0
  31. package/lib/device/humidifier-H7160.js +446 -0
  32. package/lib/device/ice-maker-H7162.js +46 -0
  33. package/lib/device/index.js +105 -0
  34. package/lib/device/kettle.js +269 -0
  35. package/lib/device/light-switch.js +86 -0
  36. package/lib/device/light.js +617 -0
  37. package/lib/device/outlet-double.js +121 -0
  38. package/lib/device/outlet-single.js +172 -0
  39. package/lib/device/outlet-triple.js +160 -0
  40. package/lib/device/purifier-H7120.js +336 -0
  41. package/lib/device/purifier-H7121.js +336 -0
  42. package/lib/device/purifier-H7122.js +449 -0
  43. package/lib/device/purifier-H7123.js +411 -0
  44. package/lib/device/purifier-H7124.js +411 -0
  45. package/lib/device/purifier-H7126.js +296 -0
  46. package/lib/device/purifier-H7127.js +296 -0
  47. package/lib/device/purifier-H712C.js +296 -0
  48. package/lib/device/purifier-single.js +119 -0
  49. package/lib/device/sensor-button.js +22 -0
  50. package/lib/device/sensor-contact.js +22 -0
  51. package/lib/device/sensor-leak.js +87 -0
  52. package/lib/device/sensor-monitor.js +190 -0
  53. package/lib/device/sensor-presence.js +53 -0
  54. package/lib/device/sensor-thermo.js +144 -0
  55. package/lib/device/sensor-thermo4.js +55 -0
  56. package/lib/device/switch-double.js +121 -0
  57. package/lib/device/switch-single.js +95 -0
  58. package/lib/device/switch-triple.js +160 -0
  59. package/lib/device/tap-single.js +108 -0
  60. package/lib/device/template.js +43 -0
  61. package/lib/device/tv-single.js +84 -0
  62. package/lib/device/valve-single.js +155 -0
  63. package/lib/fakegato/LICENSE +21 -0
  64. package/lib/fakegato/fakegato-history.js +814 -0
  65. package/lib/fakegato/fakegato-storage.js +108 -0
  66. package/lib/fakegato/fakegato-timer.js +125 -0
  67. package/lib/fakegato/uuid.js +27 -0
  68. package/lib/homebridge-ui/public/index.html +433 -0
  69. package/lib/homebridge-ui/server.js +10 -0
  70. package/lib/index.js +8 -0
  71. package/lib/platform.js +1967 -0
  72. package/lib/utils/colour.js +564 -0
  73. package/lib/utils/constants.js +579 -0
  74. package/lib/utils/custom-chars.js +225 -0
  75. package/lib/utils/eve-chars.js +68 -0
  76. package/lib/utils/functions.js +117 -0
  77. package/lib/utils/lang-en.js +131 -0
  78. package/package.json +75 -0
@@ -0,0 +1,449 @@
1
+ import {
2
+ base64ToHex,
3
+ getTwoItemPosition,
4
+ hexToBase64,
5
+ hexToDecimal,
6
+ hexToTwoItems,
7
+ parseError,
8
+ statusToActionCode,
9
+ } from '../utils/functions.js'
10
+ import platformLang from '../utils/lang-en.js'
11
+
12
+ /*
13
+ H7122
14
+ {
15
+ "mode": {
16
+ "options": [
17
+ {
18
+ "name": "Low",
19
+ "value": 1
20
+ },
21
+ {
22
+ "name": "Medium",
23
+ "value": 2
24
+ },
25
+ {
26
+ "name": "High",
27
+ "value": 3
28
+ },
29
+ {
30
+ "name": "Auto mode",
31
+ "value": 4
32
+ },
33
+ {
34
+ "name": "Sleep mode",
35
+ "value": 5
36
+ },
37
+ {
38
+ "name": "CustomMode mode",
39
+ "value": 6
40
+ }
41
+ ]
42
+ }
43
+ }
44
+ */
45
+
46
+ export default class {
47
+ constructor(platform, accessory) {
48
+ // Set up variables from the platform
49
+ this.cusChar = platform.cusChar
50
+ this.hapChar = platform.api.hap.Characteristic
51
+ this.hapErr = platform.api.hap.HapStatusError
52
+ this.hapServ = platform.api.hap.Service
53
+ this.platform = platform
54
+
55
+ // Set up variables from the accessory
56
+ this.accessory = accessory
57
+
58
+ // Speed codes
59
+ this.value2Code = {
60
+ 1: 'OgUFAAAAAAAAAAAAAAAAAAAAADo=', // sleep
61
+ 2: 'OgUBAQAAAAAAAAAAAAAAAAAAAD8=', // low
62
+ 3: 'OgUBAgAAAAAAAAAAAAAAAAAAADw=', // med
63
+ 4: 'OgUBAwAAAAAAAAAAAAAAAAAAAD0=', // high
64
+ 5: 'OgUDAAAAAAAAAAAAAAAAAAAAADw=', // auto
65
+ }
66
+
67
+ this.value2Label = {
68
+ 0: 'off',
69
+ 1: 'sleep',
70
+ 2: 'low',
71
+ 3: 'medium',
72
+ 4: 'high',
73
+ 5: 'auto',
74
+ }
75
+
76
+ // Add the purifier service if it doesn't already exist
77
+ this.service = this.accessory.getService(this.hapServ.AirPurifier)
78
+ || this.accessory.addService(this.hapServ.AirPurifier)
79
+
80
+ // Add the air quality service if it doesn't already exist
81
+ this.airService = this.accessory.getService(this.hapServ.AirQualitySensor)
82
+
83
+ if (!this.airService) {
84
+ this.airService = this.accessory.addService(this.hapServ.AirQualitySensor)
85
+ this.airService.addCharacteristic(this.hapChar.PM2_5Density)
86
+ }
87
+ this.cacheAir = this.airService.getCharacteristic(this.hapChar.PM2_5Density).value
88
+
89
+ // Add the set handler to the switch on/off characteristic
90
+ this.service.getCharacteristic(this.hapChar.Active).onSet(async (value) => {
91
+ await this.internalStateUpdate(value)
92
+ })
93
+ this.cacheState = this.service.getCharacteristic(this.hapChar.Active).value === 1 ? 'on' : 'off'
94
+
95
+ // Add options to the purifier target state characteristic
96
+ this.service
97
+ .getCharacteristic(this.hapChar.TargetAirPurifierState)
98
+ .updateValue(1)
99
+ .setProps({
100
+ minValue: 1,
101
+ maxValue: 1,
102
+ validValues: [1],
103
+ })
104
+
105
+ // Add the set handler to the fan rotation speed characteristic
106
+ this.service
107
+ .getCharacteristic(this.hapChar.RotationSpeed)
108
+ .setProps({
109
+ minStep: 20,
110
+ validValues: [0, 20, 40, 60, 80, 100],
111
+ })
112
+ .onSet(async value => this.internalSpeedUpdate(value))
113
+ this.cacheMode = this.service.getCharacteristic(this.hapChar.RotationSpeed).value / 20
114
+
115
+ // Add the set handler to the lock controls characteristic
116
+ this.service.getCharacteristic(this.hapChar.LockPhysicalControls).onSet(async (value) => {
117
+ await this.internalLockUpdate(value)
118
+ })
119
+ this.cacheLock = this.service.getCharacteristic(this.hapChar.LockPhysicalControls).value === 0 ? 'off' : 'on'
120
+
121
+ // Add night light Eve characteristic if it doesn't exist already
122
+ if (!this.service.testCharacteristic(this.cusChar.NightLight)) {
123
+ this.service.addCharacteristic(this.cusChar.NightLight)
124
+ }
125
+
126
+ // Add display light Eve characteristic if it doesn't exist already
127
+ if (!this.service.testCharacteristic(this.cusChar.DisplayLight)) {
128
+ this.service.addCharacteristic(this.cusChar.DisplayLight)
129
+ }
130
+
131
+ // Add the set handler to the custom display light characteristic
132
+ this.service.getCharacteristic(this.cusChar.DisplayLight).onSet(async (value) => {
133
+ await this.internalDisplayLightUpdate(value)
134
+ })
135
+ this.cacheDisplay = this.service.getCharacteristic(this.cusChar.DisplayLight).value
136
+ ? 'on'
137
+ : 'off'
138
+
139
+ // Output the customised options to the log
140
+ const opts = JSON.stringify({})
141
+ platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
142
+ }
143
+
144
+ async internalStateUpdate(value) {
145
+ try {
146
+ const newValue = value === 1 ? 'on' : 'off'
147
+
148
+ // Don't continue if the new value is the same as before
149
+ if (this.cacheState === newValue) {
150
+ return
151
+ }
152
+
153
+ // Send the request to the platform sender function
154
+ await this.platform.sendDeviceUpdate(this.accessory, {
155
+ cmd: 'statePuri',
156
+ value: value ? 1 : 0,
157
+ })
158
+
159
+ // Update the current state characteristic
160
+ this.service.updateCharacteristic(this.hapChar.CurrentAirPurifierState, value === 1 ? 2 : 0)
161
+
162
+ // Cache the new state and log if appropriate
163
+ this.cacheState = newValue
164
+ this.accessory.log(`${platformLang.curState} [${newValue}]`)
165
+ } catch (err) {
166
+ // Catch any errors during the process
167
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
168
+
169
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
170
+ setTimeout(() => {
171
+ this.service.updateCharacteristic(this.hapChar.Active, this.cacheState === 'on' ? 1 : 0)
172
+ }, 2000)
173
+ throw new this.hapErr(-70402)
174
+ }
175
+ }
176
+
177
+ async internalSpeedUpdate(value) {
178
+ try {
179
+ // Don't continue if the speed is 0
180
+ if (value === 0) {
181
+ return
182
+ }
183
+
184
+ // Get the single Govee value {1, 2, 3, 4}
185
+ const newValueKey = value / 20
186
+
187
+ // Don't continue if the speed value won't have effect
188
+ if (!newValueKey || newValueKey === this.cacheMode) {
189
+ return
190
+ }
191
+
192
+ // Send the request to the platform sender function
193
+ await this.platform.sendDeviceUpdate(this.accessory, {
194
+ cmd: 'ptReal',
195
+ value: this.value2Code[newValueKey],
196
+ })
197
+
198
+ // Cache the new state and log if appropriate
199
+ this.cacheMode = newValueKey
200
+ this.accessory.log(`${platformLang.curMode} [${this.value2Label[this.cacheMode]}]`)
201
+ } catch (err) {
202
+ // Catch any errors during the process
203
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
204
+
205
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
206
+ setTimeout(() => {
207
+ this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheMode * 20)
208
+ }, 2000)
209
+ throw new this.hapErr(-70402)
210
+ }
211
+ }
212
+
213
+ async internalLockUpdate(value) {
214
+ try {
215
+ const newValue = value === 1 ? 'on' : 'off'
216
+
217
+ // Don't continue if the new value is the same as before
218
+ if (this.cacheLock === newValue) {
219
+ return
220
+ }
221
+
222
+ // Send the request to the platform sender function
223
+ await this.platform.sendDeviceUpdate(this.accessory, {
224
+ cmd: 'ptReal',
225
+ value: value ? 'MxABAAAAAAAAAAAAAAAAAAAAACI=' : 'MxAAAAAAAAAAAAAAAAAAAAAAACM=',
226
+ })
227
+
228
+ // Cache the new state and log if appropriate
229
+ this.cacheLock = newValue
230
+ this.accessory.log(`${platformLang.curLock} [${newValue}]`)
231
+ } catch (err) {
232
+ // Catch any errors during the process
233
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
234
+
235
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
236
+ setTimeout(() => {
237
+ this.service.updateCharacteristic(
238
+ this.hapChar.LockPhysicalControls,
239
+ this.cacheLock === 'on' ? 1 : 0,
240
+ )
241
+ }, 2000)
242
+ throw new this.hapErr(-70402)
243
+ }
244
+ }
245
+
246
+ async internalDisplayLightUpdate(value) {
247
+ try {
248
+ const newValue = value ? 'on' : 'off'
249
+
250
+ // Don't continue if the new value is the same as before
251
+ if (this.cacheDisplay === newValue) {
252
+ return
253
+ }
254
+
255
+ // Generate the code to send
256
+ let codeToSend
257
+ if (value) {
258
+ codeToSend = this.accessory.context.cacheDisplayCode
259
+ ? hexToBase64(statusToActionCode(this.accessory.context.cacheDisplayCode))
260
+ : 'MxYBAAAAAAAAAAAAAAAAAAAAACQ='
261
+ } else {
262
+ codeToSend = 'MxYAAAAAAAAAAAAAAAAAAAAAACU='
263
+ }
264
+
265
+ // Send the request to the platform sender function
266
+ await this.platform.sendDeviceUpdate(this.accessory, {
267
+ cmd: 'ptReal',
268
+ value: codeToSend,
269
+ })
270
+
271
+ // Cache the new state and log if appropriate
272
+ this.cacheDisplay = newValue
273
+ this.accessory.log(`${platformLang.curDisplay} [${newValue}]`)
274
+ } catch (err) {
275
+ // Catch any errors during the process
276
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
277
+
278
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
279
+ setTimeout(() => {
280
+ this.service.updateCharacteristic(this.cusChar.DisplayLight, this.cacheDisplay === 'on')
281
+ }, 2000)
282
+ throw new this.hapErr(-70402)
283
+ }
284
+ }
285
+
286
+ externalUpdate(params) {
287
+ // Check for an ON/OFF change
288
+ if (params.state && params.state !== this.cacheState) {
289
+ this.cacheState = params.state
290
+ this.service.updateCharacteristic(this.hapChar.Active, this.cacheState === 'on')
291
+ this.service.updateCharacteristic(this.hapChar.CurrentAirPurifierState, this.cacheState === 'on' ? 2 : 0)
292
+
293
+ // Log the change
294
+ this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
295
+ }
296
+
297
+ (params.commands || []).forEach((command) => {
298
+ const hexString = base64ToHex(command)
299
+ const hexParts = hexToTwoItems(hexString)
300
+
301
+ const deviceFunction = `${getTwoItemPosition(hexParts, 1)}${getTwoItemPosition(hexParts, 2)}`
302
+
303
+ switch (deviceFunction) {
304
+ case 'aa05': // speed
305
+ case '3a05': { // speed
306
+ const newSpeedCode = `${getTwoItemPosition(hexParts, 3)}${getTwoItemPosition(hexParts, 4)}`
307
+
308
+ // Different behaviour for custom speed
309
+ if (newSpeedCode === '0202') {
310
+ this.accessory.log(`${platformLang.curMode} [custom]`)
311
+ return
312
+ }
313
+
314
+ let newMode
315
+
316
+ switch (newSpeedCode) {
317
+ case '0500': {
318
+ // Sleep
319
+ newMode = 1
320
+ break
321
+ }
322
+ case '0101': {
323
+ // Low
324
+ newMode = 2
325
+ break
326
+ }
327
+ case '0102': {
328
+ // Medium
329
+ newMode = 3
330
+ break
331
+ }
332
+ case '0103': {
333
+ // High
334
+ newMode = 4
335
+ break
336
+ }
337
+ case '0300': {
338
+ // Auto
339
+ newMode = 5
340
+ break
341
+ }
342
+ }
343
+
344
+ if (newMode && newMode !== this.cacheMode) {
345
+ this.cacheMode = newMode
346
+ this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheMode * 20)
347
+ this.accessory.log(`${platformLang.curMode} [${this.value2Label[this.cacheMode]}]`)
348
+ }
349
+ break
350
+ }
351
+ case 'aa10': { // lock
352
+ const newLock = getTwoItemPosition(hexParts, 3) === '01' ? 'on' : 'off'
353
+ if (newLock !== this.cacheLock) {
354
+ this.cacheLock = newLock
355
+ this.service.updateCharacteristic(this.hapChar.LockPhysicalControls, this.cacheLock === 'on' ? 1 : 0)
356
+ this.accessory.log(`${platformLang.curLock} [${this.cacheLock}]`)
357
+ }
358
+ break
359
+ }
360
+ case 'aa16': { // display light
361
+ const newDisplay = getTwoItemPosition(hexParts, 3) === '01' ? 'on' : 'off'
362
+ if (newDisplay === 'on') {
363
+ this.accessory.context.cacheDisplayCode = hexString
364
+ }
365
+ if (newDisplay !== this.cacheDisplay) {
366
+ this.cacheDisplay = newDisplay
367
+ this.service.updateCharacteristic(this.cusChar.DisplayLight, this.cacheDisplay === 'on')
368
+
369
+ // Log the change
370
+ this.accessory.log(`${platformLang.curDisplay} [${this.cacheDisplay}]`)
371
+ }
372
+ break
373
+ }
374
+ case 'aa1c': {
375
+ // Check air quality reading
376
+ // part 3 may be the air quality reading, (i.e. 1=green, 2=blue, 3=yellow, 4=red)
377
+ const qualHex = `${getTwoItemPosition(hexParts, 4)}${getTwoItemPosition(hexParts, 5)}`
378
+ const qualDec = hexToDecimal(`0x${qualHex}`)
379
+ if (qualDec !== this.cacheAir) {
380
+ // Air quality is different so update Homebridge with new values
381
+ this.cacheAir = qualDec
382
+ this.airService.updateCharacteristic(this.hapChar.PM2_5Density, this.cacheAir)
383
+
384
+ // Log the change
385
+ this.accessory.log(`${platformLang.curPM25} [${qualDec}µg/m³]`)
386
+
387
+ // Check for any change to the main air quality characteristic
388
+ // PM2.5 has a range of 0-1000µg/m³
389
+ // HK characteristic ranges from 1-5 (excellent, good, fair, inferior, poor)
390
+ // Scales based on Govee manual
391
+ // 0-12.0µg/m³ = excellent
392
+ // 12-35µg/m³ = good
393
+ // 35-75µg/m³ = fair
394
+ // 75-115µg/m³ = inferior
395
+ // 115-500µg/m³ = poor (use 1000 for HK)
396
+ if (this.cacheAir <= 12) {
397
+ const newValue = 'excellent'
398
+ if (this.cacheAirQual !== newValue) {
399
+ this.cacheAirQual = newValue
400
+ this.airService.updateCharacteristic(this.hapChar.AirQuality, 1)
401
+ this.accessory.log(`${platformLang.curAirQual} [${newValue}]`)
402
+ }
403
+ } else if (this.cacheAir <= 35) {
404
+ const newValue = 'good'
405
+ if (this.cacheAirQual !== newValue) {
406
+ this.cacheAirQual = newValue
407
+ this.airService.updateCharacteristic(this.hapChar.AirQuality, 2)
408
+ this.accessory.log(`${platformLang.curAirQual} [${newValue}]`)
409
+ }
410
+ } else if (this.cacheAir <= 75) {
411
+ const newValue = 'fair'
412
+ if (this.cacheAirQual !== newValue) {
413
+ this.cacheAirQual = newValue
414
+ this.airService.updateCharacteristic(this.hapChar.AirQuality, 3)
415
+ this.accessory.log(`${platformLang.curAirQual} [${newValue}]`)
416
+ }
417
+ } else if (this.cacheAir <= 115) {
418
+ const newValue = 'inferior'
419
+ if (this.cacheAirQual !== newValue) {
420
+ this.cacheAirQual = newValue
421
+ this.airService.updateCharacteristic(this.hapChar.AirQuality, 4)
422
+ this.accessory.log(`${platformLang.curAirQual} [${newValue}]`)
423
+ }
424
+ } else {
425
+ const newValue = 'poor'
426
+ if (this.cacheAirQual !== newValue) {
427
+ this.cacheAirQual = newValue
428
+ this.airService.updateCharacteristic(this.hapChar.AirQuality, 5)
429
+ this.accessory.log(`${platformLang.curAirQual} [${newValue}]`)
430
+ }
431
+ }
432
+ }
433
+ break
434
+ }
435
+ case 'aa11': // timer
436
+ case 'aa13': // scheduling
437
+ case '3310': // lock (same command for on and off)
438
+ case '3311': // timer
439
+ case '3313': // scheduling
440
+ case '3316': { // display light
441
+ break
442
+ }
443
+ default:
444
+ this.accessory.logDebugWarn(`${platformLang.newScene}: [${command}] [${hexString}]`)
445
+ break
446
+ }
447
+ })
448
+ }
449
+ }