homebridge-deconz 0.1.17 → 0.1.19

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/deconz.js CHANGED
@@ -1,984 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // homebridge-deconz/cli/deconz.js
3
+ // deconz.js
4
4
  // Copyright © 2018-2023 Erik Baauw. All rights reserved.
5
5
  //
6
6
  // Command line interface to deCONZ gateway.
7
7
 
8
8
  'use strict'
9
9
 
10
- const fs = require('fs')
11
- const Deconz = require('../lib/Deconz')
12
- const {
13
- CommandLineParser, CommandLineTool, JsonFormatter, OptionParser
14
- } = require('hb-lib-tools')
15
- const packageJson = require('../package.json')
10
+ const { DeconzTool } = require('hb-deconz-tools')
11
+ const pkgJson = require('../package.json')
16
12
 
17
- const { b, u } = CommandLineTool
18
- const { UsageError } = CommandLineParser
19
-
20
- const usage = {
21
- deconz: `${b('deconz')} [${b('-hVDp')}] [${b('-H')} ${u('hostname')}[${b(':')}${u('port')}]] [${b('-K')} ${u('api key')}] [${b('-t')} ${u('timeout')}] ${u('command')} [${u('argument')} ...]`,
22
-
23
- get: `${b('get')} [${b('-hsnjuatlkv')}] [${u('path')}]`,
24
- put: `${b('put')} [${b('-hv')}] ${u('resource')} [${u('body')}]`,
25
- post: `${b('post')} [${b('-hv')}] ${u('resource')} [${u('body')}]`,
26
- delete: `${b('delete')} [${b('-hv')}] ${u('resource')} [${u('body')}]`,
27
-
28
- eventlog: `${b('eventlog')} [${b('-hnrs')}]`,
29
-
30
- discover: `${b('discover')} [${b('-hS')}]`,
31
- config: `${b('config')} [${b('-hs')}]`,
32
- description: `${b('description')} [${b('-hs')}]`,
33
- getApiKey: `${b('getApiKey')} [${b('-hv')}]`,
34
- unlock: `${b('unlock')} [${b('-hv')}]`,
35
- search: `${b('search')} [${b('-hv')}]`,
36
-
37
- probe: `${b('probe')} [${b('-hv')}] [${b('-t')} ${u('timeout')}] ${u('light')}`,
38
- restart: `${b('restart')} [${b('-hv')}]`
39
- }
40
- const description = {
41
- deconz: 'Command line interface to deCONZ gateway.',
42
-
43
- get: `Retrieve ${u('path')} from gateway.`,
44
- put: `Update ${u('resource')} on gateway with ${u('body')}.`,
45
- post: `Create ${u('resource')} on gateway with ${u('body')}.`,
46
- delete: `Delete ${u('resource')} from gateway with ${u('body')}.`,
47
-
48
- eventlog: 'Log web socket notifications by the gateway.',
49
-
50
- discover: 'Discover gateways.',
51
- config: 'Retrieve gateway configuration (unauthenticated).',
52
- description: 'Retrieve gateway description.',
53
-
54
- getApiKey: 'Obtain an API key for the gateway.',
55
- unlock: 'Unlock the gateway so new clients can obtain an API key.',
56
- search: 'Initiate a seach for new devices.',
57
-
58
- probe: `Probe ${u('light')} for supported colour (temperature) range.`,
59
- restart: 'Restart the gateway.'
60
- }
61
- const help = {
62
- deconz: `${description.deconz}
63
-
64
- Usage: ${usage.deconz}
65
-
66
- Parameters:
67
- ${b('-h')}, ${b('--help')}
68
- Print this help and exit.
69
-
70
- ${b('-V')}, ${b('--version')}
71
- Print version and exit.
72
-
73
- ${b('-D')}, ${b('--debug')}
74
- Print debug messages for communication with the gateway.
75
-
76
- ${b('-p')}, ${b('--phoscon')}
77
- Imitate the Phoscon app.
78
-
79
- ${b('-H')} ${u('hostname')}[${b(':')}${u('port')}], ${b('--host=')}${u('hostname')}[${b(':')}${u('port')}]
80
- Connect to ${u('hostname')}${b(':80')} or ${u('hostname')}${b(':')}${u('port')} instead of the default ${b('localhost:80')}.
81
- The hostname and port can also be specified by setting ${b('DECONZ_HOST')}.
82
-
83
- ${b('-K')} ${u('API key')}, ${b('--apiKey=')}${u('API key')}
84
- Use ${u('API key')} instead of the API key saved in ${b('~/.deconz')}.
85
- The API key can also be specified by setting ${b('DECONZ_API_KEY')}.
86
-
87
- ${b('-t')} ${u('timeout')}, ${b('--timeout=')}${u('timeout')}
88
- Set timeout to ${u('timeout')} seconds instead of default ${b(5)}.
89
-
90
- Commands:
91
- ${usage.get}
92
- ${description.get}
93
-
94
- ${usage.put}
95
- ${description.put}
96
-
97
- ${usage.post}
98
- ${description.post}
99
-
100
- ${usage.delete}
101
- ${description.delete}
102
-
103
- ${usage.eventlog}
104
- ${description.eventlog}
105
-
106
- ${usage.discover}
107
- ${description.discover}
108
-
109
- ${usage.config}
110
- ${description.config}
111
-
112
- ${usage.description}
113
- ${description.description}
114
-
115
- ${usage.getApiKey}
116
- ${description.getApiKey}
117
-
118
- ${usage.unlock}
119
- ${description.unlock}
120
-
121
- ${usage.search}
122
- ${description.search}
123
-
124
- ${usage.probe}
125
- ${description.probe}
126
-
127
- ${usage.restart}
128
- ${description.restart}
129
-
130
- For more help, issue: ${b('deconz')} ${u('command')} ${b('-h')}`,
131
- get: `${description.deconz}
132
-
133
- Usage: ${b('deconz')} ${usage.get}
134
-
135
- ${description.get}
136
-
137
- Parameters:
138
- ${b('-h')}, ${b('--help')}
139
- Print this help and exit.
140
-
141
- ${b('-s')}, ${b('--sortKeys')}
142
- Sort object key/value pairs alphabetically on key.
143
-
144
- ${b('-n')}, ${b('-noWhiteSpace')}
145
- Do not include spaces nor newlines in the output.
146
-
147
- ${b('-j')}, ${b('--jsonArray')}
148
- Output a JSON array of objects for each key/value pair.
149
- Each object contains two key/value pairs: key "keys" with an array
150
- of keys as value and key "value" with the value as value.
151
-
152
- ${b('-u')}, ${b('--joinKeys')}
153
- Output JSON array of objects for each key/value pair.
154
- Each object contains one key/value pair: the path (concatenated
155
- keys separated by '/') as key and the value as value.
156
-
157
- ${b('-a')}, ${b('--ascii')}
158
- Output path:value in plain text instead of JSON.
159
-
160
- ${b('-t')}, ${b('--topOnly')}
161
- Limit output to top-level key/values.
162
-
163
- ${b('-l')}, ${b('--leavesOnly')}
164
- Limit output to leaf (non-array, non-object) key/values.
165
-
166
- ${b('-k')}, ${b('--keysOnly')}
167
- Limit output to keys. With ${b('-u')}, output a JSON array of paths.
168
-
169
- ${b('-v')}, ${b('--valuesOnly')}
170
- Limit output to values. With ${b('-u')}, output a JSON array of values.
171
-
172
- ${u('path')}
173
- Path to retrieve from the gateway.`,
174
- put: `${description.deconz}
175
-
176
- Usage: ${b('deconz')} ${usage.put}
177
-
178
- ${description.put}
179
-
180
- Parameters:
181
- ${b('-h')}, ${b('--help')}
182
- Print this help and exit.
183
-
184
- ${b('-v')}, ${b('--verbose')}
185
- Print full API output.
186
-
187
- ${u('resource')}
188
- Resource to update.
189
-
190
- ${u('body')}
191
- Body in JSON.`,
192
- post: `${description.deconz}
193
-
194
- Usage: ${b('deconz')} ${usage.post}
195
-
196
- ${description.post}
197
-
198
- Parameters:
199
- ${b('-h')}, ${b('--help')}
200
- Print this help and exit.
201
-
202
- ${b('-v')}, ${b('--verbose')}
203
- Print full API output.
204
-
205
- ${u('resource')}
206
- Resource to create.
207
-
208
- ${u('body')}
209
- Body in JSON.`,
210
- delete: `${description.deconz}
211
-
212
- Usage: ${b('deconz')} ${usage.delete}
213
-
214
- ${description.delete}
215
-
216
- Parameters:
217
- ${b('-h')}, ${b('--help')}
218
- Print this help and exit.
219
-
220
- ${b('-v')}, ${b('--verbose')}
221
- Print full API output.
222
-
223
- ${u('resource')}
224
- Resource to delete.
225
-
226
- ${u('body')}
227
- Body in JSON.`,
228
- eventlog: `${description.deconz}
229
-
230
- Usage: ${b('deconz')} ${usage.eventlog}
231
-
232
- ${description.eventlog}
233
-
234
- Parameters:
235
- ${b('-h')}, ${b('--help')}
236
- Print this help and exit.
237
-
238
- ${b('-v')}, ${b('--verbose')}
239
- Print full API output.
240
-
241
- ${b('-n')}, ${b('--noRetry')}
242
- Do not retry when connection is closed.
243
-
244
- ${b('-r')}, ${b('--raw')}
245
- Do not parse events, output raw event data.
246
-
247
- ${b('-s')}, ${b('--service')}
248
- Do not output timestamps (useful when running as service).`,
249
- discover: `${description.deconz}
250
-
251
- Usage: ${b('deconz')} ${usage.discover}
252
-
253
- ${description.discover}
254
-
255
- Parameters:
256
- ${b('-h')}, ${b('--help')}
257
- Print this help and exit.
258
-
259
- ${b('-S')}, ${b('--stealth')}
260
- Stealth mode, only use local discovery.`,
261
- config: `${description.deconz}
262
-
263
- Usage: ${b('deconz')} ${usage.config}
264
-
265
- ${description.config}
266
-
267
- Parameters:
268
- ${b('-h')}, ${b('--help')}
269
- Print this help and exit.
270
-
271
- ${b('-s')}, ${b('--sortKeys')}
272
- Sort object key/value pairs alphabetically on key.`,
273
- description: `${description.deconz}
274
-
275
- Usage: ${b('deconz')} ${usage.description}
276
-
277
- ${description.description}
278
-
279
- Parameters:
280
- ${b('-h')}, ${b('--help')}
281
- Print this help and exit.
282
-
283
- ${b('-s')}, ${b('--sortKeys')}
284
- Sort object key/value pairs alphabetically on key.`,
285
- getApiKey: `${description.deconz}
286
-
287
- Usage: ${b('deconz')} ${usage.getApiKey}
288
-
289
- ${description.getApiKey}
290
- You need to unlock the deCONZ gateway prior to issuing this command,
291
- unless you're running it on the gateway's local host.
292
- The API key is saved to ${b('~/.deconz')}.
293
-
294
- Parameters:
295
- ${b('-h')}, ${b('--help')}
296
- Print this help and exit.
297
-
298
- ${b('-v')}, ${b('--verbose')}
299
- Print full API output.`,
300
- unlock: `${description.deconz}
301
-
302
- Usage: ${b('deconz')} ${usage.unlock}
303
-
304
- ${description.unlock}
305
-
306
- Parameters:
307
- ${b('-h')}, ${b('--help')}
308
- Print this help and exit.
309
-
310
- ${b('-v')}, ${b('--verbose')}
311
- Print full API output.`,
312
- search: `${description.search}
313
-
314
- Usage: ${b('deconz')} ${usage.search}
315
-
316
- ${description.search}
317
-
318
- Parameters:
319
- ${b('-h')}, ${b('--help')}
320
- Print this help and exit.
321
-
322
- ${b('-v')}, ${b('--verbose')}
323
- Print full API output.`,
324
- probe: `${description.deconz}
325
-
326
- Usage: ${b('deconz')} ${usage.probe}
327
-
328
- ${description.probe}
329
-
330
- Parameters:
331
- ${b('-h')}, ${b('--help')}
332
- Print this help and exit.
333
-
334
- ${b('-v')}, ${b('--verbose')}
335
- Print full API output.
336
-
337
- ${b('-t')} ${u('timeout')}, ${b('--timeout=')}${u('timeout')}
338
- Timeout after ${u('timeout')} minutes (default: 5).
339
-
340
- ${u('light')}
341
- Lights resource to probe.`,
342
- restart: `${description.deconz}
343
-
344
- Usage: ${b('deconz')} ${usage.restart}
345
-
346
- ${description.restart}
347
-
348
- Parameters:
349
- ${b('-h')}, ${b('--help')}
350
- Print this help and exit.
351
-
352
- ${b('-v')}, ${b('--verbose')}
353
- Print full API output.`
354
- }
355
-
356
- class Main extends CommandLineTool {
357
- constructor () {
358
- super({ mode: 'command', debug: false })
359
- this.usage = usage.deconz
360
- try {
361
- this.readGateways()
362
- } catch (error) {
363
- if (error.code !== 'ENOENT') {
364
- this.error(error)
365
- }
366
- this.gateways = {}
367
- }
368
- }
369
-
370
- // ===========================================================================
371
-
372
- readGateways () {
373
- const text = fs.readFileSync(process.env.HOME + '/.deconz')
374
- try {
375
- this.gateways = JSON.parse(text)
376
- } catch (error) {
377
- this.warn('%s/.deconz: file corrupted', process.env.HOME)
378
- this.gateways = {}
379
- }
380
- }
381
-
382
- writeGateways () {
383
- const jsonFormatter = new JsonFormatter(
384
- { noWhiteSpace: true, sortKeys: true }
385
- )
386
- const text = jsonFormatter.stringify(this.gateways)
387
- fs.writeFileSync(process.env.HOME + '/.deconz', text, { mode: 0o600 })
388
- }
389
-
390
- parseArguments () {
391
- const parser = new CommandLineParser(packageJson)
392
- const clargs = {
393
- options: {
394
- host: process.env.DECONZ_HOST || 'localhost',
395
- timeout: 5
396
- }
397
- }
398
- parser
399
- .help('h', 'help', help.deconz)
400
- .version('V', 'version')
401
- .option('H', 'host', (value) => {
402
- OptionParser.toHost('host', value, false, true)
403
- clargs.options.host = value
404
- })
405
- .option('K', 'apiKey', (value) => {
406
- clargs.options.apiKey = OptionParser.toString(
407
- 'apiKey', value, true, true
408
- )
409
- })
410
- .flag('p', 'phoscon', () => {
411
- clargs.options.phoscon = true
412
- })
413
- .flag('D', 'debug', () => {
414
- if (this.debugEnabled) {
415
- this.setOptions({ vdebug: true })
416
- } else {
417
- this.setOptions({ debug: true, chalk: true })
418
- }
419
- })
420
- .option('t', 'timeout', (value) => {
421
- clargs.options.timeout = OptionParser.toInt(
422
- 'timeout', value, 1, 60, true
423
- )
424
- })
425
- .parameter('command', (value) => {
426
- if (usage[value] == null || typeof this[value] !== 'function') {
427
- throw new UsageError(`${value}: unknown command`)
428
- }
429
- clargs.command = value
430
- })
431
- .remaining((list) => { clargs.args = list })
432
- parser
433
- .parse()
434
- return clargs
435
- }
436
-
437
- async main () {
438
- try {
439
- await this._main()
440
- } catch (error) {
441
- if (error.request == null) {
442
- this.error(error)
443
- }
444
- }
445
- }
446
-
447
- async _main () {
448
- this.clargs = this.parseArguments()
449
- this.deconzDiscovery = new Deconz.Discovery({
450
- timeout: this.clargs.options.timeout
451
- })
452
- this.deconzDiscovery
453
- .on('error', (error) => {
454
- if (error.request != null) {
455
- this.log(
456
- '%s: request %d: %s %s', error.request.name,
457
- error.request.id, error.request.method, error.request.resource
458
- )
459
- this.warn(
460
- '%s: request %d: %s', error.request.name, error.request.id, error
461
- )
462
- return
463
- }
464
- this.warn(error)
465
- })
466
- .on('request', (request) => {
467
- this.debug(
468
- '%s: request %d: %s %s', request.name,
469
- request.id, request.method, request.resource
470
- )
471
- this.vdebug(
472
- '%s: request %d: %s %s', request.name,
473
- request.id, request.method, request.url
474
- )
475
- })
476
- .on('response', (response) => {
477
- this.vdebug(
478
- '%s: request %d: response: %j', response.request.name,
479
- response.request.id, response.body
480
- )
481
- this.debug(
482
- '%s: request %d: %d %s', response.request.name,
483
- response.request.id, response.statusCode, response.statusMessage
484
- )
485
- })
486
- .on('found', (name, id, address) => {
487
- this.debug('%s: found %s at %s', name, id, address)
488
- })
489
- .on('searching', (host) => {
490
- this.debug('upnp: listening on %s', host)
491
- })
492
- .on('searchDone', () => { this.debug('upnp: search done') })
493
-
494
- if (this.clargs.command === 'discover') {
495
- return this.discover(this.clargs.args)
496
- }
497
- try {
498
- this.gatewayConfig = await this.deconzDiscovery.config(
499
- this.clargs.options.host
500
- )
501
- } catch (error) {
502
- if (error.request == null) {
503
- await this.fatal('%s: %s', this.clargs.options.host, error)
504
- }
505
- await this.fatal('%s: deCONZ gateway not found', this.clargs.options.host)
506
- }
507
-
508
- this.name = 'deconz ' + this.clargs.command
509
- this.usage = `${b('deconz')} ${usage[this.clargs.command]}`
510
-
511
- if (this.clargs.command === 'config' || this.clargs.command === 'description') {
512
- return this[this.clargs.command](this.clargs.args)
513
- }
514
-
515
- this.bridgeid = this.gatewayConfig.bridgeid
516
- if (this.clargs.options.apiKey == null) {
517
- if (this.gateways[this.bridgeid]?.apiKey != null) {
518
- this.clargs.options.apiKey = this.gateways[this.bridgeid].apiKey
519
- } else if (process.env.DECONZ_API_KEY != null) {
520
- this.clargs.options.apiKey = process.env.DECONZ_API_KEY
521
- }
522
- }
523
- if (this.clargs.options.apiKey == null && this.clargs.command !== 'getApiKey') {
524
- let args = ''
525
- if (
526
- this.clargs.options.host !== 'localhost' &&
527
- this.clargs.options.host !== process.env.DECONZ_HOST
528
- ) {
529
- args += ' -H ' + this.clargs.options.host
530
- }
531
- await this.fatal(
532
- 'missing API key - unlock gateway and run "deconz%s getApiKey"', args
533
- )
534
- }
535
- this.client = new Deconz.ApiClient(this.clargs.options)
536
- this.client
537
- .on('error', (error) => {
538
- if (error.request.id !== this.requestId) {
539
- if (error.request.body == null) {
540
- this.log(
541
- 'request %d: %s %s', error.request.id,
542
- error.request.method, error.request.resource
543
- )
544
- } else {
545
- this.log(
546
- 'request %d: %s %s %s', error.request.id,
547
- error.request.method, error.request.resource, error.request.body
548
- )
549
- }
550
- this.requestId = error.request.id
551
- }
552
- if (error.nonCritical) {
553
- this.warn('request %d: %s', error.request.id, error)
554
- } else {
555
- this.error('request %d: %s', error.request.id, error)
556
- }
557
- })
558
- .on('request', (request) => {
559
- if (request.body == null) {
560
- this.debug(
561
- 'request %d: %s %s', request.id, request.method, request.resource
562
- )
563
- this.vdebug(
564
- 'request %d: %s %s', request.id, request.method, request.url
565
- )
566
- } else {
567
- this.debug(
568
- 'request %d: %s %s %s', request.id,
569
- request.method, request.resource, request.body
570
- )
571
- this.vdebug(
572
- 'request %d: %s %s %s', request.id,
573
- request.method, request.url, request.body
574
- )
575
- }
576
- })
577
- .on('response', (response) => {
578
- this.vdebug(
579
- 'request %d: response: %j', response.request.id, response.body
580
- )
581
- this.debug(
582
- 'request %d: %d %s', response.request.id,
583
- response.statusCode, response.statusMessage
584
- )
585
- })
586
- return this[this.clargs.command](this.clargs.args)
587
- }
588
-
589
- // ===== GET =================================================================
590
-
591
- async get (...args) {
592
- const parser = new CommandLineParser(packageJson)
593
- const clargs = {
594
- options: {}
595
- }
596
- parser
597
- .help('h', 'help', help.get)
598
- .flag('s', 'sortKeys', () => { clargs.options.sortKeys = true })
599
- .flag('n', 'noWhiteSpace', () => {
600
- clargs.options.noWhiteSpace = true
601
- })
602
- .flag('j', 'jsonArray', () => { clargs.options.noWhiteSpace = true })
603
- .flag('u', 'joinKeys', () => { clargs.options.joinKeys = true })
604
- .flag('a', 'ascii', () => { clargs.options.ascii = true })
605
- .flag('t', 'topOnly', () => { clargs.options.topOnly = true })
606
- .flag('l', 'leavesOnly', () => { clargs.options.leavesOnly = true })
607
- .flag('k', 'keysOnly', () => { clargs.options.keysOnly = true })
608
- .flag('v', 'valuesOnly', () => { clargs.options.valuesOnly = true })
609
- .remaining((list) => {
610
- if (list.length > 1) {
611
- throw new UsageError('too many parameters')
612
- }
613
- clargs.resource = list.length === 0
614
- ? '/'
615
- : OptionParser.toPath('resource', list[0])
616
- })
617
- .parse(...args)
618
- const jsonFormatter = new JsonFormatter(clargs.options)
619
- const response = await this.client.get(clargs.resource)
620
- this.print(jsonFormatter.stringify(response))
621
- }
622
-
623
- // ===== PUT, POST, DELETE ===================================================
624
-
625
- async resourceCommand (command, ...args) {
626
- const parser = new CommandLineParser(packageJson)
627
- const clargs = {
628
- options: {}
629
- }
630
- parser
631
- .help('h', 'help', help[command])
632
- .flag('v', 'verbose', () => { clargs.options.verbose = true })
633
- .parameter('resource', (resource) => {
634
- clargs.resource = OptionParser.toPath('resource', resource)
635
- if (clargs.resource === '/') {
636
- // deCONZ will crash otherwise, see deconz-rest-plugin#2520.
637
- throw new UsageError(`/: invalid resource for ${command}`)
638
- }
639
- })
640
- .remaining((list) => {
641
- if (list.length > 1) {
642
- throw new Error('too many parameters')
643
- }
644
- if (list.length === 1) {
645
- try {
646
- clargs.body = JSON.parse(list[0])
647
- } catch (error) {
648
- throw new Error(error.message) // Covert TypeError to Error.
649
- }
650
- }
651
- })
652
- .parse(...args)
653
- const response = await this.client[command](clargs.resource, clargs.body)
654
- const jsonFormatter = new JsonFormatter()
655
- if (clargs.options.verbose || response.success == null) {
656
- this.print(jsonFormatter.stringify(response.body))
657
- return
658
- }
659
- if (command !== 'put') {
660
- if (response.success.id != null) {
661
- this.print(jsonFormatter.stringify(response.success.id))
662
- } else {
663
- this.print(jsonFormatter.stringify(response.success))
664
- }
665
- return
666
- }
667
- this.print(jsonFormatter.stringify(response.success))
668
- }
669
-
670
- async put (...args) {
671
- return this.resourceCommand('put', ...args)
672
- }
673
-
674
- async post (...args) {
675
- return this.resourceCommand('post', ...args)
676
- }
677
-
678
- async delete (...args) {
679
- return this.resourceCommand('delete', ...args)
680
- }
681
-
682
- // ===========================================================================
683
-
684
- async destroy () {
685
- if (this.wsMonitor != null) {
686
- await this.wsMonitor.close()
687
- }
688
- if (this.eventStream != null) {
689
- await this.eventStream.close()
690
- }
691
- }
692
-
693
- async eventlog (...args) {
694
- const parser = new CommandLineParser(packageJson)
695
- let mode = 'daemon'
696
- const options = {}
697
- parser
698
- .help('h', 'help', help.eventlog)
699
- .flag('n', 'noRetry', () => { options.retryTime = 0 })
700
- .flag('r', 'raw', () => { options.raw = true })
701
- .flag('s', 'service', () => { mode = 'service' })
702
- .parse(...args)
703
- this.jsonFormatter = new JsonFormatter(
704
- mode === 'service' ? { noWhiteSpace: true } : {}
705
- )
706
- const { websocketport } = await this.client.get('/config')
707
- options.host = this.client.host + ':' + websocketport
708
- this.wsMonitor = new Deconz.WsClient(options)
709
- this.setOptions({ mode })
710
- this.wsMonitor
711
- .on('error', (error) => { this.error(error) })
712
- .on('listening', (url) => { this.log('listening on %s', url) })
713
- .on('closed', (url) => { this.log('connection to %s closed', url) })
714
- .on('changed', (rtype, rid, body) => {
715
- let resource = '/' + rtype + '/' + rid
716
- if (Object.keys(body).length === 1) {
717
- if (body.capabilities != null) {
718
- resource += '/capabilities'
719
- body = body.capabilities
720
- } else if (body.config != null) {
721
- resource += '/config'
722
- body = body.config
723
- } else if (body.state != null) {
724
- resource += '/state'
725
- body = body.state
726
- }
727
- }
728
- this.log('%s: %s', resource, this.jsonFormatter.stringify(body))
729
- })
730
- .on('added', (rtype, rid, body) => {
731
- this.log(
732
- '/%s/%d: added: %s', rtype, rid, this.jsonFormatter.stringify(body)
733
- )
734
- })
735
- .on('sceneRecall', (resource) => {
736
- this.log('%s: recall', resource)
737
- })
738
- .on('notification', (body) => {
739
- this.log(this.jsonFormatter.stringify(body))
740
- })
741
- .listen()
742
- }
743
-
744
- // ===========================================================================
745
-
746
- async simpleCommand (command, ...args) {
747
- const parser = new CommandLineParser(packageJson)
748
- const clargs = {
749
- options: {}
750
- }
751
- parser
752
- .help('h', 'help', help[command])
753
- .flag('v', 'verbose', () => { clargs.options.verbose = true })
754
- .parse(...args)
755
- const response = await this.client[command]()
756
- const jsonFormatter = new JsonFormatter()
757
- for (const error of response.errors) {
758
- this.warn('api error %d: %s', error.type, error.description)
759
- }
760
- if (clargs.options.verbose || response.success == null) {
761
- this.print(jsonFormatter.stringify(response.body))
762
- return
763
- }
764
- if (response.success.id != null) {
765
- this.print(jsonFormatter.stringify(response.success.id))
766
- return
767
- }
768
- if (response.success != null) {
769
- this.print(jsonFormatter.stringify(response.success))
770
- return
771
- }
772
- this.print(jsonFormatter.stringify(response.body))
773
- }
774
-
775
- async discover (...args) {
776
- const parser = new CommandLineParser(packageJson)
777
- let stealth = false
778
- parser
779
- .help('h', 'help', help.discover)
780
- .flag('S', 'stealth', () => { stealth = true })
781
- .parse(...args)
782
- const jsonFormatter = new JsonFormatter({ sortKeys: true })
783
- const bridges = await this.deconzDiscovery.discover(stealth)
784
- this.print(jsonFormatter.stringify(bridges))
785
- }
786
-
787
- async config (...args) {
788
- const parser = new CommandLineParser(packageJson)
789
- const options = {}
790
- parser
791
- .help('h', 'help', help.config)
792
- .flag('s', 'sortKeys', () => { options.sortKeys = true })
793
- .parse(...args)
794
- const jsonFormatter = new JsonFormatter(options)
795
- const json = jsonFormatter.stringify(this.gatewayConfig)
796
- this.print(json)
797
- }
798
-
799
- async description (...args) {
800
- const parser = new CommandLineParser(packageJson)
801
- const options = {}
802
- parser
803
- .help('h', 'help', help.description)
804
- .flag('s', 'sortKeys', () => { options.sortKeys = true })
805
- .parse(...args)
806
- const response = await this.deconzDiscovery.description(this.clargs.options.host)
807
- const jsonFormatter = new JsonFormatter(options)
808
- const json = jsonFormatter.stringify(response)
809
- this.print(json)
810
- }
811
-
812
- async getApiKey (...args) {
813
- const parser = new CommandLineParser(packageJson)
814
- const jsonFormatter = new JsonFormatter(
815
- { noWhiteSpace: true, sortKeys: true }
816
- )
817
- parser
818
- .help('h', 'help', help.getApiKey)
819
- .parse(...args)
820
- const apiKey = await this.client.getApiKey('deconz')
821
- this.print(jsonFormatter.stringify(apiKey))
822
- this.gateways[this.bridgeid] = { apiKey }
823
- if (this.client.fingerprint != null) {
824
- this.gateways[this.bridgeid].fingerprint = this.client.fingerprint
825
- }
826
- this.writeGateways()
827
- }
828
-
829
- async unlock (...args) {
830
- return this.simpleCommand('unlock', ...args)
831
- }
832
-
833
- async search (...args) {
834
- return this.simpleCommand('search', ...args)
835
- }
836
-
837
- // ===== LIGHTVALUES =========================================================
838
-
839
- async probe (...args) {
840
- const parser = new CommandLineParser(packageJson)
841
- const clargs = {
842
- maxCount: 60
843
- }
844
- parser
845
- .help('h', 'help', help.probe)
846
- .flag('v', 'verbose', () => { clargs.verbose = true })
847
- .option('t', 'timeout', (value) => {
848
- OptionParser.toInt('timeout', value, 1, 10, true)
849
- clargs.maxCount = value * 12
850
- })
851
- .parameter('light', (value) => {
852
- if (value.substring(0, 8) !== '/lights/') {
853
- throw new UsageError(`${value}: invalid light`)
854
- }
855
- clargs.light = value
856
- })
857
- .parse(...args)
858
- const light = await this.client.get(clargs.light)
859
-
860
- async function probeCt (name, value) {
861
- clargs.verbose && this.log(`${clargs.light}: ${name} ...\\c`)
862
- await this.client.put(clargs.light + '/state', { ct: value })
863
- let count = 0
864
- return new Promise((resolve, reject) => {
865
- const interval = setInterval(async () => {
866
- const ct = await this.client.get(clargs.light + '/state/ct')
867
- if (ct !== value || ++count > clargs.maxCount) {
868
- clearInterval(interval)
869
- clargs.verbose && this.logc(
870
- count > clargs.maxCount ? ' timeout' : ' done'
871
- )
872
- return resolve(ct)
873
- }
874
- clargs.verbose && this.logc('.\\c')
875
- }, 5000)
876
- })
877
- }
878
-
879
- function round (f) {
880
- return Math.round(f * 10000) / 10000
881
- }
882
-
883
- async function probeXy (name, value) {
884
- clargs.verbose && this.log(`${clargs.light}: ${name} ...\\c`)
885
- await this.client.put(clargs.light + '/state', { xy: value })
886
- let count = 0
887
- return new Promise((resolve, reject) => {
888
- const interval = setInterval(async () => {
889
- let xy = await this.client.get(clargs.light + '/state/xy')
890
- if (this.client.isDeconz) {
891
- xy = [round(xy[0]), round(xy[1])]
892
- }
893
- if (
894
- xy[0] !== value[0] || xy[1] !== value[1] ||
895
- ++count > clargs.maxCount
896
- ) {
897
- clearInterval(interval)
898
- clargs.verbose && this.logc(
899
- count > clargs.maxCount ? ' timeout' : ' done'
900
- )
901
- return resolve(xy)
902
- }
903
- clargs.verbose && this.logc('.\\c')
904
- }, 5000)
905
- })
906
- }
907
-
908
- this.verbose && this.log(
909
- '%s: %s %s %s "%s"', clargs.light, light.manufacturername,
910
- light.modelid, light.type, light.name
911
- )
912
- const response = {
913
- manufacturername: light.manufacturername,
914
- modelid: light.modelid,
915
- type: light.type,
916
- bri: light.state.bri != null
917
- }
918
- await this.client.put(clargs.light + '/state', { on: true })
919
- if (light.state.ct != null) {
920
- response.ct = {}
921
- response.ct.min = await probeCt.call(this, 'cool', 1)
922
- response.ct.max = await probeCt.call(this, 'warm', 1000)
923
- }
924
- if (light.state.xy != null) {
925
- const zero = 0.0001
926
- const one = 0.9961
927
- response.xy = {}
928
- response.xy.r = await probeXy.call(this, 'red', [one, zero])
929
- response.xy.g = await probeXy.call(this, 'green', [zero, one])
930
- response.xy.b = await probeXy.call(this, 'blue', [zero, zero])
931
- }
932
- await this.client.put(clargs.light + '/state', { on: light.state.on })
933
- this.jsonFormatter = new JsonFormatter()
934
- const json = this.jsonFormatter.stringify(response)
935
- this.print(json)
936
- }
937
-
938
- // ===== BRIDGE/GATEWAY DISCOVERY ==============================================
939
-
940
- async restart (...args) {
941
- const parser = new CommandLineParser(packageJson)
942
- const clargs = {}
943
- parser
944
- .help('h', 'help', help.restart)
945
- .flag('v', 'verbose', () => { clargs.verbose = true })
946
- .parse(...args)
947
- if (this.client.isHue) {
948
- const response = await this.client.put('/config', { reboot: true })
949
- if (!response.success.reboot) {
950
- return false
951
- }
952
- } else if (this.client.isDeconz) {
953
- const response = await this.client.post('/config/restartapp')
954
- if (!response.success.restartapp) {
955
- return false
956
- }
957
- } else {
958
- await this.fatal('restart: only supported for Hue bridge or deCONZ gateway')
959
- }
960
- clargs.verbose && this.log('restarting ...\\c')
961
- return new Promise((resolve, reject) => {
962
- let busy = false
963
- const interval = setInterval(async () => {
964
- try {
965
- if (!busy) {
966
- busy = true
967
- const bridgeid = await this.client.get('/config/bridgeid')
968
- if (bridgeid === this.bridgeid) {
969
- clearInterval(interval)
970
- clargs.verbose && this.logc(' done')
971
- return resolve(true)
972
- }
973
- busy = false
974
- }
975
- } catch (error) {
976
- busy = false
977
- }
978
- clargs.verbose && this.logc('.\\c')
979
- }, 2500)
980
- })
981
- }
982
- }
983
-
984
- new Main().main()
13
+ new DeconzTool(pkgJson).main()