homebridge-lib 5.2.2 → 5.3.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/index.js +7 -0
- package/lib/CommandLineTool.js +30 -2
- package/lib/EveHomeKitTypes.js +36 -12
- package/lib/Platform.js +112 -9
- package/lib/ServiceDelegate/History/Motion.js +15 -19
- package/lib/ServiceDelegate/History/Weather.js +19 -6
- package/lib/ServiceDelegate/History/index.js +51 -25
- package/lib/UiServer.js +237 -0
- package/package.json +3 -2
package/index.js
CHANGED
|
@@ -196,6 +196,13 @@ class homebridgeLib {
|
|
|
196
196
|
*/
|
|
197
197
|
static get SystemInfo () { return require('./lib/SystemInfo') }
|
|
198
198
|
|
|
199
|
+
/** Server for dynamic configuration settings through Homebridge UI.
|
|
200
|
+
* <br>See {@link UiServer}.
|
|
201
|
+
* @type {Class}
|
|
202
|
+
* @memberof module:homebridgeLib
|
|
203
|
+
*/
|
|
204
|
+
static get UiServer () { return require('./lib/UiServer') }
|
|
205
|
+
|
|
199
206
|
/** Universal Plug and Play client.
|
|
200
207
|
* <br>See {@link UpnpClient}.
|
|
201
208
|
* @type {Class}
|
package/lib/CommandLineTool.js
CHANGED
|
@@ -150,8 +150,9 @@ class CommandLineTool {
|
|
|
150
150
|
return !!this._options.debug
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
|
|
154
|
-
|
|
153
|
+
/** Usage string.
|
|
154
|
+
* @type {string}
|
|
155
|
+
*/
|
|
155
156
|
get usage () { return this._usage }
|
|
156
157
|
set usage (usage) {
|
|
157
158
|
this._usage = usage
|
|
@@ -167,6 +168,9 @@ class CommandLineTool {
|
|
|
167
168
|
// ===== Logging =============================================================
|
|
168
169
|
|
|
169
170
|
/** Print debug message to stderr.
|
|
171
|
+
* @param {string|Error} format - The printf-style message or an instance of
|
|
172
|
+
* [Error](https://nodejs.org/dist/latest-v14.x/docs/api/errors.html#errors_class_error).
|
|
173
|
+
* @param {...string} args - Arguments to the printf-style message.
|
|
170
174
|
*/
|
|
171
175
|
debug (format, ...args) {
|
|
172
176
|
if (this._options.debug) {
|
|
@@ -175,12 +179,18 @@ class CommandLineTool {
|
|
|
175
179
|
}
|
|
176
180
|
|
|
177
181
|
/** Print error message to stderr.
|
|
182
|
+
* @param {string|Error} format - The printf-style message or an instance of
|
|
183
|
+
* [Error](https://nodejs.org/dist/latest-v14.x/docs/api/errors.html#errors_class_error).
|
|
184
|
+
* @param {...string} args - Arguments to the printf-style message.
|
|
178
185
|
*/
|
|
179
186
|
error (format, ...args) {
|
|
180
187
|
this._log({ label: 'error', chalk: chalk.bold.red }, format, ...args)
|
|
181
188
|
}
|
|
182
189
|
|
|
183
190
|
/** Print error message to stderr and abort program.
|
|
191
|
+
* @param {string|Error} format - The printf-style message or an instance of
|
|
192
|
+
* [Error](https://nodejs.org/dist/latest-v14.x/docs/api/errors.html#errors_class_error).
|
|
193
|
+
* @param {...string} args - Arguments to the printf-style message.
|
|
184
194
|
*/
|
|
185
195
|
async fatal (format, ...args) {
|
|
186
196
|
this._log({ label: 'fatal', chalk: chalk.bold.red }, format, ...args)
|
|
@@ -195,24 +205,36 @@ class CommandLineTool {
|
|
|
195
205
|
}
|
|
196
206
|
|
|
197
207
|
/** Print log message to stderr.
|
|
208
|
+
* @param {string|Error} format - The printf-style message or an instance of
|
|
209
|
+
* [Error](https://nodejs.org/dist/latest-v14.x/docs/api/errors.html#errors_class_error).
|
|
210
|
+
* @param {...string} args - Arguments to the printf-style message.
|
|
198
211
|
*/
|
|
199
212
|
log (format, ...args) {
|
|
200
213
|
this._log({}, format, ...args)
|
|
201
214
|
}
|
|
202
215
|
|
|
203
216
|
/** Print log message continuation to stderr.
|
|
217
|
+
* @param {string|Error} format - The printf-style message or an instance of
|
|
218
|
+
* [Error](https://nodejs.org/dist/latest-v14.x/docs/api/errors.html#errors_class_error).
|
|
219
|
+
* @param {...string} args - Arguments to the printf-style message.
|
|
204
220
|
*/
|
|
205
221
|
logc (format, ...args) {
|
|
206
222
|
this._log({ noLabel: true }, format, ...args)
|
|
207
223
|
}
|
|
208
224
|
|
|
209
225
|
/** Print message to stdout.
|
|
226
|
+
* @param {string|Error} format - The printf-style message or an instance of
|
|
227
|
+
* [Error](https://nodejs.org/dist/latest-v14.x/docs/api/errors.html#errors_class_error).
|
|
228
|
+
* @param {...string} args - Arguments to the printf-style message.
|
|
210
229
|
*/
|
|
211
230
|
print (format, ...args) {
|
|
212
231
|
this._log({ noLabel: true, stdout: true }, format, ...args)
|
|
213
232
|
}
|
|
214
233
|
|
|
215
234
|
/** Print verbose debug message to stderr.
|
|
235
|
+
* @param {string|Error} format - The printf-style message or an instance of
|
|
236
|
+
* [Error](https://nodejs.org/dist/latest-v14.x/docs/api/errors.html#errors_class_error).
|
|
237
|
+
* @param {...string} args - Arguments to the printf-style message.
|
|
216
238
|
*/
|
|
217
239
|
vdebug (format, ...args) {
|
|
218
240
|
if (this._options.vdebug) {
|
|
@@ -221,6 +243,9 @@ class CommandLineTool {
|
|
|
221
243
|
}
|
|
222
244
|
|
|
223
245
|
/** Print very verbose debug message to stderr.
|
|
246
|
+
* @param {string|Error} format - The printf-style message or an instance of
|
|
247
|
+
* [Error](https://nodejs.org/dist/latest-v14.x/docs/api/errors.html#errors_class_error).
|
|
248
|
+
* @param {...string} args - Arguments to the printf-style message.
|
|
224
249
|
*/
|
|
225
250
|
vvdebug (format, ...args) {
|
|
226
251
|
if (this._options.vvdebug) {
|
|
@@ -229,6 +254,9 @@ class CommandLineTool {
|
|
|
229
254
|
}
|
|
230
255
|
|
|
231
256
|
/** Print warning message to stderr.
|
|
257
|
+
* @param {string|Error} format - The printf-style message or an instance of
|
|
258
|
+
* [Error](https://nodejs.org/dist/latest-v14.x/docs/api/errors.html#errors_class_error).
|
|
259
|
+
* @param {...string} args - Arguments to the printf-style message.
|
|
232
260
|
*/
|
|
233
261
|
warn (format, ...args) {
|
|
234
262
|
this._log({ label: 'warning', chalk: chalk.yellow }, format, ...args)
|
package/lib/EveHomeKitTypes.js
CHANGED
|
@@ -40,13 +40,12 @@ class EveHomeKitTypes extends homebridgeLib.CustomHomeKitTypes {
|
|
|
40
40
|
super(homebridge)
|
|
41
41
|
|
|
42
42
|
/** @member EveHomeKitTypes#Characteristics
|
|
43
|
-
* @property {Class} AirParticulateDensity -
|
|
44
|
-
*
|
|
45
|
-
* <br>Used by: Eve Room.
|
|
43
|
+
* @property {Class} AirParticulateDensity - Deprecated. use `VOCLevel`
|
|
44
|
+
* instead.
|
|
46
45
|
* @property {Class} AirPressure - Air pressure (in hPa).
|
|
47
46
|
* <br>Used by: Eve Weather.
|
|
48
|
-
* @property {Class} ClosedDuration -
|
|
49
|
-
*
|
|
47
|
+
* @property {Class} ClosedDuration - Duration (in seconds) that door has
|
|
48
|
+
* been closed.
|
|
50
49
|
* <br>Used by: Eve Door.
|
|
51
50
|
* @property {Class} Clouds - Cloud coverage (in %).
|
|
52
51
|
* <br>Used by: weather station.
|
|
@@ -59,6 +58,10 @@ class EveHomeKitTypes extends homebridgeLib.CustomHomeKitTypes {
|
|
|
59
58
|
* @property {Class} ConditionCategory - Weather condition
|
|
60
59
|
* (as numberic code).
|
|
61
60
|
* <br>Used by: weather station.
|
|
61
|
+
* @property {Class} ConfigCommand - Used by Eve app to set configuration.
|
|
62
|
+
* <br> Used by: `History` service.
|
|
63
|
+
* @property {Class} ConfigData - Used by Eve app to read configuration.
|
|
64
|
+
* <br> Used by: `History` service.
|
|
62
65
|
* @property {Class} CurrentConsumption - Current electric consumption
|
|
63
66
|
* (in W).
|
|
64
67
|
* <br>Used by: Eve Energy.
|
|
@@ -98,7 +101,7 @@ class EveHomeKitTypes extends homebridgeLib.CustomHomeKitTypes {
|
|
|
98
101
|
* @property {Class} ObservationTime - Time of observation.
|
|
99
102
|
* <br>Used by: weather station.
|
|
100
103
|
* @property {Class} OpenDuration - Duration (in seconds) that door has
|
|
101
|
-
* been
|
|
104
|
+
* been open.
|
|
102
105
|
* <br>Used by: Eve Door.
|
|
103
106
|
* @property {Class} Ozone - Ozone level (in DU).
|
|
104
107
|
* <br>Used by: weather station.
|
|
@@ -138,6 +141,8 @@ class EveHomeKitTypes extends homebridgeLib.CustomHomeKitTypes {
|
|
|
138
141
|
* <br>Used by: Eve Thermo.
|
|
139
142
|
* @property {Class} Visibility - Visibility (in km).
|
|
140
143
|
* <br>Used by: weather station.
|
|
144
|
+
* @property {Class} VOCLevel - Volatile Organic Compound level (in ppm).
|
|
145
|
+
* <br>Used by: Eve Room (1st gen).
|
|
141
146
|
* @property {Class} Voltage - Electric voltage (in V).
|
|
142
147
|
* <br>Used by: Eve Energy.
|
|
143
148
|
* @property {Class} WindDirection - Wind direction (as text).
|
|
@@ -167,23 +172,32 @@ class EveHomeKitTypes extends homebridgeLib.CustomHomeKitTypes {
|
|
|
167
172
|
maxValue: 5000,
|
|
168
173
|
minStep: 1,
|
|
169
174
|
perms: [this.Perms.READ, this.Perms.NOTIFY]
|
|
170
|
-
}, '
|
|
175
|
+
}, 'VOC Level')
|
|
176
|
+
|
|
177
|
+
this.createCharacteristicClass('VOCLevel', uuid('10B'), {
|
|
178
|
+
format: this.Formats.INT16,
|
|
179
|
+
unit: 'ppm',
|
|
180
|
+
minValue: 5,
|
|
181
|
+
maxValue: 5000,
|
|
182
|
+
minStep: 5,
|
|
183
|
+
perms: [this.Perms.READ, this.Perms.NOTIFY]
|
|
184
|
+
}, 'VOC Level')
|
|
171
185
|
|
|
172
186
|
this.createCharacteristicClass('TotalConsumption', uuid('10C'), {
|
|
173
187
|
format: this.Formats.FLOAT,
|
|
188
|
+
unit: 'kWh',
|
|
174
189
|
minValue: 0,
|
|
175
190
|
maxValue: 1000000,
|
|
176
191
|
minStep: 0.1,
|
|
177
|
-
unit: 'kWh',
|
|
178
192
|
perms: [this.Perms.READ, this.Perms.NOTIFY]
|
|
179
193
|
}, 'Total Consumption')
|
|
180
194
|
|
|
181
195
|
this.createCharacteristicClass('CurrentConsumption', uuid('10D'), {
|
|
182
196
|
format: this.Formats.FLOAT,
|
|
197
|
+
unit: 'W',
|
|
183
198
|
minValue: 0,
|
|
184
199
|
maxValue: 12000,
|
|
185
200
|
minStep: 0.1,
|
|
186
|
-
unit: 'W',
|
|
187
201
|
perms: [this.Perms.READ, this.Perms.NOTIFY]
|
|
188
202
|
}, 'Current Consumption')
|
|
189
203
|
|
|
@@ -236,6 +250,11 @@ class EveHomeKitTypes extends homebridgeLib.CustomHomeKitTypes {
|
|
|
236
250
|
perms: [this.Perms.WRITE, this.Perms.HIDDEN]
|
|
237
251
|
}, 'History Request')
|
|
238
252
|
|
|
253
|
+
this.createCharacteristicClass('ConfigCommand', uuid('11D'), {
|
|
254
|
+
format: this.Formats.DATA,
|
|
255
|
+
perms: [this.Perms.WRITE, this.Perms.HIDDEN]
|
|
256
|
+
}, 'Config Command')
|
|
257
|
+
|
|
239
258
|
this.createCharacteristicClass('Sensitivity', uuid('120'), {
|
|
240
259
|
format: this.Formats.UINT8,
|
|
241
260
|
minValue: 0,
|
|
@@ -314,6 +333,11 @@ class EveHomeKitTypes extends homebridgeLib.CustomHomeKitTypes {
|
|
|
314
333
|
adminOnlyAccess: [this.Access.WRITE]
|
|
315
334
|
})
|
|
316
335
|
|
|
336
|
+
this.createCharacteristicClass('ConfigData', uuid('131'), {
|
|
337
|
+
format: this.Formats.DATA,
|
|
338
|
+
perms: [this.Perms.READ, this.Perms.NOTIFY, this.Perms.HIDDEN]
|
|
339
|
+
}, 'Config Data')
|
|
340
|
+
|
|
317
341
|
// =========================================================================
|
|
318
342
|
|
|
319
343
|
// The following custom characteristics are supported by the Eve app.
|
|
@@ -541,10 +565,10 @@ class EveHomeKitTypes extends homebridgeLib.CustomHomeKitTypes {
|
|
|
541
565
|
this.Characteristics.HistoryStatus,
|
|
542
566
|
this.Characteristics.HistoryEntries
|
|
543
567
|
], [
|
|
544
|
-
// this.Characteristics.Char11E,
|
|
568
|
+
// this.Characteristics.Char11E, // Used for firmware update?
|
|
545
569
|
this.Characteristics.ResetTotal
|
|
546
|
-
// this.Characteristics.
|
|
547
|
-
// this.Characteristics.
|
|
570
|
+
// this.Characteristics.ConfigCommand,
|
|
571
|
+
// this.Characteristics.ConfigData
|
|
548
572
|
])
|
|
549
573
|
|
|
550
574
|
this.createServiceClass('AirPressureSensor', uuid('00A'), [
|
package/lib/Platform.js
CHANGED
|
@@ -9,8 +9,10 @@ const homebridgeLib = require('../index')
|
|
|
9
9
|
|
|
10
10
|
const events = require('events')
|
|
11
11
|
const fs = require('fs')
|
|
12
|
+
const http = require('http')
|
|
12
13
|
const semver = require('semver')
|
|
13
14
|
const util = require('util')
|
|
15
|
+
|
|
14
16
|
const libPackageJson = require('../package.json')
|
|
15
17
|
|
|
16
18
|
const uuid = /^[0-9A-F]{8}-[0-9A-F]{4}-[1-5][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/
|
|
@@ -37,7 +39,8 @@ const context = {
|
|
|
37
39
|
* the plugin through Homebridge;
|
|
38
40
|
* - Persist HomeKit accessories across Homebridge restarts;
|
|
39
41
|
* - Support for device polling by providing a heartbeat;
|
|
40
|
-
* - Support for UPnP device discovery
|
|
42
|
+
* - Support for UPnP device discovery;
|
|
43
|
+
* - Support dynamic configuration through the Homebridge UI.
|
|
41
44
|
* @abstract
|
|
42
45
|
* @extends Delegate
|
|
43
46
|
*/
|
|
@@ -172,11 +175,7 @@ class Platform extends homebridgeLib.Delegate {
|
|
|
172
175
|
this.log('os: %s', this.systemInfo.osInfo.prettyName)
|
|
173
176
|
this._heartbeatStart = new Date()
|
|
174
177
|
setTimeout(() => { this._beat(-1) }, 1000)
|
|
175
|
-
this.on('exit', () => {
|
|
176
|
-
this.debug('flush cachedAccessories')
|
|
177
|
-
this._homebridge.updatePlatformAccessories()
|
|
178
|
-
this.log('goodbye')
|
|
179
|
-
})
|
|
178
|
+
this.on('exit', () => { this._flushCachedAccessories() })
|
|
180
179
|
|
|
181
180
|
const n = Object.keys(this._accessories).length
|
|
182
181
|
if (n > 0) {
|
|
@@ -188,7 +187,13 @@ class Platform extends homebridgeLib.Delegate {
|
|
|
188
187
|
if (this.listenerCount('upnpDeviceFound') > 0) {
|
|
189
188
|
this._upnpMonitor.search()
|
|
190
189
|
}
|
|
190
|
+
if (typeof this.onUiRequest === 'function') {
|
|
191
|
+
try {
|
|
192
|
+
await this._createUiServer()
|
|
193
|
+
} catch (error) { this.error(error) }
|
|
194
|
+
}
|
|
191
195
|
await events.once(this, 'initialised')
|
|
196
|
+
this._flushCachedAccessories()
|
|
192
197
|
for (const id in this._accessories) {
|
|
193
198
|
if (this._accessoryDelegates[id] == null) {
|
|
194
199
|
const accessory = this._accessories[id]
|
|
@@ -205,6 +210,12 @@ class Platform extends homebridgeLib.Delegate {
|
|
|
205
210
|
}
|
|
206
211
|
}
|
|
207
212
|
|
|
213
|
+
// Write `cachedAccessories` to disk.
|
|
214
|
+
_flushCachedAccessories () {
|
|
215
|
+
this.debug('flush cachedAccessories')
|
|
216
|
+
this._homebridge.updatePlatformAccessories()
|
|
217
|
+
}
|
|
218
|
+
|
|
208
219
|
// Called every second.
|
|
209
220
|
_beat (beat) {
|
|
210
221
|
beat += 1
|
|
@@ -221,9 +232,7 @@ class Platform extends homebridgeLib.Delegate {
|
|
|
221
232
|
}, 1000 - drift)
|
|
222
233
|
|
|
223
234
|
if (beat % context.saveInterval === 30) {
|
|
224
|
-
|
|
225
|
-
this.debug('flush cachedAccessories')
|
|
226
|
-
this._homebridge.updatePlatformAccessories()
|
|
235
|
+
this._flushCachedAccessories()
|
|
227
236
|
}
|
|
228
237
|
|
|
229
238
|
if (beat % context.checkInterval === 0) {
|
|
@@ -255,6 +264,9 @@ class Platform extends homebridgeLib.Delegate {
|
|
|
255
264
|
this._shuttingDown = true
|
|
256
265
|
this.removeAllListeners('upnpDeviceAlive')
|
|
257
266
|
this.removeAllListeners('upnpDeviceFound')
|
|
267
|
+
if (this._ui != null && this._ui.abortController != null) {
|
|
268
|
+
this._ui.abortController.abort()
|
|
269
|
+
}
|
|
258
270
|
for (const id in this._accessoryDelegates) {
|
|
259
271
|
/** Emitted when Homebridge is shutting down.
|
|
260
272
|
*
|
|
@@ -545,6 +557,97 @@ class Platform extends homebridgeLib.Delegate {
|
|
|
545
557
|
})
|
|
546
558
|
}
|
|
547
559
|
|
|
560
|
+
// ===== Dynamic Configuration through Homebridge UI =========================
|
|
561
|
+
|
|
562
|
+
/** Handler for requests from the Homebridge Plugin UI Server.
|
|
563
|
+
* @function Platform#onUiRequest
|
|
564
|
+
* @async
|
|
565
|
+
* @abstract
|
|
566
|
+
* @param {string} method - The request method.
|
|
567
|
+
* @param {string} resource - The request resource.
|
|
568
|
+
* @param {*} body - The request body.
|
|
569
|
+
* @returns {*} - The response body.
|
|
570
|
+
*/
|
|
571
|
+
|
|
572
|
+
// Create HTTP server for Homebridge Plugin UI Settings.
|
|
573
|
+
async _createUiServer () {
|
|
574
|
+
this._ui = {}
|
|
575
|
+
this._ui.server = new http.Server()
|
|
576
|
+
this._ui.server
|
|
577
|
+
.on('listening', () => {
|
|
578
|
+
this._ui.port = this._ui.server.address().port
|
|
579
|
+
this.log('ui server: listening on http://127.0.0.1:%d/', this._ui.port)
|
|
580
|
+
for (const id in this._accessoryDelegates) {
|
|
581
|
+
this._accessoryDelegates[id]._context.uiPort = this._ui.port
|
|
582
|
+
}
|
|
583
|
+
})
|
|
584
|
+
.on('error', (error) => { this.error(error) })
|
|
585
|
+
.on('close', () => {
|
|
586
|
+
this.debug('ui server: closed port %d', this._ui.port)
|
|
587
|
+
})
|
|
588
|
+
.on('request', async (request, response) => {
|
|
589
|
+
let buffer = ''
|
|
590
|
+
request.on('data', (data) => { buffer += data })
|
|
591
|
+
request.on('end', async () => {
|
|
592
|
+
try {
|
|
593
|
+
if (buffer !== '') {
|
|
594
|
+
try {
|
|
595
|
+
request.body = JSON.parse(buffer)
|
|
596
|
+
} catch (error) {
|
|
597
|
+
this.log(
|
|
598
|
+
'ui request %s: %s %s %s', ++this._ui.requestId,
|
|
599
|
+
request.method, request.url, buffer
|
|
600
|
+
)
|
|
601
|
+
this.warn('ui request %d: %s', this._ui.requestId, error.message)
|
|
602
|
+
response.writeHead(400) // Bad Request
|
|
603
|
+
response.end()
|
|
604
|
+
return
|
|
605
|
+
}
|
|
606
|
+
this.debug(
|
|
607
|
+
'ui request %s: %s %s %j', ++this._ui.requestId,
|
|
608
|
+
request.method, request.url, request.body
|
|
609
|
+
)
|
|
610
|
+
} else {
|
|
611
|
+
this.debug(
|
|
612
|
+
'ui request %s: %s %s', ++this._ui.requestId,
|
|
613
|
+
request.method, request.url
|
|
614
|
+
)
|
|
615
|
+
}
|
|
616
|
+
const { status, body } =
|
|
617
|
+
request.method === 'GET' && request.url === '/ping'
|
|
618
|
+
? { status: 200, body: 'pong' }
|
|
619
|
+
: await this.onUiRequest(
|
|
620
|
+
request.method, request.url, request.body
|
|
621
|
+
)
|
|
622
|
+
this.debug(
|
|
623
|
+
'ui request %d: %d %s', this._ui.requestId,
|
|
624
|
+
status, http.STATUS_CODES[status]
|
|
625
|
+
)
|
|
626
|
+
if (status === 200) {
|
|
627
|
+
this.vdebug('ui request %d: response: %j', this._ui.requestId, body)
|
|
628
|
+
response.writeHead(status, { 'Content-Type': 'application/json' })
|
|
629
|
+
response.end(JSON.stringify(body))
|
|
630
|
+
} else {
|
|
631
|
+
response.writeHead(status)
|
|
632
|
+
response.end()
|
|
633
|
+
}
|
|
634
|
+
} catch (error) {
|
|
635
|
+
this.warn('ui request %d: %s', this._ui.requestId, error)
|
|
636
|
+
response.writeHead(500) // Internal Server Error
|
|
637
|
+
response.end()
|
|
638
|
+
}
|
|
639
|
+
})
|
|
640
|
+
})
|
|
641
|
+
this._ui.abortController = new AbortController() // eslint-disable-line no-undef
|
|
642
|
+
this._ui.requestId = 0
|
|
643
|
+
this._ui.server.listen({
|
|
644
|
+
port: 0,
|
|
645
|
+
host: '127.0.0.1',
|
|
646
|
+
signal: this._ui.abortController.signal
|
|
647
|
+
})
|
|
648
|
+
await events.once(this._ui.server, 'listening')
|
|
649
|
+
}
|
|
650
|
+
|
|
548
651
|
// ===== Logging =============================================================
|
|
549
652
|
|
|
550
653
|
// Do the heavy lifting for debug(), error(), fatal(), log(), and warn(),
|
|
@@ -75,15 +75,14 @@ class Motion extends History {
|
|
|
75
75
|
const now = Math.round(new Date().valueOf() / 1000)
|
|
76
76
|
lastActivationDelegate.value = now - this._h.initialTime
|
|
77
77
|
this._entry.status = value
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
// }
|
|
78
|
+
if (this._entry.temp != null) {
|
|
79
|
+
const temp = this._entry.temp
|
|
80
|
+
this._entry.temp = null
|
|
81
|
+
this._addEntry(now)
|
|
82
|
+
this._entry.temp = temp
|
|
83
|
+
} else {
|
|
84
|
+
this._addEntry(now)
|
|
85
|
+
}
|
|
87
86
|
})
|
|
88
87
|
if (temperatureDelegate != null) {
|
|
89
88
|
this._entry.temp = temperatureDelegate.value
|
|
@@ -94,27 +93,24 @@ class Motion extends History {
|
|
|
94
93
|
}
|
|
95
94
|
|
|
96
95
|
get _fingerPrint () {
|
|
97
|
-
|
|
98
|
-
return '02 1c01 0102'
|
|
99
|
-
}
|
|
100
|
-
return '01 1c01'
|
|
96
|
+
return '02 1c01 0102'
|
|
101
97
|
}
|
|
102
98
|
|
|
103
99
|
_entryStream (entry) {
|
|
104
|
-
if (
|
|
100
|
+
if (entry.temp == null) {
|
|
105
101
|
return util.format(
|
|
106
|
-
'|
|
|
102
|
+
'|0b %s %s 01 %s',
|
|
107
103
|
numToHex(swap32(this._h.currentEntry), 8),
|
|
108
104
|
numToHex(swap32(entry.time - this._h.initialTime), 8),
|
|
109
|
-
numToHex(entry.status, 2)
|
|
110
|
-
numToHex(swap16(entry.temp * 100), 4)
|
|
105
|
+
numToHex(entry.status, 2)
|
|
111
106
|
)
|
|
112
107
|
}
|
|
113
108
|
return util.format(
|
|
114
|
-
'|
|
|
109
|
+
'|0d %s %s 03 %s %s',
|
|
115
110
|
numToHex(swap32(this._h.currentEntry), 8),
|
|
116
111
|
numToHex(swap32(entry.time - this._h.initialTime), 8),
|
|
117
|
-
numToHex(entry.status, 2)
|
|
112
|
+
numToHex(entry.status, 2),
|
|
113
|
+
numToHex(swap16(entry.temp * 100), 4)
|
|
118
114
|
)
|
|
119
115
|
}
|
|
120
116
|
}
|
|
@@ -74,9 +74,7 @@ class Weather extends ServiceDelegate.History {
|
|
|
74
74
|
}
|
|
75
75
|
this._entry = {
|
|
76
76
|
time: 0,
|
|
77
|
-
temp: temperatureDelegate.value
|
|
78
|
-
humidity: 0,
|
|
79
|
-
pressure: 0
|
|
77
|
+
temp: temperatureDelegate.value
|
|
80
78
|
}
|
|
81
79
|
temperatureDelegate.on('didSet', (value) => {
|
|
82
80
|
this._entry.temp = value
|
|
@@ -95,11 +93,26 @@ class Weather extends ServiceDelegate.History {
|
|
|
95
93
|
}
|
|
96
94
|
}
|
|
97
95
|
|
|
98
|
-
get _fingerPrint () {
|
|
99
|
-
return '03 0102 0202 0302'
|
|
100
|
-
}
|
|
96
|
+
get _fingerPrint () { return '03 0102 0202 0302' }
|
|
101
97
|
|
|
102
98
|
_entryStream (entry) {
|
|
99
|
+
if (entry.humidity == null) {
|
|
100
|
+
return util.format(
|
|
101
|
+
'|0c %s %s 01 %s',
|
|
102
|
+
numToHex(swap32(this._h.currentEntry), 8),
|
|
103
|
+
numToHex(swap32(entry.time - this._h.initialTime), 8),
|
|
104
|
+
numToHex(swap16(entry.temp * 100), 4)
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
if (entry.pressure == null) {
|
|
108
|
+
return util.format(
|
|
109
|
+
'|0e %s %s 03 %s %s',
|
|
110
|
+
numToHex(swap32(this._h.currentEntry), 8),
|
|
111
|
+
numToHex(swap32(entry.time - this._h.initialTime), 8),
|
|
112
|
+
numToHex(swap16(entry.temp * 100), 4),
|
|
113
|
+
numToHex(swap16(entry.humidity * 100), 4)
|
|
114
|
+
)
|
|
115
|
+
}
|
|
103
116
|
return util.format(
|
|
104
117
|
'|10 %s %s 07 %s %s %s',
|
|
105
118
|
numToHex(swap32(this._h.currentEntry), 8),
|
|
@@ -144,6 +144,7 @@ class History extends ServiceDelegate {
|
|
|
144
144
|
setter: this._onSetHistoryRequest.bind(this),
|
|
145
145
|
silent: true
|
|
146
146
|
})
|
|
147
|
+
|
|
147
148
|
this.addCharacteristicDelegate({
|
|
148
149
|
key: 'setTime',
|
|
149
150
|
Characteristic: this.Characteristics.eve.SetTime,
|
|
@@ -155,12 +156,14 @@ class History extends ServiceDelegate {
|
|
|
155
156
|
const date = dateToString(buffer.readUInt32LE())
|
|
156
157
|
this.debug('SetTime changed to %s', date)
|
|
157
158
|
})
|
|
159
|
+
|
|
158
160
|
this.addCharacteristicDelegate({
|
|
159
161
|
key: 'historyStatus',
|
|
160
162
|
Characteristic: this.Characteristics.eve.HistoryStatus,
|
|
161
163
|
value: params.historyStatus,
|
|
162
164
|
silent: true
|
|
163
165
|
})
|
|
166
|
+
|
|
164
167
|
this.addCharacteristicDelegate({
|
|
165
168
|
key: 'historyEntries',
|
|
166
169
|
Characteristic: this.Characteristics.eve.HistoryEntries,
|
|
@@ -168,6 +171,21 @@ class History extends ServiceDelegate {
|
|
|
168
171
|
getter: this._onGetEntries.bind(this),
|
|
169
172
|
silent: true
|
|
170
173
|
})
|
|
174
|
+
|
|
175
|
+
// this.addCharacteristicDelegate({
|
|
176
|
+
// key: 'configCommand',
|
|
177
|
+
// Characteristic: this.Characteristics.eve.ConfigCommand,
|
|
178
|
+
// setter: this._onSetConfig.bind(this)
|
|
179
|
+
// // silent: true
|
|
180
|
+
// })
|
|
181
|
+
|
|
182
|
+
// this.addCharacteristicDelegate({
|
|
183
|
+
// key: 'configData',
|
|
184
|
+
// Characteristic: this.Characteristics.eve.ConfigData,
|
|
185
|
+
// getter: this._onGetConfig.bind(this)
|
|
186
|
+
// // silent: true
|
|
187
|
+
// })
|
|
188
|
+
|
|
171
189
|
this._accessoryDelegate.heartbeatEnabled = true
|
|
172
190
|
this._accessoryDelegate
|
|
173
191
|
.once('heartbeat', (beat) => {
|
|
@@ -214,30 +232,31 @@ class History extends ServiceDelegate {
|
|
|
214
232
|
|
|
215
233
|
const usedMemeoryOffset = this._h.usedMemory < this._memorySize ? 1 : 0
|
|
216
234
|
const firstEntryOffset = this._h.usedMemory < this._memorySize ? 0 : 1
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
this.
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
235
|
+
|
|
236
|
+
// const buffer = Buffer.alloc(1024)
|
|
237
|
+
// let offset = 0
|
|
238
|
+
// buffer.writeUInt32LE(this._entry.time - this._h.initialTime, offset)
|
|
239
|
+
// offset += 4
|
|
240
|
+
// buffer.writeUInt32LE(0, offset)
|
|
241
|
+
// offset += 4
|
|
242
|
+
// buffer.writeUInt32LE(this._h.initialTime - epoch, offset)
|
|
243
|
+
// offset += 4
|
|
244
|
+
// buffer.write(this._fingerPrint.replace(/[^0-9A-F]/ig, ''), offset, 'hex')
|
|
245
|
+
// const length = 1 + 2 * parseInt(this._fingerPrint.slice(0, 2))
|
|
246
|
+
// this.debug('fingerprint length: %d', length)
|
|
247
|
+
// offset += length
|
|
248
|
+
// buffer.writeUInt16LE(this._h.usedMemory + usedMemeoryOffset, offset)
|
|
249
|
+
// offset += 2
|
|
250
|
+
// buffer.writeUInt16LE(this._memorySize, offset)
|
|
251
|
+
// offset += 2
|
|
252
|
+
// buffer.writeUInt32LE(this._h.firstEntry + firstEntryOffset, offset)
|
|
253
|
+
// offset += 4
|
|
254
|
+
// buffer.writeUInt32LE(0, offset)
|
|
255
|
+
// offset += 4
|
|
256
|
+
// buffer.writeUint8(1, offset)
|
|
257
|
+
// offset += 1
|
|
258
|
+
// buffer.writeUint8(1, offset)
|
|
259
|
+
// offset += 1
|
|
241
260
|
|
|
242
261
|
const value = util.format(
|
|
243
262
|
'%s 00000000 %s [%s] %s %s %s 000000000101',
|
|
@@ -251,7 +270,7 @@ class History extends ServiceDelegate {
|
|
|
251
270
|
|
|
252
271
|
this.debug('add entry %d: %j', this._h.lastEntry, this._entry)
|
|
253
272
|
this.debug('set history status to: %j', value)
|
|
254
|
-
this.debug('set history status to: %j', buffer.slice(0, offset).toString('hex'))
|
|
273
|
+
// this.debug('set history status to: %j', buffer.slice(0, offset).toString('hex'))
|
|
255
274
|
this.values.historyStatus = hexToBase64(value)
|
|
256
275
|
}
|
|
257
276
|
|
|
@@ -313,6 +332,13 @@ class History extends ServiceDelegate {
|
|
|
313
332
|
this.debug('send data: %s', dataStream)
|
|
314
333
|
return hexToBase64(dataStream)
|
|
315
334
|
}
|
|
335
|
+
|
|
336
|
+
// async _onGetConfig () {
|
|
337
|
+
// return hexToBase64('D200')
|
|
338
|
+
// }
|
|
339
|
+
|
|
340
|
+
// async _onSetConfig () {
|
|
341
|
+
// }
|
|
316
342
|
}
|
|
317
343
|
|
|
318
344
|
module.exports = History
|
package/lib/UiServer.js
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
// homebridge-deconz/homebridge-lib/UiServer.js
|
|
2
|
+
//
|
|
3
|
+
// Library for Homebridge plugins.
|
|
4
|
+
// Copyright © 2022 Erik Baauw. All rights reserved.
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
HomebridgePluginUiServer // , RequestError
|
|
10
|
+
} = require('@homebridge/plugin-ui-utils')
|
|
11
|
+
const { HttpClient, formatError } = require('../index')
|
|
12
|
+
const chalk = require('chalk')
|
|
13
|
+
const fs = require('fs/promises')
|
|
14
|
+
const path = require('path')
|
|
15
|
+
const util = require('util')
|
|
16
|
+
|
|
17
|
+
/** Server for handling Homebridge Plugin UI requests.
|
|
18
|
+
*
|
|
19
|
+
* See {@link https://github.com/homebridge/plugin-ui-utils plugin-ui-utils}.
|
|
20
|
+
*
|
|
21
|
+
* The Homebridge Plugin UI Server runs in a separate process, which is spawned
|
|
22
|
+
* by the Homebridge UI when the plugin _Settings_ are opened.
|
|
23
|
+
* It implements an {@link HttpClient} to connect to the HTTP server provided
|
|
24
|
+
* by {@link Platform} to change plugin settings dynamically.
|
|
25
|
+
*
|
|
26
|
+
* `UiServer` implemensts the following requests, which are documented as events:
|
|
27
|
+
* - {@link UiServer#event:get get} - Send a GET request to the plugin instance.
|
|
28
|
+
* - {@link UiServer#event:put put} - Send a put request to the plugin instance.
|
|
29
|
+
* - {@link UiServer#event:cachedAccessories cachedAccessories} - Get the
|
|
30
|
+
* cachedAccessories for a (child) bridge instance.
|
|
31
|
+
* @extends HomebridgePluginUiServer
|
|
32
|
+
*/
|
|
33
|
+
class UiServer extends HomebridgePluginUiServer {
|
|
34
|
+
constructor () {
|
|
35
|
+
super()
|
|
36
|
+
this.clients = {}
|
|
37
|
+
|
|
38
|
+
/** Do a GET request to the plugin instance.
|
|
39
|
+
* @event UiServer#get
|
|
40
|
+
* @type {object}
|
|
41
|
+
* @property {integer} uiPort - The port of the plugin instance UI server.
|
|
42
|
+
* @property {string} resource - The requested resource.
|
|
43
|
+
* @returns {*} - The response body.
|
|
44
|
+
*/
|
|
45
|
+
this.onRequest('get', async (params) => {
|
|
46
|
+
try {
|
|
47
|
+
const { uiPort, path } = params
|
|
48
|
+
const client = this._getClient(uiPort)
|
|
49
|
+
const { body } = await client.get(path)
|
|
50
|
+
return body
|
|
51
|
+
} catch (error) {
|
|
52
|
+
if (!(error instanceof HttpClient.HttpError)) {
|
|
53
|
+
this.error(error)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
/** Do a PUT request to the plugin instance.
|
|
59
|
+
* @event UiServer#put
|
|
60
|
+
* @param {object}
|
|
61
|
+
* @property {integer} uiPort - The port of the plugin instance UI server.
|
|
62
|
+
* @property {string} resource - The requested resource.
|
|
63
|
+
* @property {*} body - The body of the request.
|
|
64
|
+
* @returns {HttpResponse} - The response.
|
|
65
|
+
*/
|
|
66
|
+
this.onRequest('put', async (params) => {
|
|
67
|
+
try {
|
|
68
|
+
const { uiPort, path, body } = params
|
|
69
|
+
const client = this._getClient(uiPort)
|
|
70
|
+
const response = await client.put(path, JSON.stringify(body))
|
|
71
|
+
return response
|
|
72
|
+
} catch (error) {
|
|
73
|
+
if (!(error instanceof HttpClient.HttpError)) {
|
|
74
|
+
this.error(error)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
/** Get the cached accessories for a single (child) bridge instance.
|
|
80
|
+
*
|
|
81
|
+
* This endpoint is needed because `homebridge.getCachedAccessories()` from
|
|
82
|
+
* `plugin-ui-utils` doesn't indicate to which child bridge an accessory
|
|
83
|
+
* belongs.
|
|
84
|
+
* @event UiServer#cachedAccessories
|
|
85
|
+
* @type {object}
|
|
86
|
+
* @property {?string} username - The virtual MAC address of the child
|
|
87
|
+
* bridge. Use `null` for the main bridge.
|
|
88
|
+
* @returns {Object} cachedAccessories - The cached accessories.
|
|
89
|
+
*/
|
|
90
|
+
this.onRequest('cachedAccessories', async (params) => {
|
|
91
|
+
try {
|
|
92
|
+
const { username } = params
|
|
93
|
+
let fileName = 'cachedAccessories'
|
|
94
|
+
if (username != null) {
|
|
95
|
+
fileName += '.' + username.replace(/:/g, '').toUpperCase()
|
|
96
|
+
}
|
|
97
|
+
const fullFileName = path.join(
|
|
98
|
+
this.homebridgeStoragePath,
|
|
99
|
+
'accessories',
|
|
100
|
+
fileName
|
|
101
|
+
)
|
|
102
|
+
const json = await fs.readFile(fullFileName)
|
|
103
|
+
const cachedAccessories = JSON.parse(json)
|
|
104
|
+
return cachedAccessories
|
|
105
|
+
} catch (error) {
|
|
106
|
+
this.error(error)
|
|
107
|
+
}
|
|
108
|
+
return []
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
_getClient (uiPort) {
|
|
113
|
+
if (this.clients[uiPort] == null) {
|
|
114
|
+
this.clients[uiPort] = new HttpClient({
|
|
115
|
+
host: 'localhost:' + uiPort,
|
|
116
|
+
json: true,
|
|
117
|
+
keepAlive: true
|
|
118
|
+
})
|
|
119
|
+
this.clients[uiPort]
|
|
120
|
+
.on('error', (error) => {
|
|
121
|
+
this.warn('request %d: %s', error.request.id, error)
|
|
122
|
+
})
|
|
123
|
+
.on('request', (request) => {
|
|
124
|
+
if (request.body == null) {
|
|
125
|
+
this.debug(
|
|
126
|
+
'request %d: %s %s', request.id, request.method, request.resource
|
|
127
|
+
)
|
|
128
|
+
} else {
|
|
129
|
+
this.debug(
|
|
130
|
+
'request %d: %s %s %j', request.id,
|
|
131
|
+
request.method, request.resource, request.body
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
})
|
|
135
|
+
.on('response', (response) => {
|
|
136
|
+
this.vdebug(
|
|
137
|
+
'request %d: response: %j', response.request.id, response.body
|
|
138
|
+
)
|
|
139
|
+
this.debug(
|
|
140
|
+
'request %d: %s %s', response.request.id,
|
|
141
|
+
response.statusCode, response.statusMessage
|
|
142
|
+
)
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
return this.clients[uiPort]
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ===== Logging =============================================================
|
|
149
|
+
|
|
150
|
+
/** Print debug message to stdout.
|
|
151
|
+
* @param {string|Error} format - The printf-style message or an instance of
|
|
152
|
+
* [Error](https://nodejs.org/dist/latest-v14.x/docs/api/errors.html#errors_class_error).
|
|
153
|
+
* @param {...string} args - Arguments to the printf-style message.
|
|
154
|
+
*/
|
|
155
|
+
debug (format, ...args) {
|
|
156
|
+
this._log({ chalk: chalk.grey }, format, ...args)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Print error message to stdout.
|
|
160
|
+
* @param {string|Error} format - The printf-style message or an instance of
|
|
161
|
+
* [Error](https://nodejs.org/dist/latest-v14.x/docs/api/errors.html#errors_class_error).
|
|
162
|
+
* @param {...string} args - Arguments to the printf-style message.
|
|
163
|
+
*/
|
|
164
|
+
error (format, ...args) {
|
|
165
|
+
this._log({ label: 'error', chalk: chalk.bold.red }, format, ...args)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Print log message to stdout.
|
|
169
|
+
* @param {string|Error} format - The printf-style message or an instance of
|
|
170
|
+
* [Error](https://nodejs.org/dist/latest-v14.x/docs/api/errors.html#errors_class_error).
|
|
171
|
+
* @param {...string} args - Arguments to the printf-style message.
|
|
172
|
+
*/
|
|
173
|
+
log (format, ...args) {
|
|
174
|
+
this._log({}, format, ...args)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Print verbose debug message to stdout.
|
|
178
|
+
* @param {string|Error} format - The printf-style message or an instance of
|
|
179
|
+
* [Error](https://nodejs.org/dist/latest-v14.x/docs/api/errors.html#errors_class_error).
|
|
180
|
+
* @param {...string} args - Arguments to the printf-style message.
|
|
181
|
+
*/
|
|
182
|
+
vdebug (format, ...args) {
|
|
183
|
+
this._log({ chalk: chalk.grey }, format, ...args)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Print very verbose debug message to stdout.
|
|
187
|
+
* @param {string|Error} format - The printf-style message or an instance of
|
|
188
|
+
* [Error](https://nodejs.org/dist/latest-v14.x/docs/api/errors.html#errors_class_error).
|
|
189
|
+
* @param {...string} args - Arguments to the printf-style message.
|
|
190
|
+
*/
|
|
191
|
+
vvdebug (format, ...args) {
|
|
192
|
+
this._log({ chalk: chalk.grey }, format, ...args)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Print warning message to stdout.
|
|
196
|
+
* @param {string|Error} format - The printf-style message or an instance of
|
|
197
|
+
* [Error](https://nodejs.org/dist/latest-v14.x/docs/api/errors.html#errors_class_error).
|
|
198
|
+
* @param {...string} args - Arguments to the printf-style message.
|
|
199
|
+
*/
|
|
200
|
+
warn (format, ...args) {
|
|
201
|
+
this._log({ label: 'warning', chalk: chalk.yellow }, format, ...args)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Do the heavy lifting for debug(), error(), fatal(), log(), and warn(),
|
|
205
|
+
// taking into account the options, and errors vs exceptions.
|
|
206
|
+
_log (params = {}, ...args) {
|
|
207
|
+
const output = process.stdout
|
|
208
|
+
let message = ''
|
|
209
|
+
|
|
210
|
+
// If last argument is Error convert it to string.
|
|
211
|
+
if (args.length > 0) {
|
|
212
|
+
let lastArg = args.pop()
|
|
213
|
+
if (lastArg instanceof Error) {
|
|
214
|
+
lastArg = formatError(lastArg, true)
|
|
215
|
+
}
|
|
216
|
+
args.push(lastArg)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Format message.
|
|
220
|
+
if (args[0] == null) {
|
|
221
|
+
message = ''
|
|
222
|
+
} else if (typeof args[0] === 'string') {
|
|
223
|
+
message = util.format(...args)
|
|
224
|
+
} else {
|
|
225
|
+
message = util.format('%o', ...args)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Handle colours.
|
|
229
|
+
if (params.chalk != null) {
|
|
230
|
+
message = params.chalk(message)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
output.write(message)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
module.exports = UiServer
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Library for homebridge plugins",
|
|
4
4
|
"author": "Erik Baauw",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
|
-
"version": "5.
|
|
6
|
+
"version": "5.3.0",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"homekit",
|
|
9
9
|
"homebridge"
|
|
@@ -22,9 +22,10 @@
|
|
|
22
22
|
},
|
|
23
23
|
"engines": {
|
|
24
24
|
"homebridge": "^1.4.0",
|
|
25
|
-
"node": "^16.
|
|
25
|
+
"node": "^16.14.0"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
+
"@homebridge/plugin-ui-utils": "~0.0.19",
|
|
28
29
|
"bonjour-hap": "^3.6.3",
|
|
29
30
|
"chalk": "^4.1.2",
|
|
30
31
|
"semver": "^7.3.5"
|