homebridge-deconz 0.0.13 → 0.0.16

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/README.md CHANGED
@@ -1,11 +1,13 @@
1
1
  <p align="center">
2
- <img src="homebridge-deconz.png" height="200px">
3
- </p><span align="center">
2
+ <img src="homebridge-ui/public/homebridge-deconz.png" height="200px">
3
+ </p>
4
+ <span align="center">
4
5
 
5
6
  # Homebridge deCONZ
6
7
  [![Downloads](https://img.shields.io/npm/dt/homebridge-deconz)](https://www.npmjs.com/package/homebridge-deconz)
7
8
  [![Version](https://img.shields.io/npm/v/homebridge-deconz)](https://www.npmjs.com/package/homebridge-deconz)
8
- [![Homebridge Discord](https://img.shields.io/discord/432663330281226270?color=728ED5&logo=discord&label=discord)](https://discord.gg/hZubhrz)
9
+ [![Homebridge Discord](https://img.shields.io/discord/432663330281226270?color=728ED5&logo=discord&label=discord)](https://discord.gg/zUhSZSNb4P)
10
+ [![verified-by-homebridge](https://badgen.net/badge/homebridge/verified/purple)](https://github.com/homebridge/homebridge/wiki/Verified-Plugins)
9
11
 
10
12
  [![GitHub issues](https://img.shields.io/github/issues/ebaauw/homebridge-deconz)](https://github.com/ebaauw/homebridge-deconz/issues)
11
13
  [![GitHub pull requests](https://img.shields.io/github/issues-pr/ebaauw/homebridge-deconz)](https://github.com/ebaauw/homebridge-deconz/pulls)
@@ -22,7 +24,7 @@ See [Future Development of Homebridge Hue](https://github.com/ebaauw/homebridge-
22
24
  Homebridge deCONZ is still under development.
23
25
  See [Releases](https://github.com/ebaauw/homebridge-deconz/releases) for more details.
24
26
 
25
- If you have a question, please post a message to the **#hue** channel of the Homebridge community on [Discord](https://discord.gg/hZubhrz).
27
+ If you have a question, please post a message to the **#deconz** channel of the Homebridge community on [Discord](https://discord.gg/zUhSZSNb4P).
26
28
 
27
29
  ### Introduction
28
30
  This [Homebridge](https://github.com/homebridge/homebridge) plugin exposes to Apple's [HomeKit](http://www.apple.com/ios/home/) ZigBee devices (lights, plugs, sensors, switches, ...) and virtual devices on a deCONZ gateway by dresden elektronik.
package/cli/deconz.js CHANGED
@@ -703,7 +703,7 @@ class Main extends homebridgeLib.CommandLineTool {
703
703
  const { websocketport } = await this.client.get('/config')
704
704
  options.host = this.client.host + ':' + websocketport
705
705
  this.wsMonitor = new Deconz.WsClient(options)
706
- this.setOptions({ mode: mode })
706
+ this.setOptions({ mode })
707
707
  this.wsMonitor
708
708
  .on('error', (error) => { this.error(error) })
709
709
  .on('listening', (url) => { this.log('listening on %s', url) })
@@ -813,7 +813,7 @@ class Main extends homebridgeLib.CommandLineTool {
813
813
  .parse(...args)
814
814
  const apiKey = await this.client.getApiKey('deconz')
815
815
  this.print(jsonFormatter.stringify(apiKey))
816
- this.gateways[this.bridgeid] = { apiKey: apiKey }
816
+ this.gateways[this.bridgeid] = { apiKey }
817
817
  if (this.client.fingerprint != null) {
818
818
  this.gateways[this.bridgeid].fingerprint = this.client.fingerprint
819
819
  }
@@ -2,7 +2,7 @@
2
2
  "pluginAlias": "deCONZ",
3
3
  "pluginType": "platform",
4
4
  "singular": true,
5
- "customUi": true,
5
+ "customUi": false,
6
6
  "headerDisplay": "Homebridge plugin for deCONZ",
7
7
  "footerDisplay": "For a detailed description, see the [wiki](https://github.com/ebaauw/homebridge-deconz/wiki/Configuration).",
8
8
  "schema": {
@@ -77,7 +77,7 @@
77
77
  }
78
78
  }
79
79
  },
80
- "form": [
80
+ "layout": [
81
81
  "name",
82
82
  {
83
83
  "key": "hosts",
@@ -6,19 +6,499 @@ Copyright © 2022 Erik Baauw. All rights reserved.
6
6
  -->
7
7
 
8
8
  <link rel="stylesheet" href="style.css">
9
+ <p align="center">
10
+ <a href="https://github.com/ebaauw/homebridge-deconz/wiki/Configuration" target="_blank">
11
+ <img src="homebridge-deconz.png" height="200px">
12
+ </a>
13
+ </p>
9
14
 
10
15
  <script>
11
- (async () => {
12
- try {
16
+
17
+ async function showFormPluginConfig () {
18
+ homebridge.showSpinner()
19
+ const pluginConfig = await homebridge.getPluginConfig()
20
+ console.log('pluginConfig: %o', pluginConfig)
21
+ // const pluginConfigSchema = await homebridge.getPluginConfigSchema()
22
+ // console.log('pluginConfigSchema: %o', pluginConfigSchema)
23
+ // const cachedAccessories = await homebridge.getCachedAccessories()
24
+ // console.log('cachedAccessories: %o', cachedAccessories)
25
+ // const discoveredGateways = await homebridge.request('discover')
26
+ // console.log('discovered gateways: %o', discoveredGateways)
27
+ for (const config of pluginConfig) {
28
+ if (config._bridge != null) {
29
+ const cachedAccessories = await homebridge.request('cachedAccessories', {
30
+ username: config._bridge.username
31
+ })
32
+ console.log('%s: cachedAccessories: %o', config.name, cachedAccessories)
33
+ const cachedGateways = cachedAccessories.filter((accessory) => {
34
+ return accessory.plugin === 'homebridge-deconz' &&
35
+ accessory.context != null &&
36
+ accessory.context.className === 'Gateway'
37
+ })
38
+ const result = {}
39
+ for (const gateway of cachedGateways) {
40
+ if (gateway.context.uiPort == null) {
41
+ continue
42
+ }
43
+ const pong = await homebridge.request(
44
+ 'get', { uiPort: gateway.context.uiPort, path: '/ping' }
45
+ )
46
+ if (pong === 'pong') {
47
+ result[gateway.context.host] = gateway.context
48
+ }
49
+ }
50
+ const gateways = Object.keys(result).sort()
51
+ console.log('%s: gateways: %j',config.name, gateways)
52
+ }
53
+ }
54
+ homebridge.hideSpinner()
55
+
56
+ const form = homebridge.createForm(
57
+ {
58
+ schema: {
59
+ type: 'object',
60
+ properties: {
61
+ config: {
62
+ title: 'Gateways',
63
+ description: 'Configure a child bridge per deCONZ gateway. See <a href="https://github.com/ebaauw/homebridge-deconz/wiki/Configuration" target="_blank">wiki</a> for details.',
64
+ type: 'array',
65
+ disabled: true,
66
+ items: {
67
+ type: 'object',
68
+ properties: {
69
+ host: {
70
+ description: 'Gateway hostname and port.',
71
+ default: 'localhost:80',
72
+ type: 'string',
73
+ required: true
74
+ },
75
+ name: {
76
+ description: 'Homebridge log plugin name.',
77
+ default: 'deCONZ',
78
+ type: 'string'
79
+ },
80
+ _bridge: {
81
+ type: 'object',
82
+ required: true,
83
+ properties: {
84
+ name: {
85
+ type: 'string',
86
+ required: true
87
+ },
88
+ username: {
89
+ type: 'string',
90
+ pattern: '^([A-F0-9]{2}:){5}[A-F0-9]{2}$',
91
+ placeholder: 'AA:BB:CC:DD:EE:FF',
92
+ required: true
93
+ },
94
+ port: {
95
+ type: 'integer',
96
+ minimum: 1025,
97
+ // maximum: 65535,
98
+ required: true
99
+ },
100
+ manufacturer: {
101
+ type: 'string',
102
+ enabled: false,
103
+ required: true
104
+ },
105
+ model: {
106
+ type: 'string',
107
+ required: true
108
+ }
109
+ }
110
+ }
111
+ }
112
+ }
113
+ }
114
+ }
115
+ },
116
+ // layout: null
117
+ layout: [
118
+ {
119
+ type: 'tabarray',
120
+ title: '{{ value.name }}',
121
+ items: [
122
+ {
123
+ type: 'fieldset',
124
+ title: 'Gateway Settings',
125
+ key: 'config[]',
126
+ items: [
127
+ {
128
+ type: 'flex',
129
+ 'flex-flow': 'row',
130
+ items: [
131
+ 'config[].host',
132
+ 'config[].name',
133
+ ]
134
+ }
135
+ ]
136
+ },
137
+ {
138
+ type: 'flex',
139
+ 'flex-flow': 'row',
140
+ key: 'config[]',
141
+ items: [
142
+ {
143
+ type: 'button',
144
+ title: 'Connect',
145
+ key: 'config[].connect'
146
+ },
147
+ {
148
+ type: 'button',
149
+ title: 'Get API Key',
150
+ key: 'config[].getApiKey'
151
+ },
152
+ {
153
+ type: 'submit',
154
+ title: 'Configure',
155
+ key: 'config[].configure'
156
+ }
157
+ ]
158
+ },
159
+ {
160
+ type: 'fieldset',
161
+ key: 'config[]._bridge',
162
+ // expandable: true,
163
+ title: 'Child Bridge Accessory Settings',
164
+ items: [
165
+ {
166
+ type: 'flex',
167
+ 'flex-flow': 'row',
168
+ items: [
169
+ 'config[]._bridge.username',
170
+ 'config[]._bridge.port'
171
+ ]
172
+ },
173
+ 'config[]._bridge.name',
174
+ {
175
+ type: 'flex',
176
+ 'flex-flow': 'row',
177
+ items: [
178
+ 'config[]._bridge.manufacturer',
179
+ 'config[]._bridge.model'
180
+ ]
181
+ }
182
+ ]
183
+ }
184
+ ]
185
+ }
186
+ ]
187
+ }, {
188
+ config: pluginConfig,
189
+ },
190
+ 'Gateway Settings',
191
+ 'Homebridge Settings'
192
+ )
193
+ form.onChange(async (form) => {
194
+ console.log('change: %o', form)
195
+ })
196
+ form.onSubmit(async (form) => {
197
+ console.log('submit: %o', form)
198
+ })
199
+ form.onCancel(async (form) => {
200
+ console.log('cancel: %o', form)
201
+ })
202
+
203
+ }
204
+
205
+ async function showFormGateways (gateway) {
206
+ homebridge.showSpinner()
207
+ const cachedAccessories = await homebridge.getCachedAccessories()
208
+ const cachedGateways = cachedAccessories.filter((accessory) => {
209
+ return accessory.plugin === 'homebridge-deconz' &&
210
+ accessory.context != null &&
211
+ accessory.context.className === 'Gateway'
212
+ })
213
+ const result = {}
214
+ for (const gateway of cachedGateways) {
215
+ if (gateway.context.uiPort == null) {
216
+ continue
217
+ }
218
+ const pong = await homebridge.request(
219
+ 'get', { uiPort: gateway.context.uiPort, path: '/ping' }
220
+ )
221
+ if (pong === 'pong') {
222
+ result[gateway.context.host] = gateway.context
223
+ }
224
+ }
225
+ const gateways = Object.keys(result).sort()
226
+ homebridge.hideSpinner()
227
+ if (gateways.length === 0) {
13
228
  homebridge.showSchemaForm()
229
+ return
230
+ }
231
+ // const form = homebridge.createForm({
232
+ // schema: {
233
+ // type: 'object',
234
+ // properties: {
235
+ // gateway: {
236
+ // title: 'Connected Gateways',
237
+ // type: 'string',
238
+ // oneOf: gateways.map((name) => {
239
+ // const config = result[name].context.config
240
+ // return {
241
+ // title: `${name}: dresden elektronik ${config.modelid} gateway v${config.swversion} / ${config.devicename} ${config.bridgeid}`,
242
+ // enum: [name]
243
+ // }
244
+ // }),
245
+ // required: true
246
+ // }
247
+ // }
248
+ // },
249
+ // layout: null,
250
+ // form: null
251
+ // }, {
252
+ // gateway: gateway != null ? gateway : gateways[0]
253
+ // }, 'Gateway Settings', 'Homebridge Settings')
254
+ const form = homebridge.createForm({
255
+ footerDisplay: 'For a detailed description, see the [wiki](https://github.com/ebaauw/homebridge-deconz/wiki/Configuration).',
256
+ schema: {
257
+ type: 'object',
258
+ properties: {
259
+ name: {
260
+ description: 'Plugin name as displayed in the Homebridge log.',
261
+ type: 'string',
262
+ required: true,
263
+ default: 'deCONZ'
264
+ },
265
+ gateways: {
266
+ title: 'Gateways',
267
+ type: 'array',
268
+ disabled: true,
269
+ items: {
270
+ type: 'object',
271
+ properties: {
272
+ host: {
273
+ description: 'Hostname and port of the deCONZ gateway.',
274
+ type: 'string'
275
+ },
276
+ expose: {
277
+ description: 'Expose gateway to HomeKit.',
278
+ type: 'boolean'
279
+ }
280
+ }
281
+ }
282
+
283
+ }
284
+ }
285
+ } //,
286
+ // layout: [
287
+ // 'name',
288
+ // {
289
+ // key: 'gateways',
290
+ // type: 'array',
291
+ // buttonText: 'Add Gateway',
292
+ // items: [
293
+ // {
294
+ // type: 'section',
295
+ // htmlClass: 'row',
296
+ // items: [
297
+ // {
298
+ // type: 'section',
299
+ // htmlClass: 'col',
300
+ // items: [
301
+ // 'gateways[].host'
302
+ // ]
303
+ // },
304
+ // {
305
+ // type: 'section',
306
+ // htmlClass: 'col',
307
+ // items: [
308
+ // {
309
+ // key: 'gateways[].expose',
310
+ // disabled: true
311
+ // }
312
+ // ]
313
+ // }
314
+ // ]
315
+ // }
316
+ // ]
317
+ // }
318
+ // ]
319
+ }, {
14
320
 
15
- // const pluginConfig = await homebridge.getPluginConfig()
16
- // const pluginConfigSchema = await homebridge.getPluginConfigSchema()
17
- const cachedAccessories = await homebridge.getCachedAccessories()
321
+ }, 'Gateway Settings', 'Homebridge Settings')
322
+ form.onChange(async (form) => {
323
+ // showFormGatewaySettings(result[form.gateway])
324
+ })
325
+ form.onSubmit(async (form) => {
326
+ await showFormGatewaySettings(result[form.gateways])
327
+ })
328
+ form.onCancel(() => { homebridge.showSchemaForm() })
329
+ }
330
+
331
+ async function showFormGatewaySettings (gateway, device) {
332
+ homebridge.showSpinner()
333
+ const data = await homebridge.request(
334
+ 'get', {
335
+ uiPort: gateway.uiPort,
336
+ path: '/gateways/' + gateway.id
337
+ }
338
+ )
339
+ const values = {}
340
+ for (const rtype in data.deviceByRidByRtype) {
341
+ values[rtype] = []
342
+ for (const rid in data.deviceByRidByRtype[rtype]) {
343
+ const device = data.deviceByRidByRtype[rtype][rid]
344
+ values[rtype].push({
345
+ title: ['', rtype, rid].join('/') + ': ' +
346
+ device.resourceBySubtype[device.primary].body.name,
347
+ enum: [device.id]
348
+ })
349
+ }
350
+ }
351
+ data.lightsDevice = values.lights[0].enum[0]
352
+ data.sensorsDevice = values.sensors[0].enum[0]
353
+ data.groupsDevice = values.groups[0].enum[0]
354
+ homebridge.hideSpinner()
355
+ const form = homebridge.createForm({
356
+ schema: {
357
+ type: 'object',
358
+ properties: {
359
+ expose: {
360
+ title: 'Expose',
361
+ type: 'boolean'
362
+ },
363
+ lights: {
364
+ title: 'Lights',
365
+ type: 'boolean',
366
+ },
367
+ sensors: {
368
+ title: 'Sensors',
369
+ type: 'boolean',
370
+ },
371
+ groups: {
372
+ title: 'Groups',
373
+ type: 'boolean',
374
+ },
375
+ schedules: {
376
+ title: 'Schedules',
377
+ type: 'boolean',
378
+ },
379
+ logLevel: {
380
+ title: 'Log Level',
381
+ type: 'string',
382
+ oneOf: ['0', '1', '2', '3'].map((level) => { return { title: level, enum: [level] } }),
383
+ required: true,
384
+ condition: {
385
+ functionBody: 'return model.expose'
386
+ }
387
+ },
388
+ lightsDevice: {
389
+ title: 'Device',
390
+ type: 'string',
391
+ oneOf: values.lights,
392
+ required: true
393
+ },
394
+ sensorsDevice: {
395
+ title: 'Device',
396
+ type: 'string',
397
+ oneOf: values.sensors,
398
+ required: true
399
+ },
400
+ groupsDevice: {
401
+ title: 'Device',
402
+ type: 'string',
403
+ oneOf: values.groups,
404
+ required: true
405
+ }
406
+ }
407
+ },
408
+ layout: [
409
+ {
410
+ type: 'fieldset',
411
+ title: `${gateway.context.host} Gateway Settings`
412
+ },
413
+ 'expose',
414
+ 'logLevel',
415
+ {
416
+ type: 'flex',
417
+ 'flex-flow': 'row',
418
+ title: 'Automatically Expose New',
419
+ items: [
420
+ 'lights',
421
+ 'sensors',
422
+ 'groups',
423
+ 'schedules'
424
+ ],
425
+ condition: {
426
+ functionBody: 'return model.expose'
427
+ }
428
+ },
429
+ {
430
+ type: 'fieldset',
431
+ items: [
432
+ {
433
+ type: 'tabs',
434
+ tabs: [
435
+ {
436
+ title: 'Lights',
437
+ items: [
438
+ 'lightsDevice'
439
+ ]
440
+ },
441
+ {
442
+ title: 'Sensors',
443
+ items: [
444
+ 'sensorsDevice'
445
+ ]
446
+ },
447
+ {
448
+ title: 'Groups',
449
+ items: [
450
+ 'groupsDevice'
451
+ ]
452
+ }
453
+ ]
454
+ }
455
+ ],
456
+ condition: {
457
+ functionBody: 'return model.expose'
458
+ }
459
+ }
460
+ ]
461
+ }, data, 'Device Settings', 'Done')
462
+ form.onChange((form) => {})
463
+ form.onSubmit((form) => {
464
+ showFormDeviceSettings(gateway, form.lightsDevice)
465
+ })
466
+ form.onCancel((form) => {
467
+ showFormGateways(gateway.context.host)
468
+ })
469
+ }
18
470
 
19
- const result = await homebridge.request('/cachedAccessories', cachedAccessories)
20
- const nGateways = Object.keys(result).length
21
- homebridge.toast.success(`${nGateways} gateways`)
471
+ async function showFormDeviceSettings (gateway, device) {
472
+ homebridge.showSpinner()
473
+ homebridge.hideSpinner()
474
+ const form = homebridge.createForm({
475
+ schema: {
476
+ type: 'object',
477
+ properties: {
478
+ gateway: {
479
+ type: 'string'
480
+ },
481
+ device: {
482
+ type: 'string'
483
+ }
484
+ }
485
+ }
486
+ }, {
487
+ gateway: gateway.context.host,
488
+ device: device
489
+ }, 'OK', 'Cancel')
490
+ form.onChange((form) => {})
491
+ form.onSubmit((form) => {
492
+ showFormGatewaySettings(gateway)
493
+ })
494
+ form.onCancel((form) => {
495
+ showFormGatewaySettings(gateway)
496
+ })
497
+ }
498
+
499
+ (async () => {
500
+ try {
501
+ await showFormPluginConfig()
22
502
  } catch (error) {
23
503
  console.error(error)
24
504
  }
@@ -5,39 +5,54 @@
5
5
 
6
6
  'use strict'
7
7
 
8
- const {
9
- HomebridgePluginUiServer, RequestError
10
- } = require('@homebridge/plugin-ui-utils')
8
+ const { UiServer } = require('homebridge-lib')
9
+ const Deconz = require('../lib/Deconz')
11
10
 
12
- class UiServer extends HomebridgePluginUiServer {
11
+ class DeconzUiServer extends UiServer {
13
12
  constructor () {
14
13
  super()
15
14
 
16
- this.onRequest('/cachedAccessories', async (cachedAccessories) => {
17
- try {
18
- // console.log('%d accessories', cachedAccessories.length)
19
- const gateways = cachedAccessories.filter((accessory) => {
20
- return accessory.plugin === 'homebridge-deconz' &&
21
- accessory.context != null &&
22
- accessory.context.className === 'Gateway'
15
+ this.onRequest('discover', async (params) => {
16
+ if (this.discovery == null) {
17
+ this.discovery = new Deconz.Discovery({
18
+ // forceHttp: this.config.forceHttp,
19
+ // timeout: this.config.timeout
23
20
  })
24
- // console.log('%d gateways', gateways.length)
25
- const result = {}
26
- for (const gateway of gateways) {
27
- const { host, apiKey } = gateway.context
28
- if (apiKey != null) {
29
- result[host] = apiKey
30
- }
31
- }
32
- console.log('%d gateways: %j', Object.keys(result).length, result)
33
- return result
34
- } catch (error) {
35
- throw new RequestError(error)
21
+ this.discovery
22
+ .on('error', (error) => {
23
+ this.log(
24
+ '%s: request %d: %s %s', error.request.name,
25
+ error.request.id, error.request.method, error.request.resource
26
+ )
27
+ this.warn(
28
+ '%s: request %d: %s', error.request.name, error.request.id, error
29
+ )
30
+ })
31
+ .on('request', (request) => {
32
+ this.debug(
33
+ '%s: request %d: %s %s', request.name,
34
+ request.id, request.method, request.resource
35
+ )
36
+ })
37
+ .on('response', (response) => {
38
+ this.debug(
39
+ '%s: request %d: %d %s', response.request.name,
40
+ response.request.id, response.statusCode, response.statusMessage
41
+ )
42
+ })
43
+ .on('found', (name, id, address) => {
44
+ this.debug('%s: found %s at %s', name, id, address)
45
+ })
46
+ .on('searching', (host) => {
47
+ this.debug('upnp: listening on %s', host)
48
+ })
49
+ .on('searchDone', () => { this.debug('upnp: search done') })
36
50
  }
51
+ const configs = await this.discovery.discover()
52
+ return configs
37
53
  })
38
-
39
54
  this.ready()
40
55
  }
41
56
  }
42
57
 
43
- new UiServer() // eslint-disable-line no-new
58
+ new DeconzUiServer() // eslint-disable-line no-new
@@ -149,7 +149,7 @@ class ApiClient extends homebridgeLib.HttpClient {
149
149
  maxSockets: _options.maxSockets,
150
150
  path: '/api',
151
151
  timeout: _options.timeout,
152
- validStatusCodes: [200, 400, 403, 404]
152
+ validStatusCodes: [200, 400, 403] //, 404]
153
153
  }
154
154
  if (_options.phoscon) {
155
155
  // options.headers = { Accept: 'application/vnd.ddel.v1' }
@@ -44,7 +44,7 @@ class Discovery extends events.EventEmitter {
44
44
  */
45
45
  async config (host) {
46
46
  const client = new homebridgeLib.HttpClient({
47
- host: host,
47
+ host,
48
48
  json: true,
49
49
  path: '/api',
50
50
  timeout: this._options.timeout
@@ -96,7 +96,7 @@ class Discovery extends events.EventEmitter {
96
96
  */
97
97
  async description (host) {
98
98
  const options = {
99
- host: host,
99
+ host,
100
100
  timeout: this._options.timeout
101
101
  }
102
102
  const client = new homebridgeLib.HttpClient(options)