@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,411 @@
1
+ import {
2
+ base64ToHex,
3
+ getTwoItemPosition,
4
+ hexToBase64,
5
+ hexToTwoItems,
6
+ parseError,
7
+ statusToActionCode,
8
+ } from '../utils/functions.js'
9
+ import platformLang from '../utils/lang-en.js'
10
+
11
+ /*
12
+ H7123
13
+ {
14
+ "mode": {
15
+ "options": [
16
+ {
17
+ "name": "Low",
18
+ "value": 1
19
+ },
20
+ {
21
+ "name": "Medium",
22
+ "value": 2
23
+ },
24
+ {
25
+ "name": "High",
26
+ "value": 3
27
+ },
28
+ {
29
+ "name": "Auto mode",
30
+ "value": 4
31
+ },
32
+ {
33
+ "name": "Sleep mode",
34
+ "value": 5
35
+ }
36
+ ]
37
+ }
38
+ }
39
+ */
40
+
41
+ export default class {
42
+ constructor(platform, accessory) {
43
+ // Set up variables from the platform
44
+ this.cusChar = platform.cusChar
45
+ this.hapChar = platform.api.hap.Characteristic
46
+ this.hapErr = platform.api.hap.HapStatusError
47
+ this.hapServ = platform.api.hap.Service
48
+ this.platform = platform
49
+
50
+ // Set up variables from the accessory
51
+ this.accessory = accessory
52
+
53
+ // Speed codes
54
+ this.value2Code = {
55
+ 1: 'OgUFAAAAAAAAAAAAAAAAAAAAADo=', // sleep
56
+ 2: 'OgUBAQAAAAAAAAAAAAAAAAAAAD8=', // low
57
+ 3: 'OgUBAgAAAAAAAAAAAAAAAAAAADw=', // med
58
+ 4: 'OgUBAwAAAAAAAAAAAAAAAAAAAD0=', // high
59
+ 5: 'OgUDAAAAAAAAAAAAAAAAAAAAADw=', // auto
60
+ }
61
+
62
+ this.value2Label = {
63
+ 0: 'off',
64
+ 1: 'sleep',
65
+ 2: 'low',
66
+ 3: 'medium',
67
+ 4: 'high',
68
+ 5: 'auto',
69
+ }
70
+
71
+ this.airValue2Label = {
72
+ 1: 'excellent',
73
+ 2: 'good',
74
+ 3: 'moderate',
75
+ 4: 'poor',
76
+ }
77
+
78
+ // Add the purifier service if it doesn't already exist
79
+ this.service = this.accessory.getService(this.hapServ.AirPurifier)
80
+ || this.accessory.addService(this.hapServ.AirPurifier)
81
+
82
+ // Add the air quality service if it doesn't already exist
83
+ this.airService = this.accessory.getService(this.hapServ.AirQualitySensor)
84
+ || this.accessory.addService(this.hapServ.AirQualitySensor)
85
+
86
+ if (this.airService.testCharacteristic(this.hapChar.PM2_5Density)) {
87
+ this.airService.removeCharacteristic(this.airService.getCharacteristic(this.hapChar.PM2_5Density))
88
+ }
89
+
90
+ this.cacheAir = this.airService.getCharacteristic(this.hapChar.AirQuality).value
91
+
92
+ // Add the set handler to the switch on/off characteristic
93
+ this.service.getCharacteristic(this.hapChar.Active).onSet(async (value) => {
94
+ await this.internalStateUpdate(value)
95
+ })
96
+ this.cacheState = this.service.getCharacteristic(this.hapChar.Active).value === 1 ? 'on' : 'off'
97
+
98
+ // Add options to the purifier target state characteristic
99
+ this.service
100
+ .getCharacteristic(this.hapChar.TargetAirPurifierState)
101
+ .updateValue(1)
102
+ .setProps({
103
+ minValue: 1,
104
+ maxValue: 1,
105
+ validValues: [1],
106
+ })
107
+
108
+ // Add the set handler to the fan rotation speed characteristic
109
+ this.service
110
+ .getCharacteristic(this.hapChar.RotationSpeed)
111
+ .setProps({
112
+ minStep: 20,
113
+ validValues: [0, 20, 40, 60, 80, 100],
114
+ })
115
+ .onSet(async value => this.internalSpeedUpdate(value))
116
+ this.cacheMode = this.service.getCharacteristic(this.hapChar.RotationSpeed).value / 20
117
+
118
+ // Add the set handler to the lock controls characteristic
119
+ this.service.getCharacteristic(this.hapChar.LockPhysicalControls).onSet(async (value) => {
120
+ await this.internalLockUpdate(value)
121
+ })
122
+ this.cacheLock = this.service.getCharacteristic(this.hapChar.LockPhysicalControls).value === 0 ? 'off' : 'on'
123
+
124
+ // Add night light Eve characteristic if it doesn't exist already
125
+ if (!this.service.testCharacteristic(this.cusChar.NightLight)) {
126
+ this.service.addCharacteristic(this.cusChar.NightLight)
127
+ }
128
+
129
+ // Add display light Eve characteristic if it doesn't exist already
130
+ if (!this.service.testCharacteristic(this.cusChar.DisplayLight)) {
131
+ this.service.addCharacteristic(this.cusChar.DisplayLight)
132
+ }
133
+
134
+ // Add the set handler to the custom display light characteristic
135
+ this.service.getCharacteristic(this.cusChar.DisplayLight).onSet(async (value) => {
136
+ await this.internalDisplayLightUpdate(value)
137
+ })
138
+ this.cacheDisplay = this.service.getCharacteristic(this.cusChar.DisplayLight).value
139
+ ? 'on'
140
+ : 'off'
141
+
142
+ // Output the customised options to the log
143
+ const opts = JSON.stringify({})
144
+ platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
145
+ }
146
+
147
+ async internalStateUpdate(value) {
148
+ try {
149
+ const newValue = value === 1 ? 'on' : 'off'
150
+
151
+ // Don't continue if the new value is the same as before
152
+ if (this.cacheState === newValue) {
153
+ return
154
+ }
155
+
156
+ // Send the request to the platform sender function
157
+ await this.platform.sendDeviceUpdate(this.accessory, {
158
+ cmd: 'statePuri',
159
+ value: value ? 1 : 0,
160
+ })
161
+
162
+ // Update the current state characteristic
163
+ this.service.updateCharacteristic(this.hapChar.CurrentAirPurifierState, value === 1 ? 2 : 0)
164
+
165
+ // Cache the new state and log if appropriate
166
+ this.cacheState = newValue
167
+ this.accessory.log(`${platformLang.curState} [${newValue}]`)
168
+ } catch (err) {
169
+ // Catch any errors during the process
170
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
171
+
172
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
173
+ setTimeout(() => {
174
+ this.service.updateCharacteristic(this.hapChar.Active, this.cacheState === 'on' ? 1 : 0)
175
+ }, 2000)
176
+ throw new this.hapErr(-70402)
177
+ }
178
+ }
179
+
180
+ async internalSpeedUpdate(value) {
181
+ try {
182
+ // Don't continue if the speed is 0
183
+ if (value === 0) {
184
+ return
185
+ }
186
+
187
+ // Get the single Govee value {1, 2, 3, 4}
188
+ const newValueKey = value / 20
189
+
190
+ // Don't continue if the speed value won't have effect
191
+ if (!newValueKey || newValueKey === this.cacheMode) {
192
+ return
193
+ }
194
+
195
+ // Send the request to the platform sender function
196
+ await this.platform.sendDeviceUpdate(this.accessory, {
197
+ cmd: 'ptReal',
198
+ value: this.value2Code[newValueKey],
199
+ })
200
+
201
+ // Cache the new state and log if appropriate
202
+ this.cacheMode = newValueKey
203
+ this.accessory.log(`${platformLang.curMode} [${this.value2Label[this.cacheMode]}]`)
204
+ } catch (err) {
205
+ // Catch any errors during the process
206
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
207
+
208
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
209
+ setTimeout(() => {
210
+ this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheMode * 20)
211
+ }, 2000)
212
+ throw new this.hapErr(-70402)
213
+ }
214
+ }
215
+
216
+ async internalLockUpdate(value) {
217
+ try {
218
+ const newValue = value === 1 ? 'on' : 'off'
219
+
220
+ // Don't continue if the new value is the same as before
221
+ if (this.cacheLock === newValue) {
222
+ return
223
+ }
224
+
225
+ // Send the request to the platform sender function
226
+ await this.platform.sendDeviceUpdate(this.accessory, {
227
+ cmd: 'ptReal',
228
+ value: value ? 'MxABAAAAAAAAAAAAAAAAAAAAACI=' : 'MxAAAAAAAAAAAAAAAAAAAAAAACM=',
229
+ })
230
+
231
+ // Cache the new state and log if appropriate
232
+ this.cacheLock = newValue
233
+ this.accessory.log(`${platformLang.curLock} [${newValue}]`)
234
+ } catch (err) {
235
+ // Catch any errors during the process
236
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
237
+
238
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
239
+ setTimeout(() => {
240
+ this.service.updateCharacteristic(
241
+ this.hapChar.LockPhysicalControls,
242
+ this.cacheLock === 'on' ? 1 : 0,
243
+ )
244
+ }, 2000)
245
+ throw new this.hapErr(-70402)
246
+ }
247
+ }
248
+
249
+ async internalDisplayLightUpdate(value) {
250
+ try {
251
+ const newValue = value ? 'on' : 'off'
252
+
253
+ // Don't continue if the new value is the same as before
254
+ if (this.cacheDisplay === newValue) {
255
+ return
256
+ }
257
+
258
+ // Generate the code to send
259
+ let codeToSend
260
+ if (value) {
261
+ codeToSend = this.accessory.context.cacheDisplayCode
262
+ ? hexToBase64(statusToActionCode(this.accessory.context.cacheDisplayCode))
263
+ : 'MxYBAAAAAAAAAAAAAAAAAAAAACQ='
264
+ } else {
265
+ codeToSend = 'MxYAAAAAAAAAAAAAAAAAAAAAACU='
266
+ }
267
+
268
+ // Send the request to the platform sender function
269
+ await this.platform.sendDeviceUpdate(this.accessory, {
270
+ cmd: 'ptReal',
271
+ value: codeToSend,
272
+ })
273
+
274
+ // Cache the new state and log if appropriate
275
+ this.cacheDisplay = newValue
276
+ this.accessory.log(`${platformLang.curDisplay} [${newValue}]`)
277
+ } catch (err) {
278
+ // Catch any errors during the process
279
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
280
+
281
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
282
+ setTimeout(() => {
283
+ this.service.updateCharacteristic(this.cusChar.DisplayLight, this.cacheDisplay === 'on')
284
+ }, 2000)
285
+ throw new this.hapErr(-70402)
286
+ }
287
+ }
288
+
289
+ externalUpdate(params) {
290
+ // Check for an ON/OFF change
291
+ if (params.state && params.state !== this.cacheState) {
292
+ this.cacheState = params.state
293
+ this.service.updateCharacteristic(this.hapChar.Active, this.cacheState === 'on')
294
+ this.service.updateCharacteristic(this.hapChar.CurrentAirPurifierState, this.cacheState === 'on' ? 2 : 0)
295
+
296
+ // Log the change
297
+ this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
298
+ }
299
+
300
+ (params.commands || []).forEach((command) => {
301
+ const hexString = base64ToHex(command)
302
+ const hexParts = hexToTwoItems(hexString)
303
+
304
+ const deviceFunction = `${getTwoItemPosition(hexParts, 1)}${getTwoItemPosition(hexParts, 2)}`
305
+
306
+ switch (deviceFunction) {
307
+ case 'aa05': // speed
308
+ case '3a05': { // speed
309
+ const newSpeedCode = `${getTwoItemPosition(hexParts, 3)}${getTwoItemPosition(hexParts, 4)}`
310
+
311
+ // Different behaviour for custom speed
312
+ if (newSpeedCode === '0202') {
313
+ this.accessory.log(`${platformLang.curMode} [custom]`)
314
+ return
315
+ }
316
+
317
+ let newMode
318
+
319
+ switch (newSpeedCode) {
320
+ case '0500': {
321
+ // Sleep
322
+ newMode = 1
323
+ break
324
+ }
325
+ case '0101': {
326
+ // Low
327
+ newMode = 2
328
+ break
329
+ }
330
+ case '0102': {
331
+ // Medium
332
+ newMode = 3
333
+ break
334
+ }
335
+ case '0103': {
336
+ // High
337
+ newMode = 4
338
+ break
339
+ }
340
+ case '0300': {
341
+ // Auto
342
+ newMode = 5
343
+ break
344
+ }
345
+ }
346
+
347
+ if (newMode && newMode !== this.cacheMode) {
348
+ this.cacheMode = newMode
349
+ this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheMode * 20)
350
+ this.accessory.log(`${platformLang.curMode} [${this.value2Label[this.cacheMode]}]`)
351
+ }
352
+ break
353
+ }
354
+ case 'aa10': { // lock
355
+ const newLock = getTwoItemPosition(hexParts, 3) === '01' ? 'on' : 'off'
356
+ if (newLock !== this.cacheLock) {
357
+ this.cacheLock = newLock
358
+ this.service.updateCharacteristic(this.hapChar.LockPhysicalControls, this.cacheLock === 'on' ? 1 : 0)
359
+ this.accessory.log(`${platformLang.curLock} [${this.cacheLock}]`)
360
+ }
361
+ break
362
+ }
363
+ case 'aa16': { // display light
364
+ const newDisplay = getTwoItemPosition(hexParts, 3) === '01' ? 'on' : 'off'
365
+ if (newDisplay === 'on') {
366
+ this.accessory.context.cacheDisplayCode = hexString
367
+ }
368
+ if (newDisplay !== this.cacheDisplay) {
369
+ this.cacheDisplay = newDisplay
370
+ this.service.updateCharacteristic(this.cusChar.DisplayLight, this.cacheDisplay === 'on')
371
+
372
+ // Log the change
373
+ this.accessory.log(`${platformLang.curDisplay} [${this.cacheDisplay}]`)
374
+ }
375
+ break
376
+ }
377
+ case 'aa19': {
378
+ // Check air quality reading (i.e. 1=green, 2=blue, 3=yellow, 4=red)
379
+ // Cache will be in {1, 2, 3, 5} which relates to Govee {1, 2, 3, 4}
380
+ let newQual = Number.parseInt(getTwoItemPosition(hexParts, 5), 10)
381
+ if (newQual === 4) {
382
+ newQual = 5
383
+ }
384
+
385
+ if (newQual !== this.cacheAir) {
386
+ this.airService.updateCharacteristic(this.hapChar.AirQuality, newQual)
387
+ this.accessory.log(`${platformLang.curAirQual} [${this.airValue2Label[newQual]}]`)
388
+ }
389
+ break
390
+ }
391
+ case 'aa11': // timer
392
+ case 'aa13': // scheduling
393
+ case '3310': // lock (same command for on and off)
394
+ case '3311': // timer
395
+ case '3313': // scheduling
396
+ case '3316': { // display light
397
+ break
398
+ }
399
+ default:
400
+ this.accessory.logDebugWarn(`${platformLang.newScene}: [${command}] [${hexString}]`)
401
+ break
402
+ }
403
+ })
404
+ }
405
+ }
406
+
407
+ // GOOD
408
+ // [28/12/2022, 00:44:48] [Govee] [Pet Air Purifier] new scene code: [qhkAKQJdAAAAAAAAAAAAAAAAAMU=] [aa 19 00 29 02 5d 00000000000000000000000000c5].
409
+
410
+ // EXC
411
+ // [28/12/2022, 01:12:05] [Govee] [Pet Air Purifier] new scene code: [qhkAHQFdAAAAAAAAAAAAAAAAAPI=] [aa 19 00 1d 01 5d 00000000000000000000000000f2].