@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,616 @@
|
|
|
1
|
+
import {
|
|
2
|
+
base64ToHex,
|
|
3
|
+
farToCen,
|
|
4
|
+
getTwoItemPosition,
|
|
5
|
+
hasProperty,
|
|
6
|
+
hexToTwoItems,
|
|
7
|
+
nearestHalf,
|
|
8
|
+
parseError,
|
|
9
|
+
} from '../utils/functions.js'
|
|
10
|
+
import platformLang from '../utils/lang-en.js'
|
|
11
|
+
|
|
12
|
+
/*
|
|
13
|
+
H7130 (with temperature reporting)
|
|
14
|
+
{
|
|
15
|
+
"mode": {
|
|
16
|
+
"options": [
|
|
17
|
+
{
|
|
18
|
+
"name": "Low",
|
|
19
|
+
"value": "1"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"name": "Medium",
|
|
23
|
+
"value": "2"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"name": "High",
|
|
27
|
+
"value": "3"
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
*/
|
|
33
|
+
export default class {
|
|
34
|
+
constructor(platform, accessory) {
|
|
35
|
+
// Set up variables from the platform
|
|
36
|
+
this.hapChar = platform.api.hap.Characteristic
|
|
37
|
+
this.hapErr = platform.api.hap.HapStatusError
|
|
38
|
+
this.hapServ = platform.api.hap.Service
|
|
39
|
+
this.platform = platform
|
|
40
|
+
|
|
41
|
+
this.log = platform.log
|
|
42
|
+
|
|
43
|
+
// Set up variables from the accessory
|
|
44
|
+
this.accessory = accessory
|
|
45
|
+
|
|
46
|
+
// Set up objects
|
|
47
|
+
this.speedCode = {
|
|
48
|
+
33: 'MwUBAAAAAAAAAAAAAAAAAAAAADc=',
|
|
49
|
+
66: 'MwUCAAAAAAAAAAAAAAAAAAAAADQ=',
|
|
50
|
+
99: 'MwUDAAAAAAAAAAAAAAAAAAAAADU=',
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
this.speedCodeLabel = {
|
|
54
|
+
33: 'low',
|
|
55
|
+
66: 'medium',
|
|
56
|
+
99: 'high',
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
this.tempCodeAuto = {
|
|
60
|
+
5: 'MxoBAJAEAAAAAAAAAAAAAAAAALw=',
|
|
61
|
+
6: 'MxoBAJBoAAAAAAAAAAAAAAAAANA=',
|
|
62
|
+
7: 'MxoBAJEwAAAAAAAAAAAAAAAAAIk=',
|
|
63
|
+
8: 'MxoBAJH4AAAAAAAAAAAAAAAAAEE=',
|
|
64
|
+
9: 'MxoBAJLAAAAAAAAAAAAAAAAAAHo=',
|
|
65
|
+
10: 'MxoBAJOIAAAAAAAAAAAAAAAAADM=',
|
|
66
|
+
11: 'MxoBAJPsAAAAAAAAAAAAAAAAAFc=',
|
|
67
|
+
12: 'MxoBAJS0AAAAAAAAAAAAAAAAAAg=',
|
|
68
|
+
13: 'MxoBAJV8AAAAAAAAAAAAAAAAAME=',
|
|
69
|
+
14: 'MxoBAJZEAAAAAAAAAAAAAAAAAPo=',
|
|
70
|
+
15: 'MxoBAJcMAAAAAAAAAAAAAAAAALM=',
|
|
71
|
+
16: 'MxoBAJdwAAAAAAAAAAAAAAAAAM8=',
|
|
72
|
+
17: 'MxoBAJg4AAAAAAAAAAAAAAAAAIg=',
|
|
73
|
+
18: 'MxoBAJkAAAAAAAAAAAAAAAAAALE=',
|
|
74
|
+
19: 'MxoBAJnIAAAAAAAAAAAAAAAAAHk=',
|
|
75
|
+
20: 'MxoBAJqQAAAAAAAAAAAAAAAAACI=',
|
|
76
|
+
21: 'MxoBAJr0AAAAAAAAAAAAAAAAAEY=',
|
|
77
|
+
22: 'MxoBAJu8AAAAAAAAAAAAAAAAAA8=',
|
|
78
|
+
23: 'MxoBAJyEAAAAAAAAAAAAAAAAADA=',
|
|
79
|
+
24: 'MxoBAJ1MAAAAAAAAAAAAAAAAAPk=',
|
|
80
|
+
25: 'MxoBAJ4UAAAAAAAAAAAAAAAAAKI=',
|
|
81
|
+
26: 'MxoBAJ54AAAAAAAAAAAAAAAAAM4=',
|
|
82
|
+
27: 'MxoBAJ9AAAAAAAAAAAAAAAAAAPc=',
|
|
83
|
+
28: 'MxoBAKAIAAAAAAAAAAAAAAAAAIA=',
|
|
84
|
+
29: 'MxoBAKDQAAAAAAAAAAAAAAAAAFg=',
|
|
85
|
+
30: 'MxoBAKGYAAAAAAAAAAAAAAAAABE=',
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
this.tempCodeHeat = {
|
|
89
|
+
5: 'MxoAAJAEAAAAAAAAAAAAAAAAAL0=',
|
|
90
|
+
6: 'MxoAAJBoAAAAAAAAAAAAAAAAANE=',
|
|
91
|
+
7: 'MxoAAJEwAAAAAAAAAAAAAAAAAIg=',
|
|
92
|
+
8: 'MxoAAJH4AAAAAAAAAAAAAAAAAEA=',
|
|
93
|
+
9: 'MxoAAJLAAAAAAAAAAAAAAAAAAHs=',
|
|
94
|
+
10: 'MxoAAJOIAAAAAAAAAAAAAAAAADI=',
|
|
95
|
+
11: 'MxoAAJPsAAAAAAAAAAAAAAAAAFY=',
|
|
96
|
+
12: 'MxoAAJS0AAAAAAAAAAAAAAAAAAk=',
|
|
97
|
+
13: 'MxoAAJV8AAAAAAAAAAAAAAAAAMA=',
|
|
98
|
+
14: 'MxoAAJZEAAAAAAAAAAAAAAAAAPs=',
|
|
99
|
+
15: 'MxoAAJcMAAAAAAAAAAAAAAAAALI=',
|
|
100
|
+
16: 'MxoAAJdwAAAAAAAAAAAAAAAAAM4=',
|
|
101
|
+
17: 'MxoAAJg4AAAAAAAAAAAAAAAAAIk=',
|
|
102
|
+
18: 'MxoAAJkAAAAAAAAAAAAAAAAAALA=',
|
|
103
|
+
19: 'MxoAAJnIAAAAAAAAAAAAAAAAAHg=',
|
|
104
|
+
20: 'MxoAAJqQAAAAAAAAAAAAAAAAACM=',
|
|
105
|
+
21: 'MxoAAJr0AAAAAAAAAAAAAAAAAEc=',
|
|
106
|
+
22: 'MxoAAJu8AAAAAAAAAAAAAAAAAA4=',
|
|
107
|
+
23: 'MxoAAJyEAAAAAAAAAAAAAAAAADE=',
|
|
108
|
+
24: 'MxoAAJ1MAAAAAAAAAAAAAAAAAPg=',
|
|
109
|
+
25: 'MxoAAJ4UAAAAAAAAAAAAAAAAAKM=',
|
|
110
|
+
26: 'MxoAAJ54AAAAAAAAAAAAAAAAAM8=',
|
|
111
|
+
27: 'MxoAAJ9AAAAAAAAAAAAAAAAAAPY=',
|
|
112
|
+
28: 'MxoAAKAIAAAAAAAAAAAAAAAAAIE=',
|
|
113
|
+
29: 'MxoAAKDQAAAAAAAAAAAAAAAAAFk=',
|
|
114
|
+
30: 'MxoAAKGYAAAAAAAAAAAAAAAAABA=',
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Remove any old light service
|
|
118
|
+
if (this.accessory.getService(this.hapServ.Lightbulb)) {
|
|
119
|
+
this.accessory.removeService(this.accessory.getService(this.hapServ.Lightbulb))
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Remove any old fanv2 service
|
|
123
|
+
if (this.accessory.getService(this.hapServ.Fanv2)) {
|
|
124
|
+
this.accessory.removeService(this.accessory.getService(this.hapServ.Fanv2))
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Add the heater service if it doesn't already exist
|
|
128
|
+
this.service = this.accessory.getService(this.hapServ.HeaterCooler)
|
|
129
|
+
if (!this.service) {
|
|
130
|
+
this.service = this.accessory.addService(this.hapServ.HeaterCooler)
|
|
131
|
+
this.service.updateCharacteristic(this.hapChar.CurrentTemperature, 20)
|
|
132
|
+
this.service.updateCharacteristic(this.hapChar.HeatingThresholdTemperature, 20)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Add the fan service if it doesn't already exist
|
|
136
|
+
this.fanService = this.accessory.getService(this.hapServ.Fan)
|
|
137
|
+
|| this.accessory.addService(this.hapServ.Fan)
|
|
138
|
+
|
|
139
|
+
// Add the set handler to the heater active characteristic
|
|
140
|
+
this.service
|
|
141
|
+
.getCharacteristic(this.hapChar.Active)
|
|
142
|
+
.onSet(async value => this.internalStateUpdate(value))
|
|
143
|
+
this.cacheState = this.service.getCharacteristic(this.hapChar.Active).value === 1 ? 'on' : 'off'
|
|
144
|
+
|
|
145
|
+
// Add options to the heater target state characteristic
|
|
146
|
+
this.service
|
|
147
|
+
.getCharacteristic(this.hapChar.TargetHeaterCoolerState)
|
|
148
|
+
.setProps({
|
|
149
|
+
minValue: 0,
|
|
150
|
+
maxValue: 1,
|
|
151
|
+
validValues: [0, 1],
|
|
152
|
+
})
|
|
153
|
+
.onSet(async value => this.internalModeUpdate(value))
|
|
154
|
+
this.cacheMode = this.service.getCharacteristic(this.hapChar.TargetHeaterCoolerState).value === 0 ? 'auto' : 'heat'
|
|
155
|
+
|
|
156
|
+
this.cacheTemp = this.service.getCharacteristic(this.hapChar.CurrentTemperature).value
|
|
157
|
+
|
|
158
|
+
// Add the set handler and a range to the heater target temperature characteristic
|
|
159
|
+
this.service
|
|
160
|
+
.getCharacteristic(this.hapChar.HeatingThresholdTemperature)
|
|
161
|
+
.setProps({
|
|
162
|
+
minValue: 5,
|
|
163
|
+
maxValue: 30,
|
|
164
|
+
minStep: 1,
|
|
165
|
+
})
|
|
166
|
+
.onSet(async value => this.internalTempUpdate(value))
|
|
167
|
+
this.cacheTarg = this.service.getCharacteristic(this.hapChar.HeatingThresholdTemperature).value
|
|
168
|
+
|
|
169
|
+
// Add the set handler to the heater swing mode characteristic (for oscillation)
|
|
170
|
+
this.service
|
|
171
|
+
.getCharacteristic(this.hapChar.SwingMode)
|
|
172
|
+
.onSet(async value => this.internalSwingUpdate(value))
|
|
173
|
+
this.cacheSwing = this.service.getCharacteristic(this.hapChar.SwingMode).value === 1 ? 'on' : 'off'
|
|
174
|
+
|
|
175
|
+
// Add the set handler to the heater lock characteristic
|
|
176
|
+
this.service
|
|
177
|
+
.getCharacteristic(this.hapChar.LockPhysicalControls)
|
|
178
|
+
.onSet(async value => this.internalLockUpdate(value))
|
|
179
|
+
this.cacheLock = this.service.getCharacteristic(this.hapChar.LockPhysicalControls).value === 1 ? 'on' : 'off'
|
|
180
|
+
|
|
181
|
+
// Add the set handler to the fan on/off characteristic
|
|
182
|
+
this.fanService
|
|
183
|
+
.getCharacteristic(this.hapChar.On)
|
|
184
|
+
.onSet(async value => this.internalFanStateUpdate(value))
|
|
185
|
+
this.cacheFanState = this.fanService.getCharacteristic(this.hapChar.On).value ? 'on' : 'off'
|
|
186
|
+
|
|
187
|
+
// Add the set handler to the fan rotation speed characteristic
|
|
188
|
+
this.fanService
|
|
189
|
+
.getCharacteristic(this.hapChar.RotationSpeed)
|
|
190
|
+
.setProps({
|
|
191
|
+
minStep: 33,
|
|
192
|
+
validValues: [0, 33, 66, 99],
|
|
193
|
+
})
|
|
194
|
+
.onSet(async value => this.internalSpeedUpdate(value))
|
|
195
|
+
this.cacheSpeed = this.fanService.getCharacteristic(this.hapChar.RotationSpeed).value
|
|
196
|
+
|
|
197
|
+
// Output the customised options to the log
|
|
198
|
+
const opts = JSON.stringify({
|
|
199
|
+
tempReporting: true,
|
|
200
|
+
})
|
|
201
|
+
platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async internalStateUpdate(value) {
|
|
205
|
+
try {
|
|
206
|
+
const newValue = value === 1 ? 'on' : 'off'
|
|
207
|
+
|
|
208
|
+
// Don't continue if the new value is the same as before
|
|
209
|
+
if (this.cacheState === newValue) {
|
|
210
|
+
return
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Send the request to the platform sender function
|
|
214
|
+
await this.platform.sendDeviceUpdate(this.accessory, {
|
|
215
|
+
cmd: 'ptReal',
|
|
216
|
+
value: value ? 'MwEBAAAAAAAAAAAAAAAAAAAAADM=' : 'MwEAAAAAAAAAAAAAAAAAAAAAADI=',
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
// Cache the new state and log if appropriate
|
|
220
|
+
if (this.cacheState !== newValue) {
|
|
221
|
+
this.cacheState = newValue
|
|
222
|
+
this.accessory.log(`${platformLang.curState} [${newValue}]`)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Fan state should also match the new state
|
|
226
|
+
if (this.cacheFanState !== newValue) {
|
|
227
|
+
this.cacheFanState = newValue
|
|
228
|
+
this.fanService.updateCharacteristic(this.hapChar.On, newValue === 'on')
|
|
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.Active, this.cacheState === 'on' ? 1 : 0)
|
|
237
|
+
}, 2000)
|
|
238
|
+
throw new this.hapErr(-70402)
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async internalModeUpdate(value) {
|
|
243
|
+
try {
|
|
244
|
+
const newMode = value === 0 ? 'auto' : 'heat'
|
|
245
|
+
|
|
246
|
+
// Don't continue if the new value is the same as before
|
|
247
|
+
if (this.cacheMode === newMode) {
|
|
248
|
+
return
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Get the current state of the heater
|
|
252
|
+
const objectToChoose = newMode === 'auto' ? this.tempCodeAuto : this.tempCodeHeat
|
|
253
|
+
|
|
254
|
+
// Send the request to the platform sender function
|
|
255
|
+
await this.platform.sendDeviceUpdate(this.accessory, {
|
|
256
|
+
cmd: 'ptReal',
|
|
257
|
+
value: objectToChoose[this.cacheTemp],
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
// Cache the new state and log if appropriate
|
|
261
|
+
if (this.cacheMode !== newMode) {
|
|
262
|
+
this.cacheMode = newMode
|
|
263
|
+
this.accessory.log(`${platformLang.curMode} [${newMode}]`)
|
|
264
|
+
}
|
|
265
|
+
} catch (err) {
|
|
266
|
+
// Catch any errors during the process
|
|
267
|
+
this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
|
|
268
|
+
|
|
269
|
+
// Throw a 'no response' error and set a timeout to revert this after 2 seconds
|
|
270
|
+
setTimeout(() => {
|
|
271
|
+
this.service.updateCharacteristic(
|
|
272
|
+
this.hapChar.TargetHeaterCoolerState,
|
|
273
|
+
this.cacheMode === 'auto' ? 0 : 1,
|
|
274
|
+
)
|
|
275
|
+
}, 2000)
|
|
276
|
+
throw new this.hapErr(-70402)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async internalTempUpdate(value) {
|
|
281
|
+
try {
|
|
282
|
+
// Don't continue if the new value is the same as before
|
|
283
|
+
if (this.cacheTarg === value) {
|
|
284
|
+
return
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Get the current state of the heater
|
|
288
|
+
const objectToChoose = this.cacheMode === 'auto' ? this.tempCodeAuto : this.tempCodeHeat
|
|
289
|
+
|
|
290
|
+
// Send the request to the platform sender function
|
|
291
|
+
await this.platform.sendDeviceUpdate(this.accessory, {
|
|
292
|
+
cmd: 'ptReal',
|
|
293
|
+
value: objectToChoose[value],
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
// Cache the new state and log if appropriate
|
|
297
|
+
if (this.cacheTarg !== value) {
|
|
298
|
+
this.cacheTarg = value
|
|
299
|
+
this.accessory.log(`${platformLang.curTarg} [${this.cacheTarg}°C]`)
|
|
300
|
+
}
|
|
301
|
+
} catch (err) {
|
|
302
|
+
// Catch any errors during the process
|
|
303
|
+
this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
|
|
304
|
+
|
|
305
|
+
// Throw a 'no response' error and set a timeout to revert this after 2 seconds
|
|
306
|
+
setTimeout(() => {
|
|
307
|
+
this.service.updateCharacteristic(this.hapChar.HeatingThresholdTemperature, this.cacheTarg)
|
|
308
|
+
}, 2000)
|
|
309
|
+
throw new this.hapErr(-70402)
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async internalSwingUpdate(value) {
|
|
314
|
+
try {
|
|
315
|
+
// value === 0 -> swing mode OFF
|
|
316
|
+
// value === 1 -> swing mode ON
|
|
317
|
+
const newValue = value === 1 ? 'on' : 'off'
|
|
318
|
+
|
|
319
|
+
// Don't continue if the new value is the same as before
|
|
320
|
+
if (this.cacheSwing === newValue) {
|
|
321
|
+
return
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Send the request to the platform sender function
|
|
325
|
+
await this.platform.sendDeviceUpdate(this.accessory, {
|
|
326
|
+
cmd: 'ptReal',
|
|
327
|
+
value: value ? 'MxgBAAAAAAAAAAAAAAAAAAAAACo=' : 'MxgAAAAAAAAAAAAAAAAAAAAAACs=',
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
// Cache the new state and log if appropriate
|
|
331
|
+
if (this.cacheSwing !== newValue) {
|
|
332
|
+
this.cacheSwing = newValue
|
|
333
|
+
this.accessory.log(`${platformLang.curSwing} [${newValue}]`)
|
|
334
|
+
}
|
|
335
|
+
} catch (err) {
|
|
336
|
+
// Catch any errors during the process
|
|
337
|
+
this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
|
|
338
|
+
|
|
339
|
+
// Throw a 'no response' error and set a timeout to revert this after 2 seconds
|
|
340
|
+
setTimeout(() => {
|
|
341
|
+
this.service.updateCharacteristic(
|
|
342
|
+
this.hapChar.SwingMode,
|
|
343
|
+
this.cacheSwing === 'on' ? 1 : 0,
|
|
344
|
+
)
|
|
345
|
+
}, 2000)
|
|
346
|
+
throw new this.hapErr(-70402)
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async internalLockUpdate(value) {
|
|
351
|
+
try {
|
|
352
|
+
// value === 0 -> child lock OFF
|
|
353
|
+
// value === 1 -> child lock ON
|
|
354
|
+
const newValue = value === 1 ? 'on' : 'off'
|
|
355
|
+
|
|
356
|
+
// Don't continue if the new value is the same as before
|
|
357
|
+
if (this.cacheLock === newValue) {
|
|
358
|
+
return
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Send the request to the platform sender function
|
|
362
|
+
await this.platform.sendDeviceUpdate(this.accessory, {
|
|
363
|
+
cmd: 'ptReal',
|
|
364
|
+
value: value ? 'MxABAAAAAAAAAAAAAAAAAAAAACI=' : 'MxAAAAAAAAAAAAAAAAAAAAAAACM=',
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
// Cache the new state and log if appropriate
|
|
368
|
+
if (this.cacheLock !== newValue) {
|
|
369
|
+
this.cacheLock = newValue
|
|
370
|
+
this.accessory.log(`${platformLang.curLock} [${newValue}]`)
|
|
371
|
+
}
|
|
372
|
+
} catch (err) {
|
|
373
|
+
// Catch any errors during the process
|
|
374
|
+
this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
|
|
375
|
+
|
|
376
|
+
// Throw a 'no response' error and set a timeout to revert this after 2 seconds
|
|
377
|
+
setTimeout(() => {
|
|
378
|
+
this.service.updateCharacteristic(
|
|
379
|
+
this.hapChar.LockPhysicalControls,
|
|
380
|
+
this.cacheLock === 'on' ? 1 : 0,
|
|
381
|
+
)
|
|
382
|
+
}, 2000)
|
|
383
|
+
throw new this.hapErr(-70402)
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
async internalFanStateUpdate(value) {
|
|
388
|
+
try {
|
|
389
|
+
const newValue = value ? 'on' : 'off'
|
|
390
|
+
|
|
391
|
+
// The fan is used for the following modes (basically all except Auto):
|
|
392
|
+
// - 0%_: No effect - revert to previous speed
|
|
393
|
+
// - 33%: Low Mode
|
|
394
|
+
// - 66%: Medium Mode
|
|
395
|
+
// - 99%: High Mode
|
|
396
|
+
// If the main heater is turned off then this fan should be turned off too
|
|
397
|
+
// If the main heater is turned on then this fan speed should revert to the current mode
|
|
398
|
+
|
|
399
|
+
// Don't continue if the new value is the same as before
|
|
400
|
+
if (this.cacheFanState === newValue) {
|
|
401
|
+
return
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Turning the fan on should only be possible if the main heater is off, and this should not do anything
|
|
405
|
+
if (newValue === 'on') {
|
|
406
|
+
// Wait a few seconds then turn back off
|
|
407
|
+
setTimeout(() => {
|
|
408
|
+
this.fanService.updateCharacteristic(this.hapChar.On, false)
|
|
409
|
+
}, 3000)
|
|
410
|
+
return
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Turning fan off:
|
|
414
|
+
// We should wait a few seconds and then just revert to the previous fan speed
|
|
415
|
+
setTimeout(() => {
|
|
416
|
+
this.fanService.updateCharacteristic(this.hapChar.On, true)
|
|
417
|
+
this.fanService.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
|
|
418
|
+
}, 2000)
|
|
419
|
+
} catch (err) {
|
|
420
|
+
// Catch any errors during the process
|
|
421
|
+
this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
|
|
422
|
+
|
|
423
|
+
// Throw a 'no response' error and set a timeout to revert this after 2 seconds
|
|
424
|
+
setTimeout(() => {
|
|
425
|
+
this.fanService.updateCharacteristic(this.hapChar.On, this.cacheFanState === 'on')
|
|
426
|
+
}, 2000)
|
|
427
|
+
throw new this.hapErr(-70402)
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
async internalSpeedUpdate(value) {
|
|
432
|
+
try {
|
|
433
|
+
// The fan is used for the following modes (basically all except Auto):
|
|
434
|
+
// - 0%_: Not sure what to do with this yet
|
|
435
|
+
// - 33%: Low Mode
|
|
436
|
+
// - 66%: Medium Mode
|
|
437
|
+
// - 99%: High Mode
|
|
438
|
+
// If the main heater is turned off then this fan should be turned off too
|
|
439
|
+
// If the main heater is turned on then this fan speed should revert to the current mode
|
|
440
|
+
|
|
441
|
+
// Don't continue if the new value is the same as before
|
|
442
|
+
// If the new speed is 0, the on/off handler should take care of resetting to the speed before (home app only)
|
|
443
|
+
if (this.cacheSpeed === value || value === 0) {
|
|
444
|
+
return
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Send the request to the platform sender function
|
|
448
|
+
await this.platform.sendDeviceUpdate(this.accessory, {
|
|
449
|
+
cmd: 'ptReal',
|
|
450
|
+
value: this.speedCode[value],
|
|
451
|
+
})
|
|
452
|
+
|
|
453
|
+
// Cache the new state and log if appropriate
|
|
454
|
+
if (this.cacheSpeed !== value) {
|
|
455
|
+
this.cacheSpeed = value
|
|
456
|
+
this.accessory.log(`${platformLang.curSpeed} [${this.speedCodeLabel[value]}]`)
|
|
457
|
+
}
|
|
458
|
+
} catch (err) {
|
|
459
|
+
// Catch any errors during the process
|
|
460
|
+
this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
|
|
461
|
+
|
|
462
|
+
// Throw a 'no response' error and set a timeout to revert this after 2 seconds
|
|
463
|
+
setTimeout(() => {
|
|
464
|
+
this.fanService.updateCharacteristic(this.hapChar.On, this.cacheState === 'on')
|
|
465
|
+
}, 2000)
|
|
466
|
+
throw new this.hapErr(-70402)
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
externalUpdate(params) {
|
|
471
|
+
// Update the active characteristic
|
|
472
|
+
if (params.state && params.state !== this.cacheState) {
|
|
473
|
+
this.cacheState = params.state
|
|
474
|
+
this.service.updateCharacteristic(this.hapChar.Active, this.cacheState === 'on')
|
|
475
|
+
this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
|
|
476
|
+
|
|
477
|
+
// Fan state should also match the main heater state
|
|
478
|
+
if (this.cacheFanState !== this.cacheState) {
|
|
479
|
+
this.cacheFanState = this.cacheState
|
|
480
|
+
this.fanService.updateCharacteristic(this.hapChar.On, this.cacheState === 'on')
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Update the current temperature characteristic
|
|
485
|
+
if (hasProperty(params, 'temperature')) {
|
|
486
|
+
const newTemp = nearestHalf(farToCen(params.temperature / 100))
|
|
487
|
+
if (newTemp !== this.cacheTemp) {
|
|
488
|
+
if (newTemp > 100) {
|
|
489
|
+
// Device must be one that does not support ambient temperature
|
|
490
|
+
this.accessory.logWarn('you should disable `tempReporting` in the config for this device')
|
|
491
|
+
} else {
|
|
492
|
+
this.cacheTemp = newTemp
|
|
493
|
+
this.service.updateCharacteristic(this.hapChar.CurrentTemperature, this.cacheTemp)
|
|
494
|
+
this.accessory.log(`${platformLang.curTemp} [${this.cacheTemp}°C]`)
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Update the target temperature characteristic
|
|
500
|
+
if (hasProperty(params, 'setTemperature')) {
|
|
501
|
+
const newTemp = Math.round(farToCen(params.setTemperature / 100))
|
|
502
|
+
if (newTemp !== this.cacheTarg) {
|
|
503
|
+
this.cacheTarg = newTemp
|
|
504
|
+
this.service.updateCharacteristic(this.hapChar.HeatingThresholdTemperature, this.cacheTarg)
|
|
505
|
+
this.accessory.log(`${platformLang.curTarg} [${this.cacheTarg}°C]`)
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Check for some other scene/mode change
|
|
510
|
+
(params.commands || []).forEach((command) => {
|
|
511
|
+
const hexString = base64ToHex(command)
|
|
512
|
+
const hexParts = hexToTwoItems(hexString)
|
|
513
|
+
|
|
514
|
+
// Return now if not a device query update code
|
|
515
|
+
if (getTwoItemPosition(hexParts, 1) !== 'aa') {
|
|
516
|
+
return
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const deviceFunction = `${getTwoItemPosition(hexParts, 2)}${getTwoItemPosition(hexParts, 3)}`
|
|
520
|
+
|
|
521
|
+
switch (deviceFunction) {
|
|
522
|
+
case '1800':
|
|
523
|
+
case '1801': {
|
|
524
|
+
// Swing Mode
|
|
525
|
+
const newSwing = getTwoItemPosition(hexParts, 3) === '01' ? 'on' : 'off'
|
|
526
|
+
if (this.cacheSwing !== newSwing) {
|
|
527
|
+
this.cacheSwing = newSwing
|
|
528
|
+
this.service.updateCharacteristic(this.hapChar.SwingMode, this.cacheSwing === 'on' ? 1 : 0)
|
|
529
|
+
this.accessory.log(`${platformLang.curSwing} [${this.cacheSwing}]`)
|
|
530
|
+
}
|
|
531
|
+
break
|
|
532
|
+
}
|
|
533
|
+
case '1000':
|
|
534
|
+
case '1001': {
|
|
535
|
+
// Child Lock
|
|
536
|
+
const newLock = getTwoItemPosition(hexParts, 3) === '01' ? 'on' : 'off'
|
|
537
|
+
if (this.cacheLock !== newLock) {
|
|
538
|
+
this.cacheLock = newLock
|
|
539
|
+
this.service.updateCharacteristic(this.hapChar.LockPhysicalControls, this.cacheLock === 'on' ? 1 : 0)
|
|
540
|
+
this.accessory.log(`${platformLang.curLock} [${this.cacheLock}]`)
|
|
541
|
+
}
|
|
542
|
+
break
|
|
543
|
+
}
|
|
544
|
+
case '0501': // fan speed low
|
|
545
|
+
case '0502': // fan speed medium
|
|
546
|
+
case '0503': { // fan speed high
|
|
547
|
+
switch (getTwoItemPosition(hexParts, 3)) {
|
|
548
|
+
case '01': {
|
|
549
|
+
// Fan is low
|
|
550
|
+
if (this.cacheState === 'on' && this.cacheFanState !== 'on') {
|
|
551
|
+
this.cacheFanState = 'on'
|
|
552
|
+
this.fanService.updateCharacteristic(this.hapChar.On, true)
|
|
553
|
+
this.accessory.log(`${platformLang.curMode} [${this.cacheFanState}]`)
|
|
554
|
+
}
|
|
555
|
+
if (this.cacheSpeed !== 33) {
|
|
556
|
+
this.cacheSpeed = 33
|
|
557
|
+
this.fanService.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
|
|
558
|
+
this.accessory.log(`${platformLang.curSpeed} [${this.speedCodeLabel[this.cacheSpeed]}]`)
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
break
|
|
562
|
+
}
|
|
563
|
+
case '02': {
|
|
564
|
+
// Fan is medium
|
|
565
|
+
if (this.cacheState === 'on' && this.cacheFanState !== 'on') {
|
|
566
|
+
this.cacheFanState = 'on'
|
|
567
|
+
this.fanService.updateCharacteristic(this.hapChar.On, true)
|
|
568
|
+
this.accessory.log(`${platformLang.curMode} [${this.cacheFanState}]`)
|
|
569
|
+
}
|
|
570
|
+
if (this.cacheSpeed !== 66) {
|
|
571
|
+
this.cacheSpeed = 66
|
|
572
|
+
this.fanService.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
|
|
573
|
+
this.accessory.log(`${platformLang.curSpeed} [${this.speedCodeLabel[this.cacheSpeed]}]`)
|
|
574
|
+
}
|
|
575
|
+
break
|
|
576
|
+
}
|
|
577
|
+
case '03': {
|
|
578
|
+
// Fan is high
|
|
579
|
+
if (this.cacheState === 'on' && this.cacheFanState !== 'on') {
|
|
580
|
+
this.cacheFanState = 'on'
|
|
581
|
+
this.fanService.updateCharacteristic(this.hapChar.On, true)
|
|
582
|
+
this.accessory.log(`${platformLang.curMode} [${this.cacheFanState}]`)
|
|
583
|
+
}
|
|
584
|
+
if (this.cacheSpeed !== 99) {
|
|
585
|
+
this.cacheSpeed = 99
|
|
586
|
+
this.fanService.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
|
|
587
|
+
this.accessory.log(`${platformLang.curSpeed} [${this.speedCodeLabel[this.cacheSpeed]}]`)
|
|
588
|
+
}
|
|
589
|
+
break
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
break
|
|
593
|
+
}
|
|
594
|
+
case '1a00': // Target temperature (thermostat mode off)
|
|
595
|
+
case '1a01': { // Target temperature (thermostat mode on)
|
|
596
|
+
const newMode = getTwoItemPosition(hexParts, 3) === '01' ? 'auto' : 'heat'
|
|
597
|
+
if (this.cacheMode !== newMode) {
|
|
598
|
+
this.cacheMode = newMode
|
|
599
|
+
this.service.updateCharacteristic(this.hapChar.TargetHeaterCoolerState, this.cacheMode === 'auto' ? 0 : 1)
|
|
600
|
+
this.accessory.log(`${platformLang.curMode} [${this.cacheMode}]`)
|
|
601
|
+
}
|
|
602
|
+
break
|
|
603
|
+
}
|
|
604
|
+
case '1100': // timer off?
|
|
605
|
+
case '1101':// timer on
|
|
606
|
+
case '1300': // scheduling
|
|
607
|
+
case '1600': // DND off?
|
|
608
|
+
case '1601': // DND on?
|
|
609
|
+
break
|
|
610
|
+
default:
|
|
611
|
+
this.accessory.logDebugWarn(`${platformLang.newScene}: [${command}] [${hexString}]`)
|
|
612
|
+
break
|
|
613
|
+
}
|
|
614
|
+
})
|
|
615
|
+
}
|
|
616
|
+
}
|