homebridge-lib 6.2.2 → 6.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.
@@ -0,0 +1,679 @@
1
+ // homebridge-lib/lib/ServiceDelegate/History.js
2
+ //
3
+ // Library for Homebridge plugins.
4
+ // Copyright © 2017-2023 Erik Baauw. All rights reserved.
5
+ //
6
+ // The logic for handling Eve history was inspired by Simone Tisa's
7
+ // fakagato-history repository, copyright © 2017 simont77.
8
+ // See https://github.com/simont77/fakegato-history.
9
+
10
+ 'use strict'
11
+
12
+ const { CharacteristicDelegate } = require('../../index')
13
+ const homebridgeLib = require('../../index')
14
+
15
+ const { ServiceDelegate } = homebridgeLib
16
+
17
+ /** Eve history keeps time as # seconds since epoch of 2001/01/01.
18
+ * @type {integer}
19
+ */
20
+ const epoch = Math.round(new Date('2001-01-01T00:00:00Z').valueOf() / 1000)
21
+
22
+ const defaultMemorySize = 6 * 24 * 7 * 4 // 4 weeks of 1 entry per 10 minutes
23
+
24
+ /** Convert date (in # seconds since NodeJS epoch) to string.
25
+ * @param {integer} d - Seconds since NodeJS epoch.
26
+ * @returns {string} Human readable date string.
27
+ */
28
+ function dateToString (d) {
29
+ return new Date(1000 * d).toString().slice(0, 24)
30
+ }
31
+
32
+ /** Abstract superclass for type of data point in Eve History.
33
+ *
34
+ * An Eve history service supports up to seven data points, corresponding to
35
+ * an associated characteristic value. These data points are defined by tag and
36
+ * length in the fingerprint reported through `eve.HistoryStatus`.
37
+ * Different history entries can contain a different combination of data points.
38
+ * @abstract
39
+ * @memberof ServiceDelegate.History
40
+ */
41
+ class HistoryValue {
42
+ /** Create a new data point type.
43
+ * @param {ServiceDelegate.History} parent - The delegate for the `eve.History`
44
+ * service.
45
+ * @param {CharactertisticDelegate} delegate - The delefate for the associated
46
+ * characteristic.
47
+ */
48
+ constructor (parent, delegate) {
49
+ if (!(parent instanceof History)) {
50
+ throw new TypeError('parent: not a ServiceDelegate.History')
51
+ }
52
+ if (!(delegate instanceof CharacteristicDelegate)) {
53
+ throw new TypeError('delegate: not a CharacteristicDelegate')
54
+ }
55
+ this._parent = parent
56
+ this._delegate = delegate
57
+ }
58
+
59
+ /** The 1-byte tag, identifying the data point to Eve.
60
+ * @type {integer}
61
+ */
62
+ get tag () { }
63
+
64
+ /** The 1-byte length of a data point value.
65
+ * @type {integer}
66
+ */
67
+ get length () { return 2 }
68
+
69
+ /** The 1-letter id, identifying a data point in the Eve history service delegate.
70
+ * @type {string}
71
+ */
72
+ get id () { }
73
+
74
+ /** Add the data point to an entry
75
+ * @params {Object} entry - The history entry.
76
+ */
77
+ prepareEntry (entry) { entry[this.id] = this._delegate.value }
78
+
79
+ /** Write the data point from an entry to a buffer.
80
+ * @params {Object} entry - The history entry.
81
+ * @params {Buffer} buffer - The buffer to write to.
82
+ * @params {integer} offset - The offset within the buffer.
83
+ */
84
+ writeEntry (entry, buffer, offset) { buffer.writeInt16LE(entry[this.id], offset) }
85
+ }
86
+
87
+ class TemperatureValue extends HistoryValue {
88
+ get tag () { return 0x01 }
89
+ get id () { return 't' }
90
+ prepareEntry (entry) { entry[this.id] = this._delegate.value * 100 }
91
+ }
92
+
93
+ class HumidityValue extends HistoryValue {
94
+ get tag () { return 0x02 }
95
+ get id () { return 'h' }
96
+ prepareEntry (entry) { entry[this.id] = this._delegate.value * 100 }
97
+ }
98
+
99
+ class AirPressureValue extends HistoryValue {
100
+ get tag () { return 0x03 }
101
+ get id () { return 'a' }
102
+ prepareEntry (entry) { entry[this.id] = this._delegate.value * 10 }
103
+ }
104
+
105
+ class ContactValue extends HistoryValue {
106
+ get tag () { return 0x06 }
107
+ get length () { return 1 }
108
+ get id () { return 'c' }
109
+ prepareEntry (entry) { entry[this.id] = this._delegate.value ? 1 : 0 }
110
+ writeEntry (entry, buffer, offset) { buffer.writeInt8(entry[this.id], offset) }
111
+
112
+ constructor (parent, delegate) {
113
+ super(parent, delegate)
114
+ delegate.on('didSet', (value) => {
115
+ const now = History.now()
116
+ const entry = { time: now }
117
+ entry[this.id] = this._delegate.value ? 1 : 0
118
+ parent.addEntry(entry)
119
+ if (parent.lastContactDelegate != null) {
120
+ parent.lastContactDelegate = parent.lastActivationValue(now)
121
+ }
122
+ if (parent.timesOpenedDelegate != null) {
123
+ parent.timesOpenedDelegate.value += value
124
+ }
125
+ })
126
+ parent.addCharacteristicDelegate({
127
+ key: 'resetTotal',
128
+ Characteristic: this.Characteristics.eve.ResetTotal
129
+ }).on('didSet', (value) => {
130
+ parent.timesOpenedDelegate.value = 0
131
+ })
132
+ }
133
+ }
134
+
135
+ class ConsumptionValue extends HistoryValue {
136
+ get tag () { return 0x07 }
137
+ get id () { return 'p' }
138
+
139
+ constructor (parent, delegate) {
140
+ super(parent, delegate)
141
+ this._consumption = delegate.value
142
+ this._time = History.now()
143
+ }
144
+
145
+ prepareEntry (entry) {
146
+ const delta = this._delegate.value - this._consumption // kWh
147
+ const period = entry.time - this._time // s
148
+ const power = 1000 * 3600 * delta / period // W
149
+ if (this._parent.computedPowerDelegate != null) {
150
+ this._parent.computedPowerDelegate.value = Math.round(power) // W
151
+ }
152
+ entry[this.id] = Math.round(power * 10) // 0.1 W * 10 min
153
+ this._consumption = this._delegate.value
154
+ this._time = entry.time
155
+ }
156
+ }
157
+
158
+ class PowerValue extends HistoryValue {
159
+ get tag () { return 0x07 }
160
+ get id () { return 'p' }
161
+
162
+ constructor (parent, delegate) {
163
+ super(parent, delegate)
164
+ this._runningConsumption = 0 // 10-min-interval running value
165
+ this._power = delegate.value // current power
166
+ this._time = History.now() // start time of current power
167
+ delegate.on('didSet', (value) => {
168
+ const now = History.now()
169
+ const delta = this._power * (now - this._time) // Ws
170
+ this._runningConsumption += Math.round(delta / 60.0) // 0.1W * 10 min
171
+ this._totalConsumption += delta / 36000.0 // 0.01 kWh
172
+ this._power = value
173
+ this._time = now
174
+ })
175
+ parent.addCharacteristicDelegate({
176
+ key: 'resetTotal',
177
+ Characteristic: this.Characteristics.eve.ResetTotal
178
+ }).on('didSet', (value) => {
179
+ this._runningConsumption = 0
180
+ this._totalConsumption = 0
181
+ parent.computedConsumptionDelegate.value = this._totalConsumption
182
+ })
183
+ }
184
+
185
+ prepareEntry (entry) {
186
+ const delta = this._power * (entry.time - this._time) // Ws
187
+ this._runningConsumption += delta / 60.0 // 0.1 W * 10 min
188
+ this._totalConsumption += delta / 36000.0 // 0.01 kWh
189
+ this._parent.computedConsumptionDelegate.value = Math.round(this._totalConsumption) / 100.0 // kWh
190
+ entry[this.id] = Math.round(this._runningConsumption) // 0.1 W * 10 min
191
+ this._power = this._delegate.value
192
+ this._time = entry.time
193
+ this._runningConsumption = 0
194
+ }
195
+ }
196
+
197
+ class ValvePositionValue extends HistoryValue {
198
+ get tag () { return 0x10 }
199
+ get length () { return 1 }
200
+ get id () { return 'v' }
201
+ writeEntry (entry, buffer, offset) { buffer.writeInt8(entry[this.id], offset) }
202
+ }
203
+
204
+ class TargetTemperatureValue extends HistoryValue {
205
+ get tag () { return 0x11 }
206
+ get id () { return 's' }
207
+ prepareEntry (entry) { entry[this.id] = this._delegate.value * 100 }
208
+ }
209
+
210
+ class MotionValue extends HistoryValue {
211
+ get tag () { return 0x1C }
212
+ get length () { return 1 }
213
+ get id () { return 'm' }
214
+ prepareEntry (entry) { entry[this.id] = this._delegate.value ? 1 : 0 }
215
+ writeEntry (entry, buffer, offset) { buffer.writeInt8(entry[this.id], offset) }
216
+
217
+ constructor (parent, delegate) {
218
+ super(parent, delegate)
219
+ delegate.on('didSet', (value) => {
220
+ const now = History.now()
221
+ const entry = { time: now }
222
+ entry[this.id] = this._delegate.value ? 1 : 0
223
+ parent.addEntry(entry)
224
+ parent.lastMotionDelegate.value = parent.lastActivationValue(now)
225
+ })
226
+ }
227
+ }
228
+
229
+ class OnValue extends HistoryValue {
230
+ get tag () { return 0x1E }
231
+ get length () { return 1 }
232
+ get id () { return 'o' }
233
+ prepareEntry (entry) { entry[this.id] = this._delegate.value ? 1 : 0 }
234
+ writeEntry (entry, buffer, offset) { buffer.writeInt8(entry[this.id], offset) }
235
+
236
+ constructor (parent, delegate) {
237
+ super(parent, delegate)
238
+ delegate.on('didSet', (value) => {
239
+ const now = History.now()
240
+ const entry = { time: now }
241
+ entry[this.id] = this._delegate.value ? 1 : 0
242
+ this._parent.addEntry(entry)
243
+ parent.lastOnDelegate.value = parent.lastActivationValue(now)
244
+ })
245
+ }
246
+ }
247
+
248
+ class VocDensityValue extends HistoryValue {
249
+ get tag () { return 0x22 }
250
+ get id () { return 'q' }
251
+ }
252
+
253
+ class LightLevelValue extends HistoryValue {
254
+ get tag () { return 0x30 }
255
+ get id () { return 'l' }
256
+ // writeEntry (entry, buffer, offset) { buffer.writeUInt16LE(entry[this.id], offset) }
257
+ }
258
+
259
+ // Types of delegates for characteristics for history data points.
260
+ const historyValueTypes = {
261
+ temperature: TemperatureValue,
262
+ humidity: HumidityValue,
263
+ airPressure: AirPressureValue,
264
+ contact: ContactValue,
265
+ power: PowerValue,
266
+ consumption: ConsumptionValue,
267
+ valvePosition: ValvePositionValue,
268
+ targetTemperature: TargetTemperatureValue,
269
+ motion: MotionValue,
270
+ on: OnValue,
271
+ vocDensity: VocDensityValue,
272
+ lightLevel: LightLevelValue
273
+ }
274
+
275
+ // Types of delegates for characteristic maintained by Eve history.
276
+ const historyDerivedTypes = {
277
+ lastContact: 'contact',
278
+ timesOpened: 'contact',
279
+ lastMotion: 'motion',
280
+ lastOn: 'on',
281
+ lastLightOn: 'lightOn',
282
+ computedConsumption: 'power',
283
+ computedPower: 'consumption'
284
+ }
285
+
286
+ /** Class for an Eve _History_ service delegate.
287
+ *
288
+ * This delegate sets up a `Services.eve.History` HomeKit service
289
+ * with keys for the following HomeKit characteristics:
290
+ *
291
+ * key | Characteristic
292
+ * ---------------- | ----------------------------------
293
+ * `name` | `Characteristics.hap.Name`
294
+ * `historyRequest` | `Characteristics.eve.HistoryRequest`
295
+ * `setTime` | `Characteristics.eve.SetTime`
296
+ * `historyStatus` | `Characteristics.eve.HistoryStatus`
297
+ * `historyEntries` | `Characteristics.eve.HistoryEntries`
298
+ * @abstract
299
+ * @extends ServiceDelegate
300
+ * @memberof ServiceDelegate
301
+ */
302
+ class History extends ServiceDelegate {
303
+ /** Create a new instance of an Eve _History_ service delegate.
304
+ * @param {!AccessoryDelegate} accessoryDelegate - The delegate of the
305
+ * corresponding HomeKit accessory.
306
+ * @param {!object} params - The parameters for the _History_ HomeKit service.
307
+ * @param {?CharacteristicDelegate} params.contactDelegate - The
308
+ * `hap.ContactSensorState` characteristic delegate
309
+ * for a `hap.ContactSensor` service.
310
+ * @param {?CharacteristicDelegate} params.lastContactDelegate - The
311
+ * `eve.LastActivation` characteristic delegate
312
+ * for a `hap.ContactSensor` service.
313
+ * @param {?CharacteristicDelegate} params.timesOpenedDelegate - The
314
+ * `eve.TimesOpened` characteristic delegate
315
+ * for a `hap.ContactSensor` service.
316
+ * @param {?CharacteristicDelegate} params.motionDelegate - The
317
+ * `.hap.MotionDetected` characteristic delegate
318
+ * for a `hap.MotionSensor` service.
319
+ * @param {?CharacteristicDelegate} params.lastMotionDelegate - The
320
+ * `.eve.LastActivation` characteristic delegate
321
+ * for a `hap.MotionSensor` service.
322
+ * @param {?CharacteristicDelegate} params.lightLevelDelegate - The
323
+ * `.hap.CurrentAmbientLightLevel` characteristic delegate
324
+ * for a `hap.LightLevelSensor` service.
325
+ * @param {?CharacteristicDelegate} params.temperatureDelegate - The
326
+ * `.hap.CurrentTemperature` characteristic delegate
327
+ * for a `hap.TemperatureSensor`, `eve.Weather`, or `hap.Thermostat` service.
328
+ * @param {?CharacteristicDelegate} params.humidityDelegate - The
329
+ * `hap.CurrentRelativeHumidity` characteristic delegate
330
+ * for a `hap.HumiditySensor` or `eve.Weather` service.
331
+ * @param {?CharacteristicDelegate} params.airPressureDelegate - The
332
+ * `.eve.AirPressure` characteristic delegate
333
+ * for an `eve.AirPressureSensor` or `eve.Weather` service.
334
+ * @param {?CharacteristicDelegate} params.vocDensityDelegate - The
335
+ * `hap.VOCDensity` characteristic delegate
336
+ * for a `hap.AirQualilitySensor` service.
337
+ * @param {?CharacteristicDelegate} params.targetTemperatureDelegate - The
338
+ * `hap.TargetTemperature` characteristic delegate
339
+ * for a `hap.Thermostat` service.
340
+ * @param {?CharacteristicDelegate} params.valvePositionDelegate - The
341
+ * `.eve.ValvePosition` characteristic delegate
342
+ * for a `hap.Thermostat` service.
343
+ * @param {?CharacteristicDelegate} params.onDelegate - The
344
+ * `Characteristics.hap.On` characteristic delegate
345
+ * for a `hap.Outlet` service.
346
+ * @param {?CharacteristicDelegate} params.lastOnDelegate - The
347
+ * `.eve.LastActivation` characteristic delegate
348
+ * for a `hap.Outlet` service.
349
+ * @param {?CharacteristicDelegate} params.lightOnDelegate - The
350
+ * `Characteristics.hap.On` characteristic delegate
351
+ * for a `hap.Lightbulb` service.
352
+ * @param {?CharacteristicDelegate} params.lastLightOnDelegate - A
353
+ * `.eve.LastActivation` characteristic delegate
354
+ * for a `hap.Lightbulb` service.
355
+ * @param {?CharacteristicDelegate} params.powerDelegate - The
356
+ * `.eve.CurrentConsumption` characteristic delegate
357
+ * for a `hap.Outlet` or `eve.Consumption` service
358
+ * for a device that reports current consumption.
359
+ * @param {?CharacteristicDelegate} params.computedConsumptionDelegate - The
360
+ * `eve.TotalConsumption` characteristic delegate
361
+ * for a `hap.Outlet` or `eve.Consumption` service
362
+ * for a device that reports current consumption, but not total consumption.
363
+ * @param {?CharacteristicDelegate} params.consumptionDelegate - The
364
+ * `.eve.TotalConsumption` characteristic delegate
365
+ * for a `hap.Outlet` or `eve.Consumption` service
366
+ * for a device that reports total consumption.
367
+ * @param {?CharacteristicDelegate} params.computedPowerDelegate - The
368
+ * `.eve.CurrentConsumption` characteristic delegate
369
+ * for a `hap.Outlet` or `eve.Consumption` service
370
+ * for a device that reports total consumption but not current consumption.
371
+ * @param {integer} [params.memorySize=4032] - The memory size, in number of
372
+ * history entries. The default is 4 weeks of 1 entry per 10 minutes.
373
+ * @param {?boolean} params.config - Expose config.
374
+ */
375
+ constructor (accessoryDelegate, params = {}) {
376
+ params.name = accessoryDelegate.name + ' History'
377
+ params.Service = accessoryDelegate.Services.eve.History
378
+ params.hidden = true
379
+ super(accessoryDelegate, params)
380
+
381
+ const delegates = []
382
+ let i = 0
383
+ for (const param of Object.keys(params)) {
384
+ if (param.endsWith('Delegate')) {
385
+ if (!(params[param] instanceof CharacteristicDelegate)) {
386
+ throw new TypeError(`params.${param}: not a CharacteristicDelegate`)
387
+ }
388
+ const key = param.slice(0, -8)
389
+ if (historyDerivedTypes[key] != null) {
390
+ if (params[historyDerivedTypes[key] + 'Delegate'] == null) {
391
+ throw new SyntaxError(`params.${param}: missing params.${key}Delegate`)
392
+ }
393
+ this[param] = params[param]
394
+ continue
395
+ }
396
+ if (key === 'lightOn') {
397
+ if (!(params.lastLightOnDelegate instanceof CharacteristicDelegate)) {
398
+ throw new SyntaxError(`params.${param}: missing params.${key}Delegate`)
399
+ }
400
+ params[param].on('didSet', (value) => {
401
+ const now = History.now()
402
+ params.lastLightOnDelegate.value = this.lastActivationValue(now)
403
+ this.addEntry({ time: now })
404
+ })
405
+ continue
406
+ }
407
+ if (historyValueTypes[key] == null) {
408
+ throw new SyntaxError(`params.${param}: invalid parameter`)
409
+ }
410
+ if (++i > 7) {
411
+ throw new SyntaxError(`params.${param}: more than 7 history values`)
412
+ }
413
+ delegates.push({ key, param })
414
+ }
415
+ }
416
+ const fingerPrint = Buffer.alloc(15)
417
+ let offset = 0
418
+ fingerPrint.writeUInt8(i, offset); offset++
419
+ this._valueTypes = []
420
+ for (const { key, param } of delegates) {
421
+ const valueType = new historyValueTypes[key](this, params[param])
422
+ fingerPrint.writeUInt8(valueType.tag, offset); offset++
423
+ fingerPrint.writeUInt8(valueType.length, offset); offset++
424
+ this._valueTypes.push(new historyValueTypes[key](this, params[param]))
425
+ }
426
+ this.fingerPrint = fingerPrint.slice(0, offset)
427
+ this.debug('fingerPrint: 0x%s', this.fingerPrint.toString('hex').toUpperCase())
428
+ const memorySize = i === 0
429
+ ? 0
430
+ : params.memorySize == null ? defaultMemorySize : params.memorySize
431
+
432
+ this._transfer = false
433
+
434
+ this.addCharacteristicDelegate({
435
+ key: 'history',
436
+ silent: true
437
+ })
438
+ this._h = this.values.history
439
+ if (this._h == null) {
440
+ this.values.history = {
441
+ memorySize,
442
+ firstEntry: 1,
443
+ lastEntry: 1,
444
+ entryOffset: 0,
445
+ entries: [null, null],
446
+ initialTime: History.now()
447
+ }
448
+ this._h = this.values.history
449
+ } else if (this._h.memorySize !== memorySize) {
450
+ this.values.history = {
451
+ memorySize,
452
+ firstEntry: this._h.lastEntry,
453
+ lastEntry: this._h.lastEntry,
454
+ entryOffset: this._h.lastEntry - 1,
455
+ entries: [null, null],
456
+ initialTime: this._h.initialTime
457
+ }
458
+ this._h = this.values.history
459
+ } else {
460
+ this.debug(
461
+ 'restored %d history entries (%d to %d)', this._h.entries.length,
462
+ this._h.firstEntry, this._h.lastEntry
463
+ )
464
+ }
465
+
466
+ this.addCharacteristicDelegate({
467
+ key: 'historyRequest',
468
+ Characteristic: this.Characteristics.eve.HistoryRequest,
469
+ value: params.historyRequest,
470
+ setter: this._onSetHistoryRequest.bind(this),
471
+ silent: true
472
+ })
473
+
474
+ this.addCharacteristicDelegate({
475
+ key: 'setTime',
476
+ Characteristic: this.Characteristics.eve.SetTime,
477
+ value: params.setTime,
478
+ silent: true
479
+ }).on('didSet', (value) => {
480
+ const buffer = Buffer.from(value, 'base64')
481
+ this.vdebug('SetTime changed to %j', buffer.toString('hex'))
482
+ const date = dateToString(buffer.readUInt32LE() + epoch)
483
+ this.debug('SetTime changed to %s', date)
484
+ })
485
+
486
+ this.addCharacteristicDelegate({
487
+ key: 'historyStatus',
488
+ Characteristic: this.Characteristics.eve.HistoryStatus,
489
+ value: params.historyStatus,
490
+ silent: true
491
+ })
492
+
493
+ this.addCharacteristicDelegate({
494
+ key: 'historyEntries',
495
+ Characteristic: this.Characteristics.eve.HistoryEntries,
496
+ value: params.historyEntries,
497
+ getter: this._onGetEntries.bind(this),
498
+ silent: true
499
+ })
500
+
501
+ if (params.config) {
502
+ this.addCharacteristicDelegate({
503
+ key: 'configCommand',
504
+ Characteristic: this.Characteristics.eve.ConfigCommand,
505
+ setter: this._onSetConfig.bind(this)
506
+ // silent: true
507
+ })
508
+
509
+ this.addCharacteristicDelegate({
510
+ key: 'configData',
511
+ Characteristic: this.Characteristics.eve.ConfigData,
512
+ getter: this._onGetConfig.bind(this)
513
+ // silent: true
514
+ })
515
+ }
516
+
517
+ accessoryDelegate.propertyDelegate('name')
518
+ .on('didSet', (value) => {
519
+ this.values.configuredName = value + ' History'
520
+ })
521
+
522
+ this._accessoryDelegate.heartbeatEnabled = true
523
+ this._accessoryDelegate
524
+ .once('heartbeat', (beat) => {
525
+ this._historyBeat = (beat % 600) + 5
526
+ })
527
+ .on('heartbeat', (beat) => {
528
+ if (beat % 600 === this._historyBeat) {
529
+ const entry = { time: History.now() }
530
+ for (const valueType of this._valueTypes) {
531
+ valueType.prepareEntry(entry)
532
+ }
533
+ this.addEntry(entry)
534
+ }
535
+ })
536
+ .on('shutdown', () => {
537
+ this.debug(
538
+ 'saved %d history entries (%d to %d)', this._h.entries.length,
539
+ this._h.firstEntry, this._h.lastEntry
540
+ )
541
+ })
542
+ }
543
+
544
+ /** Return current time as # seconds since NodeJS epoch.
545
+ * @returns {integer} # seconds since NodeJS epoch.
546
+ */
547
+ static now () { return Math.round(new Date().valueOf() / 1000) }
548
+
549
+ /** Convert date intp `Characteristics.eve.LastActivation` characteristic value.
550
+ * @param {integer} date - Seconds since NodeJS epoch.
551
+ * @returns {integer} Value for last activation.
552
+ */
553
+ lastActivationValue (date = History.now()) { return date - this._h.initialTime }
554
+
555
+ /** Convert a history entry to a buffer.
556
+ * @abstract
557
+ * @param {object} entry - The entry.
558
+ * @returns {Buffer} A Buffer with the values from the entry.
559
+ */
560
+ entryToBuffer (entry) {
561
+ const buffer = Buffer.alloc(16)
562
+ let bitmap = 0
563
+ let offset = 1
564
+ for (const i in this._valueTypes) {
565
+ const valueType = this._valueTypes[i]
566
+ if (entry[valueType.id] != null) {
567
+ bitmap |= 0x01 << i
568
+ valueType.writeEntry(entry, buffer, offset)
569
+ offset += valueType.length
570
+ }
571
+ }
572
+ buffer.writeUInt8(bitmap, 0)
573
+ return buffer.slice(0, offset)
574
+ }
575
+
576
+ /** Add an entry to the history.
577
+ * @param {object} entry - The entry.
578
+ */
579
+ addEntry (entry) {
580
+ if (this._h.memorySize > 0) {
581
+ if (this._h.lastEntry - this._h.entryOffset >= this._h.memorySize) {
582
+ this._h.firstEntry++
583
+ }
584
+ this._h.lastEntry++
585
+ const index = (this._h.lastEntry - this._h.entryOffset) % this._h.memorySize
586
+ this.debug(
587
+ 'History Entries: set entry %d (index %d) to %j',
588
+ this._h.lastEntry, index, entry
589
+ )
590
+ this._h.entries[index] = entry
591
+ }
592
+
593
+ this.debug('set History Status to %d .. %d', this._h.firstEntry, this._h.lastEntry)
594
+ const buffer = Buffer.alloc(1024)
595
+ let offset = 0
596
+ buffer.writeUInt32LE(entry.time - this._h.initialTime, offset); offset += 4
597
+ buffer.writeUInt32LE(0, offset); offset += 4
598
+ buffer.writeUInt32LE(this._h.initialTime - epoch, offset); offset += 4
599
+ this.fingerPrint.copy(buffer, offset); offset += this.fingerPrint.length
600
+ buffer.writeUInt16LE(this._h.lastEntry - this._h.firstEntry + 1, offset); offset += 2
601
+ buffer.writeUInt16LE(this._h.memorySize, offset); offset += 2
602
+ buffer.writeUInt32LE(this._h.firstEntry, offset); offset += 4
603
+ buffer.writeUInt32LE(0, offset); offset += 4
604
+ buffer.writeUInt8(1, offset); offset += 1
605
+ buffer.writeUInt8(1, offset); offset += 1
606
+ const value = buffer.slice(0, offset)
607
+ this.values.historyStatus = value.toString('base64')
608
+ }
609
+
610
+ async _onSetHistoryRequest (value) {
611
+ const buffer = Buffer.from(value, 'base64')
612
+ this.vdebug('History Request changed to %j', buffer.toString('hex'))
613
+ const entry = buffer.readUInt32LE(2)
614
+ this.debug(
615
+ 'History Request changed to %d (%d to %d)', entry,
616
+ this._h.firstEntry, this._h.lastEntry
617
+ )
618
+ this._currentEntry = Math.max(this._h.firstEntry, entry)
619
+ this._transfer = true
620
+ }
621
+
622
+ async _onGetEntries () {
623
+ if (this._currentEntry > this._h.lastEntry || !this._transfer) {
624
+ this.debug('History Entries: no entry')
625
+ this.vdebug('History Entries: send data: 00')
626
+ this._transfer = false
627
+ return Buffer.from('00', 'hex').toString('base64')
628
+ }
629
+
630
+ const buffer = Buffer.alloc(1024)
631
+ let offset = 0
632
+ for (let i = 0; i < 11; i++) {
633
+ const index = this._h.memorySize === 0
634
+ ? 1
635
+ : (this._currentEntry - this._h.entryOffset) % this._h.memorySize
636
+ if (this._currentEntry === this._h.firstEntry) {
637
+ this.debug(
638
+ 'History Entries (%d/11): entry %d (index: %d): %j (%s)',
639
+ i, this._currentEntry, index, { initialTime: this._h.initialTime - epoch },
640
+ dateToString(this._h.initialTime)
641
+ )
642
+ buffer.writeUInt8(21, offset); offset += 1
643
+ buffer.writeUInt32LE(this._currentEntry, offset); offset += 4
644
+ buffer.write('0100000081', offset, 'hex'); offset += 5
645
+ buffer.writeUInt32LE(this._h.initialTime - epoch, offset); offset += 4
646
+ buffer.write('00000000000000', offset, 'hex'); offset += 7
647
+ } else {
648
+ const entry = this._h.entries[index]
649
+ this.debug(
650
+ 'History Entries (%d/11): entry %d (index: %d): %j (%s)',
651
+ i, this._currentEntry, index, entry, dateToString(entry.time)
652
+ )
653
+ const b = this.entryToBuffer(entry)
654
+ buffer.writeUInt8(b.length + 9, offset); offset += 1
655
+ buffer.writeUInt32LE(this._currentEntry, offset); offset += 4
656
+ buffer.writeUInt32LE(entry.time - this._h.initialTime, offset); offset += 4
657
+ b.copy(buffer, offset); offset += b.length
658
+ }
659
+ this._currentEntry++
660
+ if (this._currentEntry > this._h.lastEntry) {
661
+ break
662
+ }
663
+ }
664
+ const value = buffer.slice(0, offset)
665
+ this.vdebug('History Entries: send data: %s', value.toString('hex'))
666
+ return value.toString('base64')
667
+ }
668
+
669
+ async _onSetConfig (value) {
670
+ const buffer = Buffer.from(value, 'base64')
671
+ this.vdebug('Config Request changed to %j', buffer.toString('hex'))
672
+ }
673
+
674
+ async _onGetConfig () {
675
+ return Buffer.from('D200', 'hex').toString('base64')
676
+ }
677
+ }
678
+
679
+ module.exports = History
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": "6.2.2",
6
+ "version": "6.3.0",
7
7
  "keywords": [
8
8
  "homekit",
9
9
  "homebridge"