@homebridge-plugins/homebridge-meross 10.8.0
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 +1346 -0
- package/LICENSE +21 -0
- package/README.md +68 -0
- package/config.schema.json +2066 -0
- package/eslint.config.js +49 -0
- package/lib/connection/http.js +345 -0
- package/lib/connection/mqtt.js +174 -0
- package/lib/device/baby.js +532 -0
- package/lib/device/cooler-single.js +447 -0
- package/lib/device/diffuser.js +730 -0
- package/lib/device/fan.js +530 -0
- package/lib/device/garage-main.js +225 -0
- package/lib/device/garage-single.js +495 -0
- package/lib/device/garage-sub.js +376 -0
- package/lib/device/heater-single.js +445 -0
- package/lib/device/hub-contact.js +56 -0
- package/lib/device/hub-leak.js +86 -0
- package/lib/device/hub-main.js +403 -0
- package/lib/device/hub-sensor.js +115 -0
- package/lib/device/hub-smoke.js +40 -0
- package/lib/device/hub-valve.js +377 -0
- package/lib/device/humidifier.js +521 -0
- package/lib/device/index.js +63 -0
- package/lib/device/light-cct.js +474 -0
- package/lib/device/light-dimmer.js +312 -0
- package/lib/device/light-rgb.js +528 -0
- package/lib/device/outlet-multi.js +383 -0
- package/lib/device/outlet-single.js +405 -0
- package/lib/device/power-strip.js +282 -0
- package/lib/device/purifier-single.js +372 -0
- package/lib/device/purifier.js +403 -0
- package/lib/device/roller-location.js +317 -0
- package/lib/device/roller.js +234 -0
- package/lib/device/sensor-presence.js +201 -0
- package/lib/device/switch-multi.js +403 -0
- package/lib/device/switch-single.js +371 -0
- package/lib/device/template.js +177 -0
- package/lib/device/thermostat.js +493 -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 +316 -0
- package/lib/homebridge-ui/server.js +10 -0
- package/lib/index.js +8 -0
- package/lib/platform.js +1256 -0
- package/lib/utils/colour.js +581 -0
- package/lib/utils/constants.js +377 -0
- package/lib/utils/custom-chars.js +165 -0
- package/lib/utils/eve-chars.js +130 -0
- package/lib/utils/functions.js +39 -0
- package/lib/utils/lang-en.js +114 -0
- package/package.json +70 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import PQueue from 'p-queue'
|
|
2
|
+
import { TimeoutError } from 'p-timeout'
|
|
3
|
+
|
|
4
|
+
import platformConsts from '../utils/constants.js'
|
|
5
|
+
import { hasProperty, parseError } from '../utils/functions.js'
|
|
6
|
+
import platformLang from '../utils/lang-en.js'
|
|
7
|
+
|
|
8
|
+
export default class {
|
|
9
|
+
constructor(platform, accessory, priAcc) {
|
|
10
|
+
// Set up variables from the platform
|
|
11
|
+
this.cusChar = platform.cusChar
|
|
12
|
+
this.hapChar = platform.api.hap.Characteristic
|
|
13
|
+
this.hapErr = platform.api.hap.HapStatusError
|
|
14
|
+
this.hapServ = platform.api.hap.Service
|
|
15
|
+
this.platform = platform
|
|
16
|
+
|
|
17
|
+
// Set up variables from the accessory
|
|
18
|
+
this.accessory = accessory
|
|
19
|
+
this.lowBattThreshold = accessory.context.options.lowBattThreshold
|
|
20
|
+
? Math.min(accessory.context.options.lowBattThreshold, 100)
|
|
21
|
+
: platformConsts.defaultValues.lowBattThreshold
|
|
22
|
+
this.name = accessory.displayName
|
|
23
|
+
this.priAcc = priAcc
|
|
24
|
+
|
|
25
|
+
this.mode2Label = {
|
|
26
|
+
0: 'manual',
|
|
27
|
+
1: 'heat',
|
|
28
|
+
2: 'cool',
|
|
29
|
+
3: 'auto',
|
|
30
|
+
4: 'economy',
|
|
31
|
+
}
|
|
32
|
+
this.mode2Char = {
|
|
33
|
+
0: false,
|
|
34
|
+
1: this.cusChar.ValveHeatMode,
|
|
35
|
+
2: this.cusChar.ValveCoolMode,
|
|
36
|
+
3: this.cusChar.ValveAutoMode,
|
|
37
|
+
4: this.cusChar.ValveEconomyMode,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Add the thermostat service if it doesn't already exist
|
|
41
|
+
this.service = this.accessory.getService(this.hapServ.Thermostat)
|
|
42
|
+
|| this.accessory.addService(this.hapServ.Thermostat)
|
|
43
|
+
|
|
44
|
+
this.service
|
|
45
|
+
.getCharacteristic(this.hapChar.TargetHeatingCoolingState)
|
|
46
|
+
.setProps({
|
|
47
|
+
minValue: 0,
|
|
48
|
+
maxValue: 1,
|
|
49
|
+
validValues: [0, 1],
|
|
50
|
+
})
|
|
51
|
+
.onSet(async value => this.internalStateUpdate(value))
|
|
52
|
+
this.cacheState = this.service.getCharacteristic(this.hapChar.TargetHeatingCoolingState).value
|
|
53
|
+
|
|
54
|
+
this.service
|
|
55
|
+
.getCharacteristic(this.hapChar.TargetTemperature)
|
|
56
|
+
.setProps({
|
|
57
|
+
minValue: 5,
|
|
58
|
+
maxValue: 35,
|
|
59
|
+
minStep: 0.5,
|
|
60
|
+
})
|
|
61
|
+
.onSet(async value => this.internalTargetUpdate(value))
|
|
62
|
+
this.cacheTarg = this.service.getCharacteristic(this.hapChar.TargetTemperature).value
|
|
63
|
+
|
|
64
|
+
this.cacheTemp = this.service.getCharacteristic(this.hapChar.CurrentTemperature).value
|
|
65
|
+
this.updateCache()
|
|
66
|
+
|
|
67
|
+
if (!this.service.testCharacteristic(this.cusChar.ValveHeatMode)) {
|
|
68
|
+
this.service.addCharacteristic(this.cusChar.ValveHeatMode)
|
|
69
|
+
}
|
|
70
|
+
this.service
|
|
71
|
+
.getCharacteristic(this.cusChar.ValveHeatMode)
|
|
72
|
+
.onSet(async value => this.internalModeUpdate(value, 1))
|
|
73
|
+
if (!this.service.testCharacteristic(this.cusChar.ValveCoolMode)) {
|
|
74
|
+
this.service.addCharacteristic(this.cusChar.ValveCoolMode)
|
|
75
|
+
}
|
|
76
|
+
this.service
|
|
77
|
+
.getCharacteristic(this.cusChar.ValveCoolMode)
|
|
78
|
+
.onSet(async value => this.internalModeUpdate(value, 2))
|
|
79
|
+
if (!this.service.testCharacteristic(this.cusChar.ValveAutoMode)) {
|
|
80
|
+
this.service.addCharacteristic(this.cusChar.ValveAutoMode)
|
|
81
|
+
}
|
|
82
|
+
this.service
|
|
83
|
+
.getCharacteristic(this.cusChar.ValveAutoMode)
|
|
84
|
+
.onSet(async value => this.internalModeUpdate(value, 3))
|
|
85
|
+
if (!this.service.testCharacteristic(this.cusChar.ValveEconomyMode)) {
|
|
86
|
+
this.service.addCharacteristic(this.cusChar.ValveEconomyMode)
|
|
87
|
+
}
|
|
88
|
+
this.cacheMode = 0
|
|
89
|
+
this.service
|
|
90
|
+
.getCharacteristic(this.cusChar.ValveEconomyMode)
|
|
91
|
+
.onSet(async value => this.internalModeUpdate(value, 4))
|
|
92
|
+
if (!this.service.testCharacteristic(this.cusChar.ValveWindowOpen)) {
|
|
93
|
+
this.service.addCharacteristic(this.cusChar.ValveWindowOpen)
|
|
94
|
+
}
|
|
95
|
+
this.cacheWindow = this.service.getCharacteristic(this.cusChar.ValveWindowOpen).value
|
|
96
|
+
|
|
97
|
+
// Pass the accessory to Fakegato to set up with Eve
|
|
98
|
+
this.accessory.eveService = new platform.eveService('custom', this.accessory, { log: () => {} })
|
|
99
|
+
|
|
100
|
+
// Create the queue used for sending device requests
|
|
101
|
+
this.updateInProgress = false
|
|
102
|
+
this.queue = new PQueue({
|
|
103
|
+
concurrency: 1,
|
|
104
|
+
interval: 250,
|
|
105
|
+
intervalCap: 1,
|
|
106
|
+
timeout: 10000,
|
|
107
|
+
throwOnTimeout: true,
|
|
108
|
+
})
|
|
109
|
+
this.queue.on('idle', () => {
|
|
110
|
+
this.updateInProgress = false
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
// Output the customised options to the log
|
|
114
|
+
const opts = JSON.stringify({
|
|
115
|
+
connection: this.accessory.context.connection,
|
|
116
|
+
lowBattThreshold: this.lowBattThreshold,
|
|
117
|
+
})
|
|
118
|
+
platform.log('[%s] %s %s.', this.name, platformLang.devInitOpts, opts)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async internalStateUpdate(value) {
|
|
122
|
+
try {
|
|
123
|
+
// Add the request to the queue so updates are sent apart
|
|
124
|
+
await this.queue.add(async () => {
|
|
125
|
+
// Don't continue if the state is the same as before
|
|
126
|
+
if (value === this.cacheState) {
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// This flag stops the plugin from requesting updates while pending on others
|
|
131
|
+
this.updateInProgress = true
|
|
132
|
+
|
|
133
|
+
// Generate the payload and namespace
|
|
134
|
+
const namespace = 'Appliance.Hub.ToggleX'
|
|
135
|
+
const payload = {
|
|
136
|
+
togglex: [
|
|
137
|
+
{
|
|
138
|
+
id: this.accessory.context.subSerialNumber,
|
|
139
|
+
onoff: value ? 1 : 0,
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Use the platform function to send the update to the device
|
|
145
|
+
await this.platform.sendUpdate(this.priAcc, {
|
|
146
|
+
namespace,
|
|
147
|
+
payload,
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
// Update the cache and log the update has been successful
|
|
151
|
+
this.cacheState = value
|
|
152
|
+
|
|
153
|
+
this.accessory.log(`${platformLang.curState} [${value ? 'on' : 'off'}]`)
|
|
154
|
+
})
|
|
155
|
+
} catch (err) {
|
|
156
|
+
// Catch any errors whilst updating the device
|
|
157
|
+
const eText = err instanceof TimeoutError ? platformLang.timeout : parseError(err)
|
|
158
|
+
this.accessory.logWarn(`${platformLang.sendFailed} ${eText}`)
|
|
159
|
+
setTimeout(() => {
|
|
160
|
+
this.service.updateCharacteristic(this.hapChar.TargetHeatingCoolingState, this.cacheState)
|
|
161
|
+
}, 2000)
|
|
162
|
+
throw new this.hapErr(-70402)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async internalModeUpdate(value, newMode) {
|
|
167
|
+
try {
|
|
168
|
+
// If turning off then set to manual mode
|
|
169
|
+
if (!value) {
|
|
170
|
+
newMode = 0
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Add the request to the queue so updates are sent apart
|
|
174
|
+
await this.queue.add(async () => {
|
|
175
|
+
// Don't continue if the state is the same as before
|
|
176
|
+
if (newMode === this.cacheMode) {
|
|
177
|
+
return
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// This flag stops the plugin from requesting updates while pending on others
|
|
181
|
+
this.updateInProgress = true
|
|
182
|
+
|
|
183
|
+
// Generate the payload and namespace
|
|
184
|
+
const namespace = 'Appliance.Hub.Mts100.Mode'
|
|
185
|
+
const payload = {
|
|
186
|
+
mode: [
|
|
187
|
+
{
|
|
188
|
+
id: this.accessory.context.subSerialNumber,
|
|
189
|
+
state: newMode,
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Use the platform function to send the update to the device
|
|
195
|
+
await this.platform.sendUpdate(this.priAcc, {
|
|
196
|
+
namespace,
|
|
197
|
+
payload,
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
// Update the cache and log the update has been successful
|
|
201
|
+
this.cacheState = value
|
|
202
|
+
this.accessory.log(`${platformLang.curMode} [${this.mode2Label[newMode]}]`)
|
|
203
|
+
|
|
204
|
+
// Turn the other modes off
|
|
205
|
+
Object.entries(this.mode2Char).forEach((entry) => {
|
|
206
|
+
const [mode, char] = entry
|
|
207
|
+
if (char && mode !== newMode.toString()) {
|
|
208
|
+
this.service.updateCharacteristic(char, false)
|
|
209
|
+
}
|
|
210
|
+
})
|
|
211
|
+
})
|
|
212
|
+
} catch (err) {
|
|
213
|
+
// Catch any errors whilst updating the device
|
|
214
|
+
const eText = err instanceof TimeoutError ? platformLang.timeout : parseError(err)
|
|
215
|
+
this.accessory.logWarn(`${platformLang.sendFailed} ${eText}`)
|
|
216
|
+
setTimeout(() => {
|
|
217
|
+
this.service.updateCharacteristic(this.mode2Char[newMode], false)
|
|
218
|
+
}, 2000)
|
|
219
|
+
throw new this.hapErr(-70402)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async internalTargetUpdate(value) {
|
|
224
|
+
try {
|
|
225
|
+
// Add the request to the queue so updates are sent apart
|
|
226
|
+
await this.queue.add(async () => {
|
|
227
|
+
// Don't continue if the state is the same as before
|
|
228
|
+
if (value === this.cacheTarg) {
|
|
229
|
+
return
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// This flag stops the plugin from requesting updates while pending on others
|
|
233
|
+
this.updateInProgress = true
|
|
234
|
+
|
|
235
|
+
// Generate the payload and namespace
|
|
236
|
+
const namespace = 'Appliance.Hub.Mts100.Temperature'
|
|
237
|
+
const payload = {
|
|
238
|
+
temperature: [
|
|
239
|
+
{
|
|
240
|
+
custom: value * 10,
|
|
241
|
+
id: this.accessory.context.subSerialNumber,
|
|
242
|
+
},
|
|
243
|
+
],
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Use the platform function to send the update to the device
|
|
247
|
+
await this.platform.sendUpdate(this.priAcc, {
|
|
248
|
+
namespace,
|
|
249
|
+
payload,
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
// Update the cache and log the update has been successful
|
|
253
|
+
this.cacheTarg = value
|
|
254
|
+
this.accessory.log(`${platformLang.curTarg} [${value}°C]`)
|
|
255
|
+
|
|
256
|
+
// Update the current heating state
|
|
257
|
+
this.service.updateCharacteristic(
|
|
258
|
+
this.hapChar.CurrentHeatingCoolingState,
|
|
259
|
+
value > this.cacheTemp ? 1 : 0,
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
// Turn the modes off as back to manual mode
|
|
263
|
+
Object.values(this.mode2Char).forEach((char) => {
|
|
264
|
+
if (char) {
|
|
265
|
+
this.service.updateCharacteristic(char, false)
|
|
266
|
+
}
|
|
267
|
+
})
|
|
268
|
+
})
|
|
269
|
+
} catch (err) {
|
|
270
|
+
// Catch any errors whilst updating the device
|
|
271
|
+
const eText = err instanceof TimeoutError ? platformLang.timeout : parseError(err)
|
|
272
|
+
this.accessory.logWarn(`${platformLang.sendFailed} ${eText}`)
|
|
273
|
+
setTimeout(() => {
|
|
274
|
+
this.service.updateCharacteristic(this.hapChar.TargetTemperature, this.cacheTarg)
|
|
275
|
+
}, 2000)
|
|
276
|
+
throw new this.hapErr(-70402)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
applyUpdate(data) {
|
|
281
|
+
try {
|
|
282
|
+
let needsUpdate = false
|
|
283
|
+
if (hasProperty(data, 'state')) {
|
|
284
|
+
const newState = data.state
|
|
285
|
+
|
|
286
|
+
// Check against the cache and update HomeKit and the cache if needed
|
|
287
|
+
if (this.cacheState !== newState) {
|
|
288
|
+
this.service.updateCharacteristic(this.hapChar.TargetHeatingCoolingState, newState)
|
|
289
|
+
this.cacheState = newState
|
|
290
|
+
this.accessory.log(`${platformLang.curState} [${newState === 1 ? 'on' : 'off'}]`)
|
|
291
|
+
needsUpdate = true
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (hasProperty(data, 'targTemperature')) {
|
|
295
|
+
const newTarg = data.targTemperature
|
|
296
|
+
|
|
297
|
+
// Check against the cache and update HomeKit and the cache if needed
|
|
298
|
+
if (this.cacheTarg !== newTarg) {
|
|
299
|
+
this.service.updateCharacteristic(this.hapChar.TargetTemperature, newTarg)
|
|
300
|
+
this.cacheTarg = newTarg
|
|
301
|
+
this.accessory.log(`${platformLang.curTarg} [${newTarg}°C]`)
|
|
302
|
+
needsUpdate = true
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if (hasProperty(data, 'currTemperature')) {
|
|
306
|
+
const newTemp = data.currTemperature
|
|
307
|
+
|
|
308
|
+
// Check against the cache and update HomeKit and the cache if needed
|
|
309
|
+
if (this.cacheTemp !== newTemp) {
|
|
310
|
+
this.service.updateCharacteristic(this.hapChar.CurrentTemperature, newTemp)
|
|
311
|
+
this.cacheTemp = newTemp
|
|
312
|
+
this.accessory.eveService.addEntry({ temp: newTemp })
|
|
313
|
+
this.accessory.log(`${platformLang.curTemp} [${newTemp}°C]`)
|
|
314
|
+
needsUpdate = true
|
|
315
|
+
|
|
316
|
+
// Update the cache file with the new temperature
|
|
317
|
+
this.updateCache()
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Update the current heating state
|
|
322
|
+
if (needsUpdate) {
|
|
323
|
+
this.service.updateCharacteristic(
|
|
324
|
+
this.hapChar.CurrentHeatingCoolingState,
|
|
325
|
+
this.cacheState === 1 && this.cacheTarg > this.cacheTemp ? 1 : 0,
|
|
326
|
+
)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Todo - data.openWindow and data.mode
|
|
330
|
+
if (hasProperty(data, 'openWindow')) {
|
|
331
|
+
const newWindow = data.openWindow === 1
|
|
332
|
+
|
|
333
|
+
// Check against the cache and update HomeKit and the cache if needed
|
|
334
|
+
if (this.cacheWindow !== newWindow) {
|
|
335
|
+
this.service.updateCharacteristic(this.cusChar.ValveWindowOpen, newWindow)
|
|
336
|
+
this.cacheWindow = newWindow
|
|
337
|
+
this.accessory.log(`${platformLang.curWindow} [${newWindow ? 'open' : 'closed'}]`)
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (hasProperty(data, 'mode')) {
|
|
342
|
+
const newMode = data.mode
|
|
343
|
+
|
|
344
|
+
// Check against the cache and update HomeKit and the cache if needed
|
|
345
|
+
if (this.cacheMode !== newMode) {
|
|
346
|
+
Object.entries(this.mode2Char).forEach((entry) => {
|
|
347
|
+
const [mode, char] = entry
|
|
348
|
+
if (char) {
|
|
349
|
+
this.service.updateCharacteristic(char, mode === newMode.toString())
|
|
350
|
+
}
|
|
351
|
+
})
|
|
352
|
+
this.cacheMode = newMode
|
|
353
|
+
this.accessory.log(`${platformLang.curMode} [${this.mode2Label[newMode]}]`)
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
} catch (err) {
|
|
357
|
+
this.accessory.logWarn(`${platformLang.refFailed} ${parseError(err)}`)
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
async updateCache() {
|
|
362
|
+
// Don't continue if the storage client hasn't initialised properly
|
|
363
|
+
if (!this.platform.storageClientData) {
|
|
364
|
+
return
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Attempt to save the new temperature to the cache
|
|
368
|
+
try {
|
|
369
|
+
await this.platform.storageData.setItem(
|
|
370
|
+
`${this.accessory.context.subSerialNumber}_temp`,
|
|
371
|
+
this.cacheTemp,
|
|
372
|
+
)
|
|
373
|
+
} catch (err) {
|
|
374
|
+
this.accessory.logWarn(`${platformLang.storageWriteErr} ${parseError(err)}`)
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|