homebridge-deconz 1.0.26 → 1.1.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.
@@ -16,7 +16,7 @@ import '../DeconzService/Button.js'
16
16
  const { toInstance, toInt, toObject, toString } = OptionParser
17
17
  const { buttonEvent } = ApiClient
18
18
  const { SINGLE, DOUBLE, LONG } = DeconzService.Button
19
- const rtypes = ['lights', 'sensors', 'groups']
19
+ const rtypes = ['lights', 'sensors', 'groups', 'alarmsystems']
20
20
 
21
21
  const patterns = {
22
22
  clipId: /^(S[0-9]{1,3})-([0-9a-z]{2})-([0-9a-z]{4})$/i,
@@ -49,6 +49,19 @@ const hueTapMap = {
49
49
  99: 0 // release 3 and 4
50
50
  }
51
51
 
52
+ const ancillaryControlMap = {
53
+ disarmed: 1002,
54
+ already_disarmed: 1002,
55
+ armed_stay: 2002,
56
+ armed_night: 3002,
57
+ armed_away: 4002,
58
+ invalid_code: 5002,
59
+ not_ready: 0,
60
+ emergency: 6002,
61
+ fire: 7002,
62
+ panic: 8002
63
+ }
64
+
52
65
  /** Delegate class for a resource on a deCONZ gateway.
53
66
  *
54
67
  * @memberof Deconz
@@ -115,6 +128,9 @@ class Resource {
115
128
  body.name = body.name.replace(/[^\p{L}\p{N} ']/ug, ' ')
116
129
  .replace(/^[ ']*/, '')
117
130
  .replace(/[ ']*$/, '')
131
+ if (rtype === 'alarmsystems') {
132
+ body.type = 'AlarmSystem'
133
+ }
118
134
  toString('body.type', body.type, true)
119
135
 
120
136
  let realDevice = false
@@ -297,12 +313,15 @@ class Resource {
297
313
  case 'Window covering device': return 'WindowCovering'
298
314
  default: return null
299
315
  }
316
+ } else if (this.rtype === 'alarmsystems') {
317
+ return 'AlarmSystem'
300
318
  } else { // (this.rtype === 'sensors')
301
319
  const type = /^(CLIP|ZHA|ZGP)?([A-Za-z]*)$/.exec(this.body.type)?.[2]
302
320
  switch (type) {
303
321
  case 'AirPurifier': return 'AirPurifier'
304
322
  case 'AirQuality': return 'AirQuality'
305
323
  case 'Alarm': return 'Alarm'
324
+ case 'AncillaryControl': return 'Label'
306
325
  case 'Battery': return 'Battery'
307
326
  case 'CarbonMonoxide': return 'CarbonMonoxide'
308
327
  case 'Consumption': return 'Consumption'
@@ -421,6 +440,16 @@ class Resource {
421
440
  const buttons = []
422
441
  let dots = false
423
442
  switch (this.manufacturer) {
443
+ case 'Aqara':
444
+ switch (this.model) {
445
+ case 'lumi.switch.acn047': // Aqara T2 Module
446
+ buttons.push(1, 'S1', SINGLE)
447
+ buttons.push(2, 'S2', SINGLE)
448
+ break
449
+ default:
450
+ break
451
+ }
452
+ break
424
453
  case 'Bitron Home':
425
454
  switch (this.model) {
426
455
  case '902010/23': // Bitron remote, see #639.
@@ -1117,6 +1146,25 @@ class Resource {
1117
1146
  break
1118
1147
  }
1119
1148
  break
1149
+ case 'frient AS':
1150
+ switch (this.model) {
1151
+ case 'KEPZB-110':
1152
+ buttons.push([1, 'Disarm', SINGLE])
1153
+ buttons.push([2, 'Armed Stay', SINGLE])
1154
+ buttons.push([3, 'Armed Night', SINGLE])
1155
+ buttons.push([4, 'Armed Away', SINGLE])
1156
+ buttons.push([5, 'Invalid Code', SINGLE])
1157
+ buttons.push([6, 'SOS', SINGLE])
1158
+ // buttons.push([7, 'Fire', SINGLE])
1159
+ // buttons.push([8, 'Panic', SINGLE])
1160
+ this.capabilities.toButtonEvent = (v) => {
1161
+ return ancillaryControlMap[v]
1162
+ }
1163
+ break
1164
+ default:
1165
+ break
1166
+ }
1167
+ break
1120
1168
  case 'icasa':
1121
1169
  switch (this.model) {
1122
1170
  case 'ICZB-KPD12':
@@ -1211,6 +1259,19 @@ class Resource {
1211
1259
  }
1212
1260
  break
1213
1261
  default:
1262
+ if (this.body.type === 'ZHAAncillaryControl') {
1263
+ buttons.push([1, 'Disarm', SINGLE])
1264
+ buttons.push([2, 'Armed Stay', SINGLE])
1265
+ buttons.push([3, 'Armed Night', SINGLE])
1266
+ buttons.push([4, 'Armed Away', SINGLE])
1267
+ buttons.push([5, 'Invalid Code', SINGLE])
1268
+ buttons.push([6, 'Emergency', SINGLE])
1269
+ buttons.push([7, 'Fire', SINGLE])
1270
+ buttons.push([8, 'Panic', SINGLE])
1271
+ this.capabilities.toButtonEvent = (v) => {
1272
+ return ancillaryControlMap[v]
1273
+ }
1274
+ }
1214
1275
  break
1215
1276
  }
1216
1277
  if (buttons.length > 0) {
@@ -29,7 +29,7 @@ const migration = {
29
29
  classid: 1
30
30
  }
31
31
 
32
- const rtypes = ['lights', 'sensors', 'groups']
32
+ const rtypes = ['lights', 'sensors', 'groups', 'alarmsystems']
33
33
 
34
34
  const periodicEvents = [
35
35
  { rate: 60, event: 1002 },
@@ -992,6 +992,11 @@ class Gateway extends AccessoryDelegate {
992
992
  try {
993
993
  fullState.groups[0] = await this.client.get('/groups/0')
994
994
  } catch (error) {}
995
+ try {
996
+ fullState.alarmsystems = await this.client.get('/alarmsystems')
997
+ } catch (error) {
998
+ fullState.alarmsystems = {}
999
+ }
995
1000
  this.context.fullState = fullState
996
1001
  this.pollFullState = false
997
1002
  } else {
@@ -1016,6 +1021,9 @@ class Gateway extends AccessoryDelegate {
1016
1021
  this.context.fullState.groups[0] = await this.client.get('/groups/0')
1017
1022
  } catch (error) {}
1018
1023
  }
1024
+ if (this.nDevicesByRtype.alarmsystems > 0) {
1025
+ this.context.fullState.alarmsystems = await this.client.get('/alarmsystems')
1026
+ }
1019
1027
  if (this.values.exposeSchedules) {
1020
1028
  this.context.fullState.schedules = await this.client.get('/schedules')
1021
1029
  }
@@ -235,6 +235,7 @@ class DeconzAccessory extends AccessoryDelegate {
235
235
  logLevel: this.values.logLevel,
236
236
  lowBatteryThreshold: this.servicesByServiceName?.Battery?.[0].values.lowBatteryThreshold,
237
237
  // offset: this.servicesByServiceName?.Temperature?.[0].values.offset,
238
+ pin: this.service.values.pin,
238
239
  serviceName: this.values.serviceName,
239
240
  venetianBlind: this.service.values.venetianBlind,
240
241
  useExternalTemperature: this.service.values.useExternalTemperature,
@@ -289,6 +290,14 @@ class DeconzAccessory extends AccessoryDelegate {
289
290
  continue
290
291
  }
291
292
  break
293
+ case 'pin':
294
+ if (this.service.values[key] != null) {
295
+ value = OptionParser.toString(key, body[key])
296
+ this.service.values[key] = value
297
+ responseBody[key] = value
298
+ continue
299
+ }
300
+ break
292
301
  case 'serviceName':
293
302
  if (this.values.serviceName != null) {
294
303
  value = OptionParser.toString(key, body[key])
@@ -0,0 +1,87 @@
1
+ // homebridge-deconz/lib/DeconzService/AlarmSystem.js
2
+ // Copyright © 2022-2025 Erik Baauw. All rights reserved.
3
+ //
4
+ // Homebridge plugin for deCONZ.
5
+
6
+ import { DeconzService } from '../DeconzService/index.js'
7
+
8
+ let mapsInitialised = false
9
+ const armModeMap = {}
10
+ const armStateMap = {}
11
+ const targetStateMap = {}
12
+
13
+ function initMaps (currentState, targetState) {
14
+ if (mapsInitialised) {
15
+ return
16
+ }
17
+ armStateMap.disarmed = currentState.DISARMED
18
+ armStateMap.armed_away = currentState.AWAY_ARM
19
+ armStateMap.armed_stay = currentState.STAY_ARM
20
+ armStateMap.armed_night = currentState.NIGHT_ARM
21
+ armStateMap.in_alarm = currentState.ALARM_TRIGGERED
22
+ armModeMap.disarmed = targetState.DISARM
23
+ armModeMap.armed_away = targetState.AWAY_ARM
24
+ armModeMap.armed_stay = targetState.STAY_ARM
25
+ armModeMap.armed_night = targetState.NIGHT_ARM
26
+ targetStateMap[targetState.DISARM] = 'disarm'
27
+ targetStateMap[targetState.AWAY_ARM] = 'arm_away'
28
+ targetStateMap[targetState.STAY_ARM] = 'arm_stay'
29
+ targetStateMap[targetState.NIGHT_ARM] = 'arm_night'
30
+ mapsInitialised = true
31
+ }
32
+
33
+ /**
34
+ * @memberof DeconzService
35
+ */
36
+ class AlarmSystem extends DeconzService {
37
+ constructor (accessory, resource, params = {}) {
38
+ params.Service = accessory.Services.hap.SecuritySystem
39
+ super(accessory, resource, params)
40
+
41
+ initMaps(
42
+ this.Characteristics.hap.SecuritySystemCurrentState,
43
+ this.Characteristics.hap.SecuritySystemTargetState
44
+ )
45
+ this.addCharacteristicDelegate({
46
+ key: 'currentState',
47
+ Characteristic: this.Characteristics.hap.SecuritySystemCurrentState
48
+ })
49
+ this.addCharacteristicDelegate({
50
+ key: 'targetState',
51
+ Characteristic: this.Characteristics.hap.SecuritySystemTargetState
52
+ }).on('didSet', async (value, fromHomeKit) => {
53
+ if (fromHomeKit) {
54
+ await this.put(`/${targetStateMap[value]}`, {
55
+ code0: this.values.pin
56
+ })
57
+ }
58
+ })
59
+ this.addCharacteristicDelegate({
60
+ key: 'alarmType',
61
+ Characteristic: this.Characteristics.hap.SecuritySystemAlarmType
62
+ })
63
+ this.addCharacteristicDelegate({
64
+ key: 'pin',
65
+ value: '0000'
66
+ })
67
+
68
+ this.update(resource.body, resource.rpath)
69
+ }
70
+
71
+ updateState (state) {
72
+ if (armStateMap[state.armstate] != null) {
73
+ this.values.currentState = armStateMap[state.armstate]
74
+ }
75
+ this.values.alarmType = state.armstate === 'in_alarm'
76
+ ? 1 // this.Characteristics.hap.SecuritySystemAlarmType.UNKNOWN
77
+ : 0 // this.Characteristics.hap.SecuritySystemAlarmType.NO_ALARM
78
+ }
79
+
80
+ updateConfig (config) {
81
+ if (armModeMap[config.armmode] != null) {
82
+ this.values.targetState = armModeMap[config.armmode]
83
+ }
84
+ }
85
+ }
86
+
87
+ DeconzService.AlarmSystem = AlarmSystem
@@ -76,7 +76,7 @@ class Daylight extends DeconzService.SensorsResource {
76
76
  minValue: 100,
77
77
  maxValue: 230,
78
78
  perms: [
79
- this.Characteristic.Perms.READ,
79
+ this.Characteristic.Perms.PAIRED_READ,
80
80
  this.Characteristic.Perms.NOTIFY]
81
81
  },
82
82
  value: resource.body.state.status
@@ -20,7 +20,7 @@ class Flag extends DeconzService.SensorsResource {
20
20
  Characteristic: this.Characteristics.hap.On,
21
21
  props: {
22
22
  perms: [
23
- this.Characteristic.Perms.READ, this.Characteristic.Perms.NOTIFY
23
+ this.Characteristic.Perms.PAIRED_READ, this.Characteristic.Perms.NOTIFY
24
24
  ]
25
25
  }
26
26
  })
@@ -42,6 +42,12 @@ class Label extends DeconzService.SensorsResource {
42
42
  right: keys[0],
43
43
  left: keys[1]
44
44
  }
45
+ } else if (resource.body.type.endsWith('AncillaryControl')) {
46
+ this.buttonResources[resource.rpath] = {
47
+ buttonEvent: resource.body.state.action,
48
+ lastUpdated: new Date(),
49
+ toButtonEvent: resource.capabilities.toButtonEvent
50
+ }
45
51
  }
46
52
  for (const i in resource.capabilities.buttons) {
47
53
  const { label, events, hasRepeat } = resource.capabilities.buttons[i]
@@ -73,6 +79,8 @@ class Label extends DeconzService.SensorsResource {
73
79
  buttonResource.buttonEvent = buttonResource.toButtonEvent == null
74
80
  ? state.buttonevent
75
81
  : buttonResource.toButtonEvent(state.buttonevent)
82
+ } else if (state.action != null) {
83
+ buttonResource.buttonEvent = buttonResource.toButtonEvent(state.action)
76
84
  }
77
85
  // TODO handle repeat
78
86
  const i = Math.floor(buttonResource.buttonEvent / 1000)
@@ -44,7 +44,7 @@ class Status extends DeconzService.SensorsResource {
44
44
  Characteristic: this.Characteristics.my.Status,
45
45
  props: {
46
46
  perms: [
47
- this.Characteristic.Perms.READ, this.Characteristic.Perms.NOTIFY
47
+ this.Characteristic.Perms.PAIRED_READ, this.Characteristic.Perms.NOTIFY
48
48
  ]
49
49
  }
50
50
  })
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "ebaauw"
8
8
  ],
9
9
  "license": "Apache-2.0",
10
- "version": "1.0.26",
10
+ "version": "1.1.0",
11
11
  "keywords": [
12
12
  "homebridge-plugin",
13
13
  "homekit",
@@ -26,12 +26,12 @@
26
26
  "ui": "cli/ui.js"
27
27
  },
28
28
  "engines": {
29
- "deCONZ": "2.28.1",
29
+ "deCONZ": "2.29.5",
30
30
  "homebridge": "^1.9.0||^2.0.0-beta",
31
31
  "node": "^22||^20||^18"
32
32
  },
33
33
  "dependencies": {
34
- "hb-deconz-tools": "~2.0.8",
34
+ "hb-deconz-tools": "~2.0.9",
35
35
  "homebridge-lib": "~7.1.4"
36
36
  },
37
37
  "scripts": {