homebridge-nb 1.4.4 → 1.4.6
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/config.schema.json +7 -2
- package/lib/NbAccessory/Bridge.js +321 -0
- package/lib/NbAccessory/DoorSensor.js +57 -0
- package/lib/NbAccessory/Keypad.js +48 -0
- package/lib/NbAccessory/Opener.js +51 -0
- package/lib/NbAccessory/SmartLock.js +67 -0
- package/lib/NbAccessory/index.js +38 -0
- package/lib/NbPlatform.js +53 -40
- package/lib/NbService/Bridge.js +72 -0
- package/lib/NbService/DoorBell.js +50 -0
- package/lib/NbService/DoorSensor.js +68 -0
- package/lib/NbService/Latch.js +84 -0
- package/lib/NbService/Opener.js +130 -0
- package/lib/NbService/SmartLock.js +142 -0
- package/lib/NbService/index.js +26 -0
- package/package.json +4 -4
- package/lib/NbAccessory.js +0 -423
- package/lib/NbService.js +0 -490
package/config.schema.json
CHANGED
|
@@ -38,6 +38,10 @@
|
|
|
38
38
|
"minimum": 0,
|
|
39
39
|
"maximum": 2000
|
|
40
40
|
},
|
|
41
|
+
"removeStaleAccessories": {
|
|
42
|
+
"description": "Remove stale accessories, whose devices are no longer exposed by a Nuki bridge.",
|
|
43
|
+
"type": "boolean"
|
|
44
|
+
},
|
|
41
45
|
"timeout": {
|
|
42
46
|
"description": "The timeout in seconds to wait for a response from a Nuki bridge. Default: 15.",
|
|
43
47
|
"type": "integer",
|
|
@@ -80,8 +84,9 @@
|
|
|
80
84
|
]
|
|
81
85
|
},
|
|
82
86
|
"openerResetTimeout",
|
|
83
|
-
"
|
|
84
|
-
"
|
|
87
|
+
"port",
|
|
88
|
+
"removeStaleAccessories",
|
|
89
|
+
"timeout"
|
|
85
90
|
]
|
|
86
91
|
}
|
|
87
92
|
]
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
// homebridge-nb/lib/NbAccessory/Bridge.js
|
|
2
|
+
// Copyright © 2020-2023 Erik Baauw. All rights reserved.
|
|
3
|
+
//
|
|
4
|
+
// Homebridge plug-in for Nuki Bridge.
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
|
|
8
|
+
const { AccessoryDelegate, ServiceDelegate, toHexString } = require('homebridge-lib')
|
|
9
|
+
const NbAccessory = require('../NbAccessory')
|
|
10
|
+
const NbService = require('../NbService')
|
|
11
|
+
const { NbClient } = require('hb-nb-tools')
|
|
12
|
+
|
|
13
|
+
class Bridge extends AccessoryDelegate {
|
|
14
|
+
constructor (platform, context) {
|
|
15
|
+
super(platform, {
|
|
16
|
+
id: context.id,
|
|
17
|
+
name: context.name,
|
|
18
|
+
category: platform.Accessory.Categories.RANGE_EXTENDER,
|
|
19
|
+
model: 'Bridge',
|
|
20
|
+
firmware: context.firmware
|
|
21
|
+
})
|
|
22
|
+
this.id = context.id
|
|
23
|
+
this.context.host = context.host
|
|
24
|
+
this.context.token = context.token
|
|
25
|
+
this.context.firmware = context.firmware
|
|
26
|
+
this.log(
|
|
27
|
+
'Nuki Bridge v%s %s at %s', context.firmware, context.id, context.host
|
|
28
|
+
)
|
|
29
|
+
this.on('shutdown', this.shutdown)
|
|
30
|
+
this.heartbeatEnabled = true
|
|
31
|
+
this.once('heartbeat', this.init)
|
|
32
|
+
|
|
33
|
+
this.smartLocks = {}
|
|
34
|
+
this.doorSensors = {}
|
|
35
|
+
this.keypads = {}
|
|
36
|
+
this.openers = {}
|
|
37
|
+
this.service = new NbService.Bridge(this)
|
|
38
|
+
this.manageLogLevel(this.service.characteristicDelegate('logLevel'))
|
|
39
|
+
this.dummyService = new ServiceDelegate.Dummy(this)
|
|
40
|
+
|
|
41
|
+
this.client = new NbClient({
|
|
42
|
+
host: this.context.host,
|
|
43
|
+
timeout: platform.config.timeout,
|
|
44
|
+
token: this.context.token
|
|
45
|
+
})
|
|
46
|
+
this.client
|
|
47
|
+
.on('error', (error) => {
|
|
48
|
+
this.log(
|
|
49
|
+
'request %d: %s %s', error.request.id,
|
|
50
|
+
error.request.method, error.request.resource
|
|
51
|
+
)
|
|
52
|
+
this.warn('request %d: error: %s', error.request.id, error)
|
|
53
|
+
})
|
|
54
|
+
.on('request', (request) => {
|
|
55
|
+
this.debug(
|
|
56
|
+
'request %d: %s %s', request.id, request.method, request.resource
|
|
57
|
+
)
|
|
58
|
+
this.vdebug(
|
|
59
|
+
'request %d: %s %s', request.id, request.method, request.url
|
|
60
|
+
)
|
|
61
|
+
})
|
|
62
|
+
.on('response', (response) => {
|
|
63
|
+
this.vdebug(
|
|
64
|
+
'request %d: response: %j', response.request.id, response.body
|
|
65
|
+
)
|
|
66
|
+
this.debug(
|
|
67
|
+
'request %d: %s %s', response.request.id,
|
|
68
|
+
response.statusCode, response.statusMessage
|
|
69
|
+
)
|
|
70
|
+
})
|
|
71
|
+
.on('event', (event) => {
|
|
72
|
+
this.debug('event: %j', event)
|
|
73
|
+
const id = event.nukiId.toString(16).toUpperCase()
|
|
74
|
+
switch (event.deviceType) {
|
|
75
|
+
case NbClient.DeviceTypes.SMARTLOCK:
|
|
76
|
+
case NbClient.DeviceTypes.SMARTDOOR:
|
|
77
|
+
case NbClient.DeviceTypes.SMARTLOCK3:
|
|
78
|
+
if (this.smartLocks[id] != null) {
|
|
79
|
+
this.smartLocks[id].update(event)
|
|
80
|
+
}
|
|
81
|
+
if (this.doorSensors[id + '-S'] != null) {
|
|
82
|
+
this.doorSensors[id + '-S'].update(event)
|
|
83
|
+
}
|
|
84
|
+
break
|
|
85
|
+
case NbClient.DeviceTypes.OPENER:
|
|
86
|
+
if (this.openers[id] != null) {
|
|
87
|
+
this.openers[id].update(event)
|
|
88
|
+
}
|
|
89
|
+
break
|
|
90
|
+
default:
|
|
91
|
+
break
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
get host () { return this.context.host }
|
|
97
|
+
set host (value) {
|
|
98
|
+
if (value !== this.context.host) {
|
|
99
|
+
this.debug('now at %s', value)
|
|
100
|
+
this.client.host = value
|
|
101
|
+
this.context.host = value
|
|
102
|
+
this.once('heartbeat', this.init)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async init (beat) {
|
|
107
|
+
try {
|
|
108
|
+
await this.client.init()
|
|
109
|
+
this.values.firmware = this.client.firmware
|
|
110
|
+
this.context.firmware = this.values.firmware
|
|
111
|
+
if (this.values.firmware !== this.platform.packageJson.engines.nuki) {
|
|
112
|
+
this.warn(
|
|
113
|
+
'recommended version: Nuki Bridge v%s',
|
|
114
|
+
this.platform.packageJson.engines.nuki
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
switch (this.client.encryption) {
|
|
118
|
+
case 'none':
|
|
119
|
+
this.warn('using plain-text tokens')
|
|
120
|
+
break
|
|
121
|
+
case 'hashedToken':
|
|
122
|
+
this.warn('using deprecated hashed tokens')
|
|
123
|
+
break
|
|
124
|
+
default:
|
|
125
|
+
break
|
|
126
|
+
}
|
|
127
|
+
} catch (error) {
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
if (this.context.callbackUrl) {
|
|
131
|
+
this.warn('unclean shutdown - checking for stale subscriptions')
|
|
132
|
+
try {
|
|
133
|
+
const response = await this.client.callbackList()
|
|
134
|
+
for (const callback of response.body.callbacks) {
|
|
135
|
+
if (callback.url === this.context.callbackUrl) {
|
|
136
|
+
this.log('remove stale subscription')
|
|
137
|
+
await this.client.callbackRemove(callback.id)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
} catch (error) {
|
|
141
|
+
this.warn(error)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
this.context.callbackUrl = await this.platform.addClient(this.client)
|
|
145
|
+
this.initialBeat = beat
|
|
146
|
+
try {
|
|
147
|
+
await this.heartbeat(beat)
|
|
148
|
+
} catch (error) {}
|
|
149
|
+
this.on('heartbeat', this.heartbeat)
|
|
150
|
+
this.debug('initialised')
|
|
151
|
+
this.emit('initialised')
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async checkSubscription () {
|
|
155
|
+
if (this.callbackId == null) {
|
|
156
|
+
this.log('subscribe to event notifications')
|
|
157
|
+
const response = await this.client.callbackAdd(this.context.callbackUrl)
|
|
158
|
+
if (!response.body.success) {
|
|
159
|
+
this.error(response.body.message)
|
|
160
|
+
return
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const response = await this.client.callbackList()
|
|
164
|
+
for (const callback of response.body.callbacks) {
|
|
165
|
+
if (callback.url === this.context.callbackUrl) {
|
|
166
|
+
this.debug('subscription: %j', callback)
|
|
167
|
+
this.callbackId = callback.id
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (this.callbackId != null) {
|
|
172
|
+
this.warn('lost subscription to event notifications')
|
|
173
|
+
this.callbackId = null
|
|
174
|
+
}
|
|
175
|
+
return this.checkSubscription()
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async shutdown () {
|
|
179
|
+
if (this.client != null) {
|
|
180
|
+
const response = await this.client.callbackList()
|
|
181
|
+
for (const callback of response.body.callbacks) {
|
|
182
|
+
if (callback.url === this.context.callbackUrl) {
|
|
183
|
+
try {
|
|
184
|
+
this.log('unsubscribe from event notifications')
|
|
185
|
+
await this.client.callbackRemove(callback.id)
|
|
186
|
+
} catch (error) {
|
|
187
|
+
this.error(error)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
this.platform.removeClient(this.client)
|
|
192
|
+
delete this.context.callbackUrl
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
addDoorSensor (id, context) {
|
|
197
|
+
this.doorSensors[id] = new NbAccessory.DoorSensor(this, context)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
addKeypad (id, context) {
|
|
201
|
+
this.keypads[id] = new NbAccessory.Keypad(this, context)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
addOpener (id, context) {
|
|
205
|
+
this.openers[id] = new NbAccessory.Opener(this, context)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
addSmartLock (id, context) {
|
|
209
|
+
this.smartLocks[id] = new NbAccessory.SmartLock(this, context)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
checkDoorSensor (id, device) {
|
|
213
|
+
if (
|
|
214
|
+
device.lastKnownState.doorsensorState != null &&
|
|
215
|
+
device.lastKnownState.doorsensorState !== NbClient.DoorSensorStates.DEACTIVATED
|
|
216
|
+
) {
|
|
217
|
+
if (this.doorSensors[id + '-S'] == null) {
|
|
218
|
+
this.addDoorSensor(id + '-S', { id: id + '-S', device })
|
|
219
|
+
}
|
|
220
|
+
this.doorSensors[id + '-S'].context.device = device
|
|
221
|
+
this.doorSensors[id + '-S'].update(device.lastKnownState)
|
|
222
|
+
} else if (this.doorSensors[id + '-S'] != null) {
|
|
223
|
+
this.doorSensors[id + '-S'].destroy()
|
|
224
|
+
delete this.doorSensors[id + '-S']
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
checkKeypad (id, device) {
|
|
229
|
+
if (device.lastKnownState.keypadBatteryCritical != null) {
|
|
230
|
+
if (this.keypads[id + '-K'] == null) {
|
|
231
|
+
this.addKeypad(id + '-K', { id: id + '-K', device })
|
|
232
|
+
}
|
|
233
|
+
this.keypads[id + '-K'].context.device = device
|
|
234
|
+
this.keypads[id + '-K'].update(device.lastKnownState)
|
|
235
|
+
} else if (this.keypads[id + '-K'] != null) {
|
|
236
|
+
this.keypads[id + '-K'].destroy()
|
|
237
|
+
delete this.keypads[id + '-K']
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
checkFirmware (info) {
|
|
242
|
+
if (this.values.firmware !== info.versions.firmwareVersion) {
|
|
243
|
+
this.values.firmware = info.versions.firmwareVersion
|
|
244
|
+
this.context.firmware = this.values.firmware
|
|
245
|
+
if (this.values.firmware !== this.platform.packageJson.engines.nuki) {
|
|
246
|
+
this.warn(
|
|
247
|
+
'recommended version: Nuki Bridge v%s',
|
|
248
|
+
this.platform.packageJson.engines.nuki
|
|
249
|
+
)
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async heartbeat (beat) {
|
|
255
|
+
if ((beat - this.initialBeat) % this.service.values.heartrate === 0) {
|
|
256
|
+
try {
|
|
257
|
+
await this.checkSubscription()
|
|
258
|
+
let response = await this.client.info()
|
|
259
|
+
this.debug('bridge: %j', response.body)
|
|
260
|
+
this.checkFirmware(response.body)
|
|
261
|
+
this.service.update(response.body)
|
|
262
|
+
response = await this.client.list()
|
|
263
|
+
for (const device of response.body) {
|
|
264
|
+
try {
|
|
265
|
+
this.debug('device: %j', device)
|
|
266
|
+
if (device.firmwareVersion == null) { // Issue 93.
|
|
267
|
+
continue
|
|
268
|
+
}
|
|
269
|
+
const id = toHexString(device.nukiId, 8)
|
|
270
|
+
if (!this.platform.isWhitelisted(id)) {
|
|
271
|
+
continue
|
|
272
|
+
}
|
|
273
|
+
switch (device.deviceType) {
|
|
274
|
+
case NbClient.DeviceTypes.SMARTLOCK:
|
|
275
|
+
case NbClient.DeviceTypes.SMARTDOOR:
|
|
276
|
+
case NbClient.DeviceTypes.SMARTLOCK3:
|
|
277
|
+
if (device.lastKnownState == null) {
|
|
278
|
+
this.warn('%s: no last known state', id)
|
|
279
|
+
continue
|
|
280
|
+
}
|
|
281
|
+
if (device.name == null || device.name === '') {
|
|
282
|
+
device.name = 'Nuki_' + id
|
|
283
|
+
}
|
|
284
|
+
if (this.smartLocks[id] == null) {
|
|
285
|
+
this.addSmartLock(id, { id, device })
|
|
286
|
+
}
|
|
287
|
+
this.smartLocks[id].context.device = device
|
|
288
|
+
this.smartLocks[id].update(device.lastKnownState)
|
|
289
|
+
this.checkDoorSensor(id, device)
|
|
290
|
+
this.checkKeypad(id, device)
|
|
291
|
+
break
|
|
292
|
+
case NbClient.DeviceTypes.OPENER:
|
|
293
|
+
if (device.lastKnownState == null) {
|
|
294
|
+
this.warn('%s: no last known state', id)
|
|
295
|
+
continue
|
|
296
|
+
}
|
|
297
|
+
if (device.name == null || device.name === '') {
|
|
298
|
+
device.name = 'Nuki_Opener_' + id
|
|
299
|
+
}
|
|
300
|
+
if (this.openers[id] == null) {
|
|
301
|
+
this.addOpener(id, { id, device })
|
|
302
|
+
}
|
|
303
|
+
this.openers[id].context.device = device
|
|
304
|
+
this.openers[id].update(device.lastKnownState)
|
|
305
|
+
this.checkKeypad(id, device)
|
|
306
|
+
break
|
|
307
|
+
default:
|
|
308
|
+
break
|
|
309
|
+
}
|
|
310
|
+
} catch (error) {
|
|
311
|
+
this.warn('heartbeat error: %s', error)
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
} catch (error) {
|
|
315
|
+
this.warn('heartbeat error: %s', error)
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
module.exports = Bridge
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// homebridge-nb/lib/NbAccessory/DoorSensor.js
|
|
2
|
+
// Copyright © 2020-2023 Erik Baauw. All rights reserved.
|
|
3
|
+
//
|
|
4
|
+
// Homebridge plug-in for Nuki Bridge.
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
|
|
8
|
+
const { ServiceDelegate } = require('homebridge-lib')
|
|
9
|
+
const NbAccessory = require('.')
|
|
10
|
+
const NbService = require('../NbService')
|
|
11
|
+
|
|
12
|
+
class DoorSensor extends NbAccessory {
|
|
13
|
+
constructor (bridge, params) {
|
|
14
|
+
params.category = bridge.Accessory.Categories.DOOR
|
|
15
|
+
params.model = 'Door Sensor'
|
|
16
|
+
super(bridge, {
|
|
17
|
+
id: params.id,
|
|
18
|
+
name: params.device.name + ' Sensor',
|
|
19
|
+
device: params.device,
|
|
20
|
+
category: bridge.Accessory.Categories.DOOR,
|
|
21
|
+
model: 'Door Sensor'
|
|
22
|
+
})
|
|
23
|
+
this.service = new NbService.DoorSensor(this)
|
|
24
|
+
if (params.device.lastKnownState.doorsensorBatteryCritical) {
|
|
25
|
+
this.batteryService = new ServiceDelegate.Battery(this, {
|
|
26
|
+
statusLowBattery: params.device.lastKnownState.doorsensorBatteryCritical
|
|
27
|
+
? this.Characteristics.hap.StatusLowBattery.BATTERY_LEVEL_LOW
|
|
28
|
+
: this.Characteristics.hap.StatusLowBattery.BATTERY_LEVEL_NORMAL
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
this.historyService = new ServiceDelegate.History(this, {
|
|
32
|
+
contactDelegate: this.service.characteristicDelegate('contact'),
|
|
33
|
+
lastContactDelegate: this.service.characteristicDelegate('lastActivation'),
|
|
34
|
+
timesOpenedDelegate: this.service.characteristicDelegate('timesOpened')
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
update (state) {
|
|
39
|
+
this.service.update(state)
|
|
40
|
+
if (state.doorsensorBatteryCritical != null) {
|
|
41
|
+
this.batteryService.values.statusLowBattery = state.doorsensorBatteryCritical
|
|
42
|
+
? this.Characteristics.hap.StatusLowBattery.BATTERY_LEVEL_LOW
|
|
43
|
+
: this.Characteristics.hap.StatusLowBattery.BATTERY_LEVEL_NORMAL
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async identify () {
|
|
48
|
+
try {
|
|
49
|
+
const response = await this.client.lockState(
|
|
50
|
+
this.id, this.context.deviceType
|
|
51
|
+
)
|
|
52
|
+
this.update(response.body)
|
|
53
|
+
} catch (error) { this.error(error) }
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = DoorSensor
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// homebridge-nb/lib/NbAccessory/Keypad.js
|
|
2
|
+
// Copyright © 2020-2023 Erik Baauw. All rights reserved.
|
|
3
|
+
//
|
|
4
|
+
// Homebridge plug-in for Nuki Bridge.
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
|
|
8
|
+
const { ServiceDelegate } = require('homebridge-lib')
|
|
9
|
+
const NbAccessory = require('../NbAccessory')
|
|
10
|
+
|
|
11
|
+
class Keypad extends NbAccessory {
|
|
12
|
+
constructor (bridge, params) {
|
|
13
|
+
params.category = bridge.Accessory.Categories.DOOR_LOCK
|
|
14
|
+
params.model = 'Keypad'
|
|
15
|
+
super(bridge, {
|
|
16
|
+
id: params.id,
|
|
17
|
+
name: params.device.name + ' Keypad',
|
|
18
|
+
device: params.device,
|
|
19
|
+
category: bridge.Accessory.Categories.PROGRAMMABLE_SWITCH,
|
|
20
|
+
model: 'Keypad'
|
|
21
|
+
})
|
|
22
|
+
this.service = new ServiceDelegate.Dummy(this)
|
|
23
|
+
this.batteryService = new ServiceDelegate.Battery(this, {
|
|
24
|
+
statusLowBattery: params.device.lastKnownState.keypadBatteryCritical
|
|
25
|
+
? this.Characteristics.hap.StatusLowBattery.BATTERY_LEVEL_LOW
|
|
26
|
+
: this.Characteristics.hap.StatusLowBattery.BATTERY_LEVEL_NORMAL
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
update (state) {
|
|
31
|
+
if (state.doorsensorBatteryCritical != null) {
|
|
32
|
+
this.batteryService.values.statusLowBattery = state.doorsensorBatteryCritical
|
|
33
|
+
? this.Characteristics.hap.StatusLowBattery.BATTERY_LEVEL_LOW
|
|
34
|
+
: this.Characteristics.hap.StatusLowBattery.BATTERY_LEVEL_NORMAL
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async identify () {
|
|
39
|
+
try {
|
|
40
|
+
const response = await this.client.lockState(
|
|
41
|
+
this.id, this.context.deviceType
|
|
42
|
+
)
|
|
43
|
+
this.update(response.body)
|
|
44
|
+
} catch (error) { this.error(error) }
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = Keypad
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// homebridge-nb/lib/NbAccessory/Opener.js
|
|
2
|
+
// Copyright © 2020-2023 Erik Baauw. All rights reserved.
|
|
3
|
+
//
|
|
4
|
+
// Homebridge plug-in for Nuki Bridge.
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
|
|
8
|
+
const { ServiceDelegate } = require('homebridge-lib')
|
|
9
|
+
const NbAccessory = require('../NbAccessory')
|
|
10
|
+
const NbService = require('../NbService')
|
|
11
|
+
const { NbClient } = require('hb-nb-tools')
|
|
12
|
+
|
|
13
|
+
class Opener extends NbAccessory {
|
|
14
|
+
constructor (bridge, params) {
|
|
15
|
+
super(bridge, {
|
|
16
|
+
id: params.id,
|
|
17
|
+
name: params.device.name,
|
|
18
|
+
device: params.device,
|
|
19
|
+
category: bridge.Accessory.Categories.DOOR_LOCK,
|
|
20
|
+
model: NbClient.modelName(params.device.deviceType, params.device.firmwareVersion)
|
|
21
|
+
})
|
|
22
|
+
this.openerService = new NbService.Opener(this)
|
|
23
|
+
this.doorBellService = new NbService.DoorBell(this)
|
|
24
|
+
this.batteryService = new ServiceDelegate.Battery(this, {
|
|
25
|
+
statusLowBattery: params.device.lastKnownState.doorsensorBatteryCritical
|
|
26
|
+
? this.Characteristics.hap.StatusLowBattery.BATTERY_LEVEL_LOW
|
|
27
|
+
: this.Characteristics.hap.StatusLowBattery.BATTERY_LEVEL_NORMAL
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
update (state) {
|
|
32
|
+
this.openerService.update(state)
|
|
33
|
+
this.doorBellService.update(state)
|
|
34
|
+
if (state.batteryCritical != null) {
|
|
35
|
+
this.batteryService.values.statusLowBattery = state.batteryCritical
|
|
36
|
+
? this.Characteristics.hap.StatusLowBattery.BATTERY_LEVEL_LOW
|
|
37
|
+
: this.Characteristics.hap.StatusLowBattery.BATTERY_LEVEL_NORMAL
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async identify () {
|
|
42
|
+
try {
|
|
43
|
+
const response = await this.client.lockState(
|
|
44
|
+
this.id, NbClient.DeviceTypes.OPENER
|
|
45
|
+
)
|
|
46
|
+
this.update(response.body)
|
|
47
|
+
} catch (error) { this.error(error) }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = Opener
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// homebridge-nb/lib/NbAccessory/SmartLock.js
|
|
2
|
+
// Copyright © 2020-2023 Erik Baauw. All rights reserved.
|
|
3
|
+
//
|
|
4
|
+
// Homebridge plug-in for Nuki Bridge.
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
|
|
8
|
+
const { ServiceDelegate } = require('homebridge-lib')
|
|
9
|
+
const NbAccessory = require('../NbAccessory')
|
|
10
|
+
const NbService = require('../NbService')
|
|
11
|
+
const { NbClient } = require('hb-nb-tools')
|
|
12
|
+
|
|
13
|
+
class SmartLock extends NbAccessory {
|
|
14
|
+
constructor (bridge, params) {
|
|
15
|
+
super(bridge, {
|
|
16
|
+
id: params.id,
|
|
17
|
+
name: params.device.name,
|
|
18
|
+
device: params.device,
|
|
19
|
+
category: bridge.Accessory.Categories.DOOR_LOCK,
|
|
20
|
+
model: NbClient.modelName(params.device.deviceType, params.device.firmwareVersion)
|
|
21
|
+
})
|
|
22
|
+
this.service = new NbService.SmartLock(this)
|
|
23
|
+
if (this.platform.config.latch) {
|
|
24
|
+
this.latchService = new NbService.Latch(this)
|
|
25
|
+
}
|
|
26
|
+
this.batteryService = new ServiceDelegate.Battery(this, {
|
|
27
|
+
batteryLevel: params.device.lastKnownState.batteryChargeState,
|
|
28
|
+
chargingState: params.device.lastKnownState.batteryCharging
|
|
29
|
+
? this.Characteristics.hap.ChargingState.CHARGING
|
|
30
|
+
: this.Characteristics.hap.ChargingState.NOT_CHARGING,
|
|
31
|
+
statusLowBattery: params.device.lastKnownState.batteryCritical
|
|
32
|
+
? this.Characteristics.hap.StatusLowBattery.BATTERY_LEVEL_LOW
|
|
33
|
+
: this.Characteristics.hap.StatusLowBattery.BATTERY_LEVEL_NORMAL
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
update (state) {
|
|
38
|
+
this.service.update(state)
|
|
39
|
+
if (this.platform.config.latch) {
|
|
40
|
+
this.latchService.update(state)
|
|
41
|
+
}
|
|
42
|
+
if (state.batteryChargeState) {
|
|
43
|
+
this.batteryService.values.batteryLevel = state.batteryChargeState
|
|
44
|
+
}
|
|
45
|
+
if (state.batteryCharging != null) {
|
|
46
|
+
this.batteryService.values.chargingState = state.batteryCharging
|
|
47
|
+
? this.Characteristics.hap.ChargingState.CHARGING
|
|
48
|
+
: this.Characteristics.hap.ChargingState.NOT_CHARGING
|
|
49
|
+
}
|
|
50
|
+
if (state.batteryCritical != null) {
|
|
51
|
+
this.batteryService.values.statusLowBattery = state.batteryCritical
|
|
52
|
+
? this.Characteristics.hap.StatusLowBattery.BATTERY_LEVEL_LOW
|
|
53
|
+
: this.Characteristics.hap.StatusLowBattery.BATTERY_LEVEL_NORMAL
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async identify () {
|
|
58
|
+
try {
|
|
59
|
+
const response = await this.client.lockState(
|
|
60
|
+
this.id, this.context.deviceType
|
|
61
|
+
)
|
|
62
|
+
this.update(response.body)
|
|
63
|
+
} catch (error) { this.error(error) }
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = SmartLock
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// homebridge-nb/lib/NbAccessory/index.js
|
|
2
|
+
// Copyright © 2020-2023 Erik Baauw. All rights reserved.
|
|
3
|
+
//
|
|
4
|
+
// Homebridge plug-in for Nuki Bridge.
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
|
|
8
|
+
const { AccessoryDelegate } = require('homebridge-lib')
|
|
9
|
+
|
|
10
|
+
class NbAccessory extends AccessoryDelegate {
|
|
11
|
+
static get Bridge () { return require('./Bridge') }
|
|
12
|
+
static get DoorSensor () { return require('./DoorSensor') }
|
|
13
|
+
static get Keypad () { return require('./Keypad') }
|
|
14
|
+
static get Opener () { return require('./Opener') }
|
|
15
|
+
static get SmartLock () { return require('./SmartLock') }
|
|
16
|
+
|
|
17
|
+
constructor (bridge, params) {
|
|
18
|
+
super(bridge.platform, {
|
|
19
|
+
id: params.id,
|
|
20
|
+
name: params.name,
|
|
21
|
+
category: params.category,
|
|
22
|
+
manufacturer: 'Nuki Home Solutions GmbH',
|
|
23
|
+
model: params.model,
|
|
24
|
+
firmware: params.device.firmwareVersion
|
|
25
|
+
})
|
|
26
|
+
this.inheritLogLevel(bridge)
|
|
27
|
+
this.id = params.id
|
|
28
|
+
this.bridge = bridge
|
|
29
|
+
this.client = this.bridge.client
|
|
30
|
+
this.context.bridgeId = this.bridge.id
|
|
31
|
+
this.deviceType = params.device
|
|
32
|
+
this.log('Nuki %s v%s %s', params.model, params.device.firmwareVersion, params.id)
|
|
33
|
+
this.on('identify', this.identify)
|
|
34
|
+
setImmediate(() => { this.emit('initialised') })
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = NbAccessory
|