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.
- package/lib/Deconz/Resource.js +62 -1
- package/lib/DeconzAccessory/Gateway.js +9 -1
- package/lib/DeconzAccessory/index.js +9 -0
- package/lib/DeconzService/AlarmSystem.js +87 -0
- package/lib/DeconzService/Daylight.js +1 -1
- package/lib/DeconzService/Flag.js +1 -1
- package/lib/DeconzService/Label.js +8 -0
- package/lib/DeconzService/Status.js +1 -1
- package/package.json +3 -3
package/lib/Deconz/Resource.js
CHANGED
@@ -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
|
@@ -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.
|
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.
|
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
|
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.
|
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.
|
34
|
+
"hb-deconz-tools": "~2.0.9",
|
35
35
|
"homebridge-lib": "~7.1.4"
|
36
36
|
},
|
37
37
|
"scripts": {
|