homebridge-lib 5.2.0 → 5.2.1

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,318 @@
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
+ this.addCharacteristicDelegate({
148
+ key: 'setTime',
149
+ Characteristic: this.Characteristics.eve.SetTime,
150
+ value: params.setTime,
151
+ silent: true
152
+ }).on('didSet', (value) => {
153
+ const buffer = Buffer.from(value, 'base64')
154
+ this.debug('SetTime changed to %j', buffer.toString('hex'))
155
+ const date = dateToString(buffer.readUInt32LE())
156
+ this.debug('SetTime changed to %s', date)
157
+ })
158
+ this.addCharacteristicDelegate({
159
+ key: 'historyStatus',
160
+ Characteristic: this.Characteristics.eve.HistoryStatus,
161
+ value: params.historyStatus,
162
+ silent: true
163
+ })
164
+ this.addCharacteristicDelegate({
165
+ key: 'historyEntries',
166
+ Characteristic: this.Characteristics.eve.HistoryEntries,
167
+ value: params.historyEntries,
168
+ getter: this._onGetEntries.bind(this),
169
+ silent: true
170
+ })
171
+ this._accessoryDelegate.heartbeatEnabled = true
172
+ this._accessoryDelegate
173
+ .once('heartbeat', (beat) => {
174
+ this._historyBeat = (beat % 600) + 5
175
+ })
176
+ .on('heartbeat', this._heartbeat.bind(this))
177
+ .on('shutdown', () => {
178
+ this.debug('saved %d history entries', this._h.usedMemory)
179
+ })
180
+ }
181
+
182
+ _addEntry (now = Math.round(new Date().valueOf() / 1000)) {
183
+ this._entry.time = now
184
+ if (this._h.usedMemory < this._memorySize) {
185
+ this._h.usedMemory++
186
+ this._h.firstEntry = 0
187
+ this._h.lastEntry = this._h.usedMemory
188
+ } else {
189
+ this._h.firstEntry++
190
+ this._h.lastEntry = this._h.firstEntry + this._h.usedMemory
191
+ if (this._restarted === true) {
192
+ this._h.history[this._h.lastEntry % this._memorySize] = {
193
+ time: this._entry.time,
194
+ setRefTime: 1
195
+ }
196
+ this._h.firstEntry++
197
+ this._h.lastEntry = this._h.firstEntry + this._h.usedMemory
198
+ this._restarted = false
199
+ }
200
+ }
201
+
202
+ if (this._h.initialTime === 0) {
203
+ this._h.history[this._h.lastEntry] = {
204
+ time: this._entry.time,
205
+ setRefTime: 1
206
+ }
207
+ this._h.initialTime = this._entry.time
208
+ this._h.lastEntry++
209
+ this._h.usedMemory++
210
+ }
211
+
212
+ this._h.history[this._h.lastEntry % this._memorySize] =
213
+ Object.assign({}, this._entry)
214
+
215
+ const usedMemeoryOffset = this._h.usedMemory < this._memorySize ? 1 : 0
216
+ 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
241
+
242
+ const value = util.format(
243
+ '%s 00000000 %s [%s] %s %s %s 000000000101',
244
+ numToHex(swap32(this._entry.time - this._h.initialTime), 8),
245
+ numToHex(swap32(this._h.initialTime - epoch), 8),
246
+ this._fingerPrint,
247
+ numToHex(swap16(this._h.usedMemory + usedMemeoryOffset), 4),
248
+ numToHex(swap16(this._memorySize), 4),
249
+ numToHex(swap32(this._h.firstEntry + firstEntryOffset), 8)
250
+ )
251
+
252
+ this.debug('add entry %d: %j', this._h.lastEntry, this._entry)
253
+ this.debug('set history status to: %j', value)
254
+ this.debug('set history status to: %j', buffer.slice(0, offset).toString('hex'))
255
+ this.values.historyStatus = hexToBase64(value)
256
+ }
257
+
258
+ _heartbeat (beat) {
259
+ if (beat % 600 === this._historyBeat) {
260
+ this._addEntry()
261
+ }
262
+ }
263
+
264
+ async _onSetHistoryRequest (value) {
265
+ const buffer = Buffer.from(value, 'base64')
266
+ this.debug('History Request changed to %j', base64ToHex(value))
267
+ const entry = buffer.readUInt32LE(2)
268
+ this.debug('request entry: %d', entry)
269
+ if (entry !== 0) {
270
+ this._h.currentEntry = entry
271
+ } else {
272
+ this._h.currentEntry = 1
273
+ }
274
+ this._transfer = true
275
+ }
276
+
277
+ async _onGetEntries () {
278
+ if (this._h.currentEntry > this._h.lastEntry || !this._transfer) {
279
+ this.debug('send data: 00')
280
+ this._transfer = false
281
+ return hexToBase64('00')
282
+ }
283
+
284
+ let dataStream = ''
285
+ for (let i = 0; i < 11; i++) {
286
+ const address = this._h.currentEntry % this._memorySize
287
+ if (
288
+ this._h.history[address].setRefTime === 1 ||
289
+ this._h.currentEntry === this._h.firstEntry + 1
290
+ ) {
291
+ this.debug(
292
+ 'entry: %s, address %s, reftime: %s (%s)', this._h.currentEntry,
293
+ address, this._h.initialTime - epoch,
294
+ new Date(1000 * this._h.initialTime).toString().slice(0, 24)
295
+ )
296
+ dataStream += util.format(
297
+ '|15 %s 0100000081 %s 00000000000000',
298
+ numToHex(swap32(this._h.currentEntry), 8),
299
+ numToHex(swap32(this._h.initialTime - epoch), 8))
300
+ } else {
301
+ this.debug(
302
+ 'entry: %s, address: %s, time: %s (%s)', this._h.currentEntry,
303
+ address, this._h.history[address].time - this._h.initialTime,
304
+ new Date(1000 * this._h.history[address].time).toString().slice(0, 24)
305
+ )
306
+ dataStream += this._entryStream(this._h.history[address])
307
+ }
308
+ this._h.currentEntry++
309
+ if (this._h.currentEntry > this._h.lastEntry) {
310
+ break
311
+ }
312
+ }
313
+ this.debug('send data: %s', dataStream)
314
+ return hexToBase64(dataStream)
315
+ }
316
+ }
317
+
318
+ 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
@@ -0,0 +1,332 @@
1
+ // homebridge-lib/lib/ServiceDelegate/index.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
+ /** Delegate of a HomeKit service.
11
+ *
12
+ * This delegate sets up a HomeKit service with the following HomeKit
13
+ * characteristic:
14
+ *
15
+ * key | Characteristic
16
+ * ---------------- | -----------------------------------
17
+ * `name` | `Characteristics.hap.Name`
18
+ * `configuredName` | `Characteristics.hap.ConfiguredName`
19
+ * @abstract
20
+ * @extends Delegate
21
+ */
22
+ class ServiceDelegate extends homebridgeLib.Delegate {
23
+ static get AccessoryInformation () { return require('./AccessoryInformation') }
24
+ static get Battery () { return require('./Battery') }
25
+ static get Dummy () { return require('./Dummy') }
26
+ static get History () { return require('./History') }
27
+ static get ServiceLabel () { return require('./ServiceLabel') }
28
+
29
+ /** Create a new instance of a HomeKit service delegate.
30
+ *
31
+ * When the associated HomeKit service was restored from persistent
32
+ * storage, it is linked to the new delegate. Otherwise a new HomeKit
33
+ * service will be created, using the values from `params`.
34
+ * @param {!AccessoryDelegate} accessoryDelegate - Reference to the
35
+ * associated HomeKit accessory delegate.
36
+ * @param {!object} params - Properties of the HomeKit service.<br>
37
+ * Next to the fixed properties below, `params` also contains the value for
38
+ * each key specified in {@link ServiceDelegate#characteristics characteristics}.
39
+ * @param {!string} params.name - The (Siri) name of the service.
40
+ * Also used to prefix log and error messages.
41
+ * @param {!Service} params.Service - The type of the HomeKit service.
42
+ * @param {?string} params.subtype - The subtype of the HomeKit service.
43
+ * Needs to be specified when the accessory has multuple services of the
44
+ * same type.
45
+ * @params {?boolean} params.primaryService - This is the primary service
46
+ * for the accessory.
47
+ * @params {?ServiceDelegate} params.linkedServiceDelegate - The delegate
48
+ * of the service this service links to.
49
+ * @params {?boolean} params.hidden - Hidden service.
50
+ * @params {?boolean} params.exposeConfiguredName - Expose
51
+ * `Characteristics.hap.ConfiguredName`, so device name can be synced with
52
+ * HomeKit service name.
53
+ */
54
+ constructor (accessoryDelegate, params = {}) {
55
+ if (!(accessoryDelegate instanceof homebridgeLib.AccessoryDelegate)) {
56
+ throw new TypeError('parent: not a AccessoryDelegate')
57
+ }
58
+ if (params.name == null) {
59
+ throw new SyntaxError('params.name: missing')
60
+ }
61
+ super(accessoryDelegate.platform, params.name)
62
+ if (
63
+ typeof params.Service !== 'function' ||
64
+ typeof params.Service.UUID !== 'string'
65
+ ) {
66
+ throw new TypeError('params.Service: not a Service')
67
+ }
68
+ this._accessoryDelegate = accessoryDelegate
69
+ this._accessory = this._accessoryDelegate._accessory
70
+ this._key = params.Service.UUID
71
+ if (params.subtype != null) {
72
+ this._key += '.' + params.subtype
73
+ }
74
+
75
+ // Get or create associated Service.
76
+ this._service = params.subtype == null
77
+ ? this._accessory.getService(params.Service)
78
+ : this._accessory.getServiceByUUIDAndSubType(params.Service, params.subtype)
79
+ if (this._service == null) {
80
+ this._service = this._accessory.addService(
81
+ new params.Service(this.name, params.subtype)
82
+ )
83
+ this._service.displayName = this.name
84
+ }
85
+ this._accessoryDelegate._linkServiceDelegate(this)
86
+ this._service.setPrimaryService(!!params.primaryService)
87
+ if (params.linkedServiceDelegate != null) {
88
+ params.linkedServiceDelegate._service.addLinkedService(this._service)
89
+ }
90
+ this._service.setHiddenService(!!params.hidden)
91
+
92
+ // Fix bug in homebridge-lib@<4.3.0 that caused the service context in
93
+ // ~/.homebridge/accessories/cachedAccessories to be stored under the
94
+ // wrong key.
95
+ if (
96
+ params.subtype == null &&
97
+ this._accessory.context[this._key + '.'] != null
98
+ ) {
99
+ this._accessory.context[this._key] =
100
+ this._accessory.context[this._key + '.']
101
+ delete this._accessory.context[this._key + '.']
102
+ }
103
+
104
+ // Setup persisted storage in ~/.homebridge/accessories/cachedAccessories.
105
+ if (this._accessory.context[this._key] == null) {
106
+ this._accessory.context[this._key] = {}
107
+ }
108
+ this._context = this._accessory.context[this._key]
109
+
110
+ // Setup shortcut for characteristic values.
111
+ this._values = {} // by key
112
+
113
+ // Setup characteristics
114
+ this._characteristicDelegates = {} // by key
115
+ this._characteristics = {} // by uuid
116
+
117
+ if (!params.hidden) {
118
+ this.addCharacteristicDelegate({
119
+ key: 'name',
120
+ Characteristic: this.Characteristics.hap.Name,
121
+ silent: true,
122
+ value: params.name
123
+ })
124
+ if (params.exposeConfiguredName) {
125
+ this.addCharacteristicDelegate({
126
+ key: 'configuredName',
127
+ Characteristic: this.Characteristics.hap.ConfiguredName,
128
+ // silent: true,
129
+ props: {
130
+ perms: [
131
+ this.Characteristic.Perms.READ, this.Characteristic.Perms.WRITE
132
+ ]
133
+ },
134
+ value: params.name
135
+ }).on('didSet', (value) => {
136
+ if (value.trim() !== '') {
137
+ this.name = value
138
+ this._service.displayName = value
139
+ this.values.name = value
140
+ for (const key in this._characteristicDelegates) {
141
+ this._characteristicDelegates[key].name = value
142
+ }
143
+ }
144
+ })
145
+ }
146
+ }
147
+
148
+ this.once('initialised', () => {
149
+ const staleCharacteristics = []
150
+ for (const uuid in this._service.characteristics) {
151
+ const characteristic = this._service.characteristics[uuid]
152
+ if (this._characteristics[characteristic.UUID] == null) {
153
+ staleCharacteristics.push(characteristic)
154
+ }
155
+ }
156
+ for (const characteristic of staleCharacteristics) {
157
+ this.log('remove stale characteristic %s', characteristic.displayName)
158
+ this._service.removeCharacteristic(characteristic)
159
+ }
160
+ const staleKeys = []
161
+ for (const key in this._context) {
162
+ if (key !== 'context' && this._characteristicDelegates[key] == null) {
163
+ staleKeys.push(key)
164
+ }
165
+ }
166
+ for (const key of staleKeys) {
167
+ this.log('remove stale value %s', key)
168
+ delete this._context[key]
169
+ }
170
+ })
171
+ }
172
+
173
+ /** Destroy the HomeKit service delegate.
174
+ *
175
+ * Destroys the associated HomeKit characteristic delegates.
176
+ * Removes the associated HomeKit service.
177
+ */
178
+ destroy () {
179
+ this.debug('destroy %s (%s)', this._key, this._service.displayName)
180
+ this._accessoryDelegate._unlinkServiceDelegate(this)
181
+ this.removeAllListeners()
182
+ for (const key in this._characteristicDelegates) {
183
+ this._characteristicDelegates[key]._destroy()
184
+ }
185
+ this._accessory.removeService(this._service)
186
+ delete this._accessory.context[this._key]
187
+ }
188
+
189
+ /** Add a HomeKit characteristic delegate to the HomeKit service delegate.
190
+ *
191
+ * The characteristic delegate manages a value that:
192
+ * - Is persisted across homebridge restarts;
193
+ * - Can be monitored through homebridge's log output;
194
+ * - Can be monitored programmatially through `didSet` events; and
195
+ * - Mirrors the value of the optionally associated HomeKit characteristic.
196
+ *
197
+ * This value is accessed through {@link ServiceDelegate#values values}.
198
+ * The delegate is returned, but can also be accessed through
199
+ * {@link ServiceDelegate#characteristicDelegate characteristicDelegate()}.
200
+ *
201
+ * When the associated HomeKit characteristic was restored from persistent
202
+ * storage, it is linked to the new delegate. Otherwise a new HomeKit
203
+ * charactertistic will be created, using the values from `params`.
204
+ * @param {!object} params - Properties of the HomeKit characteristic.
205
+ * @param {!string} params.key - The key for the characteristic.
206
+ * @param {?*} params.value - The initial value when the characteristic
207
+ * is added.
208
+ * @param {?boolean} params.silent - Suppress set log messages.
209
+ * @param {?Characteristic} params.Characteristic - The type of the
210
+ * characteristic, from {@link Delegate#Characteristic Characteristic}.
211
+ * @param {?object} params.props - The properties of the HomeKit
212
+ * characteristic.<br>
213
+ * Overrides the properties from the characteristic type.
214
+ * @param {?string} params.unit - The unit of the value of the HomeKit
215
+ * characteristic.<br>
216
+ * Overrides the unit from the characteristic type.
217
+ * @param {?function} params.getter - Asynchronous function to be invoked
218
+ * when HomeKit reads the characteristic value.<br>
219
+ * This must be an `async` function returning a `Promise` to the new
220
+ * characteristic value.
221
+ * @param {?function} params.setter - Asynchronous function to be invoked
222
+ * when HomeKit writes the characteristic value.<br>
223
+ * This must be an `async` function returning a `Promise` that resolves
224
+ * when the corresonding device value has been updated.
225
+ * @returns {CharacteristicDelegate}
226
+ * @throws {TypeError} When a parameter has an invalid type.
227
+ * @throws {RangeError} When a parameter has an invalid value.
228
+ * @throws {SyntaxError} When a mandatory parameter is missing or an
229
+ * optional parameter is not applicable.
230
+ */
231
+ addCharacteristicDelegate (params = {}) {
232
+ if (typeof params.key !== 'string') {
233
+ throw new TypeError(`params.key: ${params.key}: invalid key`)
234
+ }
235
+ if (params.key === '') {
236
+ throw new RangeError(`params.key: ${params.key}: invalid key`)
237
+ }
238
+ const key = params.key
239
+ if (this.values[key] !== undefined) {
240
+ throw new SyntaxError(`${key}: duplicate key`)
241
+ }
242
+
243
+ const characteristicDelegate = new homebridgeLib.CharacteristicDelegate(
244
+ this, params
245
+ )
246
+ this._characteristicDelegates[key] = characteristicDelegate
247
+ if (params.Characteristic != null) {
248
+ this._characteristics[params.Characteristic.UUID] = true
249
+ }
250
+
251
+ // Create shortcut for characteristic value.
252
+ Object.defineProperty(this.values, key, {
253
+ configurable: true, // make sure we can delete it again
254
+ writeable: true,
255
+ get () { return characteristicDelegate.value },
256
+ set (value) { characteristicDelegate.value = value }
257
+ })
258
+
259
+ return characteristicDelegate
260
+ }
261
+
262
+ removeCharacteristicDelegate (key) {
263
+ delete this.values[key]
264
+ const characteristicDelegate = this._characteristicDelegates[key]
265
+ if (characteristicDelegate._characteristic != null) {
266
+ const characteristic = characteristicDelegate._characteristic
267
+ delete this._characteristics[characteristic.UUID]
268
+ }
269
+ characteristicDelegate._destroy()
270
+ delete this._characteristicDelegates[key]
271
+ delete this._context[key]
272
+ }
273
+
274
+ /** Values of the HomeKit characteristics for the HomeKit service.
275
+ *
276
+ * Contains the key of each characteristic added by
277
+ * {@link ServiceDelegate#addCharacteristic addCharacteristic}.
278
+ * When the value is written, the value of the corresponding HomeKit
279
+ * characteristic is updated; when the characteristic value is changed from
280
+ * HomeKit, this value is updated.
281
+ * @type {object}
282
+ */
283
+ get values () {
284
+ return this._values
285
+ }
286
+
287
+ /** Returns the HomeKit characteristic delegate correspondig to the key.
288
+ * @param {!string} key - The key for the characteristic.
289
+ * returns {CharacteristicDelegate}
290
+ */
291
+ characteristicDelegate (key) {
292
+ return this._characteristicDelegates[key]
293
+ }
294
+
295
+ /** The corrresponding HomeKit accessory delegate.
296
+ * @type {AccessoryDelegate}
297
+ */
298
+ get accessoryDelegate () {
299
+ return this._accessoryDelegate
300
+ }
301
+
302
+ /** Service context to be persisted across Homebridge restarts.
303
+ * @type {object}
304
+ */
305
+ get context () {
306
+ if (this._context.context == null) {
307
+ this._context.context = {}
308
+ }
309
+ return this._context.context
310
+ }
311
+
312
+ /** Current log level (of the associated accessory delegate).
313
+ *
314
+ * The log level determines what type of messages are printed:
315
+ *
316
+ * 0. Print error and warning messages.
317
+ * 1. Print error, warning, and log messages.
318
+ * 2. Print error, warning, log, and debug messages.
319
+ * 3. Print error, warning, log, debug, and verbose debug messages.
320
+ *
321
+ * Note that debug messages (level 2 and 3) are only printed when
322
+ * Homebridge was started with the `-D` or `--debug` command line option.
323
+ *
324
+ * @type {!integer}
325
+ * @readonly
326
+ */
327
+ get logLevel () {
328
+ return this._accessoryDelegate.logLevel
329
+ }
330
+ }
331
+
332
+ module.exports = ServiceDelegate