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 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}
@@ -150,8 +150,9 @@ class CommandLineTool {
150
150
  return !!this._options.debug
151
151
  }
152
152
 
153
- /* Usage string.
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)
@@ -40,13 +40,12 @@ class EveHomeKitTypes extends homebridgeLib.CustomHomeKitTypes {
40
40
  super(homebridge)
41
41
 
42
42
  /** @member EveHomeKitTypes#Characteristics
43
- * @property {Class} AirParticulateDensity - Density (in ppm) of air
44
- * particulates.
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 - Time in seconds that door has been
49
- * open.
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 closed.
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
- }, 'Air Particulate Density')
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.Char11D,
547
- // this.Characteristics.Char131
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
- // Persist dynamic platform accessories to cachedAccessories
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
- // Eve v5.4.2 fills in missing temperature at 0.00°C.
79
- // if (this._entry.temp != null) {
80
- // const temp = this._entry.temp
81
- // this._entry.temp = null
82
- // this._addEntry(now)
83
- // this._entry.temp = temp
84
- // } else {
85
- this._addEntry(now)
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
- if (this._entry.temp != null) {
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 (this._entry.temp != null) {
100
+ if (entry.temp == null) {
105
101
  return util.format(
106
- '|0d %s %s 03 %s %s',
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
- '|0b %s %s 01 %s',
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
- const buffer = Buffer.alloc(1024)
218
- let offset = 0
219
- buffer.writeUInt32LE(this._entry.time - this._h.initialTime, offset)
220
- offset += 4
221
- buffer.writeUInt32LE(0, offset)
222
- offset += 4
223
- buffer.writeUInt32LE(this._h.initialTime - epoch, offset)
224
- offset += 4
225
- buffer.write(this._fingerPrint.replace(/[^0-9A-F]/ig, ''), offset, 'hex')
226
- const length = 1 + 2 * parseInt(this._fingerPrint.slice(0, 2))
227
- this.debug('fingerprint length: %d', length)
228
- offset += length
229
- buffer.writeUInt16LE(this._h.usedMemory + usedMemeoryOffset, offset)
230
- offset += 2
231
- buffer.writeUInt16LE(this._memorySize, offset)
232
- offset += 2
233
- buffer.writeUInt32LE(this._h.firstEntry + firstEntryOffset, offset)
234
- offset += 4
235
- buffer.writeUInt32LE(0, offset)
236
- offset += 4
237
- buffer.writeUint8(1, offset)
238
- offset += 1
239
- buffer.writeUint8(1, offset)
240
- offset += 1
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
@@ -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.2.2",
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.13.2"
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"