@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,225 @@
1
+ import PQueue from 'p-queue'
2
+ import { TimeoutError } from 'p-timeout'
3
+
4
+ import mqttClient from '../connection/mqtt.js'
5
+ import platformConsts from '../utils/constants.js'
6
+ import { hasProperty, parseError } from '../utils/functions.js'
7
+ import platformLang from '../utils/lang-en.js'
8
+
9
+ export default class {
10
+ constructor(platform, accessory) {
11
+ // Set up variables from the platform
12
+ this.devicesInHB = platform.devicesInHB
13
+ this.hapChar = platform.api.hap.Characteristic
14
+ this.hapErr = platform.api.hap.HapStatusError
15
+ this.hapServ = platform.api.hap.Service
16
+ this.platform = platform
17
+
18
+ // Set up variables from the accessory
19
+ this.accessory = accessory
20
+ this.name = accessory.displayName
21
+ const cloudRefreshRate = hasProperty(platform.config, 'cloudRefreshRate')
22
+ ? platform.config.cloudRefreshRate
23
+ : platformConsts.defaultValues.cloudRefreshRate
24
+ const localRefreshRate = hasProperty(platform.config, 'refreshRate')
25
+ ? platform.config.refreshRate
26
+ : platformConsts.defaultValues.refreshRate
27
+ this.pollInterval = accessory.context.connection === 'local'
28
+ ? localRefreshRate
29
+ : cloudRefreshRate
30
+
31
+ // Create the queue used for sending device requests
32
+ this.updateInProgress = false
33
+ this.queue = new PQueue({
34
+ concurrency: 1,
35
+ interval: 250,
36
+ intervalCap: 1,
37
+ timeout: 10000,
38
+ throwOnTimeout: true,
39
+ })
40
+ this.queue.on('idle', () => {
41
+ this.updateInProgress = false
42
+ })
43
+
44
+ // Set up the mqtt client for cloud devices to send and receive device updates
45
+ if (accessory.context.connection !== 'local') {
46
+ this.accessory.mqtt = new mqttClient(platform, this.accessory)
47
+ this.accessory.mqtt.connect()
48
+ }
49
+
50
+ // Always request a device update on startup, then start the interval for polling
51
+ setTimeout(() => this.requestUpdate(true), 5000)
52
+ this.accessory.refreshInterval = setInterval(
53
+ () => this.requestUpdate(),
54
+ this.pollInterval * 1000,
55
+ )
56
+ }
57
+
58
+ async requestUpdate(firstRun = false) {
59
+ try {
60
+ // Don't continue if an update is currently being sent to the device
61
+ if (this.updateInProgress) {
62
+ return
63
+ }
64
+
65
+ // Add the request to the queue so updates are sent apart
66
+ await this.queue.add(async () => {
67
+ // This flag stops the plugin from requesting updates while pending on others
68
+ this.updateInProgress = true
69
+
70
+ // Send the request
71
+ const res = await this.platform.sendUpdate(this.accessory, {
72
+ namespace: 'Appliance.System.All',
73
+ payload: {},
74
+ })
75
+
76
+ // Log the received data
77
+ this.accessory.logDebug(`${platformLang.incPoll}: ${JSON.stringify(res.data)}`)
78
+
79
+ // Check the response is in a useful format
80
+ const data = res.data.payload
81
+ if (data.all) {
82
+ if (
83
+ data.all.digest
84
+ && data.all.digest.garageDoor
85
+ && Array.isArray(data.all.digest.garageDoor)
86
+ ) {
87
+ data.all.digest.garageDoor.forEach((channel) => {
88
+ // Check whether the homebridge accessory this relates to exists
89
+ const subAcc = this.devicesInHB.get(
90
+ this.platform.api.hap.uuid.generate(
91
+ this.accessory.context.serialNumber + channel.channel,
92
+ ),
93
+ )
94
+
95
+ // No need to continue if the accessory doesn't exist nor the receiver function
96
+ if (!subAcc || !subAcc.control || !subAcc.control.applyUpdate) {
97
+ return
98
+ }
99
+
100
+ // Apply the update to the accessory
101
+ subAcc.control.applyUpdate(channel)
102
+ })
103
+ }
104
+
105
+ // A flag to check if we need to update the accessory context
106
+ let needsUpdate = false
107
+
108
+ // Get the mac address and hardware version of the device
109
+ if (data.all.system) {
110
+ // Mac address and hardware don't change regularly so only get on first poll
111
+ if (firstRun && data.all.system.hardware) {
112
+ this.accessory.context.macAddress = data.all.system.hardware.macAddress.toUpperCase()
113
+ this.accessory.context.hardware = data.all.system.hardware.version
114
+ }
115
+
116
+ // Get the ip address and firmware of the device
117
+ if (data.all.system.firmware) {
118
+ // Check for an IP change each and every time the device is polled
119
+ if (this.accessory.context.ipAddress !== data.all.system.firmware.innerIp) {
120
+ this.accessory.context.ipAddress = data.all.system.firmware.innerIp
121
+ needsUpdate = true
122
+ }
123
+
124
+ // Firmware doesn't change regularly so only get on first poll
125
+ if (firstRun) {
126
+ this.accessory.context.firmware = data.all.system.firmware.version
127
+ }
128
+ }
129
+ }
130
+
131
+ // Get the cloud online status of the device
132
+ if (data.all.system.online) {
133
+ const isOnline = data.all.system.online.status === 1
134
+ if (this.accessory.context.isOnline !== isOnline) {
135
+ this.accessory.context.isOnline = isOnline
136
+ needsUpdate = true
137
+ }
138
+ }
139
+
140
+ // Update the accessory cache if anything has changed
141
+ if (needsUpdate || firstRun) {
142
+ this.devicesInHB.forEach((subAcc) => {
143
+ if (subAcc.context.serialNumber === this.accessory.context.serialNumber) {
144
+ subAcc.context = {
145
+ ...subAcc.context,
146
+
147
+ macAddress: this.accessory.context.macAddress,
148
+ hardware: this.accessory.context.hardware,
149
+ ipAddress: this.accessory.context.ipAddress,
150
+ firmware: this.accessory.context.firmware,
151
+ isOnline: this.accessory.context.isOnline
152
+ ,
153
+ }
154
+ this.platform.updateAccessory(subAcc)
155
+ }
156
+ })
157
+ }
158
+ }
159
+ })
160
+ } catch (err) {
161
+ const eText = err instanceof TimeoutError ? platformLang.timeout : parseError(err)
162
+ this.accessory.logDebugWarn(`${platformLang.reqFailed}: ${eText}`)
163
+
164
+ // Set the homebridge-ui status of the device to offline if local and error is timeout
165
+ if (
166
+ (this.accessory.context.isOnline || firstRun)
167
+ && ['EHOSTUNREACH', 'timed out'].some(el => eText.includes(el))
168
+ ) {
169
+ for (let i = 0; i <= this.accessory.context.channelCount; i += 1) {
170
+ const subAcc = this.devicesInHB.get(
171
+ this.platform.api.hap.uuid.generate(this.accessory.context.serialNumber + i),
172
+ )
173
+ if (subAcc) {
174
+ subAcc.context.isOnline = false
175
+ this.platform.updateAccessory(subAcc)
176
+ }
177
+ }
178
+ }
179
+ }
180
+ }
181
+
182
+ receiveUpdate(params) {
183
+ try {
184
+ // Log the received data
185
+ this.accessory.logDebug(`${platformLang.incMQTT}: ${JSON.stringify(params)}`)
186
+
187
+ // Validate the response, checking for payload property
188
+ if (!params.payload) {
189
+ throw new Error('invalid response received')
190
+ }
191
+ const data = params.payload
192
+
193
+ // Check the data is in a format which contains the value we need
194
+ if (data.state) {
195
+ // data.state maybe array of objects (multiple channels) or a single object
196
+ // Either way, push all items into one array
197
+ const toUpdate = []
198
+ if (Array.isArray(data.state)) {
199
+ data.state.forEach(item => toUpdate.push(item))
200
+ } else {
201
+ toUpdate.push(data.state)
202
+ }
203
+
204
+ toUpdate.forEach((channel) => {
205
+ // Check whether the homebridge accessory this relates to exists
206
+ const subAcc = this.devicesInHB.get(
207
+ this.platform.api.hap.uuid.generate(
208
+ this.accessory.context.serialNumber + channel.channel,
209
+ ),
210
+ )
211
+
212
+ // No need to continue if the accessory doesn't exist nor the receiver function
213
+ if (!subAcc || !subAcc.control || !subAcc.control.applyUpdate) {
214
+ return
215
+ }
216
+
217
+ // Apply the update to the accessory
218
+ subAcc.control.applyUpdate(channel)
219
+ })
220
+ }
221
+ } catch (err) {
222
+ this.accessory.logWarn(`${platformLang.refFailed} ${parseError(err)}`)
223
+ }
224
+ }
225
+ }