@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.
- package/CHANGELOG.md +1937 -0
- package/LICENSE +21 -0
- package/README.md +72 -0
- package/config.schema.json +1727 -0
- package/eslint.config.js +49 -0
- package/lib/connection/aws.js +174 -0
- package/lib/connection/ble.js +208 -0
- package/lib/connection/cert/AmazonRootCA1.pem +20 -0
- package/lib/connection/http.js +240 -0
- package/lib/connection/lan.js +284 -0
- package/lib/device/cooler-single.js +300 -0
- package/lib/device/dehumidifier-H7150.js +182 -0
- package/lib/device/dehumidifier-H7151.js +157 -0
- package/lib/device/diffuser-H7161.js +117 -0
- package/lib/device/diffuser-H7162.js +117 -0
- package/lib/device/fan-H7100.js +274 -0
- package/lib/device/fan-H7101.js +330 -0
- package/lib/device/fan-H7102.js +274 -0
- package/lib/device/fan-H7105.js +503 -0
- package/lib/device/fan-H7106.js +274 -0
- package/lib/device/fan-H7111.js +335 -0
- package/lib/device/heater-single.js +300 -0
- package/lib/device/heater1a.js +353 -0
- package/lib/device/heater1b.js +616 -0
- package/lib/device/heater2.js +838 -0
- package/lib/device/humidifier-H7140.js +224 -0
- package/lib/device/humidifier-H7141.js +257 -0
- package/lib/device/humidifier-H7142.js +522 -0
- package/lib/device/humidifier-H7143.js +157 -0
- package/lib/device/humidifier-H7148.js +157 -0
- package/lib/device/humidifier-H7160.js +446 -0
- package/lib/device/ice-maker-H7162.js +46 -0
- package/lib/device/index.js +105 -0
- package/lib/device/kettle.js +269 -0
- package/lib/device/light-switch.js +86 -0
- package/lib/device/light.js +617 -0
- package/lib/device/outlet-double.js +121 -0
- package/lib/device/outlet-single.js +172 -0
- package/lib/device/outlet-triple.js +160 -0
- package/lib/device/purifier-H7120.js +336 -0
- package/lib/device/purifier-H7121.js +336 -0
- package/lib/device/purifier-H7122.js +449 -0
- package/lib/device/purifier-H7123.js +411 -0
- package/lib/device/purifier-H7124.js +411 -0
- package/lib/device/purifier-H7126.js +296 -0
- package/lib/device/purifier-H7127.js +296 -0
- package/lib/device/purifier-H712C.js +296 -0
- package/lib/device/purifier-single.js +119 -0
- package/lib/device/sensor-button.js +22 -0
- package/lib/device/sensor-contact.js +22 -0
- package/lib/device/sensor-leak.js +87 -0
- package/lib/device/sensor-monitor.js +190 -0
- package/lib/device/sensor-presence.js +53 -0
- package/lib/device/sensor-thermo.js +144 -0
- package/lib/device/sensor-thermo4.js +55 -0
- package/lib/device/switch-double.js +121 -0
- package/lib/device/switch-single.js +95 -0
- package/lib/device/switch-triple.js +160 -0
- package/lib/device/tap-single.js +108 -0
- package/lib/device/template.js +43 -0
- package/lib/device/tv-single.js +84 -0
- package/lib/device/valve-single.js +155 -0
- package/lib/fakegato/LICENSE +21 -0
- package/lib/fakegato/fakegato-history.js +814 -0
- package/lib/fakegato/fakegato-storage.js +108 -0
- package/lib/fakegato/fakegato-timer.js +125 -0
- package/lib/fakegato/uuid.js +27 -0
- package/lib/homebridge-ui/public/index.html +433 -0
- package/lib/homebridge-ui/server.js +10 -0
- package/lib/index.js +8 -0
- package/lib/platform.js +1967 -0
- package/lib/utils/colour.js +564 -0
- package/lib/utils/constants.js +579 -0
- package/lib/utils/custom-chars.js +225 -0
- package/lib/utils/eve-chars.js +68 -0
- package/lib/utils/functions.js +117 -0
- package/lib/utils/lang-en.js +131 -0
- 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
|
+
}
|