homebridge-deconz 1.2.11 → 1.3.0

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.
@@ -14,10 +14,6 @@
14
14
  "required": true,
15
15
  "default": "deCONZ"
16
16
  },
17
- "forceHttp": {
18
- "description": "Use plain http instead of https.",
19
- "type": "boolean"
20
- },
21
17
  "hosts": {
22
18
  "title": "Gateways",
23
19
  "type": "array",
@@ -94,7 +90,6 @@
94
90
  "title": "Advanced Settings",
95
91
  "description": "Don't change these, unless you understand what you're doing.",
96
92
  "items": [
97
- "forceHttp",
98
93
  "noResponse",
99
94
  "parallelRequests",
100
95
  "stealth",
@@ -82,10 +82,6 @@ Copyright © 2022-2026 Erik Baauw. All rights reserved.
82
82
  default: 'deCONZ',
83
83
  type: 'string'
84
84
  },
85
- forceHttp: {
86
- description: "Use plain http instead of https.",
87
- type: "boolean"
88
- },
89
85
  noResponse: {
90
86
  description: "Report unreachable lights as <i>No Response</i> in HomeKit.",
91
87
  type: "boolean"
@@ -147,7 +143,6 @@ Copyright © 2022-2026 Erik Baauw. All rights reserved.
147
143
  title: "Advanced Settings",
148
144
  description: "Don't change these, unless you understand what you're doing.",
149
145
  items: [
150
- "forceHttp",
151
146
  "parallelRequests",
152
147
  "stealth",
153
148
  "timeout",
@@ -13,7 +13,6 @@ class DeconzUiServer extends UiServer {
13
13
  this.onRequest('discover', async (params) => {
14
14
  if (this.discovery == null) {
15
15
  this.discovery = new Discovery({
16
- // forceHttp: this.config.forceHttp,
17
16
  // timeout: this.config.timeout
18
17
  })
19
18
  this.discovery
@@ -344,7 +344,7 @@ class Resource {
344
344
  case 'Battery': return 'Battery'
345
345
  case 'CarbonMonoxide': return 'CarbonMonoxide'
346
346
  case 'Consumption': return 'Consumption'
347
- // case 'DoorLock': return null
347
+ case 'DoorLock': return 'DoorLock'
348
348
  case 'Daylight': return 'Daylight'
349
349
  case 'DaylightOffset': return ''
350
350
  case 'Fire': return 'Smoke'
@@ -78,12 +78,6 @@ class Gateway extends AccessoryDelegate {
78
78
  if (this.context.settingsById == null) {
79
79
  this.context.settingsById = {}
80
80
  }
81
- // if (this.context.fullState != null) {
82
- // this.analyseFullState(this.context.fullState, {
83
- // analyseOnly: true,
84
- // logUnsupported: true
85
- // })
86
- // }
87
81
 
88
82
  this.addPropertyDelegate({
89
83
  key: 'apiKey',
@@ -127,6 +121,11 @@ class Gateway extends AccessoryDelegate {
127
121
  this.pollNext = true
128
122
  })
129
123
 
124
+ this.addPropertyDelegate({
125
+ key: 'fingerprint',
126
+ silent: true
127
+ })
128
+
130
129
  this.addPropertyDelegate({
131
130
  key: 'heartrate',
132
131
  value: 30,
@@ -137,16 +136,15 @@ class Gateway extends AccessoryDelegate {
137
136
  key: 'host',
138
137
  value: params.host,
139
138
  silent: true
140
- }).on('didSet', (value) => {
141
- if (this.client != null) {
142
- this.client.host = value
143
- }
144
- if (this.wsClient != null) {
145
- this.wsClient.host = this.values.host.split(':')[0] +
146
- ':' + this.values.wsPort
147
- }
148
139
  })
149
140
 
141
+ this.addPropertyDelegate({
142
+ key: 'https',
143
+ value: params.config.https,
144
+ silent: true
145
+ })
146
+ this.values.https = params.config.https
147
+
150
148
  this.addPropertyDelegate({
151
149
  key: 'periodicEvents',
152
150
  value: false,
@@ -196,16 +194,32 @@ class Gateway extends AccessoryDelegate {
196
194
  }
197
195
  })
198
196
 
197
+ this.addPropertyDelegate({
198
+ key: 'url',
199
+ silent: true
200
+ }).on('didSet', async (value) => {
201
+ if (value != null) {
202
+ this.log('gateway %s at %s', this.id, value)
203
+ try {
204
+ await this.createClient()
205
+ this.values.wsPort = null
206
+ } catch (error) { this.warn(error) }
207
+ }
208
+ })
209
+ this.values.url = null
210
+
199
211
  this.addPropertyDelegate({
200
212
  key: 'wsPort',
201
- value: 443,
213
+ value: null,
202
214
  silent: true
203
- }).on('didSet', (value) => {
204
- if (this.wsClient != null) {
205
- this.wsClient.host = this.values.host.split(':')[0] +
206
- ':' + this.values.wsPort
215
+ }).on('didSet', async (value) => {
216
+ if (value != null) {
217
+ try {
218
+ await this.createWsClient()
219
+ } catch (error) { this.warn(error) }
207
220
  }
208
221
  })
222
+ this.values.wsPort = null
209
223
 
210
224
  this.log(
211
225
  '%s %s gateway v%s', this.values.manufacturer, this.values.model,
@@ -256,12 +270,15 @@ class Gateway extends AccessoryDelegate {
256
270
  */
257
271
  this.scheduleServicesByRid = {}
258
272
 
259
- this.createClient()
260
- this.createWsClient()
261
273
  this.heartbeatEnabled = true
262
274
  this
263
275
  .on('identify', this.identify)
264
- .once('heartbeat', (beat) => { this.initialBeat = beat })
276
+ .once('heartbeat', async (beat) => {
277
+ this.initialBeat = beat
278
+ try {
279
+ await this.createClient()
280
+ } catch (error) { this.warn(error) }
281
+ })
265
282
  .on('heartbeat', this.heartbeat)
266
283
  .on('shutdown', this.shutdown)
267
284
  }
@@ -330,9 +347,11 @@ class Gateway extends AccessoryDelegate {
330
347
  */
331
348
  async found (host, config) {
332
349
  try {
333
- this.values.host = host
334
350
  this.context.config = config
351
+ this.values.host = host
352
+ this.values.https = config.https
335
353
  this.values.software = config.swversion
354
+ this.values.url = 'http' + (config.https ? 's' : '') + '://' + host
336
355
  if (!this.initialised) {
337
356
  this.debug('initialising...')
338
357
  await this.connect()
@@ -344,7 +363,7 @@ class Gateway extends AccessoryDelegate {
344
363
 
345
364
  async shutdown () {
346
365
  this.service.values.statusActive = false
347
- return this.wsClient.close()
366
+ return this.wsClient?.close()
348
367
  }
349
368
 
350
369
  /** Called every second.
@@ -372,7 +391,9 @@ class Gateway extends AccessoryDelegate {
372
391
  this.values.firmware = parseInt(config.fwversion.slice(6, 8)) + '.' +
373
392
  parseInt(config.fwversion.slice(2, 4), 16) + '.' +
374
393
  parseInt(config.fwversion.slice(4, 6), 16)
375
- this.values.wsPort = config.websocketport
394
+ this.values.wsPort = this.values.https
395
+ ? config.websocketport_wss ?? config.websocketport
396
+ : config.websocketport
376
397
  this.service.update(config)
377
398
  if (this.checkApiKeys) {
378
399
  const myEntry = config.whitelist[this.values.apiKey]
@@ -390,13 +411,16 @@ class Gateway extends AccessoryDelegate {
390
411
 
391
412
  /** Create {@link DeconzAccessory.Gateway#client}.
392
413
  */
393
- createClient () {
414
+ async createClient () {
415
+ this.client?.removeAllListeners()
394
416
  /** REST API client for the gateway.
395
417
  * @type {DeconzClient}
396
418
  */
397
419
  this.client = new ApiClient({
398
420
  apiKey: this.values.apiKey,
399
421
  config: this.context.config,
422
+ https: this.values.https,
423
+ fingerprint: this.values.fingerprint,
400
424
  host: this.values.host,
401
425
  maxSockets: this.platform.config.parallelRequests,
402
426
  timeout: this.platform.config.timeout,
@@ -442,18 +466,29 @@ class Gateway extends AccessoryDelegate {
442
466
  response.statusCode, response.statusMessage
443
467
  )
444
468
  })
469
+ return this.connect()
445
470
  }
446
471
 
447
472
  /** Create {@link DeconzAccessory.Gateway#wsclient}.
448
473
  */
449
- createWsClient () {
474
+ async createWsClient () {
475
+ this.wsRestart = true
476
+ await this.wsClient?.close()
477
+ this.wsClient?.removeAllListeners()
478
+ const wss = this.values.https && this.context.fullState?.config.websocketport_wss != null
479
+ const port = wss
480
+ ? this.context.fullState?.config.websocketport_wss
481
+ : this.context.fullState?.config.websocketport
482
+ const options = {
483
+ wss,
484
+ fingerprint: this.values.fingerprint,
485
+ host: this.values.host.split(':')[0] + ':' + port,
486
+ retryTime: 0
487
+ }
450
488
  /** Client for gateway web socket notifications.
451
489
  * @type {DeconzWsClient}
452
490
  */
453
- this.wsClient = new WsClient({
454
- host: this.values.host.split(':')[0] + ':' + this.values.wsPort,
455
- retryTime: 15
456
- })
491
+ this.wsClient = new WsClient(options)
457
492
  this.wsClient
458
493
  .on('error', (error) => {
459
494
  this.warn('websocket communication error: %s', error)
@@ -490,14 +525,13 @@ class Gateway extends AccessoryDelegate {
490
525
  this.pollFullState = true
491
526
  })
492
527
  .on('closed', (url, retryTime) => {
493
- if (retryTime > 0) {
494
- this.log(
495
- 'websocket connection to %s closed - retry in %ds', url, retryTime
496
- )
497
- } else {
498
- this.log('websocket connection to %s closed', url)
528
+ this.log('websocket connection to %s closed', url)
529
+ if (!this.wsRestart) {
530
+ this.values.wsPort = null
499
531
  }
500
532
  })
533
+ this.wsClient.listen()
534
+ this.wsRestart = false
501
535
  }
502
536
 
503
537
  /** Connect to the gateway.
@@ -515,8 +549,8 @@ class Gateway extends AccessoryDelegate {
515
549
  if (this.values.apiKey == null) {
516
550
  this.values.apiKey =
517
551
  await this.client.getApiKey('homebridge-deconz')
552
+ this.values.fingerprint = this.client.fingerprint
518
553
  }
519
- this.wsClient.listen()
520
554
  this.service.values.restart = false
521
555
  this.service.values.statusActive = true
522
556
  this.checkApiKeys = true
@@ -989,6 +1023,9 @@ class Gateway extends AccessoryDelegate {
989
1023
  this.vdebug('%spolling...', this.pollNext ? 'priority ' : '')
990
1024
  if (this.context.fullState == null || this.pollFullState) {
991
1025
  const fullState = await this.client.get('/')
1026
+ if (this.values.https && (this.values.fingerprint == null || this.values.fingerprint === '')) {
1027
+ this.values.fingerprint = this.client.fingerprint
1028
+ }
992
1029
  try {
993
1030
  fullState.groups[0] = await this.client.get('/groups/0')
994
1031
  } catch (error) {}
@@ -1025,6 +1062,9 @@ class Gateway extends AccessoryDelegate {
1025
1062
  this.warn('deCONZ not ready')
1026
1063
  return
1027
1064
  }
1065
+ if (this.values.https && (this.values.fingerprint == null || this.values.fingerprint === '')) {
1066
+ this.values.fingerprint = this.client.fingerprint
1067
+ }
1028
1068
  this.context.fullState.config = config
1029
1069
  this.context.fullState.lights = await this.client.get('/lights')
1030
1070
  this.context.fullState.sensors = await this.client.get('/sensors')
@@ -158,11 +158,25 @@ class DeconzAccessory extends AccessoryDelegate {
158
158
  } else if (params.serviceName === 'Battery') {
159
159
  service = this.servicesByServiceName.Battery?.[0]
160
160
  } else if (params.serviceName === 'Consumption') {
161
+ if (this.servicesByServiceName.Consumption?.[0] != null) {
162
+ this.warn(
163
+ '%s: ignoring additional ZHAConsumption resource - consider splitting the accessory',
164
+ resource.rpath
165
+ )
166
+ return
167
+ }
161
168
  service = this.servicesByServiceName.Power?.[0]
162
169
  if (service != null) {
163
170
  service.addResource(resource)
164
171
  }
165
172
  } else if (params.serviceName === 'Power') {
173
+ if (this.servicesByServiceName.Power?.[0] != null) {
174
+ this.warn(
175
+ '%s: ignoring additional ZHAPower resource - consider splitting the accessory',
176
+ resource.rpath
177
+ )
178
+ return
179
+ }
166
180
  service = this.servicesByServiceName.Consumption?.[0]
167
181
  if (service != null) {
168
182
  service.addResource(resource)
@@ -29,7 +29,6 @@ class DeconzPlatform extends Platform {
29
29
 
30
30
  parseConfigJson (configJson) {
31
31
  this.config = {
32
- forceHttp: false,
33
32
  hosts: [],
34
33
  noResponse: false,
35
34
  parallelRequests: 10,
@@ -48,7 +47,6 @@ class DeconzPlatform extends Platform {
48
47
  })
49
48
  .stringKey('name')
50
49
  .stringKey('platform')
51
- .boolKey('forceHttp')
52
50
  .stringKey('host')
53
51
  .arrayKey('hosts')
54
52
  .boolKey('noResponse')
@@ -69,7 +67,6 @@ class DeconzPlatform extends Platform {
69
67
  this.config.hosts.push(this.config.host)
70
68
  }
71
69
  this.discovery = new Discovery({
72
- forceHttp: this.config.forceHttp,
73
70
  timeout: this.config.timeout
74
71
  })
75
72
  this.discovery
@@ -116,7 +113,9 @@ class DeconzPlatform extends Platform {
116
113
  this.gatewayMap[id] = new DeconzAccessory.Gateway(this, { config, host })
117
114
  }
118
115
  await this.gatewayMap[id].found(host, config)
119
- await once(this.gatewayMap[id], 'initialised')
116
+ if (!this.gatewayMap[id].initialised) {
117
+ await once(this.gatewayMap[id], 'initialised')
118
+ }
120
119
  this.emit('found')
121
120
  }
122
121
 
@@ -224,7 +223,9 @@ class DeconzPlatform extends Platform {
224
223
  const configs = await this.discovery.discover()
225
224
  const jobs = []
226
225
  for (const host in configs) {
227
- jobs.push(this.foundGateway(host, configs[host]))
226
+ if (this.config.hosts.length === 0 || this.gatewayMap[configs[host].bridgeid] != null) {
227
+ jobs.push(this.foundGateway(host, configs[host]))
228
+ }
228
229
  }
229
230
  for (const job of jobs) {
230
231
  try {
@@ -19,7 +19,7 @@ class Contact extends DeconzService.SensorsResource {
19
19
  Characteristic: this.Characteristics.hap.ContactSensorState
20
20
  })
21
21
 
22
- // With _Status Tapered_ Eve thinks the _Door Sensor_ is an Eve Windows Guard
22
+ // With _Status Tampered_ Eve thinks the _Door Sensor_ is an Eve Windows Guard
23
23
  // (instead of an Eve Door & Window) and won't display it.
24
24
  this.addCharacteristicDelegates({ noTampered: true })
25
25
 
@@ -0,0 +1,56 @@
1
+ // homebridge-deconz/lib/DeconzService/DoorLock.js
2
+ // Copyright © 2022-2026 Erik Baauw. All rights reserved.
3
+ //
4
+ // Homebridge plugin for deCONZ.
5
+
6
+ import { DeconzService } from '../DeconzService/index.js'
7
+ import '../DeconzService/SensorsResource.js'
8
+
9
+ /**
10
+ * @memberof DeconzService
11
+ */
12
+ class DoorLock extends DeconzService.SensorsResource {
13
+ constructor (accessory, resource, params = {}) {
14
+ params.Service = accessory.Services.hap.LockMechanism
15
+ super(accessory, resource, params)
16
+
17
+ this.addCharacteristicDelegate({
18
+ key: 'currentState',
19
+ Characteristic: this.Characteristics.hap.LockCurrentState
20
+ })
21
+ this.addCharacteristicDelegate({
22
+ key: 'targetState',
23
+ Characteristic: this.Characteristics.hap.LockTargetState
24
+ }).on('didSet', async (value, fromHomeKit) => {
25
+ if (fromHomeKit) {
26
+ await this.put('/config', {
27
+ lock: value === this.Characteristics.hap.LockTargetState.SECURED
28
+ })
29
+ this.locking = new Date()
30
+ }
31
+ })
32
+ this.addCharacteristicDelegates()
33
+
34
+ this.update(resource.body, resource.rpath)
35
+ }
36
+
37
+ updateState (state) {
38
+ if (state.lockstate != null) {
39
+ this.values.currentState = state.lockestate === 'locked'
40
+ ? this.Characteristics.hap.LockCurrentState.SECURED
41
+ : this.Characteristics.hap.LockCurrentState.UNSECURED
42
+ }
43
+ super.updateState(state)
44
+ }
45
+
46
+ updateConfig (config) {
47
+ if (config.lock != null && new Date() - this.locking >= 5000) {
48
+ this.values.targetLockState = config.lock
49
+ ? this.Characteristics.hap.LockTargetState.SECURED
50
+ : this.Characteristics.hap.LockTargetState.UNSECURED
51
+ }
52
+ super.updateConfig(config)
53
+ }
54
+ }
55
+
56
+ DeconzService.DoorLock = DoorLock
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "ebaauw"
8
8
  ],
9
9
  "license": "Apache-2.0",
10
- "version": "1.2.11",
10
+ "version": "1.3.0",
11
11
  "keywords": [
12
12
  "homebridge-plugin",
13
13
  "homekit",
@@ -26,13 +26,13 @@
26
26
  "ui": "cli/ui.js"
27
27
  },
28
28
  "engines": {
29
- "deCONZ": "2.31.2",
30
- "homebridge": "^1.11.1||^2.0.0-beta",
29
+ "deCONZ": "2.32.5",
30
+ "homebridge": "^1.11.2||^2.0.0-beta",
31
31
  "node": "^24||^22||^20"
32
32
  },
33
33
  "dependencies": {
34
- "hb-deconz-tools": "~2.0.21",
35
- "homebridge-lib": "~7.3.0"
34
+ "hb-deconz-tools": "~3.0.0",
35
+ "homebridge-lib": "~7.3.2"
36
36
  },
37
37
  "scripts": {
38
38
  "prepare": "standard && rm -rf out && jsdoc -c jsdoc.json",