homebridge-deconz 0.1.17 → 0.1.18
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 +4 -975
- package/homebridge-ui/server.js +2 -2
- package/lib/Deconz/Resource.js +2 -2
- package/lib/Deconz/index.js +0 -5
- package/lib/DeconzAccessory/Gateway.js +4 -4
- package/lib/DeconzAccessory/index.js +3 -3
- package/lib/DeconzPlatform.js +2 -2
- package/lib/DeconzService/AirQuality.js +2 -2
- package/lib/DeconzService/Battery.js +2 -2
- package/lib/DeconzService/Consumption.js +2 -2
- package/lib/DeconzService/Daylight.js +2 -2
- package/lib/DeconzService/LightLevel.js +2 -2
- package/lib/DeconzService/LightsResource.js +2 -2
- package/lib/DeconzService/Power.js +2 -2
- package/lib/DeconzService/Schedule.js +2 -2
- package/lib/DeconzService/SensorsResource.js +2 -2
- package/lib/DeconzService/index.js +2 -2
- package/package.json +2 -1
- package/lib/Deconz/ApiClient.js +0 -442
- package/lib/Deconz/ApiError.js +0 -49
- package/lib/Deconz/ApiResponse.js +0 -57
- package/lib/Deconz/Discovery.js +0 -238
- package/lib/Deconz/WsClient.js +0 -205
package/cli/deconz.js
CHANGED
@@ -1,984 +1,13 @@
|
|
1
1
|
#!/usr/bin/env node
|
2
2
|
|
3
|
-
//
|
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
|
11
|
-
const
|
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
|
-
|
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()
|