@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,300 @@
|
|
|
1
|
+
import { generateRandomString, hasProperty, parseError } from '../utils/functions.js'
|
|
2
|
+
import platformLang from '../utils/lang-en.js'
|
|
3
|
+
|
|
4
|
+
export default class {
|
|
5
|
+
constructor(platform, accessory) {
|
|
6
|
+
// Set up variables from the platform
|
|
7
|
+
this.hapChar = platform.api.hap.Characteristic
|
|
8
|
+
this.hapErr = platform.api.hap.HapStatusError
|
|
9
|
+
this.hapServ = platform.api.hap.Service
|
|
10
|
+
this.platform = platform
|
|
11
|
+
|
|
12
|
+
// Set up variables from the accessory
|
|
13
|
+
this.accessory = accessory
|
|
14
|
+
this.temperatureSource = accessory.context.temperatureSource;
|
|
15
|
+
|
|
16
|
+
// Remove any old services from simulations
|
|
17
|
+
['AirPurifier', 'Lightbulb', 'Outlet', 'Switch', 'Valve'].forEach((service) => {
|
|
18
|
+
if (this.accessory.getService(this.hapServ[service])) {
|
|
19
|
+
this.accessory.removeService(this.accessory.getService(this.hapServ[service]))
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
// Set up the accessory with default target temp when added the first time
|
|
24
|
+
if (!hasProperty(this.accessory.context, 'cacheTarget')) {
|
|
25
|
+
this.accessory.context.cacheTarget = 20
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Check to make sure user has not switched from cooler to heater
|
|
29
|
+
if (this.accessory.context.cacheType !== 'heater') {
|
|
30
|
+
// Remove and re-setup as a HeaterCooler
|
|
31
|
+
if (this.accessory.getService(this.hapServ.HeaterCooler)) {
|
|
32
|
+
this.accessory.removeService(this.accessory.getService(this.hapServ.HeaterCooler))
|
|
33
|
+
}
|
|
34
|
+
this.accessory.context.cacheType = 'heater'
|
|
35
|
+
this.accessory.context.cacheTarget = 20
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Add the heater service if it doesn't already exist
|
|
39
|
+
this.service = this.accessory.getService(this.hapServ.HeaterCooler)
|
|
40
|
+
|| this.accessory.addService(this.hapServ.HeaterCooler)
|
|
41
|
+
|
|
42
|
+
// Set custom properties of the current temperature characteristic
|
|
43
|
+
this.service.getCharacteristic(this.hapChar.CurrentTemperature).setProps({
|
|
44
|
+
minStep: 0.1,
|
|
45
|
+
})
|
|
46
|
+
this.cacheTemp = this.service.getCharacteristic(this.hapChar.CurrentTemperature).value
|
|
47
|
+
|
|
48
|
+
// Add the set handler to the heater active characteristic
|
|
49
|
+
this.service
|
|
50
|
+
.getCharacteristic(this.hapChar.Active)
|
|
51
|
+
.onSet(async value => this.internalStateUpdate(value))
|
|
52
|
+
|
|
53
|
+
// Add options to the target state characteristic
|
|
54
|
+
this.service.getCharacteristic(this.hapChar.TargetHeaterCoolerState).setProps({
|
|
55
|
+
minValue: 0,
|
|
56
|
+
maxValue: 0,
|
|
57
|
+
validValues: [0],
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
// Add the set handler to the target temperature characteristic
|
|
61
|
+
this.service
|
|
62
|
+
.getCharacteristic(this.hapChar.HeatingThresholdTemperature)
|
|
63
|
+
.updateValue(this.accessory.context.cacheTarget)
|
|
64
|
+
.setProps({ minStep: 0.5 })
|
|
65
|
+
.onSet(async value => this.internalTargetTempUpdate(value))
|
|
66
|
+
|
|
67
|
+
// Initialise these caches now since they aren't determined by the initial externalUpdate()
|
|
68
|
+
this.cacheState = this.service.getCharacteristic(this.hapChar.Active).value === 1 ? 'on' : 'off'
|
|
69
|
+
this.cacheHeat = this.cacheState === 'on'
|
|
70
|
+
&& this.service.getCharacteristic(this.hapChar.TargetHeaterCoolerState).value === 2
|
|
71
|
+
? 'on'
|
|
72
|
+
: 'off'
|
|
73
|
+
|
|
74
|
+
// Pass the accessory to Fakegato to set up with Eve
|
|
75
|
+
this.accessory.eveService = new platform.eveService('custom', this.accessory, {
|
|
76
|
+
log: () => {},
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
// Set up an interval to get regular temperature updates
|
|
80
|
+
setTimeout(() => {
|
|
81
|
+
this.getTemperature()
|
|
82
|
+
this.intervalPoll = setInterval(() => this.getTemperature(), 120000)
|
|
83
|
+
}, 5000)
|
|
84
|
+
|
|
85
|
+
// Stop the intervals on Homebridge shutdown
|
|
86
|
+
platform.api.on('shutdown', () => {
|
|
87
|
+
clearInterval(this.intervalPoll)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
// Output the customised options to the log
|
|
91
|
+
const opts = JSON.stringify({
|
|
92
|
+
showAs: 'heater',
|
|
93
|
+
temperatureSource: this.temperatureSource,
|
|
94
|
+
})
|
|
95
|
+
platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async internalStateUpdate(value) {
|
|
99
|
+
try {
|
|
100
|
+
let newState
|
|
101
|
+
let newHeat
|
|
102
|
+
let newValue
|
|
103
|
+
if (value === 0) {
|
|
104
|
+
newValue = 'off'
|
|
105
|
+
newState = 'off'
|
|
106
|
+
newHeat = 'off'
|
|
107
|
+
} else if (this.cacheTemp < this.accessory.context.cacheTarget) {
|
|
108
|
+
newValue = 'on'
|
|
109
|
+
newState = 'on'
|
|
110
|
+
newHeat = 'on'
|
|
111
|
+
} else {
|
|
112
|
+
newValue = 'off'
|
|
113
|
+
newState = 'on'
|
|
114
|
+
newHeat = 'off'
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Only send the update if either:
|
|
118
|
+
// * The new value (state) is OFF and the cacheHeat was ON
|
|
119
|
+
// * The new value (state) is ON and newHeat is 'on'
|
|
120
|
+
if ((value === 0 && this.cacheHeat === 'on') || (value === 1 && newHeat === 'on')) {
|
|
121
|
+
// Set up a one-minute timeout for the plugin to ignore incoming updates
|
|
122
|
+
const timerKey = generateRandomString(5)
|
|
123
|
+
this.updateTimeout = timerKey
|
|
124
|
+
setTimeout(() => {
|
|
125
|
+
if (this.updateTimeout === timerKey) {
|
|
126
|
+
this.updateTimeout = false
|
|
127
|
+
}
|
|
128
|
+
}, 60000)
|
|
129
|
+
|
|
130
|
+
// Send the request to the platform sender function
|
|
131
|
+
await this.platform.sendDeviceUpdate(this.accessory, {
|
|
132
|
+
cmd: 'stateOutlet',
|
|
133
|
+
value: newValue,
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Cache and log
|
|
138
|
+
if (newState !== this.cacheState) {
|
|
139
|
+
this.cacheState = newState
|
|
140
|
+
this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
|
|
141
|
+
}
|
|
142
|
+
if (newHeat !== this.cacheHeat) {
|
|
143
|
+
this.cacheHeat = newHeat
|
|
144
|
+
this.accessory.log(`${platformLang.curHeat} [${this.cacheHeat}]`)
|
|
145
|
+
}
|
|
146
|
+
const newOnState = this.cacheHeat === 'on' ? 2 : 1
|
|
147
|
+
this.service.updateCharacteristic(
|
|
148
|
+
this.hapChar.CurrentHeaterCoolerState,
|
|
149
|
+
value === 1 ? newOnState : 0,
|
|
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 internalTargetTempUpdate(value) {
|
|
164
|
+
try {
|
|
165
|
+
// Don't continue if the new value is the same as before
|
|
166
|
+
if (value === this.accessory.context.cacheTarget) {
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
this.accessory.context.cacheTarget = value
|
|
170
|
+
this.accessory.log(`${platformLang.curTarg} [${value}°C]`)
|
|
171
|
+
if (this.cacheState === 'off') {
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Check to see if we need to turn on or off
|
|
176
|
+
let newValue
|
|
177
|
+
let newHeat
|
|
178
|
+
if (this.cacheTemp < value) {
|
|
179
|
+
newValue = 'on'
|
|
180
|
+
newHeat = 'on'
|
|
181
|
+
} else {
|
|
182
|
+
newValue = 'off'
|
|
183
|
+
newHeat = 'off'
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Don't continue if no change needed to device state
|
|
187
|
+
if (newHeat === this.cacheHeat) {
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Set up a one-minute timeout for the plugin to ignore incoming updates
|
|
192
|
+
const timerKey = generateRandomString(5)
|
|
193
|
+
this.updateTimeout = timerKey
|
|
194
|
+
setTimeout(() => {
|
|
195
|
+
if (this.updateTimeout === timerKey) {
|
|
196
|
+
this.updateTimeout = false
|
|
197
|
+
}
|
|
198
|
+
}, 60000)
|
|
199
|
+
|
|
200
|
+
// Send the request to the platform sender function
|
|
201
|
+
await this.platform.sendDeviceUpdate(this.accessory, {
|
|
202
|
+
cmd: 'stateOutlet',
|
|
203
|
+
value: newValue,
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
// Cache and log
|
|
207
|
+
this.cacheHeat = newHeat
|
|
208
|
+
this.accessory.log(`${platformLang.curHeat} [${this.cacheHeat}]`)
|
|
209
|
+
this.service.updateCharacteristic(
|
|
210
|
+
this.hapChar.CurrentHeaterCoolerState,
|
|
211
|
+
this.cacheHeat === 'on' ? 2 : 1,
|
|
212
|
+
)
|
|
213
|
+
} catch (err) {
|
|
214
|
+
// Catch any errors during the process
|
|
215
|
+
this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
|
|
216
|
+
|
|
217
|
+
// Throw a 'no response' error and set a timeout to revert this after 2 seconds
|
|
218
|
+
setTimeout(() => {
|
|
219
|
+
this.service.updateCharacteristic(
|
|
220
|
+
this.hapChar.HeatingThresholdTemperature,
|
|
221
|
+
this.accessory.context.cacheTarget,
|
|
222
|
+
)
|
|
223
|
+
}, 2000)
|
|
224
|
+
throw new this.hapErr(-70402)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async internalCurrentTempUpdate() {
|
|
229
|
+
try {
|
|
230
|
+
// Don't continue if the device is off
|
|
231
|
+
if (this.cacheState === 'off') {
|
|
232
|
+
return
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Check to see if we need to turn on or off
|
|
236
|
+
let newValue
|
|
237
|
+
let newHeat
|
|
238
|
+
if (this.cacheTemp < this.accessory.context.cacheTarget) {
|
|
239
|
+
newValue = 'on'
|
|
240
|
+
newHeat = 'on'
|
|
241
|
+
} else {
|
|
242
|
+
newValue = 'off'
|
|
243
|
+
newHeat = 'off'
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Don't continue if no change needed to device state
|
|
247
|
+
if (newHeat === this.cacheHeat) {
|
|
248
|
+
return
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Set up a one-minute timeout for the plugin to ignore incoming updates
|
|
252
|
+
const timerKey = generateRandomString(5)
|
|
253
|
+
this.updateTimeout = timerKey
|
|
254
|
+
setTimeout(() => {
|
|
255
|
+
if (this.updateTimeout === timerKey) {
|
|
256
|
+
this.updateTimeout = false
|
|
257
|
+
}
|
|
258
|
+
}, 60000)
|
|
259
|
+
|
|
260
|
+
// Send the request to the platform sender function
|
|
261
|
+
await this.platform.sendDeviceUpdate(this.accessory, {
|
|
262
|
+
cmd: 'stateOutlet',
|
|
263
|
+
value: newValue,
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
// Log and cache
|
|
267
|
+
this.cacheHeat = newHeat
|
|
268
|
+
this.accessory.log(`${platformLang.curHeat} [${this.cacheHeat}]`)
|
|
269
|
+
this.service.updateCharacteristic(
|
|
270
|
+
this.hapChar.CurrentHeaterCoolerState,
|
|
271
|
+
this.cacheHeat === 'on' ? 2 : 1,
|
|
272
|
+
)
|
|
273
|
+
} catch (err) {
|
|
274
|
+
// Catch any errors during the process
|
|
275
|
+
this.accessory.logWarn(parseError(err))
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async getTemperature() {
|
|
280
|
+
try {
|
|
281
|
+
// Skip polling if the storage hasn't initialised properly
|
|
282
|
+
if (!this.platform.storageClientData) {
|
|
283
|
+
return
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const newTemp = await this.platform.storageData.getItem(`${this.temperatureSource}_temp`)
|
|
287
|
+
if (newTemp && newTemp !== this.cacheTemp) {
|
|
288
|
+
this.cacheTemp = newTemp
|
|
289
|
+
this.service.updateCharacteristic(this.hapChar.CurrentTemperature, this.cacheTemp)
|
|
290
|
+
this.accessory.eveService.addEntry({ temp: this.cacheTemp })
|
|
291
|
+
this.accessory.log(`${platformLang.curTemp} [${this.cacheTemp}°C]`)
|
|
292
|
+
await this.internalCurrentTempUpdate()
|
|
293
|
+
}
|
|
294
|
+
} catch (err) {
|
|
295
|
+
this.accessory.logWarn(parseError(err))
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
externalUpdate() {}
|
|
300
|
+
}
|
|
@@ -0,0 +1,353 @@
|
|
|
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 (without 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
|
+
// Remove any old light service
|
|
60
|
+
if (this.accessory.getService(this.hapServ.Lightbulb)) {
|
|
61
|
+
this.accessory.removeService(this.accessory.getService(this.hapServ.Lightbulb))
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Remove any old heater service
|
|
65
|
+
if (this.accessory.getService(this.hapServ.HeaterCooler)) {
|
|
66
|
+
this.accessory.removeService(this.accessory.getService(this.hapServ.HeaterCooler))
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Remove any old fan service
|
|
70
|
+
if (this.accessory.getService(this.hapServ.Fan)) {
|
|
71
|
+
this.accessory.removeService(this.accessory.getService(this.hapServ.Fan))
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Add the fan v2 service if it doesn't already exist
|
|
75
|
+
this.service = this.accessory.getService(this.hapServ.Fanv2) || this.accessory.addService(this.hapServ.Fanv2)
|
|
76
|
+
|
|
77
|
+
// Add the set handler to the fan active characteristic
|
|
78
|
+
this.service
|
|
79
|
+
.getCharacteristic(this.hapChar.Active)
|
|
80
|
+
.onSet(async value => this.internalStateUpdate(value))
|
|
81
|
+
this.cacheState = this.service.getCharacteristic(this.hapChar.Active).value === 1 ? 'on' : 'off'
|
|
82
|
+
|
|
83
|
+
// Add the set handler to the fan rotation speed characteristic
|
|
84
|
+
this.service
|
|
85
|
+
.getCharacteristic(this.hapChar.RotationSpeed)
|
|
86
|
+
.setProps({
|
|
87
|
+
minStep: 33,
|
|
88
|
+
validValues: [0, 33, 66, 99],
|
|
89
|
+
})
|
|
90
|
+
.onSet(async value => this.internalSpeedUpdate(value))
|
|
91
|
+
this.cacheSpeed = this.service.getCharacteristic(this.hapChar.RotationSpeed).value
|
|
92
|
+
|
|
93
|
+
// Add the set handler to the heater swing mode characteristic (for oscillation)
|
|
94
|
+
this.service
|
|
95
|
+
.getCharacteristic(this.hapChar.SwingMode)
|
|
96
|
+
.onSet(async value => this.internalSwingUpdate(value))
|
|
97
|
+
this.cacheSwing = this.service.getCharacteristic(this.hapChar.SwingMode).value === 1 ? 'on' : 'off'
|
|
98
|
+
|
|
99
|
+
// Add the set handler to the heater lock characteristic (for oscillation)
|
|
100
|
+
this.service
|
|
101
|
+
.getCharacteristic(this.hapChar.LockPhysicalControls)
|
|
102
|
+
.onSet(async value => this.internalLockUpdate(value))
|
|
103
|
+
this.cacheLock = this.service.getCharacteristic(this.hapChar.LockPhysicalControls).value === 1 ? 'on' : 'off'
|
|
104
|
+
|
|
105
|
+
// Output the customised options to the log
|
|
106
|
+
const opts = JSON.stringify({
|
|
107
|
+
tempReporting: false,
|
|
108
|
+
})
|
|
109
|
+
platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async internalStateUpdate(value) {
|
|
113
|
+
try {
|
|
114
|
+
const newValue = value === 1 ? 'on' : 'off'
|
|
115
|
+
|
|
116
|
+
// Don't continue if the new value is the same as before
|
|
117
|
+
if (this.cacheState === newValue) {
|
|
118
|
+
return
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Send the request to the platform sender function
|
|
122
|
+
await this.platform.sendDeviceUpdate(this.accessory, {
|
|
123
|
+
cmd: 'ptReal',
|
|
124
|
+
value: value ? 'MwEBAAAAAAAAAAAAAAAAAAAAADM=' : 'MwEAAAAAAAAAAAAAAAAAAAAAADI=',
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
// Cache the new state and log if appropriate
|
|
128
|
+
if (this.cacheState !== newValue) {
|
|
129
|
+
this.cacheState = newValue
|
|
130
|
+
this.accessory.log(`${platformLang.curState} [${newValue}]`)
|
|
131
|
+
}
|
|
132
|
+
} catch (err) {
|
|
133
|
+
// Catch any errors during the process
|
|
134
|
+
this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
|
|
135
|
+
|
|
136
|
+
// Throw a 'no response' error and set a timeout to revert this after 2 seconds
|
|
137
|
+
setTimeout(() => {
|
|
138
|
+
this.service.updateCharacteristic(this.hapChar.Active, this.cacheState === 'on' ? 1 : 0)
|
|
139
|
+
}, 2000)
|
|
140
|
+
throw new this.hapErr(-70402)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async internalSwingUpdate(value) {
|
|
145
|
+
try {
|
|
146
|
+
// value === 0 -> swing mode OFF
|
|
147
|
+
// value === 1 -> swing mode ON
|
|
148
|
+
const newValue = value === 1 ? 'on' : 'off'
|
|
149
|
+
|
|
150
|
+
// Don't continue if the new value is the same as before
|
|
151
|
+
if (this.cacheSwing === newValue) {
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Send the request to the platform sender function
|
|
156
|
+
await this.platform.sendDeviceUpdate(this.accessory, {
|
|
157
|
+
cmd: 'ptReal',
|
|
158
|
+
value: value ? 'MxgBAAAAAAAAAAAAAAAAAAAAACo=' : 'MxgAAAAAAAAAAAAAAAAAAAAAACs=',
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
// Cache the new state and log if appropriate
|
|
162
|
+
if (this.cacheSwing !== newValue) {
|
|
163
|
+
this.cacheSwing = newValue
|
|
164
|
+
this.accessory.log(`${platformLang.curSwing} [${newValue}]`)
|
|
165
|
+
}
|
|
166
|
+
} catch (err) {
|
|
167
|
+
// Catch any errors during the process
|
|
168
|
+
this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
|
|
169
|
+
|
|
170
|
+
// Throw a 'no response' error and set a timeout to revert this after 2 seconds
|
|
171
|
+
setTimeout(() => {
|
|
172
|
+
this.service.updateCharacteristic(
|
|
173
|
+
this.hapChar.SwingMode,
|
|
174
|
+
this.cacheSwing === 'on' ? 1 : 0,
|
|
175
|
+
)
|
|
176
|
+
}, 2000)
|
|
177
|
+
throw new this.hapErr(-70402)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async internalLockUpdate(value) {
|
|
182
|
+
try {
|
|
183
|
+
// value === 0 -> child lock OFF
|
|
184
|
+
// value === 1 -> child lock ON
|
|
185
|
+
const newValue = value === 1 ? 'on' : 'off'
|
|
186
|
+
|
|
187
|
+
// Don't continue if the new value is the same as before
|
|
188
|
+
if (this.cacheLock === newValue) {
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Send the request to the platform sender function
|
|
193
|
+
await this.platform.sendDeviceUpdate(this.accessory, {
|
|
194
|
+
cmd: 'ptReal',
|
|
195
|
+
value: value ? 'MxABAAAAAAAAAAAAAAAAAAAAACI=' : 'MxAAAAAAAAAAAAAAAAAAAAAAACM=',
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
// Cache the new state and log if appropriate
|
|
199
|
+
if (this.cacheLock !== newValue) {
|
|
200
|
+
this.cacheLock = newValue
|
|
201
|
+
this.accessory.log(`${platformLang.curLock} [${newValue}]`)
|
|
202
|
+
}
|
|
203
|
+
} catch (err) {
|
|
204
|
+
// Catch any errors during the process
|
|
205
|
+
this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
|
|
206
|
+
|
|
207
|
+
// Throw a 'no response' error and set a timeout to revert this after 2 seconds
|
|
208
|
+
setTimeout(() => {
|
|
209
|
+
this.service.updateCharacteristic(
|
|
210
|
+
this.hapChar.LockPhysicalControls,
|
|
211
|
+
this.cacheLock === 'on' ? 1 : 0,
|
|
212
|
+
)
|
|
213
|
+
}, 2000)
|
|
214
|
+
throw new this.hapErr(-70402)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async internalSpeedUpdate(value) {
|
|
219
|
+
try {
|
|
220
|
+
// The fan is used for the following modes (basically all except Auto):
|
|
221
|
+
// - 0%_: Not sure what to do with this yet
|
|
222
|
+
// - 33%: Low Mode
|
|
223
|
+
// - 66%: Medium Mode
|
|
224
|
+
// - 99%: High Mode
|
|
225
|
+
// If the main heater is turned off then this fan should be turned off too
|
|
226
|
+
// If the main heater is turned on then this fan speed should revert to the current mode
|
|
227
|
+
|
|
228
|
+
// Don't continue if the new value is the same as before
|
|
229
|
+
// If the new speed is 0, the on/off handler should take care of resetting to the speed before (home app only)
|
|
230
|
+
if (this.cacheSpeed === value || value === 0) {
|
|
231
|
+
return
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Send the request to the platform sender function
|
|
235
|
+
await this.platform.sendDeviceUpdate(this.accessory, {
|
|
236
|
+
cmd: 'ptReal',
|
|
237
|
+
value: this.speedCode[value],
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
// Cache the new state and log if appropriate
|
|
241
|
+
if (this.cacheSpeed !== value) {
|
|
242
|
+
this.cacheSpeed = value
|
|
243
|
+
this.accessory.log(`${platformLang.curSpeed} [${this.speedCodeLabel[value]}]`)
|
|
244
|
+
}
|
|
245
|
+
} catch (err) {
|
|
246
|
+
// Catch any errors during the process
|
|
247
|
+
this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
|
|
248
|
+
|
|
249
|
+
// Throw a 'no response' error and set a timeout to revert this after 2 seconds
|
|
250
|
+
setTimeout(() => {
|
|
251
|
+
this.service.updateCharacteristic(this.hapChar.On, this.cacheState === 'on')
|
|
252
|
+
}, 2000)
|
|
253
|
+
throw new this.hapErr(-70402)
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
externalUpdate(params) {
|
|
258
|
+
// Update the active characteristic
|
|
259
|
+
if (params.state && params.state !== this.cacheState) {
|
|
260
|
+
this.cacheState = params.state
|
|
261
|
+
this.service.updateCharacteristic(this.hapChar.Active, this.cacheState === 'on')
|
|
262
|
+
this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Update the current temperature characteristic
|
|
266
|
+
if (hasProperty(params, 'temperature')) {
|
|
267
|
+
const newTemp = nearestHalf(farToCen(params.temperature / 100))
|
|
268
|
+
if (newTemp <= 100) {
|
|
269
|
+
// Device must be one that DOES support ambient temperature
|
|
270
|
+
this.accessory.logWarn('you should enable `tempReporting` in the config for this device')
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Check for some other scene/mode change
|
|
275
|
+
(params.commands || []).forEach((command) => {
|
|
276
|
+
const hexString = base64ToHex(command)
|
|
277
|
+
const hexParts = hexToTwoItems(hexString)
|
|
278
|
+
|
|
279
|
+
// Return now if not a device query update code
|
|
280
|
+
if (getTwoItemPosition(hexParts, 1) !== 'aa') {
|
|
281
|
+
return
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const deviceFunction = `${getTwoItemPosition(hexParts, 2)}${getTwoItemPosition(hexParts, 3)}`
|
|
285
|
+
|
|
286
|
+
switch (deviceFunction) {
|
|
287
|
+
case '1800':
|
|
288
|
+
case '1801': {
|
|
289
|
+
// Swing Mode
|
|
290
|
+
const newSwing = getTwoItemPosition(hexParts, 3) === '01' ? 'on' : 'off'
|
|
291
|
+
if (this.cacheSwing !== newSwing) {
|
|
292
|
+
this.cacheSwing = newSwing
|
|
293
|
+
this.service.updateCharacteristic(this.hapChar.SwingMode, this.cacheSwing === 'on' ? 1 : 0)
|
|
294
|
+
this.accessory.log(`${platformLang.curSwing} [${this.cacheSwing}]`)
|
|
295
|
+
}
|
|
296
|
+
break
|
|
297
|
+
}
|
|
298
|
+
case '1000':
|
|
299
|
+
case '1001': {
|
|
300
|
+
// Child Lock
|
|
301
|
+
const newLock = getTwoItemPosition(hexParts, 3) === '01' ? 'on' : 'off'
|
|
302
|
+
if (this.cacheLock !== newLock) {
|
|
303
|
+
this.cacheLock = newLock
|
|
304
|
+
this.service.updateCharacteristic(this.hapChar.LockPhysicalControls, this.cacheLock === 'on' ? 1 : 0)
|
|
305
|
+
this.accessory.log(`${platformLang.curLock} [${this.cacheLock}]`)
|
|
306
|
+
}
|
|
307
|
+
break
|
|
308
|
+
}
|
|
309
|
+
case '0501': // fan speed low
|
|
310
|
+
case '0502': // fan speed medium
|
|
311
|
+
case '0503': { // fan speed high
|
|
312
|
+
switch (getTwoItemPosition(hexParts, 3)) {
|
|
313
|
+
case '01': {
|
|
314
|
+
// Fan is low
|
|
315
|
+
if (this.cacheSpeed !== 33) {
|
|
316
|
+
this.cacheSpeed = 33
|
|
317
|
+
this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
|
|
318
|
+
this.accessory.log(`${platformLang.curSpeed} [${this.speedCodeLabel[this.cacheSpeed]}]`)
|
|
319
|
+
}
|
|
320
|
+
break
|
|
321
|
+
}
|
|
322
|
+
case '02': {
|
|
323
|
+
// Fan is medium
|
|
324
|
+
if (this.cacheSpeed !== 66) {
|
|
325
|
+
this.cacheSpeed = 66
|
|
326
|
+
this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
|
|
327
|
+
this.accessory.log(`${platformLang.curSpeed} [${this.speedCodeLabel[this.cacheSpeed]}]`)
|
|
328
|
+
}
|
|
329
|
+
break
|
|
330
|
+
}
|
|
331
|
+
case '03': {
|
|
332
|
+
// Fan is high
|
|
333
|
+
if (this.cacheSpeed !== 99) {
|
|
334
|
+
this.cacheSpeed = 99
|
|
335
|
+
this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
|
|
336
|
+
this.accessory.log(`${platformLang.curSpeed} [${this.speedCodeLabel[this.cacheSpeed]}]`)
|
|
337
|
+
}
|
|
338
|
+
break
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
break
|
|
342
|
+
}
|
|
343
|
+
case '1a00': // Target temperature (thermostat mode off)
|
|
344
|
+
case '1a01': { // Target temperature (thermostat mode on)
|
|
345
|
+
break
|
|
346
|
+
}
|
|
347
|
+
default:
|
|
348
|
+
this.accessory.logDebugWarn(`${platformLang.newScene}: [${command}] [${hexString}]`)
|
|
349
|
+
break
|
|
350
|
+
}
|
|
351
|
+
})
|
|
352
|
+
}
|
|
353
|
+
}
|