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