@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.
Files changed (54) hide show
  1. package/CHANGELOG.md +1346 -0
  2. package/LICENSE +21 -0
  3. package/README.md +68 -0
  4. package/config.schema.json +2066 -0
  5. package/eslint.config.js +49 -0
  6. package/lib/connection/http.js +345 -0
  7. package/lib/connection/mqtt.js +174 -0
  8. package/lib/device/baby.js +532 -0
  9. package/lib/device/cooler-single.js +447 -0
  10. package/lib/device/diffuser.js +730 -0
  11. package/lib/device/fan.js +530 -0
  12. package/lib/device/garage-main.js +225 -0
  13. package/lib/device/garage-single.js +495 -0
  14. package/lib/device/garage-sub.js +376 -0
  15. package/lib/device/heater-single.js +445 -0
  16. package/lib/device/hub-contact.js +56 -0
  17. package/lib/device/hub-leak.js +86 -0
  18. package/lib/device/hub-main.js +403 -0
  19. package/lib/device/hub-sensor.js +115 -0
  20. package/lib/device/hub-smoke.js +40 -0
  21. package/lib/device/hub-valve.js +377 -0
  22. package/lib/device/humidifier.js +521 -0
  23. package/lib/device/index.js +63 -0
  24. package/lib/device/light-cct.js +474 -0
  25. package/lib/device/light-dimmer.js +312 -0
  26. package/lib/device/light-rgb.js +528 -0
  27. package/lib/device/outlet-multi.js +383 -0
  28. package/lib/device/outlet-single.js +405 -0
  29. package/lib/device/power-strip.js +282 -0
  30. package/lib/device/purifier-single.js +372 -0
  31. package/lib/device/purifier.js +403 -0
  32. package/lib/device/roller-location.js +317 -0
  33. package/lib/device/roller.js +234 -0
  34. package/lib/device/sensor-presence.js +201 -0
  35. package/lib/device/switch-multi.js +403 -0
  36. package/lib/device/switch-single.js +371 -0
  37. package/lib/device/template.js +177 -0
  38. package/lib/device/thermostat.js +493 -0
  39. package/lib/fakegato/LICENSE +21 -0
  40. package/lib/fakegato/fakegato-history.js +814 -0
  41. package/lib/fakegato/fakegato-storage.js +108 -0
  42. package/lib/fakegato/fakegato-timer.js +125 -0
  43. package/lib/fakegato/uuid.js +27 -0
  44. package/lib/homebridge-ui/public/index.html +316 -0
  45. package/lib/homebridge-ui/server.js +10 -0
  46. package/lib/index.js +8 -0
  47. package/lib/platform.js +1256 -0
  48. package/lib/utils/colour.js +581 -0
  49. package/lib/utils/constants.js +377 -0
  50. package/lib/utils/custom-chars.js +165 -0
  51. package/lib/utils/eve-chars.js +130 -0
  52. package/lib/utils/functions.js +39 -0
  53. package/lib/utils/lang-en.js +114 -0
  54. package/package.json +70 -0
@@ -0,0 +1,521 @@
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.hapChar = platform.api.hap.Characteristic
24
+ this.hapErr = platform.api.hap.HapStatusError
25
+ this.hapServ = platform.api.hap.Service
26
+ this.platform = platform
27
+
28
+ // Set up variables from the accessory
29
+ this.accessory = accessory
30
+ this.name = accessory.displayName
31
+ const cloudRefreshRate = hasProperty(platform.config, 'cloudRefreshRate')
32
+ ? platform.config.cloudRefreshRate
33
+ : platformConsts.defaultValues.cloudRefreshRate
34
+ const localRefreshRate = hasProperty(platform.config, 'refreshRate')
35
+ ? platform.config.refreshRate
36
+ : platformConsts.defaultValues.refreshRate
37
+ this.pollInterval = accessory.context.connection === 'local'
38
+ ? localRefreshRate
39
+ : cloudRefreshRate
40
+ this.hk2mr = (speed) => {
41
+ if (speed === 0) {
42
+ return 0
43
+ }
44
+ if (speed <= 75) {
45
+ return 2
46
+ }
47
+ return 1
48
+ }
49
+ this.hk2Label = (speed) => {
50
+ if (speed === 0) {
51
+ return 'off'
52
+ }
53
+ if (speed <= 75) {
54
+ return 'intermittent'
55
+ }
56
+ return 'continuous'
57
+ }
58
+ this.mr2hk = (speed) => {
59
+ if (speed === 0) {
60
+ return 0
61
+ }
62
+ if (speed === 1) {
63
+ return 100
64
+ }
65
+ return 50
66
+ }
67
+
68
+ // Add the switch service if it doesn't already exist
69
+ this.fanService = this.accessory.getService(this.hapServ.Fan) || this.accessory.addService(this.hapServ.Fan)
70
+
71
+ // Add the set handler to the fan on/off characteristic
72
+ this.fanService
73
+ .getCharacteristic(this.hapChar.On)
74
+ .onSet(async value => this.internalFanStateUpdate(value))
75
+ this.cacheFanState = this.fanService.getCharacteristic(this.hapChar.On).value
76
+
77
+ // Add the set handler to the fan speed characteristic
78
+ this.fanService
79
+ .getCharacteristic(this.hapChar.RotationSpeed)
80
+ .setProps({
81
+ minStep: 50,
82
+ validValues: [0, 50, 100],
83
+ })
84
+ .onSet(async value => this.internalFanSpeedUpdate(value))
85
+ this.cacheFanSpeed = this.hk2mr(
86
+ this.fanService.getCharacteristic(this.hapChar.RotationSpeed).value,
87
+ )
88
+
89
+ // Add the lightbulb service if it doesn't already exist
90
+ this.lightService = this.accessory.getService(this.hapServ.Lightbulb)
91
+ || this.accessory.addService(this.hapServ.Lightbulb)
92
+
93
+ // Add the set handler to the lightbulb on/off characteristic
94
+ this.lightService
95
+ .getCharacteristic(this.hapChar.On)
96
+ .onSet(async value => this.internalLightStateUpdate(value))
97
+ this.cacheLightState = this.lightService.getCharacteristic(this.hapChar.On).value
98
+
99
+ // No set handler for brightness as not supported
100
+ this.lightService.getCharacteristic(this.hapChar.Brightness).setProps({
101
+ minStep: 100,
102
+ validValues: [0, 100],
103
+ })
104
+
105
+ // Add the set handler to the lightbulb hue characteristic
106
+ this.lightService
107
+ .getCharacteristic(this.hapChar.Hue)
108
+ .onSet(async value => this.internalLightColourUpdate(value))
109
+ this.cacheLightHue = this.lightService.getCharacteristic(this.hapChar.Hue).value
110
+ this.cacheLightSat = this.lightService.getCharacteristic(this.hapChar.Saturation).value
111
+
112
+ // Create the queue used for sending device requests
113
+ this.updateInProgress = false
114
+ this.queue = new PQueue({
115
+ concurrency: 1,
116
+ interval: 250,
117
+ intervalCap: 1,
118
+ timeout: 10000,
119
+ throwOnTimeout: true,
120
+ })
121
+ this.queue.on('idle', () => {
122
+ this.updateInProgress = false
123
+ })
124
+
125
+ // Set up the mqtt client for cloud devices to send and receive device updates
126
+ if (accessory.context.connection !== 'local') {
127
+ this.accessory.mqtt = new mqttClient(platform, this.accessory)
128
+ this.accessory.mqtt.connect()
129
+ }
130
+
131
+ // Always request a device update on startup, then start the interval for polling
132
+ setTimeout(() => this.requestUpdate(true), 2000)
133
+ this.accessory.refreshInterval = setInterval(
134
+ () => this.requestUpdate(),
135
+ this.pollInterval * 1000,
136
+ )
137
+
138
+ // Output the customised options to the log
139
+ const opts = JSON.stringify({
140
+ connection: this.accessory.context.connection,
141
+ })
142
+ platform.log('[%s] %s %s.', this.name, platformLang.devInitOpts, opts)
143
+ }
144
+
145
+ async internalFanStateUpdate(value) {
146
+ try {
147
+ // Add the request to the queue so updates are sent apart
148
+ await this.queue.add(async () => {
149
+ // Don't continue if the state is the same as before
150
+ if (value === this.cacheFanState) {
151
+ return
152
+ }
153
+
154
+ // This flag stops the plugin from requesting updates while pending on others
155
+ this.updateInProgress = true
156
+
157
+ // Generate the payload and namespace
158
+ const namespace = 'Appliance.Control.Spray'
159
+ const payload = {
160
+ spray: {
161
+ mode: value ? Math.max(this.cacheFanSpeed, 1) : 0,
162
+ channel: 0,
163
+ },
164
+ }
165
+
166
+ // Use the platform function to send the update to the device
167
+ await this.platform.sendUpdate(this.accessory, {
168
+ namespace,
169
+ payload,
170
+ })
171
+
172
+ // Update the cache and log the update has been successful
173
+ this.cacheFanState = value
174
+
175
+ this.accessory.log(`${platformLang.curState} [${value ? 'on' : 'off'}]`)
176
+ })
177
+ } catch (err) {
178
+ // Catch any errors whilst updating the device
179
+ const eText = err instanceof TimeoutError ? platformLang.timeout : parseError(err)
180
+ this.accessory.logWarn(`${platformLang.sendFailed} ${eText}`)
181
+ setTimeout(() => {
182
+ this.fanService.updateCharacteristic(this.hapChar.On, this.cacheFanState)
183
+ }, 2000)
184
+ throw new this.hapErr(-70402)
185
+ }
186
+ }
187
+
188
+ async internalFanSpeedUpdate(value) {
189
+ try {
190
+ // Add the request to the queue so updates are sent apart
191
+ await this.queue.add(async () => {
192
+ // Some homekit apps might not support the valid values of 0, 50 and 100
193
+ if (value === 0) {
194
+ value = 0
195
+ } else if (value <= 75) {
196
+ value = 50
197
+ } else {
198
+ value = 100
199
+ }
200
+
201
+ // Don't continue if the state is the same as before
202
+ const mrVal = this.hk2mr(value)
203
+ if (mrVal === this.cacheFanSpeed) {
204
+ return
205
+ }
206
+
207
+ // This flag stops the plugin from requesting updates while pending on others
208
+ this.updateInProgress = true
209
+
210
+ // Generate the payload and namespace
211
+ const namespace = 'Appliance.Control.Spray'
212
+ const payload = {
213
+ spray: {
214
+ mode: mrVal,
215
+ channel: 0,
216
+ },
217
+ }
218
+
219
+ // Use the platform function to send the update to the device
220
+ await this.platform.sendUpdate(this.accessory, {
221
+ namespace,
222
+ payload,
223
+ })
224
+
225
+ // If using the slider to turn off then set the rotation speed back to original value
226
+ // This stops homekit turning back to 100% if using the icon after turned off
227
+ if (value === 0) {
228
+ // Update the rotation speed back to the previous value (with the fan still off)
229
+ setTimeout(() => {
230
+ this.fanService.updateCharacteristic(
231
+ this.hapChar.RotationSpeed,
232
+ this.mr2hk(this.cacheFanSpeed),
233
+ )
234
+ }, 2000)
235
+ } else {
236
+ // Update the cache and log the update has been successful
237
+ this.cacheFanSpeed = mrVal
238
+ this.accessory.log(`${platformLang.curSpray} [${this.hk2Label(value)}]`)
239
+ }
240
+ })
241
+ } catch (err) {
242
+ // Catch any errors whilst updating the device
243
+ const eText = err instanceof TimeoutError ? platformLang.timeout : parseError(err)
244
+ this.accessory.logWarn(`${platformLang.sendFailed} ${eText}`)
245
+ setTimeout(() => {
246
+ this.fanService.updateCharacteristic(
247
+ this.hapChar.RotationSpeed,
248
+ this.mr2hk(this.cacheFanSpeed),
249
+ )
250
+ }, 2000)
251
+ throw new this.hapErr(-70402)
252
+ }
253
+ }
254
+
255
+ async internalLightStateUpdate(value) {
256
+ try {
257
+ // Add the request to the queue so updates are sent apart
258
+ await this.queue.add(async () => {
259
+ // Don't continue if the state is the same as before
260
+ if (value === this.cacheLightState) {
261
+ return
262
+ }
263
+
264
+ // This flag stops the plugin from requesting updates while pending on others
265
+ this.updateInProgress = true
266
+
267
+ // Generate the payload and namespace
268
+ const namespace = 'Appliance.Control.Light'
269
+ const payload = {
270
+ light: {
271
+ onoff: value ? 1 : 0,
272
+ channel: 0,
273
+ },
274
+ }
275
+
276
+ // Use the platform function to send the update to the device
277
+ await this.platform.sendUpdate(this.accessory, {
278
+ namespace,
279
+ payload,
280
+ })
281
+
282
+ // Update the cache and log the update has been successful
283
+ this.cacheLightState = value
284
+ this.accessory.log(`${platformLang.curLightState} [${value ? 'on' : 'off'}]`)
285
+ })
286
+ } catch (err) {
287
+ // Catch any errors whilst updating the device
288
+ const eText = err instanceof TimeoutError ? platformLang.timeout : parseError(err)
289
+ this.accessory.logWarn(`${platformLang.sendFailed} ${eText}`)
290
+ setTimeout(() => {
291
+ this.lightService.updateCharacteristic(this.hapChar.On, this.cacheLightState)
292
+ }, 2000)
293
+ throw new this.hapErr(-70402)
294
+ }
295
+ }
296
+
297
+ async internalLightColourUpdate(value) {
298
+ try {
299
+ // Add the request to the queue so updates are sent apart
300
+ await this.queue.add(async () => {
301
+ // Don't continue if the state is the same as before
302
+ if (this.cacheLightHue === value) {
303
+ return
304
+ }
305
+
306
+ // Avoid multiple changes in short space of time
307
+ const updateKey = generateRandomString(5)
308
+ this.updateKeyColour = updateKey
309
+ await sleep(300)
310
+ if (updateKey !== this.updateKeyColour) {
311
+ return
312
+ }
313
+
314
+ // This flag stops the plugin from requesting updates while pending on others
315
+ this.updateInProgress = true
316
+
317
+ // Convert to RGB
318
+ const saturation = this.lightService.getCharacteristic(this.hapChar.Saturation).value
319
+ const [r, g, b] = hs2rgb(value, saturation)
320
+
321
+ // Generate the payload to send
322
+ const namespace = 'Appliance.Control.Light'
323
+ const payload = {
324
+ light: {
325
+ capacity: 1,
326
+ channel: 0,
327
+ rgb: hk2mrRGB(r, g, b),
328
+ onoff: 1,
329
+ luminance: 100,
330
+ },
331
+ }
332
+
333
+ // For some reason the device sends an mqtt with the old colour, so create a flag to ignore
334
+ this.ignoreNextMQTT = true
335
+
336
+ // Use the platform function to send the update to the device
337
+ await this.platform.sendUpdate(this.accessory, {
338
+ namespace,
339
+ payload,
340
+ })
341
+
342
+ // Update the cache and log the update has been successful
343
+ if (!this.cacheLightState) {
344
+ this.cacheLightState = true
345
+ this.lightService.updateCharacteristic(this.hapChar.On, true)
346
+ this.accessory.log(`${platformLang.curLightState} [on]`)
347
+ this.lightService.updateCharacteristic(this.hapChar.Brightness, 100)
348
+ }
349
+ this.cacheLightHue = value
350
+ this.cacheLightSat = this.lightService.getCharacteristic(this.hapChar.Saturation).value
351
+
352
+ this.accessory.log(`${platformLang.curLightColour} [${this.cacheLightHue}, ${this.cacheLightSat}] / [${r}, ${g}, ${b}]`)
353
+ })
354
+ } catch (err) {
355
+ this.ignoreNextMQTT = false
356
+ const eText = parseError(err)
357
+ this.accessory.logWarn(`${platformLang.sendFailed} ${eText}`)
358
+ setTimeout(() => {
359
+ this.lightService.updateCharacteristic(this.hapChar.Hue, this.cacheLightHue)
360
+ }, 2000)
361
+ throw new this.hapErr(-70402)
362
+ }
363
+ }
364
+
365
+ async requestUpdate(firstRun = false) {
366
+ try {
367
+ // Don't continue if an update is currently being sent to the device
368
+ if (this.updateInProgress) {
369
+ return
370
+ }
371
+
372
+ // Add the request to the queue so updates are sent apart
373
+ await this.queue.add(async () => {
374
+ // This flag stops the plugin from requesting updates while pending on others
375
+ this.updateInProgress = true
376
+
377
+ // Send the request
378
+ const res = await this.platform.sendUpdate(this.accessory, {
379
+ namespace: 'Appliance.System.All',
380
+ payload: {},
381
+ })
382
+
383
+ // Log the received data
384
+ this.accessory.logDebug(`${platformLang.incPoll}: ${JSON.stringify(res.data)}`)
385
+
386
+ // Check the response is in a useful format
387
+ const data = res.data.payload
388
+
389
+ if (data.all) {
390
+ if (data.all.digest) {
391
+ this.applyUpdate(data.all.digest)
392
+ }
393
+
394
+ // A flag to check if we need to update the accessory context
395
+ let needsUpdate = false
396
+
397
+ // Get the mac address and hardware version of the device
398
+ if (data.all.system) {
399
+ // Mac address and hardware don't change regularly so only get on first poll
400
+ if (firstRun && data.all.system.hardware) {
401
+ this.accessory.context.macAddress = data.all.system.hardware.macAddress.toUpperCase()
402
+ this.accessory.context.hardware = data.all.system.hardware.version
403
+ }
404
+
405
+ // Get the ip address and firmware of the device
406
+ if (data.all.system.firmware) {
407
+ // Check for an IP change each and every time the device is polled
408
+ if (this.accessory.context.ipAddress !== data.all.system.firmware.innerIp) {
409
+ this.accessory.context.ipAddress = data.all.system.firmware.innerIp
410
+ needsUpdate = true
411
+ }
412
+
413
+ // Firmware doesn't change regularly so only get on first poll
414
+ if (firstRun) {
415
+ this.accessory.context.firmware = data.all.system.firmware.version
416
+ }
417
+ }
418
+ }
419
+
420
+ // Get the cloud online status of the device
421
+ if (data.all.system.online) {
422
+ const isOnline = data.all.system.online.status === 1
423
+ if (this.accessory.context.isOnline !== isOnline) {
424
+ this.accessory.context.isOnline = isOnline
425
+ needsUpdate = true
426
+ }
427
+ }
428
+
429
+ // Update the accessory cache if anything has changed
430
+ if (needsUpdate || firstRun) {
431
+ this.platform.updateAccessory(this.accessory)
432
+ }
433
+ }
434
+ })
435
+ } catch (err) {
436
+ const eText = err instanceof TimeoutError ? platformLang.timeout : parseError(err)
437
+ this.accessory.logDebugWarn(`${platformLang.reqFailed}: ${eText}`)
438
+
439
+ // Set the homebridge-ui status of the device to offline if local and error is timeout
440
+ if (
441
+ (this.accessory.context.isOnline || firstRun)
442
+ && ['EHOSTUNREACH', 'timed out'].some(el => eText.includes(el))
443
+ ) {
444
+ this.accessory.context.isOnline = false
445
+ this.platform.updateAccessory(this.accessory)
446
+ }
447
+ }
448
+ }
449
+
450
+ receiveUpdate(params) {
451
+ try {
452
+ // Log the received data
453
+ this.accessory.logDebug(`${platformLang.incMQTT}: ${JSON.stringify(params)}`)
454
+
455
+ // Check the response is in a useful format
456
+ const data = params.payload
457
+ if (data.spray || data.light) {
458
+ this.applyUpdate(data)
459
+ }
460
+ } catch (err) {
461
+ this.accessory.logWarn(`${platformLang.refFailed} ${parseError(err)}`)
462
+ }
463
+ }
464
+
465
+ applyUpdate(data) {
466
+ // Update the diffuser (fan) service from the supplied data
467
+ if (data.spray && data.spray[0] && hasProperty(data.spray[0], 'mode')) {
468
+ const newSpeed = data.spray[0].mode
469
+
470
+ // Check against the cache and update HomeKit and the cache if needed
471
+ if (this.cacheFanSpeed !== newSpeed) {
472
+ this.cacheFanSpeed = newSpeed
473
+ if (this.cacheFanSpeed === 0) {
474
+ // Looks like the spray has been turned off
475
+ this.cacheFanState = false
476
+ this.fanService.updateCharacteristic(this.hapChar.On, false)
477
+ this.accessory.log(`${platformLang.curState} [off]`)
478
+ } else {
479
+ // Looks like the spray is now on (from OFF or a different mode)
480
+ if (!this.cacheFanState) {
481
+ // Looks like the spray has been turn ON from OFF
482
+ this.cacheFanState = true
483
+ this.fanService.updateCharacteristic(this.hapChar.On, true)
484
+ this.accessory.log(`${platformLang.curState} [on]`)
485
+ }
486
+ const hkValue = this.mr2hk(this.cacheFanSpeed)
487
+ this.fanService.updateCharacteristic(this.hapChar.RotationSpeed, hkValue)
488
+ this.accessory.log(`${platformLang.curSpray} [${this.hk2Label(hkValue)}]`)
489
+ }
490
+ }
491
+ }
492
+
493
+ // Update the diffuser (light) service from the supplied data
494
+ if (data.light) {
495
+ if (hasProperty(data.light, 'onoff')) {
496
+ const newState = data.light.onoff === 1
497
+
498
+ // Check against the cache and update HomeKit and the cache if needed
499
+ if (this.cacheLightState !== newState) {
500
+ this.lightService.updateCharacteristic(this.hapChar.On, newState)
501
+ this.cacheLightState = newState
502
+ this.accessory.log(`${platformLang.curLightState} [${this.cacheLightState ? 'on' : 'off'}]`)
503
+ }
504
+ }
505
+ if (hasProperty(data.light, 'rgb') && !this.ignoreNextMQTT) {
506
+ const [r, g, b] = mr2hkRGB(data.light.rgb)
507
+ const [newHue, newSat] = rgb2hs(r, g, b)
508
+
509
+ // Check against the cache and update HomeKit and the cache if needed
510
+ if (this.cacheLightHue !== newHue || this.cacheLightSat !== newSat) {
511
+ this.lightService.updateCharacteristic(this.hapChar.Hue, newHue)
512
+ this.lightService.updateCharacteristic(this.hapChar.Saturation, newSat)
513
+ this.cacheLightHue = newHue
514
+ this.cacheLightSat = newSat
515
+ this.accessory.log(`${platformLang.curLightColour} [${this.cacheLightHue}, ${this.cacheLightSat}] / [${r}, ${g}, ${b}]`)
516
+ }
517
+ }
518
+ this.ignoreNextMQTT = false
519
+ }
520
+ }
521
+ }
@@ -0,0 +1,63 @@
1
+ import deviceBaby from './baby.js'
2
+ import deviceCoolerSingle from './cooler-single.js'
3
+ import deviceDiffuser from './diffuser.js'
4
+ import deviceFan from './fan.js'
5
+ import deviceGarageMain from './garage-main.js'
6
+ import deviceGarageSingle from './garage-single.js'
7
+ import deviceGarageSub from './garage-sub.js'
8
+ import deviceHeaterSingle from './heater-single.js'
9
+ import deviceHubContact from './hub-contact.js'
10
+ import deviceHubLeak from './hub-leak.js'
11
+ import deviceHubMain from './hub-main.js'
12
+ import deviceHubSensor from './hub-sensor.js'
13
+ import deviceHubSmoke from './hub-smoke.js'
14
+ import deviceHubValve from './hub-valve.js'
15
+ import deviceHumidifier from './humidifier.js'
16
+ import deviceLightCCT from './light-cct.js'
17
+ import deviceLightDimmer from './light-dimmer.js'
18
+ import deviceLightRGB from './light-rgb.js'
19
+ import deviceOutletMulti from './outlet-multi.js'
20
+ import deviceOutletSingle from './outlet-single.js'
21
+ import devicePowerStrip from './power-strip.js'
22
+ import devicePurifier from './purifier.js'
23
+ import devicePurifierSingle from './purifier-single.js'
24
+ import deviceRoller from './roller.js'
25
+ import deviceRollerLocation from './roller-location.js'
26
+ import deviceSensorPresence from './sensor-presence.js'
27
+ import deviceSwitchMulti from './switch-multi.js'
28
+ import deviceSwitchSingle from './switch-single.js'
29
+ import deviceTemplate from './template.js'
30
+ import deviceThermostat from './thermostat.js'
31
+
32
+ export default {
33
+ deviceBaby,
34
+ deviceCoolerSingle,
35
+ deviceDiffuser,
36
+ deviceFan,
37
+ deviceGarageMain,
38
+ deviceGarageSingle,
39
+ deviceGarageSub,
40
+ deviceHeaterSingle,
41
+ deviceHubContact,
42
+ deviceHubLeak,
43
+ deviceHubMain,
44
+ deviceHubSensor,
45
+ deviceHubSmoke,
46
+ deviceHubValve,
47
+ deviceHumidifier,
48
+ deviceLightCCT,
49
+ deviceLightDimmer,
50
+ deviceLightRGB,
51
+ deviceOutletMulti,
52
+ deviceOutletSingle,
53
+ devicePowerStrip,
54
+ devicePurifierSingle,
55
+ devicePurifier,
56
+ deviceRollerLocation,
57
+ deviceRoller,
58
+ deviceSensorPresence,
59
+ deviceSwitchMulti,
60
+ deviceSwitchSingle,
61
+ deviceTemplate,
62
+ deviceThermostat,
63
+ }