@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,730 @@
|
|
|
1
|
+
import PQueue from 'p-queue'
|
|
2
|
+
import { TimeoutError } from 'p-timeout'
|
|
3
|
+
|
|
4
|
+
import mqttClient from '../connection/mqtt.js'
|
|
5
|
+
import {
|
|
6
|
+
hk2mrRGB,
|
|
7
|
+
hs2rgb,
|
|
8
|
+
mr2hkRGB,
|
|
9
|
+
rgb2hs,
|
|
10
|
+
} from '../utils/colour.js'
|
|
11
|
+
import platformConsts from '../utils/constants.js'
|
|
12
|
+
import {
|
|
13
|
+
generateRandomString,
|
|
14
|
+
hasProperty,
|
|
15
|
+
parseError,
|
|
16
|
+
sleep,
|
|
17
|
+
} from '../utils/functions.js'
|
|
18
|
+
import platformLang from '../utils/lang-en.js'
|
|
19
|
+
|
|
20
|
+
export default class {
|
|
21
|
+
constructor(platform, accessory) {
|
|
22
|
+
// Set up variables from the platform
|
|
23
|
+
this.cusChar = platform.cusChar
|
|
24
|
+
this.hapChar = platform.api.hap.Characteristic
|
|
25
|
+
this.hapErr = platform.api.hap.HapStatusError
|
|
26
|
+
this.hapServ = platform.api.hap.Service
|
|
27
|
+
this.platform = platform
|
|
28
|
+
|
|
29
|
+
// Set up variables from the accessory
|
|
30
|
+
this.accessory = accessory
|
|
31
|
+
this.brightnessStep = this.accessory.context.options.brightnessStep || platformConsts.defaultValues.brightnessStep
|
|
32
|
+
this.brightnessStep = Math.min(this.brightnessStep, 100)
|
|
33
|
+
this.name = accessory.displayName
|
|
34
|
+
const cloudRefreshRate = hasProperty(platform.config, 'cloudRefreshRate')
|
|
35
|
+
? platform.config.cloudRefreshRate
|
|
36
|
+
: platformConsts.defaultValues.cloudRefreshRate
|
|
37
|
+
const localRefreshRate = hasProperty(platform.config, 'refreshRate')
|
|
38
|
+
? platform.config.refreshRate
|
|
39
|
+
: platformConsts.defaultValues.refreshRate
|
|
40
|
+
this.pollInterval = accessory.context.connection === 'local'
|
|
41
|
+
? localRefreshRate
|
|
42
|
+
: cloudRefreshRate
|
|
43
|
+
this.hk2mr = (speed) => {
|
|
44
|
+
if (speed === 0) {
|
|
45
|
+
return 2
|
|
46
|
+
}
|
|
47
|
+
if (speed <= 75) {
|
|
48
|
+
return 0
|
|
49
|
+
}
|
|
50
|
+
return 1
|
|
51
|
+
}
|
|
52
|
+
this.hk2Label = (speed) => {
|
|
53
|
+
if (speed === 0) {
|
|
54
|
+
return 'off'
|
|
55
|
+
}
|
|
56
|
+
if (speed <= 75) {
|
|
57
|
+
return 'mild'
|
|
58
|
+
}
|
|
59
|
+
return 'full'
|
|
60
|
+
}
|
|
61
|
+
this.mr2hk = (speed) => {
|
|
62
|
+
if (speed === 0) {
|
|
63
|
+
return 50
|
|
64
|
+
}
|
|
65
|
+
if (speed === 1) {
|
|
66
|
+
return 100
|
|
67
|
+
}
|
|
68
|
+
return 0
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Add the diffuser (fan) service if it doesn't already exist
|
|
72
|
+
this.fanService = this.accessory.getService('Diffuser')
|
|
73
|
+
|| this.accessory.addService(this.hapServ.Fan, 'Diffuser', 'diffuser')
|
|
74
|
+
|
|
75
|
+
// Add the lightbulb service if it doesn't already exist
|
|
76
|
+
this.lightService = this.accessory.getService('Light')
|
|
77
|
+
|| this.accessory.addService(this.hapServ.Lightbulb, 'Light', 'light')
|
|
78
|
+
|
|
79
|
+
// Add the set handler to the diffuser (fan) on/off service
|
|
80
|
+
this.fanService
|
|
81
|
+
.getCharacteristic(this.hapChar.On)
|
|
82
|
+
.onSet(async value => this.internalFanStateUpdate(value))
|
|
83
|
+
this.cacheFanState = this.fanService.getCharacteristic(this.hapChar.On).value
|
|
84
|
+
|
|
85
|
+
this.fanService
|
|
86
|
+
.getCharacteristic(this.hapChar.RotationSpeed)
|
|
87
|
+
.setProps({
|
|
88
|
+
minStep: 50,
|
|
89
|
+
validValues: [0, 50, 100],
|
|
90
|
+
})
|
|
91
|
+
.onSet(async value => this.internalFanSpeedUpdate(value))
|
|
92
|
+
this.cacheFanSpeed = this.hk2mr(
|
|
93
|
+
this.fanService.getCharacteristic(this.hapChar.RotationSpeed).value,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
// Add the set handler to the lightbulb on/off characteristic
|
|
97
|
+
this.lightService
|
|
98
|
+
.getCharacteristic(this.hapChar.On)
|
|
99
|
+
.onSet(async value => this.internalLightStateUpdate(value))
|
|
100
|
+
this.cacheLightState = this.lightService.getCharacteristic(this.hapChar.On).value
|
|
101
|
+
|
|
102
|
+
// Add the set handler to the lightbulb brightness
|
|
103
|
+
this.lightService
|
|
104
|
+
.getCharacteristic(this.hapChar.Brightness)
|
|
105
|
+
.setProps({ minStep: this.brightnessStep })
|
|
106
|
+
.onSet(async value => this.internalLightBrightnessUpdate(value))
|
|
107
|
+
this.cacheLightBright = this.lightService.getCharacteristic(this.hapChar.Brightness).value
|
|
108
|
+
|
|
109
|
+
// Add the set handler to the lightbulb hue characteristic
|
|
110
|
+
this.lightService
|
|
111
|
+
.getCharacteristic(this.hapChar.Hue)
|
|
112
|
+
.onSet(async value => this.internalLightColourUpdate(value))
|
|
113
|
+
this.cacheLightHue = this.lightService.getCharacteristic(this.hapChar.Hue).value
|
|
114
|
+
this.cacheLightSat = this.lightService.getCharacteristic(this.hapChar.Saturation).value
|
|
115
|
+
|
|
116
|
+
// Add the set handler to the diffuser (fan) custom colour mode characteristic
|
|
117
|
+
if (!this.lightService.testCharacteristic(this.cusChar.DiffColourMode)) {
|
|
118
|
+
this.lightService.addCharacteristic(this.cusChar.DiffColourMode)
|
|
119
|
+
}
|
|
120
|
+
this.lightService
|
|
121
|
+
.getCharacteristic(this.cusChar.DiffColourMode)
|
|
122
|
+
.onSet(async value => this.internalLightModeUpdate(value, 'colour'))
|
|
123
|
+
if (this.lightService.getCharacteristic(this.cusChar.DiffColourMode).value) {
|
|
124
|
+
this.cacheLightMode = 1
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Add the set handler to the diffuser (fan) custom rainbow mode characteristic
|
|
128
|
+
if (!this.lightService.testCharacteristic(this.cusChar.DiffRainbowMode)) {
|
|
129
|
+
this.lightService.addCharacteristic(this.cusChar.DiffRainbowMode)
|
|
130
|
+
}
|
|
131
|
+
this.lightService
|
|
132
|
+
.getCharacteristic(this.cusChar.DiffRainbowMode)
|
|
133
|
+
.onSet(async value => this.internalLightModeUpdate(value, 'rainbow'))
|
|
134
|
+
if (this.lightService.getCharacteristic(this.cusChar.DiffRainbowMode).value) {
|
|
135
|
+
this.cacheLightMode = 0
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Add the set handler to the diffuser (fan) custom temperature mode characteristic
|
|
139
|
+
if (!this.lightService.testCharacteristic(this.cusChar.DiffTemperatureMode)) {
|
|
140
|
+
this.lightService.addCharacteristic(this.cusChar.DiffTemperatureMode)
|
|
141
|
+
}
|
|
142
|
+
this.lightService
|
|
143
|
+
.getCharacteristic(this.cusChar.DiffTemperatureMode)
|
|
144
|
+
.onSet(async value => this.internalLightModeUpdate(value, 'temperature'))
|
|
145
|
+
if (this.lightService.getCharacteristic(this.cusChar.DiffTemperatureMode).value) {
|
|
146
|
+
this.cacheLightMode = 2
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Create the queue used for sending device requests
|
|
150
|
+
this.updateInProgress = false
|
|
151
|
+
this.queue = new PQueue({
|
|
152
|
+
concurrency: 1,
|
|
153
|
+
interval: 250,
|
|
154
|
+
intervalCap: 1,
|
|
155
|
+
timeout: 10000,
|
|
156
|
+
throwOnTimeout: true,
|
|
157
|
+
})
|
|
158
|
+
this.queue.on('idle', () => {
|
|
159
|
+
this.updateInProgress = false
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
// Set up the mqtt client for cloud devices to send and receive device updates
|
|
163
|
+
if (accessory.context.connection !== 'local') {
|
|
164
|
+
this.accessory.mqtt = new mqttClient(platform, this.accessory)
|
|
165
|
+
this.accessory.mqtt.connect()
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Always request a device update on startup, then start the interval for polling
|
|
169
|
+
setTimeout(() => this.requestUpdate(true), 2000)
|
|
170
|
+
this.accessory.refreshInterval = setInterval(
|
|
171
|
+
() => this.requestUpdate(),
|
|
172
|
+
this.pollInterval * 1000,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
// Output the customised options to the log
|
|
176
|
+
const opts = JSON.stringify({
|
|
177
|
+
brightnessStep: this.brightnessStep,
|
|
178
|
+
connection: this.accessory.context.connection,
|
|
179
|
+
})
|
|
180
|
+
platform.log('[%s] %s %s.', this.name, platformLang.devInitOpts, opts)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async internalFanStateUpdate(value) {
|
|
184
|
+
try {
|
|
185
|
+
// Add the request to the queue so updates are sent apart
|
|
186
|
+
await this.queue.add(async () => {
|
|
187
|
+
// Don't continue if the state is the same as before
|
|
188
|
+
if (value === this.cacheFanState) {
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// This flag stops the plugin from requesting updates while pending on others
|
|
193
|
+
this.updateInProgress = true
|
|
194
|
+
|
|
195
|
+
// Generate the payload and namespace
|
|
196
|
+
const namespace = 'Appliance.Control.Diffuser.Spray'
|
|
197
|
+
const payload = {
|
|
198
|
+
type: 'mod100',
|
|
199
|
+
spray: [
|
|
200
|
+
{
|
|
201
|
+
mode: value ? this.cacheFanSpeed : 2,
|
|
202
|
+
channel: 0,
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Use the platform function to send the update to the device
|
|
208
|
+
await this.platform.sendUpdate(this.accessory, {
|
|
209
|
+
namespace,
|
|
210
|
+
payload,
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
// Update the cache and log the update has been successful
|
|
214
|
+
this.cacheFanState = value
|
|
215
|
+
|
|
216
|
+
this.accessory.log(`${platformLang.curDiffState} [${value ? 'on' : 'off'}]`)
|
|
217
|
+
})
|
|
218
|
+
} catch (err) {
|
|
219
|
+
// Catch any errors whilst updating the device
|
|
220
|
+
const eText = err instanceof TimeoutError ? platformLang.timeout : parseError(err)
|
|
221
|
+
this.accessory.logWarn(`${platformLang.sendFailed} ${eText}`)
|
|
222
|
+
setTimeout(() => {
|
|
223
|
+
this.fanService.updateCharacteristic(this.hapChar.On, this.cacheFanState)
|
|
224
|
+
}, 2000)
|
|
225
|
+
throw new this.hapErr(-70402)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async internalFanSpeedUpdate(value) {
|
|
230
|
+
try {
|
|
231
|
+
// Add the request to the queue so updates are sent apart
|
|
232
|
+
await this.queue.add(async () => {
|
|
233
|
+
// Some homekit apps might not support the valid values of 0, 50 and 100
|
|
234
|
+
if (value === 0) {
|
|
235
|
+
value = 0
|
|
236
|
+
} else if (value <= 75) {
|
|
237
|
+
value = 50
|
|
238
|
+
} else {
|
|
239
|
+
value = 100
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Don't continue if the state is the same as before
|
|
243
|
+
const mrVal = this.hk2mr(value)
|
|
244
|
+
if (mrVal === this.cacheFanSpeed) {
|
|
245
|
+
return
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// This flag stops the plugin from requesting updates while pending on others
|
|
249
|
+
this.updateInProgress = true
|
|
250
|
+
|
|
251
|
+
// Generate the payload and namespace
|
|
252
|
+
const namespace = 'Appliance.Control.Diffuser.Spray'
|
|
253
|
+
const payload = {
|
|
254
|
+
type: 'mod100',
|
|
255
|
+
spray: [
|
|
256
|
+
{
|
|
257
|
+
mode: mrVal,
|
|
258
|
+
channel: 0,
|
|
259
|
+
},
|
|
260
|
+
],
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Use the platform function to send the update to the device
|
|
264
|
+
await this.platform.sendUpdate(this.accessory, {
|
|
265
|
+
namespace,
|
|
266
|
+
payload,
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
// If using the slider to turn off then set the rotation speed back to original value
|
|
270
|
+
// This stops homekit turning back to 100% if using the icon after turned off
|
|
271
|
+
if (value === 0) {
|
|
272
|
+
// Update the rotation speed back to the previous value (with the fan still off)
|
|
273
|
+
setTimeout(() => {
|
|
274
|
+
this.fanService.updateCharacteristic(
|
|
275
|
+
this.hapChar.RotationSpeed,
|
|
276
|
+
this.mr2hk(this.cacheFanSpeed),
|
|
277
|
+
)
|
|
278
|
+
}, 2000)
|
|
279
|
+
} else {
|
|
280
|
+
// Update the cache and log the update has been successful
|
|
281
|
+
this.cacheFanSpeed = mrVal
|
|
282
|
+
this.accessory.log(`${platformLang.curDiffSpray} [${this.hk2Label(value)}]`)
|
|
283
|
+
}
|
|
284
|
+
})
|
|
285
|
+
} catch (err) {
|
|
286
|
+
// Catch any errors whilst updating the device
|
|
287
|
+
const eText = err instanceof TimeoutError ? platformLang.timeout : parseError(err)
|
|
288
|
+
this.accessory.logWarn(`${platformLang.sendFailed} ${eText}`)
|
|
289
|
+
setTimeout(() => {
|
|
290
|
+
this.fanService.updateCharacteristic(
|
|
291
|
+
this.hapChar.RotationSpeed,
|
|
292
|
+
this.mr2hk(this.cacheFanSpeed),
|
|
293
|
+
)
|
|
294
|
+
}, 2000)
|
|
295
|
+
throw new this.hapErr(-70402)
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async internalLightStateUpdate(value) {
|
|
300
|
+
try {
|
|
301
|
+
// Add the request to the queue so updates are sent apart
|
|
302
|
+
await this.queue.add(async () => {
|
|
303
|
+
// Don't continue if the state is the same as before
|
|
304
|
+
if (value === this.cacheLightState) {
|
|
305
|
+
return
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// This flag stops the plugin from requesting updates while pending on others
|
|
309
|
+
this.updateInProgress = true
|
|
310
|
+
|
|
311
|
+
// Generate the payload and namespace
|
|
312
|
+
const namespace = 'Appliance.Control.Diffuser.Light'
|
|
313
|
+
const payload = {
|
|
314
|
+
type: 'mod100',
|
|
315
|
+
light: [
|
|
316
|
+
{
|
|
317
|
+
onoff: value ? 1 : 0,
|
|
318
|
+
channel: 0,
|
|
319
|
+
},
|
|
320
|
+
],
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Use the platform function to send the update to the device
|
|
324
|
+
await this.platform.sendUpdate(this.accessory, {
|
|
325
|
+
namespace,
|
|
326
|
+
payload,
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
// Update the cache and log the update has been successful
|
|
330
|
+
this.cacheLightState = value
|
|
331
|
+
|
|
332
|
+
this.accessory.log(`${platformLang.curLightState} [${value ? 'on' : 'off'}]`)
|
|
333
|
+
})
|
|
334
|
+
} catch (err) {
|
|
335
|
+
// Catch any errors whilst updating the device
|
|
336
|
+
const eText = err instanceof TimeoutError ? platformLang.timeout : parseError(err)
|
|
337
|
+
this.accessory.logWarn(`${platformLang.sendFailed} ${eText}`)
|
|
338
|
+
setTimeout(() => {
|
|
339
|
+
this.lightService.updateCharacteristic(this.hapChar.On, this.cacheLightState)
|
|
340
|
+
}, 2000)
|
|
341
|
+
throw new this.hapErr(-70402)
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async internalLightBrightnessUpdate(value) {
|
|
346
|
+
try {
|
|
347
|
+
// Add the request to the queue so updates are sent apart
|
|
348
|
+
await this.queue.add(async () => {
|
|
349
|
+
// Don't continue if the state is the same as before
|
|
350
|
+
if (this.cacheLightBright === value) {
|
|
351
|
+
return
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Avoid multiple changes in short space of time
|
|
355
|
+
const updateKey = generateRandomString(5)
|
|
356
|
+
this.updateKeyBright = updateKey
|
|
357
|
+
await sleep(300)
|
|
358
|
+
if (updateKey !== this.updateKeyBright) {
|
|
359
|
+
return
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// This flag stops the plugin from requesting updates while pending on others
|
|
363
|
+
this.updateInProgress = true
|
|
364
|
+
|
|
365
|
+
// Generate the payload to send for the correct device model
|
|
366
|
+
const namespace = 'Appliance.Control.Diffuser.Light'
|
|
367
|
+
const payload = {
|
|
368
|
+
type: 'mod100',
|
|
369
|
+
light: [
|
|
370
|
+
{
|
|
371
|
+
luminance: value,
|
|
372
|
+
channel: 0,
|
|
373
|
+
},
|
|
374
|
+
],
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Use the platform function to send the update to the device
|
|
378
|
+
await this.platform.sendUpdate(this.accessory, {
|
|
379
|
+
namespace,
|
|
380
|
+
payload,
|
|
381
|
+
})
|
|
382
|
+
|
|
383
|
+
// Update the cache and log the update has been successful
|
|
384
|
+
this.cacheLightBright = value
|
|
385
|
+
this.accessory.log(`${platformLang.curLightBright} [${value}%]`)
|
|
386
|
+
})
|
|
387
|
+
} catch (err) {
|
|
388
|
+
const eText = parseError(err)
|
|
389
|
+
this.accessory.logWarn(`${platformLang.sendFailed} ${eText}`)
|
|
390
|
+
setTimeout(() => {
|
|
391
|
+
this.lightService.updateCharacteristic(this.hapChar.Brightness, this.cacheLightBright)
|
|
392
|
+
}, 2000)
|
|
393
|
+
throw new this.hapErr(-70402)
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
async internalLightColourUpdate(value) {
|
|
398
|
+
try {
|
|
399
|
+
// Add the request to the queue so updates are sent apart
|
|
400
|
+
await this.queue.add(async () => {
|
|
401
|
+
// Don't continue if the state is the same as before
|
|
402
|
+
if (this.cacheLightHue === value) {
|
|
403
|
+
return
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Avoid multiple changes in short space of time
|
|
407
|
+
const updateKey = generateRandomString(5)
|
|
408
|
+
this.updateKeyColour = updateKey
|
|
409
|
+
await sleep(300)
|
|
410
|
+
if (updateKey !== this.updateKeyColour) {
|
|
411
|
+
return
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// This flag stops the plugin from requesting updates while pending on others
|
|
415
|
+
this.updateInProgress = true
|
|
416
|
+
|
|
417
|
+
// Convert to RGB
|
|
418
|
+
const saturation = this.lightService.getCharacteristic(this.hapChar.Saturation).value
|
|
419
|
+
const [r, g, b] = hs2rgb(value, saturation)
|
|
420
|
+
|
|
421
|
+
// Generate the payload to send
|
|
422
|
+
const namespace = 'Appliance.Control.Diffuser.Light'
|
|
423
|
+
const payload = {
|
|
424
|
+
type: 'mod100',
|
|
425
|
+
light: [
|
|
426
|
+
{
|
|
427
|
+
rgb: hk2mrRGB(r, g, b),
|
|
428
|
+
mode: 1,
|
|
429
|
+
channel: 0,
|
|
430
|
+
},
|
|
431
|
+
],
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Use the platform function to send the update to the device
|
|
435
|
+
await this.platform.sendUpdate(this.accessory, {
|
|
436
|
+
namespace,
|
|
437
|
+
payload,
|
|
438
|
+
})
|
|
439
|
+
|
|
440
|
+
// Update the cache and log the update has been successful
|
|
441
|
+
this.cacheLightHue = value
|
|
442
|
+
this.cacheLightSat = this.lightService.getCharacteristic(this.hapChar.Saturation).value
|
|
443
|
+
this.accessory.log(`${platformLang.curLightColour} [${this.cacheLightHue}, ${this.cacheLightSat}] / [${r}, ${g}, ${b}]`)
|
|
444
|
+
})
|
|
445
|
+
} catch (err) {
|
|
446
|
+
const eText = parseError(err)
|
|
447
|
+
this.accessory.logWarn(`${platformLang.sendFailed} ${eText}`)
|
|
448
|
+
setTimeout(() => {
|
|
449
|
+
this.lightService.updateCharacteristic(this.hapChar.Hue, this.cacheLightHue)
|
|
450
|
+
}, 2000)
|
|
451
|
+
throw new this.hapErr(-70402)
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
async internalLightModeUpdate(value, mode) {
|
|
456
|
+
try {
|
|
457
|
+
// Add the request to the queue so updates are sent apart
|
|
458
|
+
await this.queue.add(async () => {
|
|
459
|
+
// Don't continue if we are turning off, can't leave a mode without selecting another
|
|
460
|
+
if (!value) {
|
|
461
|
+
return
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// This flag stops the plugin from requesting updates while pending on others
|
|
465
|
+
this.updateInProgress = true
|
|
466
|
+
|
|
467
|
+
// Get the mode value from the mode selected
|
|
468
|
+
let modeVal
|
|
469
|
+
switch (mode) {
|
|
470
|
+
case 'colour':
|
|
471
|
+
modeVal = 1
|
|
472
|
+
break
|
|
473
|
+
case 'rainbow':
|
|
474
|
+
modeVal = 0
|
|
475
|
+
break
|
|
476
|
+
case 'temperature':
|
|
477
|
+
modeVal = 2
|
|
478
|
+
break
|
|
479
|
+
default:
|
|
480
|
+
// Should never happen
|
|
481
|
+
return
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Generate the payload and namespace
|
|
485
|
+
const namespace = 'Appliance.Control.Diffuser.Light'
|
|
486
|
+
const payload = {
|
|
487
|
+
type: 'mod100',
|
|
488
|
+
light: [
|
|
489
|
+
{
|
|
490
|
+
mode: modeVal,
|
|
491
|
+
channel: 0,
|
|
492
|
+
},
|
|
493
|
+
],
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Use the platform function to send the update to the device
|
|
497
|
+
await this.platform.sendUpdate(this.accessory, {
|
|
498
|
+
namespace,
|
|
499
|
+
payload,
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
// Turn all the mode characteristics OFF, the needed one will turn ON at end of this func
|
|
503
|
+
['DiffColourMode', 'DiffRainbowMode', 'DiffTemperatureMode'].forEach((cusChar) => {
|
|
504
|
+
this.lightService.updateCharacteristic(this.cusChar[cusChar], false)
|
|
505
|
+
})
|
|
506
|
+
|
|
507
|
+
// Update the cache and log the update has been successful
|
|
508
|
+
this.cacheLightMode = modeVal
|
|
509
|
+
this.accessory.log(`${platformLang.curLightMode} [${mode}]`)
|
|
510
|
+
})
|
|
511
|
+
} catch (err) {
|
|
512
|
+
// Catch any errors whilst updating the device
|
|
513
|
+
const eText = err instanceof TimeoutError ? platformLang.timeout : parseError(err)
|
|
514
|
+
this.accessory.logWarn(`${platformLang.sendFailed} ${eText}`)
|
|
515
|
+
let charToError
|
|
516
|
+
switch (mode) {
|
|
517
|
+
case 'colour':
|
|
518
|
+
charToError = this.cusChar.DiffColourMode
|
|
519
|
+
break
|
|
520
|
+
case 'rainbow':
|
|
521
|
+
charToError = this.cusChar.DiffRainbowMode
|
|
522
|
+
break
|
|
523
|
+
case 'temperature':
|
|
524
|
+
charToError = this.cusChar.DiffTemperatureMode
|
|
525
|
+
break
|
|
526
|
+
default:
|
|
527
|
+
// Should never happen
|
|
528
|
+
return
|
|
529
|
+
}
|
|
530
|
+
setTimeout(() => {
|
|
531
|
+
this.lightService.updateCharacteristic(charToError, false)
|
|
532
|
+
}, 2000)
|
|
533
|
+
throw new this.hapErr(-70402)
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
async requestUpdate(firstRun = false) {
|
|
538
|
+
try {
|
|
539
|
+
// Don't continue if an update is currently being sent to the device
|
|
540
|
+
if (this.updateInProgress) {
|
|
541
|
+
return
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Add the request to the queue so updates are sent apart
|
|
545
|
+
await this.queue.add(async () => {
|
|
546
|
+
// This flag stops the plugin from requesting updates while pending on others
|
|
547
|
+
this.updateInProgress = true
|
|
548
|
+
|
|
549
|
+
// Send the request
|
|
550
|
+
const res = await this.platform.sendUpdate(this.accessory, {
|
|
551
|
+
namespace: 'Appliance.System.All',
|
|
552
|
+
payload: {},
|
|
553
|
+
})
|
|
554
|
+
|
|
555
|
+
// Log the received data
|
|
556
|
+
this.accessory.logDebug(`${platformLang.incPoll}: ${JSON.stringify(res.data)}`)
|
|
557
|
+
|
|
558
|
+
// Check the response is in a useful format
|
|
559
|
+
const data = res.data.payload
|
|
560
|
+
|
|
561
|
+
if (data.all) {
|
|
562
|
+
if (data.all.digest && data.all.digest.diffuser) {
|
|
563
|
+
this.applyUpdate(data.all.digest.diffuser)
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// A flag to check if we need to update the accessory context
|
|
567
|
+
let needsUpdate = false
|
|
568
|
+
|
|
569
|
+
// Get the mac address and hardware version of the device
|
|
570
|
+
if (data.all.system) {
|
|
571
|
+
// Mac address and hardware don't change regularly so only get on first poll
|
|
572
|
+
if (firstRun && data.all.system.hardware) {
|
|
573
|
+
this.accessory.context.macAddress = data.all.system.hardware.macAddress.toUpperCase()
|
|
574
|
+
this.accessory.context.hardware = data.all.system.hardware.version
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// Get the ip address and firmware of the device
|
|
578
|
+
if (data.all.system.firmware) {
|
|
579
|
+
// Check for an IP change each and every time the device is polled
|
|
580
|
+
if (this.accessory.context.ipAddress !== data.all.system.firmware.innerIp) {
|
|
581
|
+
this.accessory.context.ipAddress = data.all.system.firmware.innerIp
|
|
582
|
+
needsUpdate = true
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Firmware doesn't change regularly so only get on first poll
|
|
586
|
+
if (firstRun) {
|
|
587
|
+
this.accessory.context.firmware = data.all.system.firmware.version
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Get the cloud online status of the device
|
|
593
|
+
if (data.all.system.online) {
|
|
594
|
+
const isOnline = data.all.system.online.status === 1
|
|
595
|
+
if (this.accessory.context.isOnline !== isOnline) {
|
|
596
|
+
this.accessory.context.isOnline = isOnline
|
|
597
|
+
needsUpdate = true
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Update the accessory cache if anything has changed
|
|
602
|
+
if (needsUpdate || firstRun) {
|
|
603
|
+
this.platform.updateAccessory(this.accessory)
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
})
|
|
607
|
+
} catch (err) {
|
|
608
|
+
const eText = err instanceof TimeoutError ? platformLang.timeout : parseError(err)
|
|
609
|
+
this.accessory.logDebugWarn(`${platformLang.reqFailed}: ${eText}`)
|
|
610
|
+
|
|
611
|
+
// Set the homebridge-ui status of the device to offline if local and error is timeout
|
|
612
|
+
if (
|
|
613
|
+
(this.accessory.context.isOnline || firstRun)
|
|
614
|
+
&& ['EHOSTUNREACH', 'timed out'].some(el => eText.includes(el))
|
|
615
|
+
) {
|
|
616
|
+
this.accessory.context.isOnline = false
|
|
617
|
+
this.platform.updateAccessory(this.accessory)
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
receiveUpdate(params) {
|
|
623
|
+
try {
|
|
624
|
+
// Log the received data
|
|
625
|
+
this.accessory.logDebug(`${platformLang.incMQTT}: ${JSON.stringify(params)}`)
|
|
626
|
+
|
|
627
|
+
// Check the response is in a useful format
|
|
628
|
+
const data = params.payload
|
|
629
|
+
if (data.light || data.spray) {
|
|
630
|
+
this.applyUpdate(data)
|
|
631
|
+
}
|
|
632
|
+
} catch (err) {
|
|
633
|
+
this.accessory.logWarn(`${platformLang.refFailed} ${parseError(err)}`)
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
applyUpdate(data) {
|
|
638
|
+
// Update the diffuser (fan) service from the supplied data
|
|
639
|
+
if (data.spray && data.spray[0] && hasProperty(data.spray[0], 'mode')) {
|
|
640
|
+
const newSpeed = data.spray[0].mode
|
|
641
|
+
const newState = newSpeed !== 2
|
|
642
|
+
|
|
643
|
+
// Check against the cache and update HomeKit and the cache if needed
|
|
644
|
+
if (this.cacheFanState !== newState || this.cacheFanSpeed !== newSpeed) {
|
|
645
|
+
this.cacheFanState = newState
|
|
646
|
+
this.cacheFanSpeed = newSpeed
|
|
647
|
+
if (this.cacheFanState) {
|
|
648
|
+
// Looks like the spray is now on (from OFF or a different mode)
|
|
649
|
+
this.cacheFanState = true
|
|
650
|
+
this.fanService.updateCharacteristic(this.hapChar.On, true)
|
|
651
|
+
this.accessory.log(`${platformLang.curDiffState} [on]`)
|
|
652
|
+
const hkValue = this.mr2hk(this.cacheFanSpeed)
|
|
653
|
+
this.fanService.updateCharacteristic(this.hapChar.RotationSpeed, hkValue)
|
|
654
|
+
this.accessory.log(`${platformLang.curDiffSpray} [${this.hk2Label(hkValue)}]`)
|
|
655
|
+
} else {
|
|
656
|
+
// Looks like the spray has been turned off
|
|
657
|
+
this.fanService.updateCharacteristic(this.hapChar.On, false)
|
|
658
|
+
this.accessory.log(`${platformLang.curDiffState} [off]`)
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// Update the light from the supplied data
|
|
664
|
+
if (data.light && data.light[0]) {
|
|
665
|
+
if (hasProperty(data.light[0], 'onoff')) {
|
|
666
|
+
const newState = data.light[0].onoff === 1
|
|
667
|
+
|
|
668
|
+
// Check against the cache and update HomeKit and the cache if needed
|
|
669
|
+
if (this.cacheLightState !== newState) {
|
|
670
|
+
this.lightService.updateCharacteristic(this.hapChar.On, newState)
|
|
671
|
+
this.cacheLightState = newState
|
|
672
|
+
this.accessory.log(`${platformLang.curLightState} [${this.cacheLightState ? 'on' : 'off'}]`)
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
if (hasProperty(data.light[0], 'luminance')) {
|
|
676
|
+
const newBright = data.light[0].luminance
|
|
677
|
+
|
|
678
|
+
// Check against the cache and update HomeKit and the cache if needed
|
|
679
|
+
if (this.cacheBright !== newBright) {
|
|
680
|
+
this.lightService.updateCharacteristic(this.hapChar.Brightness, newBright)
|
|
681
|
+
this.cacheLightBright = newBright
|
|
682
|
+
this.accessory.log(`${platformLang.curLightBright} [${this.cacheLightBright}%]`)
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
if (data.light[0].mode === 0) {
|
|
686
|
+
if (!this.cacheLightMode !== 0) {
|
|
687
|
+
this.cacheLightMode = 0;
|
|
688
|
+
['DiffColourMode', 'DiffTemperatureMode'].forEach((cusChar) => {
|
|
689
|
+
this.lightService.updateCharacteristic(this.cusChar[cusChar], false)
|
|
690
|
+
})
|
|
691
|
+
this.lightService.updateCharacteristic(this.cusChar.DiffRainbowMode, true)
|
|
692
|
+
this.accessory.log(`${platformLang.curLightMode} [rainbow]`)
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
if (data.light[0].mode === 1) {
|
|
696
|
+
if (!this.cacheLightMode !== 1) {
|
|
697
|
+
this.cacheLightMode = 1;
|
|
698
|
+
['DiffRainbowMode', 'DiffTemperatureMode'].forEach((cusChar) => {
|
|
699
|
+
this.lightService.updateCharacteristic(this.cusChar[cusChar], false)
|
|
700
|
+
})
|
|
701
|
+
this.lightService.updateCharacteristic(this.cusChar.DiffColourMode, true)
|
|
702
|
+
this.accessory.log(`${platformLang.curLightMode} [colour]`)
|
|
703
|
+
}
|
|
704
|
+
if (hasProperty(data.light[0], 'rgb')) {
|
|
705
|
+
const [r, g, b] = mr2hkRGB(data.light[0].rgb)
|
|
706
|
+
const [newHue, newSat] = rgb2hs(r, g, b)
|
|
707
|
+
|
|
708
|
+
// Check against the cache and update HomeKit and the cache if needed
|
|
709
|
+
if (this.cacheLightHue !== newHue || this.cacheLightSat !== newSat) {
|
|
710
|
+
this.lightService.updateCharacteristic(this.hapChar.Hue, newHue)
|
|
711
|
+
this.lightService.updateCharacteristic(this.hapChar.Saturation, newSat)
|
|
712
|
+
this.cacheLightHue = newHue
|
|
713
|
+
this.cacheLightSat = newSat
|
|
714
|
+
this.accessory.log(`${platformLang.curLightColour} [${this.cacheLightHue}, ${this.cacheLightSat}] / [${r}, ${g}, ${b}]`)
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
if (data.light[0].mode === 2) {
|
|
719
|
+
if (!this.cacheLightMode !== 2) {
|
|
720
|
+
this.cacheLightMode = 2;
|
|
721
|
+
['DiffColourMode', 'DiffRainbowMode'].forEach((cusChar) => {
|
|
722
|
+
this.lightService.updateCharacteristic(this.cusChar[cusChar], false)
|
|
723
|
+
})
|
|
724
|
+
this.lightService.updateCharacteristic(this.cusChar.DiffTemperatureMode, true)
|
|
725
|
+
this.accessory.log(`${platformLang.curLightMode} [temperature]`)
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|