homebridge-lib 5.2.0 → 5.2.3

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,344 @@
1
+ // homebridge-lib/lib/ServiceDelegate/History/index.js
2
+ //
3
+ // Library for Homebridge plugins.
4
+ // Copyright © 2017-2022 Erik Baauw. All rights reserved.
5
+ //
6
+ // The logic for handling Eve history was copied from 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 homebridgeLib = require('../../../index')
13
+
14
+ const { ServiceDelegate } = homebridgeLib
15
+
16
+ const util = require('util')
17
+
18
+ /** Eve history keeps time as # seconds since epoch of 2001/01/01.
19
+ * @type {integer}
20
+ */
21
+ const epoch = Math.round(new Date('2001-01-01T00:00:00Z').valueOf() / 1000)
22
+
23
+ /** Convert Eve date (in # seconds since epoch) to string.
24
+ * @param {integer} d - Eve date as # seconds since epoch.
25
+ * @returns {string} Human readable date string.
26
+ */
27
+ function dateToString (d) {
28
+ return new Date(1000 * (epoch + d)).toString().slice(0, 24)
29
+ }
30
+
31
+ function hexToBase64 (value) {
32
+ if (value == null || typeof value !== 'string') {
33
+ throw new TypeError('value: not a string')
34
+ }
35
+ return Buffer.from((value).replace(/[^0-9A-F]/ig, ''), 'hex')
36
+ .toString('base64')
37
+ }
38
+
39
+ function base64ToHex (value) {
40
+ if (value == null || typeof value !== 'string') {
41
+ throw new TypeError('value: not a string')
42
+ }
43
+ return Buffer.from(value, 'base64').toString('hex')
44
+ }
45
+
46
+ function swap16 (value) {
47
+ return ((value & 0xFF) << 8) | ((value >>> 8) & 0xFF)
48
+ }
49
+
50
+ function swap32 (value) {
51
+ return ((value & 0xFF) << 24) | ((value & 0xFF00) << 8) |
52
+ ((value >>> 8) & 0xFF00) | ((value >>> 24) & 0xFF)
53
+ }
54
+
55
+ function numToHex (value, length) {
56
+ let s = Number(value >>> 0).toString(16)
57
+ if (s.length % 2 !== 0) {
58
+ s = '0' + s
59
+ }
60
+ if (length) {
61
+ return ('0000000000000' + s).slice(-length)
62
+ }
63
+ return s
64
+ }
65
+
66
+ /** Abstract superclass for an Eve _History_ service delegate.
67
+ *
68
+ * This delegate sets up a `Services.eve.History` HomeKit service
69
+ * with keys for the following HomeKit characteristics:
70
+ *
71
+ * key | Characteristic
72
+ * ---------------- | ----------------------------------
73
+ * `name` | `Characteristics.hap.Name`
74
+ * `historyRequest` | `Characteristics.eve.HistoryRequest`
75
+ * `setTime` | `Characteristics.eve.SetTime`
76
+ * `historyStatus` | `Characteristics.eve.HistoryStatus`
77
+ * `historyEntries` | `Characteristics.eve.HistoryEntries`
78
+ * @abstract
79
+ * @extends ServiceDelegate
80
+ * @memberof ServiceDelegate
81
+ */
82
+ class History extends ServiceDelegate {
83
+ static get Consumption () { return require('./Consumption') }
84
+ static get Contact () { return require('./Contact') }
85
+ static get Motion () { return require('./Motion') }
86
+ static get On () { return require('./On') }
87
+ static get Power () { return require('./Power') }
88
+ static get Room () { return require('./Room') }
89
+ static get Thermo () { return require('./Thermo') }
90
+ static get Weather () { return require('./Weather') }
91
+
92
+ static get hexToBase64 () { return hexToBase64 }
93
+ static get base64ToHex () { return base64ToHex }
94
+ static get swap16 () { return swap16 }
95
+ static get swap32 () { return swap32 }
96
+ static get numToHex () { return numToHex }
97
+
98
+ /** Create a new instance of an Eve _History_ service delegate.
99
+ * @param {!AccessoryDelegate} accessoryDelegate - The delegate of the
100
+ * corresponding HomeKit accessory.
101
+ * @param {!object} params - The parameters for the
102
+ * _History_ HomeKit service.
103
+ */
104
+ constructor (accessoryDelegate, params = {}) {
105
+ params.name = accessoryDelegate.name + ' History'
106
+ params.Service = accessoryDelegate.Services.eve.History
107
+ params.hidden = true
108
+ super(accessoryDelegate, params)
109
+ this._memorySize = 6 * 24 * 7 * 4 // Four weeks.
110
+ this._transfer = false
111
+ this._restarted = true
112
+
113
+ this.addCharacteristicDelegate({
114
+ key: 'history',
115
+ silent: true
116
+ })
117
+ if (this.context._history != null) {
118
+ this.values.history = this.context._history
119
+ delete this.context._history
120
+ }
121
+ this._h = this.values.history
122
+ if (this._h == null) {
123
+ this.values.history = {
124
+ firstEntry: 0,
125
+ lastEntry: 0,
126
+ history: ['noValue'],
127
+ usedMemory: 0,
128
+ currentEntry: 1,
129
+ initialTime: 0
130
+ }
131
+ this._h = this.values.history
132
+ } else {
133
+ this.debug('restored %d history entries', this._h.usedMemory)
134
+ }
135
+ if (this._h.refTime != null) {
136
+ this._h.initialTime = this._h.refTime + epoch
137
+ delete this._h.refTime
138
+ }
139
+
140
+ this.addCharacteristicDelegate({
141
+ key: 'historyRequest',
142
+ Characteristic: this.Characteristics.eve.HistoryRequest,
143
+ value: params.historyRequest,
144
+ setter: this._onSetHistoryRequest.bind(this),
145
+ silent: true
146
+ })
147
+
148
+ this.addCharacteristicDelegate({
149
+ key: 'setTime',
150
+ Characteristic: this.Characteristics.eve.SetTime,
151
+ value: params.setTime,
152
+ silent: true
153
+ }).on('didSet', (value) => {
154
+ const buffer = Buffer.from(value, 'base64')
155
+ this.debug('SetTime changed to %j', buffer.toString('hex'))
156
+ const date = dateToString(buffer.readUInt32LE())
157
+ this.debug('SetTime changed to %s', date)
158
+ })
159
+
160
+ this.addCharacteristicDelegate({
161
+ key: 'historyStatus',
162
+ Characteristic: this.Characteristics.eve.HistoryStatus,
163
+ value: params.historyStatus,
164
+ silent: true
165
+ })
166
+
167
+ this.addCharacteristicDelegate({
168
+ key: 'historyEntries',
169
+ Characteristic: this.Characteristics.eve.HistoryEntries,
170
+ value: params.historyEntries,
171
+ getter: this._onGetEntries.bind(this),
172
+ silent: true
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
+
189
+ this._accessoryDelegate.heartbeatEnabled = true
190
+ this._accessoryDelegate
191
+ .once('heartbeat', (beat) => {
192
+ this._historyBeat = (beat % 600) + 5
193
+ })
194
+ .on('heartbeat', this._heartbeat.bind(this))
195
+ .on('shutdown', () => {
196
+ this.debug('saved %d history entries', this._h.usedMemory)
197
+ })
198
+ }
199
+
200
+ _addEntry (now = Math.round(new Date().valueOf() / 1000)) {
201
+ this._entry.time = now
202
+ if (this._h.usedMemory < this._memorySize) {
203
+ this._h.usedMemory++
204
+ this._h.firstEntry = 0
205
+ this._h.lastEntry = this._h.usedMemory
206
+ } else {
207
+ this._h.firstEntry++
208
+ this._h.lastEntry = this._h.firstEntry + this._h.usedMemory
209
+ if (this._restarted === true) {
210
+ this._h.history[this._h.lastEntry % this._memorySize] = {
211
+ time: this._entry.time,
212
+ setRefTime: 1
213
+ }
214
+ this._h.firstEntry++
215
+ this._h.lastEntry = this._h.firstEntry + this._h.usedMemory
216
+ this._restarted = false
217
+ }
218
+ }
219
+
220
+ if (this._h.initialTime === 0) {
221
+ this._h.history[this._h.lastEntry] = {
222
+ time: this._entry.time,
223
+ setRefTime: 1
224
+ }
225
+ this._h.initialTime = this._entry.time
226
+ this._h.lastEntry++
227
+ this._h.usedMemory++
228
+ }
229
+
230
+ this._h.history[this._h.lastEntry % this._memorySize] =
231
+ Object.assign({}, this._entry)
232
+
233
+ const usedMemeoryOffset = this._h.usedMemory < this._memorySize ? 1 : 0
234
+ const firstEntryOffset = this._h.usedMemory < this._memorySize ? 0 : 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
260
+
261
+ const value = util.format(
262
+ '%s 00000000 %s [%s] %s %s %s 000000000101',
263
+ numToHex(swap32(this._entry.time - this._h.initialTime), 8),
264
+ numToHex(swap32(this._h.initialTime - epoch), 8),
265
+ this._fingerPrint,
266
+ numToHex(swap16(this._h.usedMemory + usedMemeoryOffset), 4),
267
+ numToHex(swap16(this._memorySize), 4),
268
+ numToHex(swap32(this._h.firstEntry + firstEntryOffset), 8)
269
+ )
270
+
271
+ this.debug('add entry %d: %j', this._h.lastEntry, this._entry)
272
+ this.debug('set history status to: %j', value)
273
+ // this.debug('set history status to: %j', buffer.slice(0, offset).toString('hex'))
274
+ this.values.historyStatus = hexToBase64(value)
275
+ }
276
+
277
+ _heartbeat (beat) {
278
+ if (beat % 600 === this._historyBeat) {
279
+ this._addEntry()
280
+ }
281
+ }
282
+
283
+ async _onSetHistoryRequest (value) {
284
+ const buffer = Buffer.from(value, 'base64')
285
+ this.debug('History Request changed to %j', base64ToHex(value))
286
+ const entry = buffer.readUInt32LE(2)
287
+ this.debug('request entry: %d', entry)
288
+ if (entry !== 0) {
289
+ this._h.currentEntry = entry
290
+ } else {
291
+ this._h.currentEntry = 1
292
+ }
293
+ this._transfer = true
294
+ }
295
+
296
+ async _onGetEntries () {
297
+ if (this._h.currentEntry > this._h.lastEntry || !this._transfer) {
298
+ this.debug('send data: 00')
299
+ this._transfer = false
300
+ return hexToBase64('00')
301
+ }
302
+
303
+ let dataStream = ''
304
+ for (let i = 0; i < 11; i++) {
305
+ const address = this._h.currentEntry % this._memorySize
306
+ if (
307
+ this._h.history[address].setRefTime === 1 ||
308
+ this._h.currentEntry === this._h.firstEntry + 1
309
+ ) {
310
+ this.debug(
311
+ 'entry: %s, address %s, reftime: %s (%s)', this._h.currentEntry,
312
+ address, this._h.initialTime - epoch,
313
+ new Date(1000 * this._h.initialTime).toString().slice(0, 24)
314
+ )
315
+ dataStream += util.format(
316
+ '|15 %s 0100000081 %s 00000000000000',
317
+ numToHex(swap32(this._h.currentEntry), 8),
318
+ numToHex(swap32(this._h.initialTime - epoch), 8))
319
+ } else {
320
+ this.debug(
321
+ 'entry: %s, address: %s, time: %s (%s)', this._h.currentEntry,
322
+ address, this._h.history[address].time - this._h.initialTime,
323
+ new Date(1000 * this._h.history[address].time).toString().slice(0, 24)
324
+ )
325
+ dataStream += this._entryStream(this._h.history[address])
326
+ }
327
+ this._h.currentEntry++
328
+ if (this._h.currentEntry > this._h.lastEntry) {
329
+ break
330
+ }
331
+ }
332
+ this.debug('send data: %s', dataStream)
333
+ return hexToBase64(dataStream)
334
+ }
335
+
336
+ // async _onGetConfig () {
337
+ // return hexToBase64('D200')
338
+ // }
339
+
340
+ // async _onSetConfig () {
341
+ // }
342
+ }
343
+
344
+ module.exports = History
@@ -0,0 +1,47 @@
1
+ // homebridge-lib/lib/ServiceDelegate/ServiceLabel.js
2
+ //
3
+ // Library for Homebridge plugins.
4
+ // Copyright © 2017-2022 Erik Baauw. All rights reserved.
5
+
6
+ 'use strict'
7
+
8
+ const homebridgeLib = require('../../index')
9
+
10
+ const { ServiceDelegate } = homebridgeLib
11
+
12
+ /** Class for a _ServiceLabel_ service delegate.
13
+ *
14
+ * This delegate sets up a `Services.hap.ServiceLabel` HomeKit service
15
+ * with the following HomeKit characteristics:
16
+ *
17
+ * key | Characteristic | isOptional
18
+ * -------------- | ------------------------------------------- | ----------
19
+ * `name` | `Characteristics.hap.Name` |
20
+ * `namespace` | `Characteristics.hap.ServiceLabelNamespace` |
21
+ * @extends ServiceDelegate
22
+ * @memberof ServiceDelegate
23
+ */
24
+ class ServiceLabel extends ServiceDelegate {
25
+ /** Create a new instance of an _ServiceLabel_ service delegate.
26
+ * @param {!AccessoryDelegate} accessoryDelegate - The delegate of the
27
+ * corresponding HomeKit accessory.
28
+ * @param {!object} params - The parameters for the
29
+ * _AccessoryInformation_ HomeKit service.
30
+ * @param {!string} params.name - Initial value for
31
+ * `Characteristics.hap.Name`. Also used to prefix log and error messages.
32
+ * @param {!string} params.namespace - Initial value for
33
+ * `Characteristics.hap.ServiceLabelNamespace`.
34
+ */
35
+ constructor (accessoryDelegate, params = {}) {
36
+ params.name = accessoryDelegate.name
37
+ params.Service = accessoryDelegate.Services.hap.ServiceLabel
38
+ super(accessoryDelegate, params)
39
+ this.addCharacteristicDelegate({
40
+ key: 'namespace',
41
+ Characteristic: this.Characteristics.hap.ServiceLabelNamespace,
42
+ value: params.namespace
43
+ })
44
+ }
45
+ }
46
+
47
+ module.exports = ServiceLabel