@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,157 @@
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.hapChar = platform.api.hap.Characteristic
13
+ this.hapErr = platform.api.hap.HapStatusError
14
+ this.hapServ = platform.api.hap.Service
15
+ this.platform = platform
16
+
17
+ // Set up variables from the accessory
18
+ this.accessory = accessory
19
+
20
+ // Rotation speed to value in {1, 2, ..., 8}
21
+ this.speed2Value = speed => Math.min(Math.max(Number.parseInt(Math.round(speed / 10), 10), 1), 8)
22
+
23
+ // Speed codes
24
+ this.value2Code = {
25
+ 1: 'MwUBAQAAAAAAAAAAAAAAAAAAADY=',
26
+ 2: 'MwUBAgAAAAAAAAAAAAAAAAAAADU=',
27
+ 3: 'MwUBAwAAAAAAAAAAAAAAAAAAADQ=',
28
+ 4: 'MwUBBAAAAAAAAAAAAAAAAAAAADM=',
29
+ 5: 'MwUBBQAAAAAAAAAAAAAAAAAAADI=',
30
+ 6: 'MwUBBgAAAAAAAAAAAAAAAAAAADE=',
31
+ 7: 'MwUBBwAAAAAAAAAAAAAAAAAAADA=',
32
+ 8: 'MwUBCAAAAAAAAAAAAAAAAAAAAD8=',
33
+ }
34
+
35
+ // Add the fan service if it doesn't already exist
36
+ this.service = this.accessory.getService(this.hapServ.Fan) || this.accessory.addService(this.hapServ.Fan)
37
+
38
+ // Add the set handler to the fan on/off characteristic
39
+ this.service
40
+ .getCharacteristic(this.hapChar.On)
41
+ .onSet(async value => this.internalStateUpdate(value))
42
+ this.cacheState = this.service.getCharacteristic(this.hapChar.On).value ? 'on' : 'off'
43
+
44
+ // Add the set handler to the fan rotation speed characteristic
45
+ this.service
46
+ .getCharacteristic(this.hapChar.RotationSpeed)
47
+ .setProps({
48
+ minStep: 10,
49
+ validValues: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100],
50
+ })
51
+ .onSet(async value => this.internalSpeedUpdate(value))
52
+ this.cacheSpeed = this.service.getCharacteristic(this.hapChar.RotationSpeed).value
53
+
54
+ // Output the customised options to the log
55
+ const opts = JSON.stringify({})
56
+ platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
57
+ }
58
+
59
+ async internalStateUpdate(value) {
60
+ try {
61
+ const newValue = value ? 'on' : 'off'
62
+
63
+ // Don't continue if the new value is the same as before
64
+ if (this.cacheState === newValue) {
65
+ return
66
+ }
67
+
68
+ // Send the request to the platform sender function
69
+ await this.platform.sendDeviceUpdate(this.accessory, {
70
+ cmd: 'stateHumi',
71
+ value: value ? 1 : 0,
72
+ })
73
+
74
+ // Cache the new state and log if appropriate
75
+ this.cacheState = newValue
76
+ this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
77
+ } catch (err) {
78
+ // Catch any errors during the process
79
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
80
+
81
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
82
+ setTimeout(() => {
83
+ this.service.updateCharacteristic(this.hapChar.On, this.cacheState === 'on')
84
+ }, 2000)
85
+ throw new this.hapErr(-70402)
86
+ }
87
+ }
88
+
89
+ async internalSpeedUpdate(value) {
90
+ try {
91
+ // Don't continue if the speed is 0
92
+ if (value === 0) {
93
+ return
94
+ }
95
+
96
+ // Get the single Govee value {1, 2, ..., 8}
97
+ const newValue = this.speed2Value(value)
98
+
99
+ // Don't continue if the speed value won't have effect
100
+ if (newValue * 10 === this.cacheSpeed) {
101
+ return
102
+ }
103
+
104
+ // Get the scene code for this value
105
+ const newCode = this.value2Code[newValue]
106
+
107
+ // Send the request to the platform sender function
108
+ await this.platform.sendDeviceUpdate(this.accessory, {
109
+ cmd: 'ptReal',
110
+ value: newCode,
111
+ })
112
+
113
+ // Cache the new state and log if appropriate
114
+ this.cacheSpeed = newValue * 10
115
+ this.accessory.log(`${platformLang.curSpeed} [${newValue}]`)
116
+ } catch (err) {
117
+ // Catch any errors during the process
118
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
119
+
120
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
121
+ setTimeout(() => {
122
+ this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
123
+ }, 2000)
124
+ throw new this.hapErr(-70402)
125
+ }
126
+ }
127
+
128
+ externalUpdate(params) {
129
+ // Check for an ON/OFF change
130
+ if (params.state && params.state !== this.cacheState) {
131
+ this.cacheState = params.state
132
+ this.service.updateCharacteristic(this.hapChar.On, this.cacheState === 'on')
133
+
134
+ // Log the change
135
+ this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
136
+ }
137
+
138
+ // Check for some other scene/mode change
139
+ (params.commands || []).forEach((command) => {
140
+ const hexString = base64ToHex(command)
141
+ const hexParts = hexToTwoItems(hexString)
142
+
143
+ // Return now if not a device query update code
144
+ if (getTwoItemPosition(hexParts, 1) !== 'aa') {
145
+ return
146
+ }
147
+
148
+ const deviceFunction = `${getTwoItemPosition(hexParts, 1)}${getTwoItemPosition(hexParts, 2)}`
149
+
150
+ switch (deviceFunction) {
151
+ default:
152
+ this.accessory.logDebugWarn(`${platformLang.newScene}: [${command}] [${hexString}]`)
153
+ break
154
+ }
155
+ })
156
+ }
157
+ }
@@ -0,0 +1,446 @@
1
+ import { hs2rgb, rgb2hs } from '../utils/colour.js'
2
+ import {
3
+ base64ToHex,
4
+ generateCodeFromHexValues,
5
+ generateRandomString,
6
+ getTwoItemPosition,
7
+ hexToDecimal,
8
+ hexToTwoItems,
9
+ parseError,
10
+ sleep,
11
+ } from '../utils/functions.js'
12
+ import platformLang from '../utils/lang-en.js'
13
+
14
+ export default class {
15
+ constructor(platform, accessory) {
16
+ // Set up variables from the platform
17
+ this.hapChar = platform.api.hap.Characteristic
18
+ this.hapErr = platform.api.hap.HapStatusError
19
+ this.hapServ = platform.api.hap.Service
20
+ this.platform = platform
21
+
22
+ // Set up variables from the accessory
23
+ this.accessory = accessory
24
+
25
+ // Rotation speed to value in {1, 2, ..., 8}
26
+ this.speed2Value = speed => Math.min(Math.max(Number.parseInt(Math.round(speed / 10), 10), 1), 9)
27
+
28
+ // Speed codes
29
+ this.value2Code = {
30
+ 1: 'MwUBAQAAAAAAAAAAAAAAAAAAADY=',
31
+ 2: 'MwUBAgAAAAAAAAAAAAAAAAAAADU=',
32
+ 3: 'MwUBAwAAAAAAAAAAAAAAAAAAADQ=',
33
+ 4: 'MwUBBAAAAAAAAAAAAAAAAAAAADM=',
34
+ 5: 'MwUBBQAAAAAAAAAAAAAAAAAAADI=',
35
+ 6: 'MwUBBgAAAAAAAAAAAAAAAAAAADE=',
36
+ 7: 'MwUBBwAAAAAAAAAAAAAAAAAAADA=',
37
+ 8: 'MwUBCAAAAAAAAAAAAAAAAAAAAD8=',
38
+ 9: 'MwUBCQAAAAAAAAAAAAAAAAAAAD4=',
39
+ }
40
+
41
+ // Add the fan service if it doesn't already exist
42
+ this.service = this.accessory.getService(this.hapServ.Fan) || this.accessory.addService(this.hapServ.Fan)
43
+
44
+ // Remove humidity sensor service if it exists
45
+ if (this.accessory.getService(this.hapServ.HumiditySensor)) {
46
+ this.accessory.removeService(this.accessory.getService(this.hapServ.HumiditySensor))
47
+ }
48
+
49
+ // Add the night light service if it doesn't already exist
50
+ this.lightService = this.accessory.getService(this.hapServ.Lightbulb)
51
+ || this.accessory.addService(this.hapServ.Lightbulb)
52
+
53
+ // Add the set handler to the fan on/off characteristic
54
+ this.service
55
+ .getCharacteristic(this.hapChar.On)
56
+ .onSet(async value => this.internalStateUpdate(value))
57
+ this.cacheState = this.service.getCharacteristic(this.hapChar.On).value ? 'on' : 'off'
58
+
59
+ // Add the set handler to the fan rotation speed characteristic
60
+ this.service
61
+ .getCharacteristic(this.hapChar.RotationSpeed)
62
+ .setProps({
63
+ minStep: 10,
64
+ validValues: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100],
65
+ })
66
+ .onSet(async value => this.internalSpeedUpdate(value))
67
+ this.cacheSpeed = this.service.getCharacteristic(this.hapChar.RotationSpeed).value
68
+ this.cacheSpeedRaw = `0${this.cacheSpeed / 10}` // example '02' for 20%
69
+
70
+ // Add the set handler to the lightbulb on/off characteristic
71
+ this.lightService.getCharacteristic(this.hapChar.On).onSet(async (value) => {
72
+ await this.internalLightStateUpdate(value)
73
+ })
74
+ this.cacheLightState = this.lightService.getCharacteristic(this.hapChar.On).value ? 'on' : 'off'
75
+
76
+ // Add the set handler to the lightbulb brightness characteristic
77
+ this.lightService
78
+ .getCharacteristic(this.hapChar.Brightness)
79
+ .onSet(async (value) => {
80
+ await this.internalBrightnessUpdate(value)
81
+ })
82
+ this.cacheBright = this.lightService.getCharacteristic(this.hapChar.Brightness).value
83
+
84
+ // Add the set handler to the lightbulb hue characteristic
85
+ this.lightService.getCharacteristic(this.hapChar.Hue).onSet(async (value) => {
86
+ await this.internalColourUpdate(value)
87
+ })
88
+ this.cacheHue = this.lightService.getCharacteristic(this.hapChar.Hue).value
89
+ this.cacheSat = this.lightService.getCharacteristic(this.hapChar.Saturation).value
90
+
91
+ // Output the customised options to the log
92
+ const opts = JSON.stringify({})
93
+ platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
94
+ }
95
+
96
+ async internalStateUpdate(value) {
97
+ try {
98
+ const newValue = value ? 'on' : 'off'
99
+
100
+ // Don't continue if the new value is the same as before
101
+ if (this.cacheState === newValue) {
102
+ return
103
+ }
104
+
105
+ // Send the request to the platform sender function
106
+ await this.platform.sendDeviceUpdate(this.accessory, {
107
+ cmd: 'stateHumi',
108
+ value: value ? 1 : 0,
109
+ })
110
+
111
+ // Cache the new state and log if appropriate
112
+ if (this.cacheState !== newValue) {
113
+ this.cacheState = newValue
114
+ this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
115
+ }
116
+
117
+ // Also turn the light off if turning off
118
+ if (!value && this.cacheLightState === 'on') {
119
+ this.lightService.updateCharacteristic(this.hapChar.On, false)
120
+ this.accessory.log(`current light state [${this.cacheLightState}]`)
121
+ }
122
+ } catch (err) {
123
+ // Catch any errors during the process
124
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
125
+
126
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
127
+ setTimeout(() => {
128
+ this.service.updateCharacteristic(this.hapChar.On, this.cacheState === 'on')
129
+ }, 2000)
130
+ throw new this.hapErr(-70402)
131
+ }
132
+ }
133
+
134
+ async internalSpeedUpdate(value) {
135
+ try {
136
+ // Don't continue if the speed is 0
137
+ if (value === 0) {
138
+ return
139
+ }
140
+
141
+ // Get the single Govee value {1, 2, ..., 8}
142
+ const newValue = this.speed2Value(value)
143
+
144
+ // Don't continue if the speed value won't have effect
145
+ if (newValue * 10 === this.cacheSpeed) {
146
+ return
147
+ }
148
+
149
+ // Get the scene code for this value
150
+ const newCode = this.value2Code[newValue]
151
+
152
+ this.accessory.log(newCode)
153
+
154
+ // Send the request to the platform sender function
155
+ await this.platform.sendDeviceUpdate(this.accessory, {
156
+ cmd: 'ptReal',
157
+ value: newCode,
158
+ })
159
+
160
+ // Cache the new state and log if appropriate
161
+ this.cacheSpeed = newValue * 10
162
+ this.accessory.log(`${platformLang.curSpeed} [${newValue}]`)
163
+ } catch (err) {
164
+ // Catch any errors during the process
165
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
166
+
167
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
168
+ setTimeout(() => {
169
+ this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
170
+ }, 2000)
171
+ throw new this.hapErr(-70402)
172
+ }
173
+ }
174
+
175
+ async internalLightStateUpdate(value) {
176
+ try {
177
+ const newValue = value ? 'on' : 'off'
178
+
179
+ // Don't continue if the new value is the same as before
180
+ if (this.cacheLightState === newValue) {
181
+ return
182
+ }
183
+
184
+ // Generate the hex values for the code
185
+ let hexValues
186
+ if (value) {
187
+ // Calculate current RGB values
188
+ const newRGB = hs2rgb(
189
+ this.lightService.getCharacteristic(this.hapChar.Hue).value,
190
+ this.lightService.getCharacteristic(this.hapChar.Saturation).value,
191
+ )
192
+ hexValues = [0x33, 0x1B, 0x01, this.cacheBright, ...newRGB]
193
+ } else {
194
+ hexValues = [0x33, 0x1B, 0x00]
195
+ }
196
+
197
+ // Send the request to the platform sender function
198
+ await this.platform.sendDeviceUpdate(this.accessory, {
199
+ cmd: 'ptReal',
200
+ value: generateCodeFromHexValues(hexValues),
201
+ })
202
+
203
+ // Cache the new state and log if appropriate
204
+ if (this.cacheLightState !== newValue) {
205
+ this.cacheLightState = newValue
206
+ this.accessory.log(`${platformLang.curLight} [${newValue}]`)
207
+ }
208
+ } catch (err) {
209
+ // Catch any errors during the process
210
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
211
+
212
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
213
+ setTimeout(() => {
214
+ this.lightService.updateCharacteristic(this.hapChar.On, this.cacheLightState === 'on')
215
+ }, 2000)
216
+ throw new this.hapErr(-70402)
217
+ }
218
+ }
219
+
220
+ async internalBrightnessUpdate(value) {
221
+ try {
222
+ // This acts like a debounce function when endlessly sliding the brightness scale
223
+ const updateKeyBright = generateRandomString(5)
224
+ this.updateKeyBright = updateKeyBright
225
+ await sleep(350)
226
+ if (updateKeyBright !== this.updateKeyBright) {
227
+ return
228
+ }
229
+
230
+ // Don't continue if the new value is the same as before
231
+ if (value === this.cacheBright || value === 0) {
232
+ return
233
+ }
234
+
235
+ // Generate the hex values for the code
236
+ const newRGB = hs2rgb(
237
+ this.lightService.getCharacteristic(this.hapChar.Hue).value,
238
+ this.lightService.getCharacteristic(this.hapChar.Saturation).value,
239
+ )
240
+ const hexValues = [0x33, 0x1B, 0x01, value, ...newRGB]
241
+
242
+ // Send the request to the platform sender function
243
+ await this.platform.sendDeviceUpdate(this.accessory, {
244
+ cmd: 'ptReal',
245
+ value: generateCodeFromHexValues(hexValues),
246
+ })
247
+
248
+ // Govee considers 0% brightness to be off
249
+ if (value === 0) {
250
+ setTimeout(() => {
251
+ this.cacheLightState = 'off'
252
+ if (this.lightService.getCharacteristic(this.hapChar.On).value) {
253
+ this.lightService.updateCharacteristic(this.hapChar.On, false)
254
+ this.accessory.log(`${platformLang.curLight} [${this.cacheLightState}]`)
255
+ }
256
+ this.lightService.updateCharacteristic(this.hapChar.Brightness, this.cacheBright)
257
+ }, 1500)
258
+ return
259
+ }
260
+
261
+ // Cache the new state and log if appropriate
262
+ if (this.cacheBright !== value) {
263
+ this.cacheBright = value
264
+ this.accessory.log(`${platformLang.curBright} [${value}%]`)
265
+ }
266
+ } catch (err) {
267
+ // Catch any errors during the process
268
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
269
+
270
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
271
+ setTimeout(() => {
272
+ this.lightService.updateCharacteristic(this.hapChar.Brightness, this.cacheBright)
273
+ }, 2000)
274
+ throw new this.hapErr(-70402)
275
+ }
276
+ }
277
+
278
+ async internalColourUpdate(value) {
279
+ try {
280
+ // This acts like a debounce function when endlessly sliding the colour wheel
281
+ const updateKeyColour = generateRandomString(5)
282
+ this.updateKeyColour = updateKeyColour
283
+ await sleep(300)
284
+ if (updateKeyColour !== this.updateKeyColour) {
285
+ return
286
+ }
287
+
288
+ // Don't continue if the new value is the same as before
289
+ if (value === this.cacheHue) {
290
+ return
291
+ }
292
+
293
+ // Generate the hex values for the code
294
+ const newRGB = hs2rgb(
295
+ value,
296
+ this.lightService.getCharacteristic(this.hapChar.Saturation).value,
297
+ )
298
+ const hexValues = [0x33, 0x1B, 0x01, this.cacheBright, ...newRGB]
299
+
300
+ // Send the request to the platform sender function
301
+ await this.platform.sendDeviceUpdate(this.accessory, {
302
+ cmd: 'ptReal',
303
+ value: generateCodeFromHexValues(hexValues),
304
+ })
305
+
306
+ // Cache the new state and log if appropriate
307
+ if (this.cacheHue !== value) {
308
+ this.cacheHue = value
309
+ this.accessory.log(`${platformLang.curColour} [rgb ${newRGB.join(' ')}]`)
310
+ }
311
+ } catch (err) {
312
+ // Catch any errors during the process
313
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
314
+
315
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
316
+ setTimeout(() => {
317
+ this.lightService.updateCharacteristic(this.hapChar.Hue, this.cacheHue)
318
+ }, 2000)
319
+ throw new this.hapErr(-70402)
320
+ }
321
+ }
322
+
323
+ externalUpdate(params) {
324
+ // Check for an ON/OFF change
325
+ if (params.state && params.state !== this.cacheState) {
326
+ this.cacheState = params.state
327
+ this.service.updateCharacteristic(this.hapChar.On, this.cacheState === 'on')
328
+
329
+ // Log the change
330
+ this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
331
+ }
332
+
333
+ // Check for some other scene/mode change
334
+ (params.commands || []).forEach((command) => {
335
+ const hexString = base64ToHex(command)
336
+ const hexParts = hexToTwoItems(hexString)
337
+
338
+ // Return now if not a device query update code
339
+ if (getTwoItemPosition(hexParts, 1) !== 'aa') {
340
+ return
341
+ }
342
+
343
+ const deviceFunction = `${getTwoItemPosition(hexParts, 2)}${getTwoItemPosition(hexParts, 3)}`
344
+
345
+ switch (deviceFunction) {
346
+ case '0500': { // mode
347
+ // Mode
348
+ const newModeRaw = getTwoItemPosition(hexParts, 4)
349
+ let newMode
350
+ switch (newModeRaw) {
351
+ case '01': {
352
+ // Manual
353
+ newMode = 'manual'
354
+ break
355
+ }
356
+ case '02': {
357
+ // Custom
358
+ newMode = 'custom'
359
+ break
360
+ }
361
+ case '03': {
362
+ // Auto
363
+ newMode = 'auto'
364
+ break
365
+ }
366
+ default:
367
+ return
368
+ }
369
+ if (this.cacheMode !== newMode) {
370
+ this.cacheMode = newMode
371
+ this.accessory.log(`${platformLang.curMode} [${this.cacheMode}]`)
372
+ }
373
+ break
374
+ }
375
+ case '0501': {
376
+ // Manual speed
377
+ const newSpeedRaw = getTwoItemPosition(hexParts, 4)
378
+ if (newSpeedRaw !== this.cacheSpeedRaw) {
379
+ this.cacheSpeedRaw = newSpeedRaw
380
+ this.cacheSpeed = Number.parseInt(newSpeedRaw, 10) * 10
381
+ this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
382
+ this.accessory.log(`${platformLang.curSpeed} [${this.cacheSpeed}]`)
383
+ }
384
+ break
385
+ }
386
+ case '1800': // display off
387
+ case '1801': { // display on
388
+ const newDisplay = deviceFunction === '1801' ? 'on' : 'off'
389
+ if (newDisplay !== this.cacheDisplay) {
390
+ this.cacheDisplay = newDisplay
391
+ this.accessory.log(`${platformLang.curDisplay} [${this.cacheDisplay ? 'on' : 'off'}]`)
392
+ }
393
+ break
394
+ }
395
+ case '1b00': // night light off
396
+ case '1b01': { // night light on
397
+ const newNight = deviceFunction === '1b01' ? 'on' : 'off'
398
+ if (newNight !== this.cacheLightState) {
399
+ this.cacheLightState = newNight
400
+ this.lightService.updateCharacteristic(this.hapChar.On, this.cacheLightState === 'on')
401
+ this.accessory.log(`current night light state [${this.cacheLightState}]`)
402
+ }
403
+
404
+ // Brightness and colour
405
+ if (this.cacheLightState === 'on') {
406
+ const newBrightHex = getTwoItemPosition(hexParts, 4)
407
+ const newBrightDec = Math.round(hexToDecimal(newBrightHex))
408
+ if (newBrightDec !== this.cacheBright) {
409
+ this.cacheBright = newBrightDec
410
+ this.lightService.updateCharacteristic(this.hapChar.Brightness, this.cacheBright)
411
+ this.accessory.log(`${platformLang.curBright} [${this.cacheBright}%]`)
412
+ }
413
+
414
+ const newR = hexToDecimal(getTwoItemPosition(hexParts, 5))
415
+ const newG = hexToDecimal(getTwoItemPosition(hexParts, 6))
416
+ const newB = hexToDecimal(getTwoItemPosition(hexParts, 7))
417
+ const hs = rgb2hs(newR, newG, newB)
418
+
419
+ // Check for a colour change
420
+ if (hs[0] !== this.cacheHue) {
421
+ // Colour is different so update Homebridge with new values
422
+ this.lightService.updateCharacteristic(this.hapChar.Hue, hs[0])
423
+ this.lightService.updateCharacteristic(this.hapChar.Saturation, hs[1]);
424
+ [this.cacheHue] = hs
425
+
426
+ // Log the change
427
+ this.accessory.log(`${platformLang.curColour} [rgb ${newR} ${newG} ${newB}]`)
428
+ }
429
+ }
430
+ break
431
+ }
432
+ case '0502': // custom mode
433
+ case '0503': // auto mode
434
+ case '1100': // timer
435
+ case '1101': // timer
436
+ case '1300': // scheduling
437
+ case '1500': { // scheduling
438
+ break
439
+ }
440
+ default:
441
+ this.accessory.logDebugWarn(`${platformLang.newScene}: [${command}] [${hexString}]`)
442
+ break
443
+ }
444
+ })
445
+ }
446
+ }
@@ -0,0 +1,46 @@
1
+ import { base64ToHex, getTwoItemPosition, hexToTwoItems } from '../utils/functions.js'
2
+ import platformLang from '../utils/lang-en.js'
3
+
4
+ export default class {
5
+ constructor(platform, accessory) {
6
+ // Set up variables from the platform
7
+ this.hapChar = platform.api.hap.Characteristic
8
+ this.hapErr = platform.api.hap.HapStatusError
9
+ this.hapServ = platform.api.hap.Service
10
+ this.platform = platform
11
+
12
+ // Set up variables from the accessory
13
+ this.accessory = accessory
14
+
15
+ // Output the customised options to the log
16
+ const opts = JSON.stringify({})
17
+ platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
18
+ }
19
+
20
+ externalUpdate(params) {
21
+ // Update the active characteristic
22
+ if (params.state && params.state !== this.cacheState) {
23
+ this.cacheState = params.state
24
+ this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
25
+ }
26
+
27
+ // Check for some other scene/mode change
28
+ (params.commands || []).forEach((command) => {
29
+ const hexString = base64ToHex(command)
30
+ const hexParts = hexToTwoItems(hexString)
31
+
32
+ // Return now if not a device query update code
33
+ if (getTwoItemPosition(hexParts, 1) !== 'aa') {
34
+ return
35
+ }
36
+
37
+ const deviceFunction = `${getTwoItemPosition(hexParts, 2)}${getTwoItemPosition(hexParts, 3)}`
38
+
39
+ switch (deviceFunction) {
40
+ default:
41
+ this.accessory.logDebugWarn(`${platformLang.newScene}: [${command}] [${hexString}]`)
42
+ break
43
+ }
44
+ })
45
+ }
46
+ }