@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,274 @@
1
+ import {
2
+ base64ToHex,
3
+ generateCodeFromHexValues,
4
+ getTwoItemPosition,
5
+ hexToTwoItems,
6
+ parseError,
7
+ } from '../utils/functions.js'
8
+ import platformLang from '../utils/lang-en.js'
9
+
10
+ export default class {
11
+ constructor(platform, accessory) {
12
+ // Set up variables from the platform
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
+ // Codes etc
22
+ this.speedCodes = {
23
+ 11: 'MwUBAQAAAAAAAAAAAAAAAAAAADY=',
24
+ 22: 'MwUBAgAAAAAAAAAAAAAAAAAAADU=',
25
+ 33: 'MwUBAwAAAAAAAAAAAAAAAAAAADQ=',
26
+ 44: 'MwUBBAAAAAAAAAAAAAAAAAAAADM=',
27
+ 55: 'MwUBBQAAAAAAAAAAAAAAAAAAADI=',
28
+ 66: 'MwUBBgAAAAAAAAAAAAAAAAAAADE=',
29
+ 77: 'MwUBBwAAAAAAAAAAAAAAAAAAADA=',
30
+ 88: 'MwUBCAAAAAAAAAAAAAAAAAAAAD8=',
31
+ }
32
+
33
+ // Remove any old original Fan services
34
+ if (this.accessory.getService(this.hapServ.Fan)) {
35
+ this.accessory.removeService(this.accessory.getService(this.hapServ.Fan))
36
+ }
37
+
38
+ // Add the fan service for the fan if it doesn't already exist
39
+ this.service = this.accessory.getService(this.hapServ.Fanv2) || this.accessory.addService(this.hapServ.Fanv2)
40
+
41
+ // Add the set handler to the fan on/off characteristic
42
+ this.service
43
+ .getCharacteristic(this.hapChar.Active)
44
+ .onSet(async value => this.internalStateUpdate(value))
45
+ this.cacheState = this.service.getCharacteristic(this.hapChar.Active).value ? 'on' : 'off'
46
+
47
+ // Add the set handler to the fan rotation speed characteristic
48
+ this.service
49
+ .getCharacteristic(this.hapChar.RotationSpeed)
50
+ .setProps({
51
+ minStep: 11,
52
+ minValue: 0,
53
+ validValues: [0, 11, 22, 33, 44, 55, 66, 77, 88, 99],
54
+ })
55
+ .onSet(async value => this.internalSpeedUpdate(value))
56
+ this.cacheSpeed = this.service.getCharacteristic(this.hapChar.RotationSpeed).value
57
+ this.cacheMode = this.cacheSpeed === 99 ? 'auto' : 'manual'
58
+
59
+ // Add the set handler to the fan swing mode
60
+ this.service
61
+ .getCharacteristic(this.hapChar.SwingMode)
62
+ .onSet(async value => this.internalSwingUpdate(value))
63
+ this.cacheSwing = this.service.getCharacteristic(this.hapChar.SwingMode).value === 1 ? 'on' : 'off'
64
+
65
+ // Output the customised options to the log
66
+ const opts = JSON.stringify({})
67
+ platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
68
+ }
69
+
70
+ async internalStateUpdate(value) {
71
+ try {
72
+ const newValue = value ? 'on' : 'off'
73
+
74
+ // Don't continue if the new value is the same as before
75
+ if (this.cacheState === newValue) {
76
+ return
77
+ }
78
+
79
+ // Send the request to the platform sender function
80
+ await this.platform.sendDeviceUpdate(this.accessory, {
81
+ cmd: 'ptReal',
82
+ value: value ? 'MwEBAAAAAAAAAAAAAAAAAAAAADM=' : 'MwEAAAAAAAAAAAAAAAAAAAAAADI=',
83
+ })
84
+
85
+ // Cache the new state and log if appropriate
86
+ if (this.cacheState !== newValue) {
87
+ this.cacheState = newValue
88
+ this.accessory.log(`${platformLang.curState} [${newValue}]`)
89
+ }
90
+ } catch (err) {
91
+ // Catch any errors during the process
92
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
93
+
94
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
95
+ setTimeout(() => {
96
+ this.service.updateCharacteristic(this.hapChar.Active, this.cacheState === 'on' ? 1 : 0)
97
+ }, 2000)
98
+ throw new this.hapErr(-70402)
99
+ }
100
+ }
101
+
102
+ async internalSpeedUpdate(value) {
103
+ try {
104
+ // Don't continue if the value is lower than 11
105
+ if (value < 11) {
106
+ return
107
+ }
108
+ let newMode = value === 99 ? 'auto' : 'manual'
109
+
110
+ // Don't continue if the new value is the same as before
111
+ if (this.cacheSpeed === value) {
112
+ return
113
+ }
114
+
115
+ // Don't continue if trying to access auto mode but there is no sensor attached
116
+ let codeToSend
117
+ if (newMode === 'auto') {
118
+ if (!this.accessory.context.sensorAttached || !this.cacheAutoCode) {
119
+ this.accessory.logWarn('auto mode not supported without a linked sensor')
120
+ codeToSend = this.speedCodes[88]
121
+ newMode = 'manual'
122
+ value = 88
123
+ } else {
124
+ codeToSend = this.cacheAutoCode
125
+ }
126
+ } else {
127
+ codeToSend = this.speedCodes[value]
128
+ }
129
+
130
+ await this.platform.sendDeviceUpdate(this.accessory, {
131
+ cmd: 'ptReal',
132
+ value: codeToSend,
133
+ })
134
+
135
+ // Cache the new state and log if appropriate
136
+ if (this.cacheMode !== newMode) {
137
+ this.cacheMode = newMode
138
+ this.accessory.log(`${platformLang.curMode} [${this.cacheMode}]`)
139
+ }
140
+ if (this.cacheSpeed !== value) {
141
+ this.cacheSpeed = value
142
+ this.accessory.log(`${platformLang.curSpeed} [${value}%]`)
143
+ }
144
+ } catch (err) {
145
+ // Catch any errors during the process
146
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
147
+
148
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
149
+ setTimeout(() => {
150
+ this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
151
+ }, 2000)
152
+ throw new this.hapErr(-70402)
153
+ }
154
+ }
155
+
156
+ async internalSwingUpdate(value) {
157
+ try {
158
+ const newValue = value ? 'on' : 'off'
159
+ // Don't continue if the new value is the same as before
160
+ if (this.cacheSwing === value) {
161
+ return
162
+ }
163
+
164
+ await this.platform.sendDeviceUpdate(this.accessory, {
165
+ cmd: 'ptReal',
166
+ value: value ? 'Mx8BAQAAAAAAAAAAAAAAAAAAACw=' : 'Mx8BAAAAAAAAAAAAAAAAAAAAAC0=',
167
+ })
168
+
169
+ // Cache the new state and log if appropriate
170
+ if (this.cacheSwing !== newValue) {
171
+ this.cacheSwing = newValue
172
+ this.accessory.log(`${platformLang.curSwing} [${newValue}]`)
173
+ }
174
+ } catch (err) {
175
+ // Catch any errors during the process
176
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
177
+
178
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
179
+ setTimeout(() => {
180
+ this.service.updateCharacteristic(this.hapChar.SwingMode, this.cacheSwing === 'on' ? 1 : 0)
181
+ }, 2000)
182
+ throw new this.hapErr(-70402)
183
+ }
184
+ }
185
+
186
+ externalUpdate(params) {
187
+ // Update the active characteristic
188
+ if (params.state && params.state !== this.cacheState) {
189
+ this.cacheState = params.state
190
+ this.service.updateCharacteristic(this.hapChar.Active, this.cacheState === 'on' ? 1 : 0)
191
+ this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
192
+ }
193
+
194
+ // Check for some other scene/mode change
195
+ (params.commands || []).forEach((command) => {
196
+ const hexString = base64ToHex(command)
197
+ const hexParts = hexToTwoItems(hexString)
198
+
199
+ // Return now if not a device query update code
200
+ if (getTwoItemPosition(hexParts, 1) !== 'aa') {
201
+ return
202
+ }
203
+
204
+ if (getTwoItemPosition(hexParts, 2) === '08') {
205
+ // Sensor Attached?
206
+ const dev = hexString.substring(4, hexString.length - 24)
207
+ this.accessory.context.sensorAttached = dev !== '000000000000'
208
+ return
209
+ }
210
+
211
+ const deviceFunction = `${getTwoItemPosition(hexParts, 2)}${getTwoItemPosition(hexParts, 3)}`
212
+
213
+ switch (deviceFunction) {
214
+ case '0501': {
215
+ // Fan speed
216
+ const newSpeed = getTwoItemPosition(hexParts, 4)
217
+ const newSpeedInt = Number.parseInt(newSpeed, 10) * 11
218
+ const newMode = 'manual'
219
+ if (this.cacheMode !== newMode) {
220
+ this.cacheMode = newMode
221
+ this.accessory.log(`${platformLang.curMode} [${this.cacheMode}]`)
222
+ }
223
+ if (this.cacheSpeed !== newSpeedInt) {
224
+ this.cacheSpeed = newSpeedInt
225
+ this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
226
+ this.accessory.log(`${platformLang.curSpeed} [${this.cacheSpeed}%]`)
227
+ }
228
+ break
229
+ }
230
+ case '0500': {
231
+ // Auto mode on/off
232
+ // Maybe this relates to
233
+ // (Guess) Fixed Speed: 1
234
+ // Custom: 2
235
+ // Auto: 3
236
+ // Sleep: 5
237
+ // Nature: 6
238
+ // Turbo: 7
239
+ const newMode = getTwoItemPosition(hexParts, 4) === '03' ? 'auto' : 'manual'
240
+ if (this.cacheMode !== newMode) {
241
+ this.cacheMode = newMode
242
+ this.accessory.log(`${platformLang.curMode} [${this.cacheMode}]`)
243
+
244
+ if (this.cacheMode === 'auto' && this.cacheSpeed !== 99) {
245
+ this.cacheSpeed = 99
246
+ this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
247
+ this.accessory.log(`${platformLang.curSpeed} [${this.cacheSpeed}%]`)
248
+ }
249
+ }
250
+ break
251
+ }
252
+ case '0503': {
253
+ // Auto mode, we need to keep this code to send it back to the device
254
+ const code = hexToTwoItems(`33${hexString.substring(2, hexString.length - 2)}`)
255
+ this.cacheAutoCode = generateCodeFromHexValues(code.map(p => Number.parseInt(p, 16)))
256
+ break
257
+ }
258
+ case '1f01': {
259
+ // Swing Mode
260
+ const newSwing = getTwoItemPosition(hexParts, 4) === '01' ? 'on' : 'off'
261
+ if (this.cacheSwing !== newSwing) {
262
+ this.cacheSwing = newSwing
263
+ this.service.updateCharacteristic(this.hapChar.SwingMode, this.cacheSwing === 'on' ? 1 : 0)
264
+ this.accessory.log(`${platformLang.curSwing} [${this.cacheSwing}]`)
265
+ }
266
+ break
267
+ }
268
+ default:
269
+ this.accessory.logDebugWarn(`${platformLang.newScene}: [${command}] [${hexString}]`)
270
+ break
271
+ }
272
+ })
273
+ }
274
+ }
@@ -0,0 +1,335 @@
1
+ import {
2
+ base64ToHex,
3
+ generateCodeFromHexValues,
4
+ getTwoItemPosition,
5
+ hexToTwoItems,
6
+ parseError,
7
+ } from '../utils/functions.js'
8
+ import platformLang from '../utils/lang-en.js'
9
+
10
+ /*
11
+
12
+ H7111
13
+ {
14
+ "mode": {
15
+ "options": [
16
+ {
17
+ "name": "Custom",
18
+ "value": 2
19
+ },
20
+ {
21
+ "name": "Auto",
22
+ "value": 3
23
+ },
24
+ {
25
+ "name": "Sleep",
26
+ "value": 5
27
+ },
28
+ {
29
+ "name": "Nature",
30
+ "value": 6
31
+ },
32
+ {
33
+ "name": "Turbo",
34
+ "value": 7
35
+ }
36
+ ]
37
+ },
38
+ "gear": {
39
+ "options": [
40
+ {
41
+ "name": "gear",
42
+ "value": [
43
+ 1,
44
+ 2,
45
+ 3,
46
+ 4,
47
+ 5,
48
+ 6,
49
+ 7,
50
+ 8
51
+ ]
52
+ }
53
+ ]
54
+ }
55
+ }
56
+
57
+ // NOTES
58
+ H7111 -> Sleep, Nature, Custom, Auto
59
+
60
+ sleep
61
+ [31/10/2022, 20:52:08] [Govee] [Device1] [AWS] receiving update {"source":"AWS","result":1,"commands":["qgUFAAAAAAAAAAAAAAAAAAAAAKo=","qgUABQAAAAAAAAAAAAAAAAAAAKo="]}.
62
+ [31/10/2022, 20:52:08] [Govee] [Device1] new scene code: [qgUFAAAAAAAAAAAAAAAAAAAAAKo=] [aa050500000000000000000000000000000000aa].
63
+ [31/10/2022, 20:52:08] [Govee] [Device1] new scene code: [qgUABQAAAAAAAAAAAAAAAAAAAKo=] [aa050005000000000000000000000000000000aa].
64
+
65
+ nature
66
+ [31/10/2022, 20:52:43] [Govee] [Device1] [AWS] receiving update {"source":"AWS","result":1,"commands":["qgUGAAAAAAAAAAAAAAAAAAAAAKk=","qgUABgAAAAAAAAAAAAAAAAAAAKk="]}.
67
+ [31/10/2022, 20:52:43] [Govee] [Device1] new scene code: [qgUGAAAAAAAAAAAAAAAAAAAAAKk=] [aa050600000000000000000000000000000000a9].
68
+ [31/10/2022, 20:52:43] [Govee] [Device1] new scene code: [qgUABgAAAAAAAAAAAAAAAAAAAKk=] [aa050006000000000000000000000000000000a9].
69
+ */
70
+
71
+ export default class {
72
+ constructor(platform, accessory) {
73
+ // Set up variables from the platform
74
+ this.hapChar = platform.api.hap.Characteristic
75
+ this.hapErr = platform.api.hap.HapStatusError
76
+ this.hapServ = platform.api.hap.Service
77
+ this.platform = platform
78
+
79
+ // Set up variables from the accessory
80
+ this.accessory = accessory
81
+
82
+ // Codes etc
83
+ this.speedCodes = {
84
+ 11: 'MwUBAQAAAAAAAAAAAAAAAAAAADY=',
85
+ 22: 'MwUBAgAAAAAAAAAAAAAAAAAAADU=',
86
+ 33: 'MwUBAwAAAAAAAAAAAAAAAAAAADQ=',
87
+ 44: 'MwUBBAAAAAAAAAAAAAAAAAAAADM=',
88
+ 55: 'MwUBBQAAAAAAAAAAAAAAAAAAADI=',
89
+ 66: 'MwUBBgAAAAAAAAAAAAAAAAAAADE=',
90
+ 77: 'MwUBBwAAAAAAAAAAAAAAAAAAADA=',
91
+ 88: 'MwUBCAAAAAAAAAAAAAAAAAAAAD8=',
92
+ }
93
+
94
+ // Remove any old original Fan services
95
+ if (this.accessory.getService(this.hapServ.Fan)) {
96
+ this.accessory.removeService(this.accessory.getService(this.hapServ.Fan))
97
+ }
98
+
99
+ // Add the fan service for the fan if it doesn't already exist
100
+ this.service = this.accessory.getService(this.hapServ.Fanv2) || this.accessory.addService(this.hapServ.Fanv2)
101
+
102
+ // Add the set handler to the fan on/off characteristic
103
+ this.service
104
+ .getCharacteristic(this.hapChar.Active)
105
+ .onSet(async value => this.internalStateUpdate(value))
106
+ this.cacheState = this.service.getCharacteristic(this.hapChar.Active).value ? 'on' : 'off'
107
+
108
+ // Add the set handler to the fan rotation speed characteristic
109
+ this.service
110
+ .getCharacteristic(this.hapChar.RotationSpeed)
111
+ .setProps({
112
+ minStep: 11,
113
+ minValue: 0,
114
+ validValues: [0, 11, 22, 33, 44, 55, 66, 77, 88, 99],
115
+ })
116
+ .onSet(async value => this.internalSpeedUpdate(value))
117
+ this.cacheSpeed = this.service.getCharacteristic(this.hapChar.RotationSpeed).value
118
+ this.cacheMode = this.cacheSpeed === 99 ? 'auto' : 'manual'
119
+
120
+ // Add the set handler to the fan swing mode
121
+ this.service
122
+ .getCharacteristic(this.hapChar.SwingMode)
123
+ .onSet(async value => this.internalSwingUpdate(value))
124
+ this.cacheSwing = this.service.getCharacteristic(this.hapChar.SwingMode).value === 1 ? 'on' : 'off'
125
+
126
+ // Output the customised options to the log
127
+ const opts = JSON.stringify({})
128
+ platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
129
+ }
130
+
131
+ async internalStateUpdate(value) {
132
+ try {
133
+ const newValue = value ? 'on' : 'off'
134
+
135
+ // Don't continue if the new value is the same as before
136
+ if (this.cacheState === newValue) {
137
+ return
138
+ }
139
+
140
+ // Send the request to the platform sender function
141
+ await this.platform.sendDeviceUpdate(this.accessory, {
142
+ cmd: 'ptReal',
143
+ value: value ? 'MwEBAAAAAAAAAAAAAAAAAAAAADM=' : 'MwEAAAAAAAAAAAAAAAAAAAAAADI=',
144
+ })
145
+
146
+ // Cache the new state and log if appropriate
147
+ if (this.cacheState !== newValue) {
148
+ this.cacheState = newValue
149
+ this.accessory.log(`${platformLang.curState} [${newValue}]`)
150
+ }
151
+ } catch (err) {
152
+ // Catch any errors during the process
153
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
154
+
155
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
156
+ setTimeout(() => {
157
+ this.service.updateCharacteristic(this.hapChar.Active, this.cacheState === 'on' ? 1 : 0)
158
+ }, 2000)
159
+ throw new this.hapErr(-70402)
160
+ }
161
+ }
162
+
163
+ async internalSpeedUpdate(value) {
164
+ try {
165
+ // Don't continue if the value is lower than 11
166
+ if (value < 11) {
167
+ return
168
+ }
169
+ let newMode = value === 99 ? 'auto' : 'manual'
170
+
171
+ // Don't continue if the new value is the same as before
172
+ if (this.cacheSpeed === value) {
173
+ return
174
+ }
175
+
176
+ // Don't continue if trying to access auto mode but there is no sensor attached
177
+ let codeToSend
178
+ if (newMode === 'auto') {
179
+ if (!this.accessory.context.sensorAttached || !this.cacheAutoCode) {
180
+ this.accessory.logWarn('auto mode not supported without a linked sensor')
181
+ codeToSend = this.speedCodes[88]
182
+ newMode = 'manual'
183
+ value = 88
184
+ } else {
185
+ codeToSend = this.cacheAutoCode
186
+ }
187
+ } else {
188
+ codeToSend = this.speedCodes[value]
189
+ }
190
+
191
+ await this.platform.sendDeviceUpdate(this.accessory, {
192
+ cmd: 'ptReal',
193
+ value: codeToSend,
194
+ })
195
+
196
+ // Cache the new state and log if appropriate
197
+ if (this.cacheMode !== newMode) {
198
+ this.cacheMode = newMode
199
+ this.accessory.log(`${platformLang.curMode} [${this.cacheMode}]`)
200
+ }
201
+ if (this.cacheSpeed !== value) {
202
+ this.cacheSpeed = value
203
+ this.accessory.log(`${platformLang.curSpeed} [${value}%]`)
204
+ }
205
+ } catch (err) {
206
+ // Catch any errors during the process
207
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
208
+
209
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
210
+ setTimeout(() => {
211
+ this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
212
+ }, 2000)
213
+ throw new this.hapErr(-70402)
214
+ }
215
+ }
216
+
217
+ async internalSwingUpdate(value) {
218
+ try {
219
+ const newValue = value ? 'on' : 'off'
220
+ // Don't continue if the new value is the same as before
221
+ if (this.cacheSwing === value) {
222
+ return
223
+ }
224
+
225
+ await this.platform.sendDeviceUpdate(this.accessory, {
226
+ cmd: 'ptReal',
227
+ value: value ? 'Mx8BAQAAAAAAAAAAAAAAAAAAACw=' : 'Mx8BAAAAAAAAAAAAAAAAAAAAAC0=',
228
+ })
229
+
230
+ // Cache the new state and log if appropriate
231
+ if (this.cacheSwing !== newValue) {
232
+ this.cacheSwing = newValue
233
+ this.accessory.log(`${platformLang.curSwing} [${newValue}]`)
234
+ }
235
+ } catch (err) {
236
+ // Catch any errors during the process
237
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
238
+
239
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
240
+ setTimeout(() => {
241
+ this.service.updateCharacteristic(this.hapChar.SwingMode, this.cacheSwing === 'on' ? 1 : 0)
242
+ }, 2000)
243
+ throw new this.hapErr(-70402)
244
+ }
245
+ }
246
+
247
+ externalUpdate(params) {
248
+ // Update the active characteristic
249
+ if (params.state && params.state !== this.cacheState) {
250
+ this.cacheState = params.state
251
+ this.service.updateCharacteristic(this.hapChar.Active, this.cacheState === 'on' ? 1 : 0)
252
+ this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
253
+ }
254
+
255
+ // Check for some other scene/mode change
256
+ (params.commands || []).forEach((command) => {
257
+ const hexString = base64ToHex(command)
258
+ const hexParts = hexToTwoItems(hexString)
259
+
260
+ // Return now if not a device query update code
261
+ if (getTwoItemPosition(hexParts, 1) !== 'aa') {
262
+ return
263
+ }
264
+
265
+ if (getTwoItemPosition(hexParts, 2) === '08') {
266
+ // Sensor Attached?
267
+ const dev = hexString.substring(4, hexString.length - 24)
268
+ this.accessory.context.sensorAttached = dev !== '000000000000'
269
+ return
270
+ }
271
+
272
+ const deviceFunction = `${getTwoItemPosition(hexParts, 2)}${getTwoItemPosition(hexParts, 3)}`
273
+
274
+ switch (deviceFunction) {
275
+ case '0501': {
276
+ // Fan speed
277
+ const newSpeed = getTwoItemPosition(hexParts, 4)
278
+ const newSpeedInt = Number.parseInt(newSpeed, 10) * 11
279
+ const newMode = 'manual'
280
+ if (this.cacheMode !== newMode) {
281
+ this.cacheMode = newMode
282
+ this.accessory.log(`${platformLang.curMode} [${this.cacheMode}]`)
283
+ }
284
+ if (this.cacheSpeed !== newSpeedInt) {
285
+ this.cacheSpeed = newSpeedInt
286
+ this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
287
+ this.accessory.log(`${platformLang.curSpeed} [${this.cacheSpeed}%]`)
288
+ }
289
+ break
290
+ }
291
+ case '0500': {
292
+ // Auto mode on/off
293
+ // Maybe this relates to
294
+ // (Guess) Fixed Speed: 1
295
+ // Custom: 2
296
+ // Auto: 3
297
+ // Sleep: 5
298
+ // Nature: 6
299
+ // Turbo: 7
300
+ const newMode = getTwoItemPosition(hexParts, 4) === '03' ? 'auto' : 'manual'
301
+ if (this.cacheMode !== newMode) {
302
+ this.cacheMode = newMode
303
+ this.accessory.log(`${platformLang.curMode} [${this.cacheMode}]`)
304
+
305
+ if (this.cacheMode === 'auto' && this.cacheSpeed !== 99) {
306
+ this.cacheSpeed = 99
307
+ this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
308
+ this.accessory.log(`${platformLang.curSpeed} [${this.cacheSpeed}%]`)
309
+ }
310
+ }
311
+ break
312
+ }
313
+ case '0503': {
314
+ // Auto mode, we need to keep this code to send it back to the device
315
+ const code = hexToTwoItems(`33${hexString.substring(2, hexString.length - 2)}`)
316
+ this.cacheAutoCode = generateCodeFromHexValues(code.map(p => Number.parseInt(p, 16)))
317
+ break
318
+ }
319
+ case '1f01': {
320
+ // Swing Mode
321
+ const newSwing = getTwoItemPosition(hexParts, 4) === '01' ? 'on' : 'off'
322
+ if (this.cacheSwing !== newSwing) {
323
+ this.cacheSwing = newSwing
324
+ this.service.updateCharacteristic(this.hapChar.SwingMode, this.cacheSwing === 'on' ? 1 : 0)
325
+ this.accessory.log(`${platformLang.curSwing} [${this.cacheSwing}]`)
326
+ }
327
+ break
328
+ }
329
+ default:
330
+ this.accessory.logDebugWarn(`${platformLang.newScene}: [${command}] [${hexString}]`)
331
+ break
332
+ }
333
+ })
334
+ }
335
+ }