@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,403 @@
|
|
|
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.mtsList = []
|
|
21
|
+
this.name = accessory.displayName
|
|
22
|
+
const cloudRefreshRate = hasProperty(platform.config, 'cloudRefreshRate')
|
|
23
|
+
? platform.config.cloudRefreshRate
|
|
24
|
+
: platformConsts.defaultValues.cloudRefreshRate
|
|
25
|
+
const localRefreshRate = hasProperty(platform.config, 'refreshRate')
|
|
26
|
+
? platform.config.refreshRate
|
|
27
|
+
: platformConsts.defaultValues.refreshRate
|
|
28
|
+
this.pollInterval = accessory.context.connection === 'local'
|
|
29
|
+
? localRefreshRate
|
|
30
|
+
: cloudRefreshRate
|
|
31
|
+
|
|
32
|
+
// Not sure how realtime mqtt updates work with this device, so force enable cloud polling
|
|
33
|
+
if (this.pollInterval === 0) {
|
|
34
|
+
this.pollInterval = 30000
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Create the queue used for sending device requests
|
|
38
|
+
this.updateInProgress = false
|
|
39
|
+
this.queue = new PQueue({
|
|
40
|
+
concurrency: 1,
|
|
41
|
+
interval: 250,
|
|
42
|
+
intervalCap: 1,
|
|
43
|
+
timeout: 10000,
|
|
44
|
+
throwOnTimeout: true,
|
|
45
|
+
})
|
|
46
|
+
this.queue.on('idle', () => {
|
|
47
|
+
this.updateInProgress = false
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
// Set up the mqtt client for cloud devices to send and receive device updates
|
|
51
|
+
if (accessory.context.connection !== 'local') {
|
|
52
|
+
this.accessory.mqtt = new mqttClient(platform, this.accessory)
|
|
53
|
+
this.accessory.mqtt.connect()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Always request a device update on startup, then start the interval for polling
|
|
57
|
+
setTimeout(() => this.requestUpdate(true), 5000)
|
|
58
|
+
this.accessory.refreshInterval = setInterval(
|
|
59
|
+
() => this.requestUpdate(),
|
|
60
|
+
this.pollInterval * 1000,
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async requestUpdate(firstRun = false) {
|
|
65
|
+
try {
|
|
66
|
+
// Don't continue if an update is currently being sent to the device
|
|
67
|
+
if (this.updateInProgress) {
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Add the request to the queue so updates are sent apart
|
|
72
|
+
await this.queue.add(async () => {
|
|
73
|
+
// This flag stops the plugin from requesting updates while pending on others
|
|
74
|
+
this.updateInProgress = true
|
|
75
|
+
|
|
76
|
+
// Send the request
|
|
77
|
+
const res = await this.platform.sendUpdate(this.accessory, {
|
|
78
|
+
namespace: 'Appliance.System.All',
|
|
79
|
+
payload: {},
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
// Log the received data
|
|
83
|
+
this.accessory.logDebug(`${platformLang.incPoll}: ${JSON.stringify(res.data)}`)
|
|
84
|
+
|
|
85
|
+
// Check the response is in a useful format
|
|
86
|
+
const data = res.data.payload
|
|
87
|
+
if (data.all) {
|
|
88
|
+
if (
|
|
89
|
+
data.all.digest
|
|
90
|
+
&& data.all.digest.hub
|
|
91
|
+
&& data.all.digest.hub.subdevice
|
|
92
|
+
&& Array.isArray(data.all.digest.hub.subdevice)
|
|
93
|
+
) {
|
|
94
|
+
data.all.digest.hub.subdevice.forEach((subdevice) => {
|
|
95
|
+
// Check whether the homebridge accessory this relates to exists
|
|
96
|
+
const subAcc = this.devicesInHB.get(
|
|
97
|
+
this.platform.api.hap.uuid.generate(
|
|
98
|
+
this.accessory.context.serialNumber + subdevice.id,
|
|
99
|
+
),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
// No need to continue if the accessory doesn't exist nor the receiver function
|
|
103
|
+
if (!subAcc || !subAcc.control || !subAcc.control.applyUpdate) {
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Properties we need are in ms100 object
|
|
108
|
+
if (subdevice.ms100) {
|
|
109
|
+
// Apply the update to the accessory
|
|
110
|
+
const update = {}
|
|
111
|
+
if (hasProperty(subdevice.ms100, 'latestTemperature')) {
|
|
112
|
+
update.temperature = subdevice.ms100.latestTemperature
|
|
113
|
+
}
|
|
114
|
+
if (hasProperty(subdevice.ms100, 'latestHumidity')) {
|
|
115
|
+
update.humidity = subdevice.ms100.latestHumidity
|
|
116
|
+
}
|
|
117
|
+
if (hasProperty(subdevice.ms100, 'voltage')) {
|
|
118
|
+
update.voltage = subdevice.ms100.voltage
|
|
119
|
+
}
|
|
120
|
+
subAcc.control.applyUpdate(update)
|
|
121
|
+
} else if (subdevice.waterLeak) {
|
|
122
|
+
// Apply the update to the accessory
|
|
123
|
+
subAcc.control.applyUpdate(subdevice)
|
|
124
|
+
} else if (subdevice.status === 2) {
|
|
125
|
+
// If the status is 2 then has been reported offline - report a battery of 0
|
|
126
|
+
subAcc.control.applyUpdate({ voltage: 0 })
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Check to see if any MTS exist
|
|
130
|
+
if (hasProperty(subdevice, 'scheduleBMode')) {
|
|
131
|
+
this.mtsList.push(subdevice.id)
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// A flag to check if we need to update the accessory context
|
|
137
|
+
let needsUpdate = false
|
|
138
|
+
|
|
139
|
+
// Get the mac address and hardware version of the device
|
|
140
|
+
if (data.all.system) {
|
|
141
|
+
// Mac address and hardware don't change regularly so only get on first poll
|
|
142
|
+
if (firstRun && data.all.system.hardware) {
|
|
143
|
+
this.accessory.context.macAddress = data.all.system.hardware.macAddress.toUpperCase()
|
|
144
|
+
this.accessory.context.hardware = data.all.system.hardware.version
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Get the ip address and firmware of the device
|
|
148
|
+
if (data.all.system.firmware) {
|
|
149
|
+
// Check for an IP change each and every time the device is polled
|
|
150
|
+
if (this.accessory.context.ipAddress !== data.all.system.firmware.innerIp) {
|
|
151
|
+
this.accessory.context.ipAddress = data.all.system.firmware.innerIp
|
|
152
|
+
needsUpdate = true
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Firmware doesn't change regularly so only get on first poll
|
|
156
|
+
if (firstRun) {
|
|
157
|
+
this.accessory.context.firmware = data.all.system.firmware.version
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Get the cloud online status of the device
|
|
163
|
+
if (data.all.system.online) {
|
|
164
|
+
const isOnline = data.all.system.online.status === 1
|
|
165
|
+
if (this.accessory.context.isOnline !== isOnline) {
|
|
166
|
+
this.accessory.context.isOnline = isOnline
|
|
167
|
+
needsUpdate = true
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Update the accessory cache if anything has changed
|
|
172
|
+
if (needsUpdate || firstRun) {
|
|
173
|
+
this.devicesInHB.forEach((subAcc) => {
|
|
174
|
+
if (subAcc.context.serialNumber === this.accessory.context.serialNumber) {
|
|
175
|
+
subAcc.context = {
|
|
176
|
+
...subAcc.context,
|
|
177
|
+
macAddress: this.accessory.context.macAddress,
|
|
178
|
+
hardware: this.accessory.context.hardware,
|
|
179
|
+
ipAddress: this.accessory.context.ipAddress,
|
|
180
|
+
firmware: this.accessory.context.firmware,
|
|
181
|
+
isOnline: this.accessory.context.isOnline,
|
|
182
|
+
}
|
|
183
|
+
this.platform.updateAccessory(subAcc)
|
|
184
|
+
}
|
|
185
|
+
})
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Request status for any MTS devices that exist
|
|
190
|
+
if (this.mtsList.length > 0) {
|
|
191
|
+
const payload = { all: [] }
|
|
192
|
+
this.mtsList.forEach(id => payload.all.push({ id }))
|
|
193
|
+
|
|
194
|
+
// Send the request
|
|
195
|
+
const res2 = await this.platform.sendUpdate(this.accessory, {
|
|
196
|
+
namespace: 'Appliance.Hub.Mts100.All',
|
|
197
|
+
payload,
|
|
198
|
+
method: 'GET',
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
// Log the received data
|
|
202
|
+
this.accessory.logDebug(`${platformLang.incPoll}: ${JSON.stringify(res2.data)}`)
|
|
203
|
+
|
|
204
|
+
const data2 = res2.data.payload
|
|
205
|
+
if (data2.all && Array.isArray(data2.all)) {
|
|
206
|
+
data2.all.forEach((entry) => {
|
|
207
|
+
// Check whether the homebridge accessory this relates to exists
|
|
208
|
+
const subAcc = this.devicesInHB.get(
|
|
209
|
+
this.platform.api.hap.uuid.generate(this.accessory.context.serialNumber + entry.id),
|
|
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
|
+
const toReturn = {}
|
|
218
|
+
if (entry.togglex && hasProperty(entry.togglex, 'onoff')) {
|
|
219
|
+
toReturn.state = entry.togglex.onoff
|
|
220
|
+
}
|
|
221
|
+
if (entry.temperature) {
|
|
222
|
+
if (hasProperty(entry.temperature, 'room')) {
|
|
223
|
+
toReturn.currTemperature = entry.temperature.room / 10
|
|
224
|
+
}
|
|
225
|
+
if (hasProperty(entry.temperature, 'currentSet')) {
|
|
226
|
+
toReturn.targTemperature = entry.temperature.currentSet / 10
|
|
227
|
+
}
|
|
228
|
+
if (hasProperty(entry.temperature, 'openWindow')) {
|
|
229
|
+
toReturn.openWindow = entry.temperature.openWindow
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (entry.mode && hasProperty(entry.mode, 'state')) {
|
|
233
|
+
toReturn.mode = entry.mode.state
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Apply the update
|
|
237
|
+
if (Object.keys(toReturn).length > 0) {
|
|
238
|
+
subAcc.control.applyUpdate(toReturn)
|
|
239
|
+
}
|
|
240
|
+
})
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
})
|
|
244
|
+
} catch (err) {
|
|
245
|
+
const eText = err instanceof TimeoutError ? platformLang.timeout : parseError(err)
|
|
246
|
+
this.accessory.logDebugWarn(`${platformLang.reqFailed}: ${eText}`)
|
|
247
|
+
|
|
248
|
+
// Set the homebridge-ui status of the device to offline if local and error is timeout
|
|
249
|
+
if (
|
|
250
|
+
(this.accessory.context.isOnline || firstRun)
|
|
251
|
+
&& ['EHOSTUNREACH', 'timed out'].some(el => eText.includes(el))
|
|
252
|
+
) {
|
|
253
|
+
this.accessory.context.isOnline = false
|
|
254
|
+
this.platform.updateAccessory(this.accessory)
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async requestSubdevices() {
|
|
260
|
+
try {
|
|
261
|
+
/*
|
|
262
|
+
This function is unused but would be nice to find the correct payload to
|
|
263
|
+
be able to request a subdevice list from the device itself rather than
|
|
264
|
+
from the cloud.
|
|
265
|
+
*/
|
|
266
|
+
// Add the request to the queue so updates are sent apart
|
|
267
|
+
await this.queue.add(async () => {
|
|
268
|
+
// This flag stops the plugin from requesting updates while pending on others
|
|
269
|
+
this.updateInProgress = true
|
|
270
|
+
|
|
271
|
+
// Send the request
|
|
272
|
+
const res = await this.platform.sendUpdate(this.accessory, {
|
|
273
|
+
namespace: 'Appliance.Hub.SubdeviceList',
|
|
274
|
+
payload: {
|
|
275
|
+
all: [],
|
|
276
|
+
},
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
// Log the received data
|
|
280
|
+
this.accessory.logDebug(`${platformLang.incSubDevices}: ${JSON.stringify(res.data)}`)
|
|
281
|
+
})
|
|
282
|
+
} catch (err) {
|
|
283
|
+
const eText = parseError(err)
|
|
284
|
+
this.accessory.logWarn(`${platformLang.reqFailedSubs} ${eText}`)
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
receiveUpdate(params) {
|
|
289
|
+
try {
|
|
290
|
+
// Log the received data
|
|
291
|
+
this.accessory.logDebug(`${platformLang.incMQTT}: ${JSON.stringify(params)}`)
|
|
292
|
+
|
|
293
|
+
// Validate the response, checking for payload property
|
|
294
|
+
if (!params.payload) {
|
|
295
|
+
throw new Error('invalid response received')
|
|
296
|
+
}
|
|
297
|
+
const data = params.payload
|
|
298
|
+
|
|
299
|
+
// Switches
|
|
300
|
+
if (data.togglex && Array.isArray(data.togglex)) {
|
|
301
|
+
data.togglex.forEach((entry) => {
|
|
302
|
+
// Check whether the homebridge accessory this relates to exists
|
|
303
|
+
const subAcc = this.devicesInHB.get(
|
|
304
|
+
this.platform.api.hap.uuid.generate(this.accessory.context.serialNumber + entry.id),
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
// No need to continue if the accessory doesn't exist nor the receiver function
|
|
308
|
+
if (!subAcc || !subAcc.control || !subAcc.control.applyUpdate) {
|
|
309
|
+
return
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const toReturn = {}
|
|
313
|
+
if (hasProperty(entry, 'onoff')) {
|
|
314
|
+
toReturn.state = entry.onoff
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Apply the update
|
|
318
|
+
if (Object.keys(toReturn).length > 0) {
|
|
319
|
+
subAcc.control.applyUpdate(toReturn)
|
|
320
|
+
}
|
|
321
|
+
})
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Temperature updates
|
|
325
|
+
if (data.temperature && Array.isArray(data.temperature)) {
|
|
326
|
+
data.temperature.forEach((entry) => {
|
|
327
|
+
// Check whether the homebridge accessory this relates to exists
|
|
328
|
+
const subAcc = this.devicesInHB.get(
|
|
329
|
+
this.platform.api.hap.uuid.generate(this.accessory.context.serialNumber + entry.id),
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
// No need to continue if the accessory doesn't exist nor the receiver function
|
|
333
|
+
if (!subAcc || !subAcc.control || !subAcc.control.applyUpdate) {
|
|
334
|
+
return
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const toReturn = {}
|
|
338
|
+
if (hasProperty(entry, 'currentSet')) {
|
|
339
|
+
toReturn.targTemperature = entry.currentSet / 10
|
|
340
|
+
}
|
|
341
|
+
if (hasProperty(entry, 'room')) {
|
|
342
|
+
toReturn.currTemperature = entry.room / 10
|
|
343
|
+
}
|
|
344
|
+
if (hasProperty(entry, 'openWindow')) {
|
|
345
|
+
toReturn.openWindow = entry.openWindow
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Apply the update
|
|
349
|
+
if (Object.keys(toReturn).length > 0) {
|
|
350
|
+
subAcc.control.applyUpdate(toReturn)
|
|
351
|
+
}
|
|
352
|
+
})
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Mode updates
|
|
356
|
+
if (data.mode && Array.isArray(data.mode)) {
|
|
357
|
+
data.mode.forEach((entry) => {
|
|
358
|
+
// Check whether the homebridge accessory this relates to exists
|
|
359
|
+
const subAcc = this.devicesInHB.get(
|
|
360
|
+
this.platform.api.hap.uuid.generate(this.accessory.context.serialNumber + entry.id),
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
// No need to continue if the accessory doesn't exist nor the receiver function
|
|
364
|
+
if (!subAcc || !subAcc.control || !subAcc.control.applyUpdate) {
|
|
365
|
+
return
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const toReturn = {}
|
|
369
|
+
if (hasProperty(entry, 'state')) {
|
|
370
|
+
toReturn.mode = entry.state
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Apply the update
|
|
374
|
+
if (Object.keys(toReturn).length > 0) {
|
|
375
|
+
subAcc.control.applyUpdate(toReturn)
|
|
376
|
+
}
|
|
377
|
+
})
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Leak sensor updates
|
|
381
|
+
if (data.waterLeak && Array.isArray(data.waterLeak)) {
|
|
382
|
+
data.waterLeak.forEach((entry) => {
|
|
383
|
+
// Check whether the homebridge accessory this relates to exists
|
|
384
|
+
const subAcc = this.devicesInHB.get(
|
|
385
|
+
this.platform.api.hap.uuid.generate(this.accessory.context.serialNumber + entry.id),
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
// No need to continue if the accessory doesn't exist nor the receiver function
|
|
389
|
+
if (!subAcc || !subAcc.control || !subAcc.control.applyUpdate) {
|
|
390
|
+
return
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Apply the update
|
|
394
|
+
if (Object.keys(entry).length > 0) {
|
|
395
|
+
subAcc.control.applyUpdate(entry)
|
|
396
|
+
}
|
|
397
|
+
})
|
|
398
|
+
}
|
|
399
|
+
} catch (err) {
|
|
400
|
+
this.accessory.logWarn(`${platformLang.refFailed} ${parseError(err)}`)
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import platformConsts from '../utils/constants.js'
|
|
2
|
+
import { hasProperty, parseError } from '../utils/functions.js'
|
|
3
|
+
import platformLang from '../utils/lang-en.js'
|
|
4
|
+
|
|
5
|
+
export default class {
|
|
6
|
+
constructor(platform, accessory) {
|
|
7
|
+
// Set up variables from the platform
|
|
8
|
+
this.hapChar = platform.api.hap.Characteristic
|
|
9
|
+
this.hapErr = platform.api.hap.HapStatusError
|
|
10
|
+
this.hapServ = platform.api.hap.Service
|
|
11
|
+
this.platform = platform
|
|
12
|
+
|
|
13
|
+
// Set up variables from the accessory
|
|
14
|
+
this.accessory = accessory
|
|
15
|
+
this.lowBattThreshold = accessory.context.options.lowBattThreshold
|
|
16
|
+
? Math.min(accessory.context.options.lowBattThreshold, 100)
|
|
17
|
+
: platformConsts.defaultValues.lowBattThreshold
|
|
18
|
+
this.name = accessory.displayName
|
|
19
|
+
|
|
20
|
+
// Add the temperature service if it doesn't already exist
|
|
21
|
+
this.tempService = this.accessory.getService(this.hapServ.TemperatureSensor)
|
|
22
|
+
|| this.accessory.addService(this.hapServ.TemperatureSensor)
|
|
23
|
+
this.cacheTemp = this.tempService.getCharacteristic(this.hapChar.CurrentTemperature).value
|
|
24
|
+
this.updateCache()
|
|
25
|
+
|
|
26
|
+
// Add the humidity service if it doesn't already exist
|
|
27
|
+
this.humiService = this.accessory.getService(this.hapServ.HumiditySensor)
|
|
28
|
+
|| this.accessory.addService(this.hapServ.HumiditySensor)
|
|
29
|
+
this.cacheHumi = this.humiService.getCharacteristic(this.hapChar.CurrentRelativeHumidity).value
|
|
30
|
+
|
|
31
|
+
// Add the battery service if it doesn't already exist
|
|
32
|
+
this.battService = this.accessory.getService(this.hapServ.Battery)
|
|
33
|
+
|| this.accessory.addService(this.hapServ.Battery)
|
|
34
|
+
this.cacheBatt = this.battService.getCharacteristic(this.hapChar.BatteryLevel).value
|
|
35
|
+
|
|
36
|
+
// Pass the accessory to Fakegato to set up with Eve
|
|
37
|
+
this.accessory.eveService = new platform.eveService('custom', this.accessory, { log: () => {} })
|
|
38
|
+
|
|
39
|
+
// Output the customised options to the log
|
|
40
|
+
const opts = JSON.stringify({
|
|
41
|
+
connection: this.accessory.context.connection,
|
|
42
|
+
lowBattThreshold: this.lowBattThreshold,
|
|
43
|
+
})
|
|
44
|
+
platform.log('[%s] %s %s.', this.name, platformLang.devInitOpts, opts)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
applyUpdate(data) {
|
|
48
|
+
try {
|
|
49
|
+
// Temperature
|
|
50
|
+
if (hasProperty(data, 'temperature')) {
|
|
51
|
+
// Divide by 10 as reading is given as whole number inc decimal
|
|
52
|
+
const newTemp = data.temperature / 10
|
|
53
|
+
if (newTemp !== this.cacheTemp) {
|
|
54
|
+
this.cacheTemp = newTemp
|
|
55
|
+
this.tempService.updateCharacteristic(this.hapChar.CurrentTemperature, newTemp)
|
|
56
|
+
this.accessory.eveService.addEntry({ temp: newTemp })
|
|
57
|
+
this.accessory.log(`${platformLang.curTemp} [${newTemp}°C]`)
|
|
58
|
+
|
|
59
|
+
// Update the cache file with the new temperature
|
|
60
|
+
this.updateCache()
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Humidity
|
|
65
|
+
if (hasProperty(data, 'humidity')) {
|
|
66
|
+
// Divide by 10 and round as reading is given as whole number inc decimal
|
|
67
|
+
const newHumi = Math.round(data.humidity / 10)
|
|
68
|
+
if (newHumi !== this.cacheHumi) {
|
|
69
|
+
this.cacheHumi = newHumi
|
|
70
|
+
this.humiService.updateCharacteristic(this.hapChar.CurrentRelativeHumidity, newHumi)
|
|
71
|
+
this.accessory.eveService.addEntry({ humidity: newHumi })
|
|
72
|
+
this.accessory.log(`${platformLang.curHumi} [${newHumi}%]`)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Battery % from reported voltage
|
|
77
|
+
if (hasProperty(data, 'voltage')) {
|
|
78
|
+
// 1. Reduce/enlarge value so in [2000, 3000]
|
|
79
|
+
let newVoltage = Math.min(Math.max(data.voltage, 2000), 3000)
|
|
80
|
+
|
|
81
|
+
// 2. Scale this from [2000, 3000] to [0, 100] and round to nearest whole number
|
|
82
|
+
newVoltage = Math.round((newVoltage - 2000) / 10)
|
|
83
|
+
|
|
84
|
+
// This should be a rough estimate of the battery %
|
|
85
|
+
if (newVoltage !== this.cacheBatt) {
|
|
86
|
+
this.cacheBatt = newVoltage
|
|
87
|
+
this.battService.updateCharacteristic(this.hapChar.BatteryLevel, this.cacheBatt)
|
|
88
|
+
this.battService.updateCharacteristic(
|
|
89
|
+
this.hapChar.StatusLowBattery,
|
|
90
|
+
this.cacheBatt < this.lowBattThreshold ? 1 : 0,
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
} catch (err) {
|
|
95
|
+
this.accessory.logWarn(`${platformLang.refFailed} ${parseError(err)}`)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async updateCache() {
|
|
100
|
+
// Don't continue if the storage client hasn't initialised properly
|
|
101
|
+
if (!this.platform.storageClientData) {
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Attempt to save the new temperature to the cache
|
|
106
|
+
try {
|
|
107
|
+
await this.platform.storageData.setItem(
|
|
108
|
+
`${this.accessory.context.subSerialNumber}_temp`,
|
|
109
|
+
this.cacheTemp,
|
|
110
|
+
)
|
|
111
|
+
} catch (err) {
|
|
112
|
+
this.accessory.logWarn(`${platformLang.storageWriteErr} ${parseError(err)}`)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import platformConsts from '../utils/constants.js'
|
|
2
|
+
import { parseError } from '../utils/functions.js'
|
|
3
|
+
import platformLang from '../utils/lang-en.js'
|
|
4
|
+
|
|
5
|
+
export default class {
|
|
6
|
+
constructor(platform, accessory) {
|
|
7
|
+
// Set up variables from the platform
|
|
8
|
+
this.hapChar = platform.api.hap.Characteristic
|
|
9
|
+
this.hapErr = platform.api.hap.HapStatusError
|
|
10
|
+
this.hapServ = platform.api.hap.Service
|
|
11
|
+
this.platform = platform
|
|
12
|
+
|
|
13
|
+
// Set up variables from the accessory
|
|
14
|
+
this.accessory = accessory
|
|
15
|
+
this.lowBattThreshold = accessory.context.options.lowBattThreshold
|
|
16
|
+
? Math.min(accessory.context.options.lowBattThreshold, 100)
|
|
17
|
+
: platformConsts.defaultValues.lowBattThreshold
|
|
18
|
+
this.name = accessory.displayName
|
|
19
|
+
|
|
20
|
+
// Add the battery service if it doesn't already exist
|
|
21
|
+
this.battService = this.accessory.getService(this.hapServ.Battery)
|
|
22
|
+
|| this.accessory.addService(this.hapServ.Battery)
|
|
23
|
+
this.cacheBatt = this.battService.getCharacteristic(this.hapChar.BatteryLevel).value
|
|
24
|
+
|
|
25
|
+
// Output the customised options to the log
|
|
26
|
+
const opts = JSON.stringify({
|
|
27
|
+
connection: this.accessory.context.connection,
|
|
28
|
+
lowBattThreshold: this.lowBattThreshold,
|
|
29
|
+
})
|
|
30
|
+
platform.log('[%s] %s %s.', this.name, platformLang.devInitOpts, opts)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
applyUpdate(data) {
|
|
34
|
+
try {
|
|
35
|
+
this.log.warn('[%s]\n%s.', this.name, JSON.stringify(data))
|
|
36
|
+
} catch (err) {
|
|
37
|
+
this.accessory.logWarn(`${platformLang.refFailed} ${parseError(err)}`)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|