homebridge-nb 1.3.4 → 1.4.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.
package/cli/nb.js CHANGED
@@ -1,654 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // homebridge-nb/cli/nb.js
4
- // Copyright © 2020-2023 Erik Baauw. All rights reserved.
3
+ // nb.js
5
4
  //
6
- // Homebridge plug-in for Nuki Bridge.
5
+ // Homebridge NB Tools.
6
+ // Copyright © 2018-2023 Erik Baauw. All rights reserved.
7
7
 
8
8
  'use strict'
9
9
 
10
- // const fs = require('fs')
11
- const NbClient = require('../lib/NbClient')
12
- const NbDiscovery = require('../lib/NbDiscovery')
13
- const NbListener = require('../lib/NbListener')
14
- const homebridgeLib = require('homebridge-lib')
15
- const packageJson = require('../package.json')
10
+ const { NbTool } = require('hb-nb-tools')
16
11
 
17
- const { b, u } = homebridgeLib.CommandLineTool
18
- const { UsageError } = homebridgeLib.CommandLineParser
19
-
20
- const usage = {
21
- nb: `${b('nb')} [${b('-hVD')}] [${b('-H')} ${u('hostname')}[${b(':')}${u('port')}]] [${b('-T')} ${u('token')}] [${b('-t')} ${u('timeout')}] ${u('command')} [${u('argument')} ...]`,
22
-
23
- discover: `${b('discover')} [${b('-h')}] [${b('-t')} ${u('timeout')}]`,
24
-
25
- auth: `${b('auth')} [${b('-h')}]`,
26
- info: `${b('info')} [${b('-h')}]`,
27
- getlog: `${b('getlog')} [${b('-h')}]`,
28
- clearlog: `${b('clearlog')} [${b('-h')}]`,
29
- reboot: `${b('reboot')} [${b('-h')}]`,
30
- fwupdate: `${b('fwupdate')} [${b('-h')}]`,
31
- list: `${b('list')} [${b('-h')}]`,
32
-
33
- lockState: `${b('lockState')} [${b('-h')}] ${u('nukiId')} ${u('deviceType')}`,
34
- lock: `${b('lock')} [${b('-h')}] ${u('nukiId')} ${u('deviceType')}`,
35
- unlock: `${b('unlock')} [${b('-h')}] ${u('nukiId')} ${u('deviceType')}`,
36
- lockAction: `${b('lockAction')} [${b('-h')}] ${u('nukiId')} ${u('deviceType')} ${u('action')}`,
37
-
38
- eventlog: `${b('eventlog')} [${b('-hns')}]`,
39
- callbackList: `${b('callbackList')} [${b('-h')}]`,
40
- callbackRemove: `${b('callbackRemove')} [${b('-h')}] ${u('id')}`
41
- }
42
-
43
- const description = {
44
- nb: 'Command line interface to Nuki bridge HTTP API.',
45
-
46
- discover: 'Discover Nuki bridges.',
47
-
48
- auth: 'Obtain Nuki bridge token.',
49
- info: 'Get Nuki bridge info.',
50
- getlog: 'Get Nuki bridge log.',
51
- clearlog: 'Clear Nuki bridge log.',
52
- reboot: 'Reboot Nuki bridge.',
53
- fwupdate: 'Trigger a firmware update of the bridge and connected devices.',
54
- list: 'Get list of paired Nuki devices.',
55
-
56
- lockState: 'Refresh state from paired Nuki device.',
57
- lock: 'Lock paired Nuki device.',
58
- unlock: 'Unlock paired Nuki device.',
59
- lockAction: 'Send action to paired Nuki device.',
60
-
61
- eventlog: 'Add Nuki bridge subscription and listen for events.',
62
- callbackList: 'List Nuki bridge subscriptions.',
63
- callbackRemove: 'Remove Nuki bridge subscription.'
64
- }
65
-
66
- const help = {
67
- nb: `${description.nb}
68
-
69
- Usage: ${usage.nb}
70
-
71
- Parameters:
72
- ${b('-h')}, ${b('--help')}
73
- Print this help and exit.
74
-
75
- ${b('-V')}, ${b('--version')}
76
- Print version and exit.
77
-
78
- ${b('-D')}, ${b('--debug')}
79
- Print debug messages for communication with Nuki bridge.
80
-
81
- ${b('-H')} ${u('hostname')}[${b(':')}${u('port')}], ${b('--host=')}${u('hostname')}[${b(':')}${u('port')}]
82
- Connect to Nuki bridge at ${u('hostname')}${b(':8080')} or ${u('hostname')}${b(':')}${u('port')}.
83
- You can also specify the hostname and port in the ${b('NB_HOST')} environment variable.
84
-
85
- ${b('-T')} ${u('token')}, ${b('--token=')}${u('token')}
86
- Use token ${u('token')} to connect to the Nuki bridge.
87
- You can also specify the token in the ${b('NB_TOKEN')} environment variable.
88
-
89
- Commands:
90
- ${usage.discover}
91
- ${description.discover}
92
-
93
- ${usage.auth}
94
- ${description.auth}
95
-
96
- ${usage.info}
97
- ${description.info}
98
-
99
- ${usage.getlog}
100
- ${description.getlog}
101
-
102
- ${usage.clearlog}
103
- ${description.clearlog}
104
-
105
- ${usage.reboot}
106
- ${description.reboot}
107
-
108
- ${usage.fwupdate}
109
- ${description.fwupdate}
110
-
111
- ${usage.list}
112
- ${description.list}
113
-
114
- ${usage.lockState}
115
- ${description.lockState}
116
-
117
- ${usage.lock}
118
- ${description.lock}
119
-
120
- ${usage.unlock}
121
- ${description.unlock}
122
-
123
- ${usage.lockAction}
124
- ${description.lockAction}
125
-
126
- ${usage.eventlog}
127
- ${description.eventlog}
128
-
129
- ${usage.callbackList}
130
- ${description.callbackList}
131
-
132
- ${usage.callbackRemove}
133
- ${description.callbackRemove}
134
-
135
- For more help, issue: ${b('nb')} ${u('command')} ${b('-h')}`,
136
- discover: `${description.discover}
137
-
138
- Usage: ${usage.discover}
139
-
140
- Parameters:
141
- ${b('-h')}, ${b('--help')}
142
- Print this help and exit.`,
143
- auth: `${description.auth}
144
-
145
- Usage: ${usage.auth}
146
-
147
- Parameters:
148
- ${b('-h')}, ${b('--help')}
149
- Print this help and exit.`,
150
- info: `${description.info}
151
-
152
- Usage: ${usage.info}
153
-
154
- Parameters:
155
- ${b('-h')}, ${b('--help')}
156
- Print this help and exit.`,
157
- getlog: `${description.getlog}
158
-
159
- Usage: ${usage.getlog}
160
-
161
- Parameters:
162
- ${b('-h')}, ${b('--help')}
163
- Print this help and exit.`,
164
- clearlog: `${description.clearlog}
165
-
166
- Usage: ${usage.clearlog}
167
-
168
- Parameters:
169
- ${b('-h')}, ${b('--help')}
170
- Print this help and exit.`,
171
- reboot: `${description.reboot}
172
-
173
- Usage: ${usage.reboot}
174
-
175
- Parameters:
176
- ${b('-h')}, ${b('--help')}
177
- Print this help and exit.`,
178
- fwupdate: `${description.fwupdate}
179
-
180
- Usage: ${usage.fwupdate}
181
-
182
- Parameters:
183
- ${b('-h')}, ${b('--help')}
184
- Print this help and exit.`,
185
- list: `${description.list}
186
-
187
- Usage: ${usage.list}
188
-
189
- Parameters:
190
- ${b('-h')}, ${b('--help')}
191
- Print this help and exit.`,
192
- lockState: `${description.lockState}
193
-
194
- Usage: ${usage.lockState}
195
-
196
- Parameters:
197
- ${b('-h')}, ${b('--help')}
198
- Print this help and exit.
199
-
200
- ${u('nukiId')}
201
- The ID of the Nuki device (from ${b('nb list')}).
202
-
203
- ${u('deviceType')}
204
- The type of the Nuki device (from ${b('nb list')}):
205
- 0: Smart Lock 1.0 or 2.0
206
- 2: Opener
207
- 3: Smart Door
208
- 4: Smart Lock 3.0`,
209
- lock: `${description.lock}
210
-
211
- Usage: ${usage.lock}
212
-
213
- Parameters:
214
- ${b('-h')}, ${b('--help')}
215
- Print this help and exit.
216
-
217
- ${u('nukiId')}
218
- The ID of the Nuki device (from ${b('nb list')}).
219
-
220
- ${u('deviceType')}
221
- The type of the Nuki device (from ${b('nb list')}):
222
- 0: Smart Lock 1.0 or 2.0
223
- 2: Opener
224
- 3: Smart Door
225
- 4: Smart Lock 3.0`,
226
- unlock: `${description.unlock}
227
-
228
- Usage: ${usage.unlock}
229
-
230
- Parameters:
231
- ${b('-h')}, ${b('--help')}
232
- Print this help and exit.
233
-
234
- ${u('nukiId')}
235
- The ID of the Nuki device (from ${b('nb list')}).
236
-
237
- ${u('deviceType')}
238
- The type of the Nuki device (from ${b('nb list')}):
239
- 0: Smart Lock 1.0 or 2.0
240
- 2: Opener
241
- 3: Smart Door
242
- 4: Smart Lock 3.0`,
243
- lockAction: `${description.lockAction}
244
-
245
- Usage: ${usage.lockAction}
246
-
247
- Parameters:
248
- ${b('-h')}, ${b('--help')}
249
- Print this help and exit.
250
-
251
- ${u('nukiId')}
252
- The ID of the Nuki device (from ${b('nb list')}).
253
-
254
- ${u('deviceType')}
255
- The type of the Nuki device (from ${b('nb list')}):
256
- 0: Smart Lock 1.0 or 2.0
257
- 2: Opener
258
- 3: Smart Door
259
- 4: Smart Lock 3.0
260
-
261
- ${u('action')}
262
- The action to send to the Nuki device:
263
- Smart Lock, Smart Door Opener
264
- - ------------------------ -------------------------
265
- 1 unlock activate rto
266
- 2 lock deactivate rto
267
- 3 unlatch electric strike actuation
268
- 4 lock ‘n’ go activate continuous mode
269
- 5 lock ‘n’ go with unlatch deactivate continuous mode`,
270
- eventlog: `${description.eventlog}
271
-
272
- Usage: ${usage.eventlog}
273
-
274
- Parameters:
275
- ${b('-h')}, ${b('--help')}
276
- Print this help and exit.
277
-
278
- ${b('-n')}, ${b('--noWhiteSpace')}
279
- Do not include spaces nor newlines in JSON output.
280
-
281
- ${b('-s')}, ${b('--service')}
282
- Do not output timestamps (useful when running as service).`,
283
- callbackList: `${description.callbackList}
284
-
285
- Usage: ${usage.callbackList}
286
-
287
- Parameters:
288
- ${b('-h')}, ${b('--help')}
289
- Print this help and exit.`,
290
- callbackRemove: `${description.callbackRemove}
291
-
292
- Usage: ${usage.callbackRemove}
293
-
294
- Parameters:
295
- ${b('-h')}, ${b('--help')}
296
- Print this help and exit.
297
-
298
- ${u('id')}
299
- Remove callback with ID ${u('id')} (from ${b('nb callbackList')}).`
300
- }
301
-
302
- class Main extends homebridgeLib.CommandLineTool {
303
- constructor () {
304
- super({ mode: 'command', debug: false })
305
- this.usage = usage.ph
306
- }
307
-
308
- parseArguments () {
309
- const parser = new homebridgeLib.CommandLineParser(packageJson)
310
- const clargs = {
311
- options: {
312
- host: process.env.NB_HOST,
313
- token: process.env.NB_TOKEN
314
- }
315
- }
316
- parser
317
- .help('h', 'help', help.nb)
318
- .version('V', 'version')
319
- .flag('D', 'debug', () => {
320
- if (this.debugEnabled) {
321
- this.setOptions({ vdebug: true })
322
- } else {
323
- this.setOptions({ debug: true, chalk: true })
324
- }
325
- })
326
- .option('H', 'host', (value) => {
327
- homebridgeLib.OptionParser.toHost('host', value, false, true)
328
- clargs.options.host = value
329
- })
330
- .option('t', 'timeout', (value) => {
331
- clargs.options.timeout = homebridgeLib.OptionParser.toInt(
332
- 'timeout', value, 1, 60, true
333
- )
334
- })
335
- .option('T', 'token', (value) => {
336
- clargs.options.token = homebridgeLib.OptionParser.toString(
337
- 'token', value, true, true
338
- )
339
- })
340
- .parameter('command', (value) => {
341
- if (usage[value] == null || typeof this[value] !== 'function') {
342
- throw new UsageError(`${value}: unknown command`)
343
- }
344
- clargs.command = value
345
- })
346
- .remaining((list) => { clargs.args = list })
347
- .parse()
348
- return clargs
349
- }
350
-
351
- async main () {
352
- try {
353
- this.usage = usage.nb
354
- const clargs = this.parseArguments()
355
- this.jsonFormatter = new homebridgeLib.JsonFormatter({ sortKeys: true })
356
- if (clargs.command !== 'discover') {
357
- if (clargs.options.host == null) {
358
- await this.fatal(`Missing host. Set ${b('NB_HOST')} or specify ${b('-H')}.`)
359
- }
360
- if (clargs.command === 'auth') {
361
- clargs.options.timeout = 60
362
- }
363
- const name = clargs.options.host
364
- this.client = new NbClient(clargs.options)
365
- this.client
366
- .on('error', (error) => {
367
- this.log(
368
- '%s: request %d: %s %s', name, error.request.id,
369
- error.request.method, error.request.resource
370
- )
371
- this.warn(
372
- '%s: request %d: error: %s', name, error.request.id, error
373
- )
374
- })
375
- .on('request', (request) => {
376
- this.debug(
377
- '%s: request %d: %s %s', name, request.id,
378
- request.method, request.resource
379
- )
380
- this.vdebug(
381
- '%s: request %d: %s %s', name, request.id,
382
- request.method, request.url
383
- )
384
- })
385
- .on('response', (response) => {
386
- this.vdebug(
387
- '%s: request %d: response: %j', name, response.request.id,
388
- response.body
389
- )
390
- this.debug(
391
- '%s: request %d: %d %s', name, response.request.id,
392
- response.statusCode, response.statusMessage
393
- )
394
- })
395
- if (clargs.options.token == null && clargs.command !== 'auth') {
396
- let args = ''
397
- if (clargs.options.host !== process.env.NB_HOST) {
398
- args += ' -H ' + clargs.options.host
399
- }
400
- await this.fatal(
401
- `Missing token. Set ${b('NB_TOKEN')} or specify ${b('-T')}. Run ${b('nb' + args + ' auth')} to obtain the token.`
402
- )
403
- }
404
- }
405
- this.name = 'nb ' + clargs.command
406
- this.usage = `${b('nb')} ${usage[clargs.command]}`
407
- this.parser = new homebridgeLib.CommandLineParser(packageJson)
408
- this.parser.help('h', 'help', help[clargs.command])
409
- await this[clargs.command](clargs.args)
410
- } catch (error) {
411
- await this.fatal(error)
412
- }
413
- }
414
-
415
- async discover (...args) {
416
- const options = {}
417
- this.parser
418
- .option('t', 'timeout', (value, key) => {
419
- options.timeout = homebridgeLib.OptionParser.toInt(
420
- 'timeout', value, 1, 60, true
421
- )
422
- })
423
- .parse(...args)
424
- const nbDiscovery = new NbDiscovery(options)
425
- nbDiscovery
426
- .on('error', (error) => {
427
- this.log(
428
- '%s: request %d: %s %s', error.request.name, error.request.id,
429
- error.request.method, error.request.resource
430
- )
431
- this.warn(
432
- '%s: request %d: error: %s', error.request.name, error.request.id, error
433
- )
434
- })
435
- .on('request', (request) => {
436
- this.debug(
437
- '%s: request %d: %s %s', request.name, request.id,
438
- request.method, request.resource
439
- )
440
- this.vdebug(
441
- '%s: request %d: %s %s', request.name, request.id,
442
- request.method, request.url
443
- )
444
- })
445
- .on('response', (response) => {
446
- this.vdebug(
447
- '%s: request %d: response: %j', response.request.name, response.request.id,
448
- response.body
449
- )
450
- this.debug(
451
- '%s: request %d: %d %s', response.request.name, response.request.id,
452
- response.statusCode, response.statusMessage
453
- )
454
- })
455
- const bridges = await nbDiscovery.discover()
456
- this.print(this.jsonFormatter.stringify(bridges))
457
- }
458
-
459
- async auth (...args) {
460
- this.parser.parse(...args)
461
- this.log('press button on Nuki bridge to obtain token')
462
- const token = await this.client.auth()
463
- this.print(token)
464
- }
465
-
466
- async info (...args) {
467
- this.parser.parse(...args)
468
- const response = await this.client.info()
469
- this.print(this.jsonFormatter.stringify(response.body))
470
- }
471
-
472
- async getlog (...args) {
473
- this.parser.parse(...args)
474
- const response = await this.client.log()
475
- this.print(this.jsonFormatter.stringify(response.body))
476
- }
477
-
478
- async clearlog (...args) {
479
- this.parser.parse(...args)
480
- const response = await this.client.clearlog()
481
- this.print(this.jsonFormatter.stringify(response.body))
482
- }
483
-
484
- async reboot (...args) {
485
- this.parser.parse(...args)
486
- const response = await this.client.reboot()
487
- this.print(this.jsonFormatter.stringify(response.body))
488
- }
489
-
490
- async fwupdate (...args) {
491
- this.parser.parse(...args)
492
- const response = await this.client.fwupdate()
493
- this.print(this.jsonFormatter.stringify(response.body))
494
- }
495
-
496
- async list (...args) {
497
- this.parser.parse(...args)
498
- const response = await this.client.list()
499
- this.print(this.jsonFormatter.stringify(response.body))
500
- }
501
-
502
- async lockState (...args) {
503
- let nukiId
504
- let deviceType
505
- this.parser
506
- .parameter('nukiId', (value) => {
507
- nukiId = homebridgeLib.OptionParser.toInt(
508
- 'nukiId', value, 0, Infinity, true
509
- )
510
- })
511
- .parameter('deviceType', (value) => {
512
- deviceType = homebridgeLib.OptionParser.toInt(
513
- 'deviceType', value, 0, 2, true
514
- )
515
- })
516
- .parse(...args)
517
- const response = await this.client.lockState(nukiId, deviceType)
518
- this.print(this.jsonFormatter.stringify(response.body))
519
- }
520
-
521
- async lock (...args) {
522
- let nukiId
523
- let deviceType
524
- this.parser
525
- .parameter('nukiId', (value) => {
526
- nukiId = homebridgeLib.OptionParser.toInt(
527
- 'nukiId', value, 0, Infinity, true
528
- )
529
- })
530
- .parameter('deviceType', (value) => {
531
- deviceType = homebridgeLib.OptionParser.toInt(
532
- 'deviceType', value, 0, 2, true
533
- )
534
- })
535
- .parse(...args)
536
- const response = await this.client.lock(nukiId, deviceType)
537
- this.print(this.jsonFormatter.stringify(response.body))
538
- }
539
-
540
- async unlock (...args) {
541
- let nukiId
542
- let deviceType
543
- this.parser
544
- .parameter('nukiId', (value) => {
545
- nukiId = homebridgeLib.OptionParser.toInt(
546
- 'nukiId', value, 0, Infinity, true
547
- )
548
- })
549
- .parameter('deviceType', (value) => {
550
- deviceType = homebridgeLib.OptionParser.toInt(
551
- 'deviceType', value, 0, 2, true
552
- )
553
- })
554
- .parse(...args)
555
- const response = await this.client.unlock(nukiId, deviceType)
556
- this.print(this.jsonFormatter.stringify(response.body))
557
- }
558
-
559
- async lockAction (...args) {
560
- let nukiId
561
- let deviceType
562
- let action
563
- this.parser
564
- .parameter('nukiId', (value) => {
565
- nukiId = homebridgeLib.OptionParser.toInt(
566
- 'nukiId', value, 0, Infinity, true
567
- )
568
- })
569
- .parameter('deviceType', (value) => {
570
- deviceType = homebridgeLib.OptionParser.toInt(
571
- 'deviceType', value, 0, 2, true
572
- )
573
- })
574
- .parameter('action', (value) => {
575
- action = homebridgeLib.OptionParser.toInt('action', value, 1, 5, true)
576
- })
577
- .parse(...args)
578
- const response = await this.client.lockAction(nukiId, deviceType, action)
579
- this.print(this.jsonFormatter.stringify(response.body))
580
- }
581
-
582
- async destroy () {
583
- if (this.listener != null) {
584
- const response = await this.client.callbackList()
585
- for (const callback of response.body.callbacks) {
586
- if (callback.url === this._callbackUrl) {
587
- this.log(
588
- 'Removing subscription %s for %s', callback.id, callback.url
589
- )
590
- try {
591
- await this.client.callbackRemove(callback.id)
592
- } catch (error) {
593
- this.error(error)
594
- }
595
- }
596
- }
597
- this.listener.removeClient(this.client)
598
- }
599
- }
600
-
601
- async eventlog (...args) {
602
- let noWhiteSpace = false
603
- let mode = 'daemon'
604
- this.parser
605
- .flag('n', 'noWhiteSpace', () => { noWhiteSpace = true })
606
- .flag('s', 'service', () => { mode = 'service' })
607
- .parse(...args)
608
- this.setOptions({ mode })
609
- const jsonFormatter = new homebridgeLib.JsonFormatter({
610
- sortKeys: true,
611
- noWhiteSpace
612
- })
613
-
614
- this.listener = new NbListener()
615
- this.listener
616
- .on('error', (error) => { this.error(error) })
617
- .on('listening', (url) => {
618
- this.log('listening on %s', url)
619
- })
620
- .on('close', (url) => {
621
- this.log('closed %s', url)
622
- })
623
- this.client.on('event', (event) => {
624
- this.log('%s', jsonFormatter.stringify(event))
625
- })
626
-
627
- await this.client.init()
628
- this._callbackUrl = await this.listener.addClient(this.client)
629
- const response = await this.client.callbackAdd(this._callbackUrl)
630
- if (!response.body.success) {
631
- this.listener.removeClient(this.client)
632
- this.error(response.body.message)
633
- }
634
- }
635
-
636
- async callbackList (...args) {
637
- this.parser.parse(...args)
638
- const response = await this.client.callbackList()
639
- this.print(this.jsonFormatter.stringify(response.body))
640
- }
641
-
642
- async callbackRemove (...args) {
643
- let id
644
- this.parser
645
- .parameter('id', (value) => {
646
- id = homebridgeLib.OptionParser.toInt('id', value, 0, Infinity, true)
647
- })
648
- .parse(...args)
649
- const response = await this.client.callbackRemove(id)
650
- this.print(this.jsonFormatter.stringify(response.body))
651
- }
652
- }
653
-
654
- new Main().main()
12
+ new NbTool(require('../package.json')).main()
@@ -7,7 +7,7 @@
7
7
 
8
8
  const homebridgeLib = require('homebridge-lib')
9
9
  const NbService = require('./NbService')
10
- const NbClient = require('./NbClient')
10
+ const { NbClient } = require('hb-nb-tools')
11
11
 
12
12
  class NbAccessory extends homebridgeLib.AccessoryDelegate {
13
13
  constructor (bridge, params) {
@@ -136,7 +136,22 @@ class Bridge extends homebridgeLib.AccessoryDelegate {
136
136
  await this.client.init()
137
137
  this.values.firmware = this.client.firmware
138
138
  this.context.firmware = this.values.firmware
139
- // TODO firmware version check
139
+ if (this.values.firmware !== this.platform.packageJson.engines.nuki) {
140
+ this.warn(
141
+ 'recommended version: Nuki Bridge v%s',
142
+ this.platform.packageJson.engines.nuki
143
+ )
144
+ }
145
+ switch (this.client.encryption) {
146
+ case 'none':
147
+ this.warn('using plain-text tokens')
148
+ break
149
+ case 'hashedToken':
150
+ this.warn('using deprecated hashed tokens')
151
+ break
152
+ default:
153
+ break
154
+ }
140
155
  } catch (error) {
141
156
  return
142
157
  }
package/lib/NbPlatform.js CHANGED
@@ -8,9 +8,7 @@
8
8
  const events = require('events')
9
9
  const homebridgeLib = require('homebridge-lib')
10
10
  const NbAccessory = require('./NbAccessory')
11
- const NbClient = require('./NbClient')
12
- const NbDiscovery = require('./NbDiscovery')
13
- const NbListener = require('./NbListener')
11
+ const { NbClient, NbDiscovery, NbListener } = require('hb-nb-tools')
14
12
 
15
13
  class NbPlatform extends homebridgeLib.Platform {
16
14
  constructor (log, configJson, homebridge) {
package/lib/NbService.js CHANGED
@@ -6,7 +6,7 @@
6
6
  'use strict'
7
7
 
8
8
  const homebridgeLib = require('homebridge-lib')
9
- const NbClient = require('./NbClient')
9
+ const { NbClient } = require('hb-nb-tools')
10
10
 
11
11
  function dateToString (date) {
12
12
  if (date == null) {
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "displayName": "Homebridge NB",
5
5
  "author": "Erik Baauw",
6
6
  "license": "Apache-2.0",
7
- "version": "1.3.4",
7
+ "version": "1.4.1",
8
8
  "keywords": [
9
9
  "homebridge-plugin",
10
10
  "homekit",
@@ -19,10 +19,12 @@
19
19
  },
20
20
  "engines": {
21
21
  "homebridge": "^1.6.0",
22
- "node": "^18.15.0"
22
+ "node": "^18.16.0",
23
+ "nuki": "2.15.0"
23
24
  },
24
25
  "dependencies": {
25
- "homebridge-lib": "~6.3.12"
26
+ "homebridge-lib": "~6.3.14",
27
+ "hb-nb-tools": "~1.0.1"
26
28
  },
27
29
  "scripts": {
28
30
  "prepare": "standard",
package/lib/NbClient.js DELETED
@@ -1,201 +0,0 @@
1
- // homebridge-nb/lib/NbClient.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 crypto = require('crypto')
9
- const homebridgeLib = require('homebridge-lib')
10
-
11
- class NbClient extends homebridgeLib.HttpClient {
12
- static get DeviceTypes () {
13
- return {
14
- SMARTLOCK: 0,
15
- OPENER: 2,
16
- SMARTDOOR: 3,
17
- SMARTLOCK3: 4
18
- }
19
- }
20
-
21
- static get LockStates () {
22
- return {
23
- UNCALIBRATED: 0,
24
- LOCKED: 1,
25
- UNLOCKING: 2,
26
- UNLOCKED: 3,
27
- LOCKING: 4,
28
- UNLATCHED: 5,
29
- UNLOCKED_LOCK_N_GO: 6,
30
- UNLATCHING: 7,
31
- MOTOR_BLOCKED: 254,
32
- UNDEFINED: 255
33
- }
34
- }
35
-
36
- static get DoorSensorStates () {
37
- return {
38
- DEACTIVATED: 0,
39
- CLOSED: 2,
40
- OPEN: 3,
41
- UNKNOWN: 4,
42
- CALIBRATING: 5
43
- }
44
- }
45
-
46
- static get LockActions () {
47
- return {
48
- UNLOCK: 1,
49
- LOCK: 2,
50
- UNLATCH: 3,
51
- LOCK_N_GO: 4,
52
- LOCK_N_GO_WITH_UNLATCH: 5
53
- }
54
- }
55
-
56
- static get OpenerModes () {
57
- return {
58
- DOOR_MODE: 2,
59
- CONTINUOUS_MODE: 3
60
- }
61
- }
62
-
63
- static get OpenerStates () {
64
- return {
65
- UNTRAINED: 0,
66
- ONLINE: 1,
67
- RTO_ACTIVE: 3,
68
- OPEN: 5,
69
- OPENING: 7,
70
- BOOT_RUN: 253,
71
- UNDEFINED: 255
72
- }
73
- }
74
-
75
- static get OpenerActions () {
76
- return {
77
- ACTIVATE_RTO: 1,
78
- DEACTIVATE_RTO: 2,
79
- OPEN: 3,
80
- ACTIVATE_CM: 4,
81
- DEACTIVATE_CM: 5
82
- }
83
- }
84
-
85
- static modelName (deviceType, firmware) {
86
- switch (deviceType) {
87
- case NbClient.DeviceTypes.SMARTLOCK:
88
- if (firmware[0] === '1') {
89
- return 'Smart Lock 1.0'
90
- }
91
- return 'Smart Lock 2.0'
92
- case NbClient.DeviceTypes.OPENER:
93
- return 'Opener'
94
- case NbClient.DeviceTypes.SMARTDOOR:
95
- return 'Smart Door'
96
- case NbClient.DeviceTypes.SMARTLOCK3:
97
- return 'Smart Lock 3.0'
98
- }
99
- }
100
-
101
- constructor (params = {}) {
102
- const _params = {
103
- hashedToken: true,
104
- port: 8080,
105
- timeout: 5
106
- }
107
- const optionParser = new homebridgeLib.OptionParser(_params)
108
- optionParser
109
- .hostKey('host')
110
- .intKey('timeout', 1, 60)
111
- .stringKey('token')
112
- .parse(params)
113
- super({
114
- host: _params.hostname + ':' + _params.port,
115
- json: true,
116
- keepAlive: true,
117
- maxSockets: 1,
118
- timeout: _params.timeout
119
- })
120
- this._params = _params
121
- this._jsonFormatter = new homebridgeLib.JsonFormatter()
122
- }
123
-
124
- get id () { return this._params.id }
125
- get name () { return 'Nuki_Bridge_' + this._params.id }
126
- get firmware () { return this._params.firmware }
127
- get token () { return this._params.token }
128
-
129
- async auth () {
130
- const response = await this._get('/auth')
131
- if (response.body.success) {
132
- this._params.token = response.body.token
133
- return response.body.token
134
- }
135
- throw new Error('Nuki bridge button not pressed')
136
- }
137
-
138
- async info () { return this._get('/info') }
139
- async list () { return this._get('/list') }
140
- async log () { return this._get('/log') }
141
- async reboot () { return this._get('/reboot') }
142
- async fwupdate () { return this._get('/fwupdate') }
143
-
144
- async lockState (nukiId, deviceType) {
145
- return this._get('/lockState', { nukiId, deviceType })
146
- }
147
-
148
- async lock (nukiId, deviceType) {
149
- return this._get('/lock', { nukiId, deviceType })
150
- }
151
-
152
- async unlock (nukiId, deviceType) {
153
- return this._get('/unlock', { nukiId, deviceType })
154
- }
155
-
156
- async lockAction (nukiId, deviceType, action) {
157
- return this._get(
158
- '/lockAction', { nukiId, deviceType, action }
159
- )
160
- }
161
-
162
- async init () {
163
- const response = await this.info()
164
- this._params.id = response.body.ids.serverId.toString(16).toUpperCase()
165
- this._params.firmware = response.body.versions.firmwareVersion
166
- }
167
-
168
- async callbackAdd (url) {
169
- return this._get('/callback/add', { url: encodeURIComponent(url) })
170
- }
171
-
172
- async callbackList () { return this._get('/callback/list') }
173
- async callbackRemove (id) { return this._get('/callback/remove', { id }) }
174
-
175
- async _get (resource, params = {}) {
176
- // Append parameters
177
- let separator = '?'
178
- for (const param in params) {
179
- resource += separator + param + '=' + params[param]
180
- separator = '&'
181
- }
182
- let suffix = ''
183
- if (resource !== '/auth') {
184
- if (this._params.hashedToken) {
185
- // Append hashed token
186
- const hash = crypto.createHash('sha256')
187
- const date = new Date().toISOString()
188
- const ts = date.slice(0, 19) + 'Z'
189
- const rnr = '1' + date.slice(20, 23)
190
- hash.update([ts, rnr, this._params.token].join(','))
191
- suffix = separator + 'ts=' + ts + '&rnr=' + rnr +
192
- '&hash=' + hash.digest('hex')
193
- } else {
194
- suffix = separator + 'token=' + this._params.token
195
- }
196
- }
197
- return super.get(resource, undefined, suffix)
198
- }
199
- }
200
-
201
- module.exports = NbClient
@@ -1,56 +0,0 @@
1
- // homebridge-nb/lib/NbDiscovery.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 homebridgeLib = require('homebridge-lib')
9
-
10
- class NbDiscovery extends homebridgeLib.HttpClient {
11
- constructor (params = {}) {
12
- const config = {
13
- timeout: 5
14
- }
15
- const optionParser = new homebridgeLib.OptionParser(config)
16
- optionParser.intKey('timeout', 1, 60)
17
- optionParser.parse(params)
18
- super({
19
- https: true,
20
- host: 'api.nuki.io',
21
- json: true,
22
- keepAlive: false,
23
- name: 'nuki server',
24
- path: '/discover',
25
- timeout: config.timeout
26
- })
27
- this.config = config
28
- }
29
-
30
- async discover () {
31
- const bridges = []
32
- const response = await super.get('/bridges')
33
- if (response == null) {
34
- return bridges
35
- }
36
- for (const bridge of response.body.bridges) {
37
- const client = new homebridgeLib.HttpClient({
38
- host: bridge.ip + ':' + bridge.port,
39
- path: '',
40
- timeout: this.config.timeout,
41
- validStatusCodes: [200, 401]
42
- })
43
- client
44
- .on('error', (error) => { this.emit('error', error) })
45
- .on('request', (request) => { this.emit('request', request) })
46
- .on('response', (response) => { this.emit('response', response) })
47
- try {
48
- await client.get('/info')
49
- bridges.push(bridge)
50
- } catch (error) {}
51
- }
52
- return bridges
53
- }
54
- }
55
-
56
- module.exports = NbDiscovery
package/lib/NbListener.js DELETED
@@ -1,105 +0,0 @@
1
- // homebridge-nb/lib/NbListener.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 events = require('events')
9
- const http = require('http')
10
-
11
- class NbListener extends events.EventEmitter {
12
- constructor (port = 0) {
13
- super()
14
- this._myPort = port
15
- this._clients = {}
16
- this._clientsByName = {}
17
- this._server = http.createServer((request, response) => {
18
- let buffer = ''
19
- request.on('data', (data) => {
20
- buffer += data
21
- })
22
- request.on('end', async () => {
23
- try {
24
- request.body = buffer
25
- if (request.method === 'GET' && request.url === '/notify') {
26
- // Provide an easy way to check that listener is reachable.
27
- response.writeHead(200, { 'Content-Type': 'text/html' })
28
- response.write('<table>')
29
- response.write(`<caption><h3>Listening to ${Object.keys(this._clients).length} clients</h3></caption>`)
30
- response.write('<tr><th scope="col">Nuki Bridge</th>')
31
- response.write('<th scope="col">IP Address</th>')
32
- response.write('<th scope="col">Local IP Address</th>')
33
- for (const name of Object.keys(this._clientsByName).sort()) {
34
- const client = this._clientsByName[name]
35
- response.write(`<tr><td>${name}</td><td>${client.address}</td>`)
36
- response.write(`<td>${client.localAddress}</td></tr>`)
37
- }
38
- response.write('</table>')
39
- } else if (request.method === 'POST') {
40
- const array = request.url.split('/')
41
- const client = this._clients[array[2]]
42
- if (array[1] === 'notify' && client !== null) {
43
- const obj = JSON.parse(request.body)
44
- client.emit('event', obj)
45
- }
46
- }
47
- response.end()
48
- } catch (error) {
49
- this.emit('error', error)
50
- }
51
- })
52
- })
53
- this._server
54
- .on('error', (error) => { this.emit('error', error) })
55
- .on('close', () => {
56
- this.emit('close', this._callbackUrl)
57
- delete this._callbackUrl
58
- })
59
- }
60
-
61
- async _listen () {
62
- if (this._server.listening) {
63
- return
64
- }
65
- return new Promise((resolve, reject) => {
66
- try {
67
- this._server.listen(this._myPort, '0.0.0.0', () => {
68
- const address = this._server.address()
69
- this._myIp = address.address
70
- this._myPort = address.port
71
- this._callbackUrl =
72
- 'http://' + this._myIp + ':' + this._myPort + '/notify'
73
- /** Emitted when the web server has started.
74
- * @event NbListener#listening
75
- * @param {string} url - The url the web server is listening on.
76
- */
77
- this.emit('listening', this._callbackUrl)
78
- return resolve()
79
- })
80
- } catch (error) {
81
- return reject(error)
82
- }
83
- })
84
- }
85
-
86
- async addClient (nbClient) {
87
- this._clients[nbClient.id] = nbClient
88
- this._clientsByName[nbClient.name] = nbClient
89
- await this._listen()
90
- const callbackUrl = 'http://' + nbClient.localAddress + ':' + this._myPort +
91
- '/notify/' + nbClient.id
92
- return callbackUrl
93
- }
94
-
95
- async removeClient (nbClient) {
96
- this.removeAllListeners(nbClient.id)
97
- delete this._clientsByName[nbClient.name]
98
- delete this._clients[nbClient.id]
99
- if (Object.keys(this._clients).length === 0) {
100
- await this._server.close()
101
- }
102
- }
103
- }
104
-
105
- module.exports = NbListener