homebridge-deconz 1.0.0 → 1.0.2
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/Device.js +26 -11
- package/lib/Deconz/Resource.js +23 -8
- package/lib/DeconzAccessory/Gateway.js +12 -2
- package/lib/DeconzAccessory/Light.js +55 -13
- package/lib/DeconzAccessory/Sensor.js +27 -3
- package/lib/DeconzAccessory/WarningDevice.js +2 -2
- package/lib/DeconzAccessory/index.js +16 -11
- package/lib/DeconzService/AirQuality.js +70 -10
- package/lib/DeconzService/Contact.js +2 -0
- package/lib/DeconzService/Humidity.js +4 -3
- package/lib/DeconzService/Label.js +12 -12
- package/lib/DeconzService/Light.js +22 -18
- package/lib/DeconzService/LightsResource.js +14 -25
- package/lib/DeconzService/Motion.js +0 -3
- package/lib/DeconzService/Outlet.js +16 -8
- package/lib/DeconzService/SensorsResource.js +4 -14
- package/lib/DeconzService/Switch.js +17 -9
- package/lib/DeconzService/Temperature.js +8 -2
- package/lib/DeconzService/Valve.js +125 -0
- package/lib/DeconzService/WarningDevice.js +4 -4
- package/lib/DeconzService/WindowCovering.js +8 -8
- package/lib/DeconzService/index.js +13 -1
- package/package.json +5 -5
package/lib/Deconz/Device.js
CHANGED
@@ -28,13 +28,24 @@ class Device {
|
|
28
28
|
*/
|
29
29
|
this.id = resource.id
|
30
30
|
|
31
|
-
/**
|
31
|
+
/** The key of the delegate for the primary resource for the device in
|
32
|
+
* {@link DeconzDevice#resourceBySubtype resourceBySubtype}
|
32
33
|
*
|
33
|
-
* This is the {@link
|
34
|
-
*
|
35
|
-
* @type {
|
34
|
+
* This is the {@link DeconzDevice.Resource#subtype subtype} of the
|
35
|
+
* HomeKit service corresponding to the primary resource.
|
36
|
+
* @type {string}
|
36
37
|
*/
|
37
|
-
this.
|
38
|
+
this.primary = resource.subtype
|
39
|
+
|
40
|
+
/** An array of keys of the delegates for the resources for the device in
|
41
|
+
* {@link DeconzDevice#resourceBySubtype resourceBySubtype} by service name.
|
42
|
+
*
|
43
|
+
* These are the {@link DeconzDevice.Resource#subtype subtype} values of the
|
44
|
+
* HomeKit service corresponding to the resource.
|
45
|
+
* @type {Object.<string, Array.<string>>}
|
46
|
+
*/
|
47
|
+
this.subtypesByServiceName = {}
|
48
|
+
this.subtypesByServiceName[resource.serviceName] = [resource.subtype]
|
38
49
|
|
39
50
|
/** The delegates of the resources for the device, by subtype of the
|
40
51
|
* corresponding HomeKit service.
|
@@ -43,14 +54,13 @@ class Device {
|
|
43
54
|
this.resourceBySubtype = {}
|
44
55
|
this.resourceBySubtype[resource.subtype] = resource
|
45
56
|
|
46
|
-
/**
|
47
|
-
* {@link DeconzDevice#resourceBySubtype resourceBySubtype}
|
57
|
+
/** Zigbee device vs virtual device.
|
48
58
|
*
|
49
|
-
* This is the {@link
|
50
|
-
*
|
51
|
-
* @type {
|
59
|
+
* This is the {@link Deconz.Resource#zigbee zigbee} of the
|
60
|
+
* delegates of all resources for the device.
|
61
|
+
* @type {boolean}
|
52
62
|
*/
|
53
|
-
this.
|
63
|
+
this.zigbee = resource.zigbee
|
54
64
|
}
|
55
65
|
|
56
66
|
/** The delegate of the primary resource of the device.
|
@@ -89,6 +99,11 @@ class Device {
|
|
89
99
|
`${resource.rpath}: cannot combine ${r.rpath}`
|
90
100
|
)
|
91
101
|
}
|
102
|
+
if (this.subtypesByServiceName[resource.serviceName] == null) {
|
103
|
+
this.subtypesByServiceName[resource.serviceName] = [resource.subtype]
|
104
|
+
} else {
|
105
|
+
this.subtypesByServiceName[resource.serviceName].push(resource.subtype)
|
106
|
+
}
|
92
107
|
this.resourceBySubtype[subtype] = resource
|
93
108
|
const p = this.resourceBySubtype[this.primary]
|
94
109
|
if (p.rtype === rtype && p.prio < prio) {
|
package/lib/Deconz/Resource.js
CHANGED
@@ -145,6 +145,11 @@ class Resource {
|
|
145
145
|
*/
|
146
146
|
this.endpoint = endpoint
|
147
147
|
|
148
|
+
/** Zigbee cluster
|
149
|
+
* @type {string}
|
150
|
+
*/
|
151
|
+
this.cluster = cluster
|
152
|
+
|
148
153
|
/** Zigbee device vs virtual device.
|
149
154
|
*
|
150
155
|
* Derived from the resource type and, for `sensors`, on the `type` in the
|
@@ -301,6 +306,7 @@ class Resource {
|
|
301
306
|
case 'LightLevel': return 'LightLevel'
|
302
307
|
case 'Moisture': return 'Humidity'
|
303
308
|
case 'OpenClose': return 'Contact'
|
309
|
+
case 'ParticulateMatter': return 'AirQuality'
|
304
310
|
case 'Power': return 'Power'
|
305
311
|
case 'Presence': return 'Motion'
|
306
312
|
case 'Pressure': return 'AirPressure'
|
@@ -482,14 +488,23 @@ class Resource {
|
|
482
488
|
buttons.push([3, 'Previous', SINGLE | LONG])
|
483
489
|
buttons.push([4, 'Next', SINGLE | LONG])
|
484
490
|
break
|
491
|
+
case 'RODRET Dimmer':
|
492
|
+
buttons.push([1, 'Dim Up', SINGLE | LONG])
|
493
|
+
buttons.push([2, 'Dim Down', SINGLE | LONG])
|
494
|
+
break
|
485
495
|
case 'SYMFONISK Sound Controller':
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
496
|
+
if (this.cluster === '1000') {
|
497
|
+
buttons.push([1, 'Button', SINGLE | DOUBLE | LONG])
|
498
|
+
if (this.body.mode === 1) {
|
499
|
+
buttons.push([2, 'Turn Right', LONG])
|
500
|
+
buttons.push([3, 'Turn Left', LONG])
|
501
|
+
} else {
|
502
|
+
buttons.push([2, 'Turn Right', SINGLE])
|
503
|
+
buttons.push([3, 'Turn Left', SINGLE])
|
504
|
+
}
|
505
|
+
} else if (this.cluster === '0008') { // ZHARelativeRotary
|
506
|
+
// buttons.push([4, 'Turn Right', SINGLE])
|
507
|
+
// buttons.push([5, 'Turn Left', SINGLE])
|
493
508
|
}
|
494
509
|
break
|
495
510
|
case 'SYMFONISK sound remote gen2':
|
@@ -754,7 +769,7 @@ class Resource {
|
|
754
769
|
buttons.push([2, '2', SINGLE | LONG, true])
|
755
770
|
buttons.push([3, '3', SINGLE | LONG, true])
|
756
771
|
buttons.push([4, '4', SINGLE | LONG, true])
|
757
|
-
} else if (this.endpoint === '14') {
|
772
|
+
} else if (this.endpoint === '14') { // ZHARelativeRotary
|
758
773
|
buttons.push([5, 'Right Turn', SINGLE])
|
759
774
|
buttons.push([6, 'Left Turn', SINGLE])
|
760
775
|
}
|
@@ -82,6 +82,12 @@ class Gateway extends AccessoryDelegate {
|
|
82
82
|
this.client.apiKey = value
|
83
83
|
})
|
84
84
|
|
85
|
+
this.addPropertyDelegate({
|
86
|
+
key: 'autoExpose',
|
87
|
+
value: true,
|
88
|
+
silent: true
|
89
|
+
})
|
90
|
+
|
85
91
|
this.addPropertyDelegate({
|
86
92
|
key: 'brightnessAdjustment',
|
87
93
|
value: 1,
|
@@ -495,7 +501,7 @@ class Gateway extends AccessoryDelegate {
|
|
495
501
|
*/
|
496
502
|
async connect (retry = 0) {
|
497
503
|
if (!this.values.expose) {
|
498
|
-
this.warn('unlock gateway and set
|
504
|
+
this.warn('unlock gateway and set expose to obtain an API key')
|
499
505
|
return
|
500
506
|
}
|
501
507
|
try {
|
@@ -782,10 +788,12 @@ class Gateway extends AccessoryDelegate {
|
|
782
788
|
name: this.name,
|
783
789
|
settings: this.values.apiKey == null
|
784
790
|
? {
|
791
|
+
autoExpose: this.values.autoExpose,
|
785
792
|
expose: this.values.expose,
|
786
793
|
logLevel: this.values.logLevel
|
787
794
|
}
|
788
795
|
: {
|
796
|
+
autoExpose: this.values.autoExpose,
|
789
797
|
brightnessAdjustment: this.values.brightnessAdjustment * 100,
|
790
798
|
expose: this.values.expose,
|
791
799
|
exposeSchedules: this.values.exposeSchedules,
|
@@ -842,6 +850,7 @@ class Gateway extends AccessoryDelegate {
|
|
842
850
|
.on('userInputError', (error) => {
|
843
851
|
this.warn(error)
|
844
852
|
})
|
853
|
+
.boolKey('autoExpose')
|
845
854
|
.boolKey('expose')
|
846
855
|
.intKey('logLevel', 0, 3)
|
847
856
|
if (this.values.apiKey != null) {
|
@@ -863,6 +872,7 @@ class Gateway extends AccessoryDelegate {
|
|
863
872
|
this.values[key] = settings[key] / 100
|
864
873
|
responseBody[key] = this.values[key]
|
865
874
|
break
|
875
|
+
case 'autoExpose':
|
866
876
|
case 'expose':
|
867
877
|
case 'exposeSchedules':
|
868
878
|
case 'heartrate':
|
@@ -1105,7 +1115,7 @@ class Gateway extends AccessoryDelegate {
|
|
1105
1115
|
try {
|
1106
1116
|
const { id, zigbee } = this.deviceByRidByRtype[rtype][rid]
|
1107
1117
|
if (this.context.settingsById[id] == null) {
|
1108
|
-
this.context.settingsById[id] = { expose: zigbee }
|
1118
|
+
this.context.settingsById[id] = { expose: zigbee && this.values.autoExpose }
|
1109
1119
|
}
|
1110
1120
|
if (this.context.settingsById[id].expose) {
|
1111
1121
|
if (this.accessoryById[id] == null) {
|
@@ -33,19 +33,8 @@ class Light extends DeconzAccessory {
|
|
33
33
|
serviceName: this.values.serviceName
|
34
34
|
})
|
35
35
|
|
36
|
-
for (const subtype in device.resourceBySubtype) {
|
37
|
-
const resource = device.resourceBySubtype[subtype]
|
38
|
-
if (subtype === device.primary) {
|
39
|
-
continue
|
40
|
-
}
|
41
|
-
if (resource.rtype === 'lights') {
|
42
|
-
this.createService(resource, { serviceName: this.values.serviceName })
|
43
|
-
} else {
|
44
|
-
this.createService(resource)
|
45
|
-
}
|
46
|
-
}
|
47
|
-
|
48
36
|
const params = {}
|
37
|
+
|
49
38
|
if (this.values.serviceName === 'Outlet') {
|
50
39
|
this.service.addCharacteristicDelegate({
|
51
40
|
key: 'lockPhysicalControls',
|
@@ -57,7 +46,14 @@ class Light extends DeconzAccessory {
|
|
57
46
|
Characteristic: this.Characteristics.eve.LastActivation,
|
58
47
|
silent: true
|
59
48
|
})
|
60
|
-
} else {
|
49
|
+
} else if (this.values.serviceName === 'Switch') {
|
50
|
+
params.onDelegate = this.service.characteristicDelegate('on')
|
51
|
+
params.lastOnDelegate = this.service.addCharacteristicDelegate({
|
52
|
+
key: 'lastActivation',
|
53
|
+
Characteristic: this.Characteristics.eve.LastActivation,
|
54
|
+
silent: true
|
55
|
+
})
|
56
|
+
} else if (this.values.serviceName === 'Light') {
|
61
57
|
params.lightOnDelegate = this.service.characteristicDelegate('on')
|
62
58
|
params.lastLightOnDelegate = this.service.addCharacteristicDelegate({
|
63
59
|
key: 'lastActivation',
|
@@ -65,6 +61,7 @@ class Light extends DeconzAccessory {
|
|
65
61
|
silent: true
|
66
62
|
})
|
67
63
|
}
|
64
|
+
|
68
65
|
if (this.service.characteristicDelegate('totalConsumption') != null) {
|
69
66
|
params.totalConsumptionDelegate = this.service.characteristicDelegate('totalConsumption')
|
70
67
|
if (this.service.values.consumption === undefined) {
|
@@ -86,6 +83,51 @@ class Light extends DeconzAccessory {
|
|
86
83
|
}
|
87
84
|
this.historyService = new ServiceDelegate.History(this, params)
|
88
85
|
|
86
|
+
for (const subtype in device.resourceBySubtype) {
|
87
|
+
const resource = device.resourceBySubtype[subtype]
|
88
|
+
if (subtype === device.primary) {
|
89
|
+
continue
|
90
|
+
}
|
91
|
+
if (resource.rtype === 'lights') {
|
92
|
+
const service = this.createService(resource, { serviceName: this.values.serviceName })
|
93
|
+
if (this.values.serviceName !== 'Valve') {
|
94
|
+
this.historyService.addLastOnDelegate(
|
95
|
+
service.characteristicDelegate('on'),
|
96
|
+
service.addCharacteristicDelegate({
|
97
|
+
key: 'lastActivation',
|
98
|
+
Characteristic: this.Characteristics.eve.LastActivation,
|
99
|
+
silent: true
|
100
|
+
})
|
101
|
+
)
|
102
|
+
}
|
103
|
+
} else {
|
104
|
+
this.createService(resource)
|
105
|
+
}
|
106
|
+
}
|
107
|
+
|
108
|
+
if (
|
109
|
+
this.values.serviceName === 'Outlet' &&
|
110
|
+
this.service.characteristicDelegate('totalConsumption') == null &&
|
111
|
+
this.service.characteristicDelegate('consumption') == null &&
|
112
|
+
this.servicesByServiceName.Outlet?.length === 1
|
113
|
+
) {
|
114
|
+
// Dumb Outlet
|
115
|
+
this.warn('dumb outlet')
|
116
|
+
this.service.addCharacteristicDelegate({
|
117
|
+
key: 'dummyTotalConsumption',
|
118
|
+
Characteristic: this.Characteristics.eve.TotalConsumption,
|
119
|
+
props: {
|
120
|
+
perms: [
|
121
|
+
this.Characteristic.Perms.PAIRED_READ,
|
122
|
+
this.Characteristic.Perms.NOTIFY,
|
123
|
+
this.Characteristic.Perms.HIDDEN
|
124
|
+
]
|
125
|
+
},
|
126
|
+
silent: true,
|
127
|
+
value: 0
|
128
|
+
})
|
129
|
+
}
|
130
|
+
|
89
131
|
setImmediate(() => {
|
90
132
|
this.debug('initialised')
|
91
133
|
this.emit('initialised')
|
@@ -106,15 +106,39 @@ class Sensor extends DeconzAccessory {
|
|
106
106
|
params.vocDensityDelegate = service.characteristicDelegate('vocDensity')
|
107
107
|
}
|
108
108
|
}
|
109
|
-
if (this.servicesByServiceName.Flag
|
109
|
+
if (this.servicesByServiceName.Flag?.length === 1) {
|
110
110
|
const service = this.servicesByServiceName.Flag[0]
|
111
|
-
params.
|
112
|
-
params.
|
111
|
+
params.onDelegate = service.characteristicDelegate('on')
|
112
|
+
params.lastOnDelegate = service.addCharacteristicDelegate({
|
113
113
|
key: 'lastActivation',
|
114
114
|
Characteristic: this.Characteristics.eve.LastActivation,
|
115
115
|
silent: true
|
116
116
|
})
|
117
117
|
}
|
118
|
+
if (
|
119
|
+
params.temperatureDelegate != null && params.humidityDelegate != null &&
|
120
|
+
params.airPressureDelegate == null && params.vocDensityDelegate == null &&
|
121
|
+
this.servicesByServiceName.Battery?.length === 1
|
122
|
+
) {
|
123
|
+
// Eve would see this as an Eve Thermo Control.
|
124
|
+
this.airPressureService = new ServiceDelegate(this, {
|
125
|
+
name: this.name + ' Pressure',
|
126
|
+
Service: this.Services.eve.AirPressureSensor,
|
127
|
+
hidden: true
|
128
|
+
})
|
129
|
+
this.airPressureService.addCharacteristicDelegate({
|
130
|
+
key: 'airPressure',
|
131
|
+
Characteristic: this.Characteristics.eve.AirPressure,
|
132
|
+
props: {
|
133
|
+
perms: [
|
134
|
+
this.Characteristic.Perms.PAIRED_READ,
|
135
|
+
this.Characteristic.Perms.NOTIFY,
|
136
|
+
this.Characteristic.Perms.HIDDEN
|
137
|
+
]
|
138
|
+
},
|
139
|
+
value: 0
|
140
|
+
})
|
141
|
+
}
|
118
142
|
if (Object.keys(params).length > 0) {
|
119
143
|
this.historyService = new ServiceDelegate.History(this, params)
|
120
144
|
}
|
@@ -35,8 +35,8 @@ class WarningDevice extends DeconzAccessory {
|
|
35
35
|
|
36
36
|
const params = {}
|
37
37
|
if (this.servicesByServiceName.WarningDevice?.length === 1) {
|
38
|
-
params.
|
39
|
-
params.
|
38
|
+
params.onDelegate = this.service.characteristicDelegate('on')
|
39
|
+
params.lastOnDelegate = this.service.addCharacteristicDelegate({
|
40
40
|
key: 'lastActivation',
|
41
41
|
Characteristic: this.Characteristics.eve.LastActivation,
|
42
42
|
silent: true
|
@@ -130,6 +130,7 @@ class DeconzAccessory extends AccessoryDelegate {
|
|
130
130
|
this.values.firmware, this.rpaths.length
|
131
131
|
)
|
132
132
|
this.debug('%d resources: %s', this.rpaths.length, this.rpaths.join(', '))
|
133
|
+
this.vdebug('device: %j', this.device)
|
133
134
|
if (this.service != null) {
|
134
135
|
await this.service.identify()
|
135
136
|
}
|
@@ -177,22 +178,26 @@ class DeconzAccessory extends AccessoryDelegate {
|
|
177
178
|
service.addResource(resource)
|
178
179
|
}
|
179
180
|
} else if (params.serviceName === 'Label') {
|
181
|
+
service = this.servicesByServiceName.Label?.[0]
|
180
182
|
// Default button
|
181
183
|
if (resource.capabilities.buttons == null) {
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
184
|
+
if (service == null) {
|
185
|
+
this.warn(
|
186
|
+
'%s: unknown %s: %j', resource.rpath, resource.body.type,
|
187
|
+
resource.body
|
188
|
+
)
|
189
|
+
resource.capabilities.buttons = {
|
190
|
+
1: {
|
191
|
+
label: 'Unknown Button',
|
192
|
+
events: SINGLE | DOUBLE | LONG
|
193
|
+
}
|
190
194
|
}
|
195
|
+
resource.capabilities.namespace =
|
196
|
+
this.Characteristics.hap.ServiceLabelNamespace.ARABIC_NUMERALS
|
197
|
+
} else {
|
198
|
+
resource.capabilities.buttons = {}
|
191
199
|
}
|
192
|
-
resource.capabilities.namespace =
|
193
|
-
this.Characteristics.hap.ServiceLabelNamespace.ARABIC_NUMERALS
|
194
200
|
}
|
195
|
-
service = this.servicesByServiceName.Label?.[0]
|
196
201
|
}
|
197
202
|
if (service == null) {
|
198
203
|
service = new DeconzService[params.serviceName](this, resource, {
|
@@ -22,16 +22,64 @@ class AirQuality extends DeconzService.SensorsResource {
|
|
22
22
|
})
|
23
23
|
}
|
24
24
|
|
25
|
-
if (resource.body.state.
|
25
|
+
if (resource.body.state.measured_value !== undefined) {
|
26
|
+
const cmv = resource.body.capabilities.measured_value
|
27
|
+
switch (cmv.substance) {
|
28
|
+
case 'PM2.5':
|
29
|
+
if (cmv.quantity !== 'density') {
|
30
|
+
service.warn('%s: unsupported substance', cmv.quantity)
|
31
|
+
break
|
32
|
+
}
|
33
|
+
if (cmv.unit !== 'ug/m^3') {
|
34
|
+
service.warn('%s: unsupported unit', cmv.unit)
|
35
|
+
break
|
36
|
+
}
|
37
|
+
service.addCharacteristicDelegate({
|
38
|
+
key: 'pm25Density',
|
39
|
+
Characteristic: service.Characteristics.hap.PM2_5Density,
|
40
|
+
unit: ' µg/m³',
|
41
|
+
props: { minValue: cmv.min, maxValue: cmv.max }
|
42
|
+
})
|
43
|
+
service.resources[resource.rpath] = {
|
44
|
+
key: 'pm25Density',
|
45
|
+
f: (v) => { return v }
|
46
|
+
}
|
47
|
+
break
|
48
|
+
case 'tVOC':
|
49
|
+
if (cmv.quantity !== 'level') {
|
50
|
+
service.warn('%s: unsupported substance', cmv.quantity)
|
51
|
+
break
|
52
|
+
}
|
53
|
+
if (cmv.unit !== 'ppb') {
|
54
|
+
service.warn('%s: unsupported unit', cmv.unit)
|
55
|
+
break
|
56
|
+
}
|
57
|
+
service.addCharacteristicDelegate({
|
58
|
+
key: 'vocDensity',
|
59
|
+
Characteristic: service.Characteristics.hap.VOCDensity,
|
60
|
+
unit: ' µg/m³',
|
61
|
+
props: {
|
62
|
+
minValue: Math.floor(cmv.min * 4.57),
|
63
|
+
maxValue: Math.ceil(cmv.max * 4.57)
|
64
|
+
}
|
65
|
+
})
|
66
|
+
service.resources[resource.rpath] = {
|
67
|
+
key: 'vocDensity',
|
68
|
+
f: (v) => { return Math.round(v * 4.57) }
|
69
|
+
}
|
70
|
+
break
|
71
|
+
default:
|
72
|
+
service.warn('%s: unsupported substance', cmv.substance)
|
73
|
+
break
|
74
|
+
}
|
75
|
+
} else if (resource.body.state.airqualityppb !== undefined) {
|
26
76
|
service.addCharacteristicDelegate({
|
27
77
|
key: 'vocDensity',
|
28
78
|
Characteristic: service.Characteristics.hap.VOCDensity,
|
29
79
|
unit: ' µg/m³',
|
30
80
|
props: { minValue: 0, maxValue: 65535, minStep: 1 }
|
31
81
|
})
|
32
|
-
}
|
33
|
-
|
34
|
-
if (resource.body.state.pm2_5 !== undefined) {
|
82
|
+
} else if (resource.body.state.pm2_5 !== undefined) {
|
35
83
|
service.addCharacteristicDelegate({
|
36
84
|
key: 'pm25Density',
|
37
85
|
Characteristic: service.Characteristics.hap.PM2_5Density,
|
@@ -68,16 +116,27 @@ class AirQuality extends DeconzService.SensorsResource {
|
|
68
116
|
}
|
69
117
|
}
|
70
118
|
|
71
|
-
static updateResourceState (service, state) {
|
72
|
-
if (state.
|
119
|
+
static updateResourceState (service, state, rpath) {
|
120
|
+
if (state.measured_value != null && service.resources[rpath] != null) {
|
121
|
+
const { key, f } = service.resources[rpath]
|
122
|
+
service.values[key] = f(state.measured_value)
|
123
|
+
if (
|
124
|
+
state.airquality != null && (
|
125
|
+
key === 'vocDensity' || service.values.vocDensity === undefined
|
126
|
+
)
|
127
|
+
) {
|
128
|
+
service.values.airQuality = AirQuality.airQualityValue(
|
129
|
+
service, state.airquality
|
130
|
+
)
|
131
|
+
}
|
132
|
+
} else if (state.airqualityppb != null) {
|
73
133
|
service.values.vocDensity = Math.round(state.airqualityppb * 4.57)
|
74
134
|
if (state.airquality != null) {
|
75
135
|
service.values.airQuality = AirQuality.airQualityValue(
|
76
136
|
service, state.airquality
|
77
137
|
)
|
78
138
|
}
|
79
|
-
}
|
80
|
-
if (state.pm2_5 != null) {
|
139
|
+
} else if (state.pm2_5 != null) {
|
81
140
|
service.values.pm25Density = state.pm2_5
|
82
141
|
if (state.airquality != null && service.values.vocDensity === undefined) {
|
83
142
|
service.values.airQuality = AirQuality.airQualityValue(
|
@@ -93,6 +152,7 @@ class AirQuality extends DeconzService.SensorsResource {
|
|
93
152
|
constructor (accessory, resource, params = {}) {
|
94
153
|
params.Service = accessory.Services.hap.AirQualitySensor
|
95
154
|
super(accessory, resource, params)
|
155
|
+
this.resources = {}
|
96
156
|
|
97
157
|
AirQuality.addResource(this, resource)
|
98
158
|
|
@@ -101,8 +161,8 @@ class AirQuality extends DeconzService.SensorsResource {
|
|
101
161
|
this.update(resource.body, resource.rpath)
|
102
162
|
}
|
103
163
|
|
104
|
-
updateState (state) {
|
105
|
-
AirQuality.updateResourceState(this, state)
|
164
|
+
updateState (state, rpath) {
|
165
|
+
AirQuality.updateResourceState(this, state, rpath)
|
106
166
|
super.updateState(state)
|
107
167
|
}
|
108
168
|
}
|
@@ -20,6 +20,8 @@ class Contact extends DeconzService.SensorsResource {
|
|
20
20
|
Characteristic: this.Characteristics.hap.ContactSensorState
|
21
21
|
})
|
22
22
|
|
23
|
+
// With _Status Tapered_ Eve thinks the _Door Sensor_ is an Eve Windows Guard
|
24
|
+
// (instead of an Eve Door & Window) and won't display it.
|
23
25
|
this.addCharacteristicDelegates({ noTampered: true })
|
24
26
|
|
25
27
|
this.update(resource.body, resource.rpath)
|
@@ -27,10 +27,11 @@ class Humidity extends DeconzService.SensorsResource {
|
|
27
27
|
}
|
28
28
|
|
29
29
|
updateState (state) {
|
30
|
-
if (state.
|
30
|
+
if (state.measured_value != null) {
|
31
|
+
this.values.humidity = Math.round(state.measured_value * 10) / 10
|
32
|
+
} else if (state.humidity != null) {
|
31
33
|
this.values.humidity = Math.round(state.humidity / 10) / 10
|
32
|
-
}
|
33
|
-
if (state.moisture != null) {
|
34
|
+
} else if (state.moisture != null) {
|
34
35
|
this.values.humidity = Math.round(state.moisture / 10) / 10
|
35
36
|
}
|
36
37
|
super.updateState(state)
|
@@ -5,7 +5,7 @@
|
|
5
5
|
|
6
6
|
'use strict'
|
7
7
|
|
8
|
-
const DeconzService = require('
|
8
|
+
const DeconzService = require('../DeconzService')
|
9
9
|
|
10
10
|
/**
|
11
11
|
* @memberof DeconzService
|
@@ -32,13 +32,14 @@ class Label extends DeconzService.SensorsResource {
|
|
32
32
|
if (resource.body.type.endsWith('Switch')) {
|
33
33
|
this.buttonResources[resource.rpath] = {
|
34
34
|
buttonEvent: resource.body.state.buttonevent,
|
35
|
-
lastUpdated:
|
35
|
+
lastUpdated: new Date(),
|
36
36
|
toButtonEvent: resource.capabilities.toButtonEvent
|
37
37
|
}
|
38
38
|
} else if (resource.body.type.endsWith('RelativeRotary')) {
|
39
39
|
const keys = Object.keys(resource.capabilities.buttons)
|
40
40
|
this.buttonResources[resource.rpath] = {
|
41
|
-
|
41
|
+
expectedRotation: resource.body.state.expectedrotation,
|
42
|
+
lastUpdated: new Date(),
|
42
43
|
right: keys[0],
|
43
44
|
left: keys[1]
|
44
45
|
}
|
@@ -64,13 +65,9 @@ class Label extends DeconzService.SensorsResource {
|
|
64
65
|
this.gateway.reExposeAccessory(this.accessory.id)
|
65
66
|
return
|
66
67
|
}
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
state.lastupdated != null &&
|
71
|
-
state.lastupdated !== buttonResource.lastUpdated
|
72
|
-
) {
|
73
|
-
buttonResource.lastUpdated = state.lastupdated
|
68
|
+
const lastUpdated = new Date(state.lastupdated + 'Z')
|
69
|
+
if (lastUpdated > buttonResource.lastUpdated) {
|
70
|
+
buttonResource.lastUpdated = lastUpdated
|
74
71
|
if (buttonResource.buttonEvent !== undefined) {
|
75
72
|
const oldValue = buttonResource.buttonEvent
|
76
73
|
if (state.buttonevent != null) {
|
@@ -85,10 +82,13 @@ class Label extends DeconzService.SensorsResource {
|
|
85
82
|
false // this.accessoryDelegate.settingsService.values.repeat
|
86
83
|
)
|
87
84
|
} else {
|
88
|
-
|
85
|
+
if (state.expectedrotation != null) {
|
86
|
+
buttonResource.expectedRotation = state.expectedrotation
|
87
|
+
}
|
88
|
+
const i = buttonResource.expectedRotation >= 0
|
89
89
|
? buttonResource.right
|
90
90
|
: buttonResource.left
|
91
|
-
this.buttonServices[i]
|
91
|
+
this.buttonServices[i]?.updateRotation()
|
92
92
|
}
|
93
93
|
}
|
94
94
|
super.updateState(state)
|
@@ -43,7 +43,7 @@ class Light extends DeconzService.LightsResource {
|
|
43
43
|
: this.resource.body.state.all_on
|
44
44
|
}).on('didSet', (value, fromHomeKit) => {
|
45
45
|
if (fromHomeKit) {
|
46
|
-
this.
|
46
|
+
this.putState({ on: value })
|
47
47
|
this.updateAdaptiveLighting()
|
48
48
|
}
|
49
49
|
})
|
@@ -55,7 +55,7 @@ class Light extends DeconzService.LightsResource {
|
|
55
55
|
value: this.resource.body.state.any_on
|
56
56
|
}).on('didSet', (value, fromHomeKit) => {
|
57
57
|
if (fromHomeKit) {
|
58
|
-
this.
|
58
|
+
this.putState({ on: value })
|
59
59
|
this.updateAdaptiveLighting()
|
60
60
|
}
|
61
61
|
})
|
@@ -70,7 +70,7 @@ class Light extends DeconzService.LightsResource {
|
|
70
70
|
}).on('didSet', (value, fromHomeKit) => {
|
71
71
|
if (fromHomeKit) {
|
72
72
|
const bri = Math.round(value * 2.54)
|
73
|
-
this.
|
73
|
+
this.putState({ bri })
|
74
74
|
this.updateAdaptiveLighting()
|
75
75
|
}
|
76
76
|
})
|
@@ -80,7 +80,7 @@ class Light extends DeconzService.LightsResource {
|
|
80
80
|
Characteristic: this.Characteristics.my.BrightnessChange,
|
81
81
|
value: 0
|
82
82
|
}).on('didSet', async (value) => {
|
83
|
-
this.
|
83
|
+
this.putState({ bri_inc: Math.round(value * 254.0 / 100.0) })
|
84
84
|
await timeout(this.platform.config.waitTimeReset)
|
85
85
|
this.values.brightnessChange = 0
|
86
86
|
})
|
@@ -104,6 +104,10 @@ class Light extends DeconzService.LightsResource {
|
|
104
104
|
this.addCharacteristicDelegate({
|
105
105
|
key: 'colorExecuteIfOff',
|
106
106
|
value: this.resource.body.config.color.execute_if_off
|
107
|
+
}).on('didSet', async (value) => {
|
108
|
+
try {
|
109
|
+
await this.put('/config', { color: { execute_if_off: value } })
|
110
|
+
} catch (error) { this.warn(error) }
|
107
111
|
})
|
108
112
|
}
|
109
113
|
|
@@ -131,7 +135,7 @@ class Light extends DeconzService.LightsResource {
|
|
131
135
|
const ct = Math.max(ctMin, Math.min(value, ctMax))
|
132
136
|
if (fromHomeKit) {
|
133
137
|
this.checkAdaptiveLighting()
|
134
|
-
this.
|
138
|
+
this.putState({ ct })
|
135
139
|
this.values.colormode = 'ct'
|
136
140
|
}
|
137
141
|
if (this.capabilities.xy && this.values.colormode === 'ct') {
|
@@ -146,7 +150,9 @@ class Light extends DeconzService.LightsResource {
|
|
146
150
|
if (
|
147
151
|
this.resource.body?.capabilities?.color?.xy?.blue == null ||
|
148
152
|
this.resource.body?.capabilities?.color?.xy?.green == null ||
|
149
|
-
this.resource.body?.capabilities?.color?.xy?.red == null
|
153
|
+
this.resource.body?.capabilities?.color?.xy?.red == null ||
|
154
|
+
this.resource.body?.capabilities?.color?.xy?.green?.[1] === 0 ||
|
155
|
+
this.resource.body?.capabilities?.color?.xy?.red?.[0] === 0
|
150
156
|
) {
|
151
157
|
if (this.capabilities.on) {
|
152
158
|
this.warn('using default xy gamut')
|
@@ -169,7 +175,7 @@ class Light extends DeconzService.LightsResource {
|
|
169
175
|
const xy = hsvToXy(
|
170
176
|
value, this.values.saturation, this.capabilities.gamut
|
171
177
|
)
|
172
|
-
this.
|
178
|
+
this.putState({ xy })
|
173
179
|
this.values.colormode = 'xy'
|
174
180
|
}
|
175
181
|
})
|
@@ -180,7 +186,7 @@ class Light extends DeconzService.LightsResource {
|
|
180
186
|
}).on('didSet', (value, fromHomeKit) => {
|
181
187
|
if (fromHomeKit) {
|
182
188
|
const xy = hsvToXy(this.values.hue, value, this.capabilities.gamut)
|
183
|
-
this.
|
189
|
+
this.putState({ xy })
|
184
190
|
this.values.colormode = 'xy'
|
185
191
|
}
|
186
192
|
})
|
@@ -192,7 +198,7 @@ class Light extends DeconzService.LightsResource {
|
|
192
198
|
}).on('didSet', (value, fromHomeKit) => {
|
193
199
|
if (fromHomeKit) {
|
194
200
|
const hue = Math.round(this.values.hue * 65535.0 / 360.0)
|
195
|
-
this.
|
201
|
+
this.putState({ hue })
|
196
202
|
this.values.colormode = 'hs'
|
197
203
|
}
|
198
204
|
})
|
@@ -203,7 +209,7 @@ class Light extends DeconzService.LightsResource {
|
|
203
209
|
}).on('didSet', (value, fromHomeKit) => {
|
204
210
|
if (fromHomeKit) {
|
205
211
|
const sat = Math.round(this.values.saturation * 254.0 / 100.0)
|
206
|
-
this.
|
212
|
+
this.putState({ sat })
|
207
213
|
this.values.colormode = 'hs'
|
208
214
|
}
|
209
215
|
})
|
@@ -220,7 +226,7 @@ class Light extends DeconzService.LightsResource {
|
|
220
226
|
if (value) {
|
221
227
|
state.colorloopspeed = this.values.colorLoopSpeed
|
222
228
|
}
|
223
|
-
this.
|
229
|
+
this.putState(state)
|
224
230
|
this.values.colormode = 'hs'
|
225
231
|
}
|
226
232
|
})
|
@@ -232,7 +238,7 @@ class Light extends DeconzService.LightsResource {
|
|
232
238
|
}).on('didSet', (value, fromHomeKit) => {
|
233
239
|
if (fromHomeKit) {
|
234
240
|
const effect = 'colorloop'
|
235
|
-
this.
|
241
|
+
this.putState({ effect, colorloopspeed: value })
|
236
242
|
this.values.colormode = 'hs'
|
237
243
|
}
|
238
244
|
})
|
@@ -328,7 +334,7 @@ class Light extends DeconzService.LightsResource {
|
|
328
334
|
}).on('didSet', (value, fromHomeKit) => {
|
329
335
|
if (fromHomeKit) {
|
330
336
|
this.checkAdaptiveLighting()
|
331
|
-
this.
|
337
|
+
this.putState({ effect: value ? effect : 'none' })
|
332
338
|
this.values.effectString = value ? effect : 'none'
|
333
339
|
}
|
334
340
|
})
|
@@ -492,10 +498,8 @@ class Light extends DeconzService.LightsResource {
|
|
492
498
|
this.checkAdaptiveLighting()
|
493
499
|
if (fromHomeKit && value) {
|
494
500
|
try {
|
495
|
-
|
496
|
-
|
497
|
-
await this.client.put(path)
|
498
|
-
} catch (error) { this.error(error) }
|
501
|
+
await this.put('/scenes/' + scene.id + '/recall')
|
502
|
+
} catch (error) { this.warn(error) }
|
499
503
|
await timeout(this.platform.config.waitTimeReset)
|
500
504
|
service.values.on = false
|
501
505
|
}
|
@@ -560,7 +564,7 @@ class Light extends DeconzService.LightsResource {
|
|
560
564
|
return
|
561
565
|
}
|
562
566
|
this.debug('adaptive lighting: set Color Temperature to %d mired', ct)
|
563
|
-
this.
|
567
|
+
this.putState({ ct })
|
564
568
|
this.fromAdaptiveLighting = true
|
565
569
|
this.values.colormode = 'ct'
|
566
570
|
if (ct !== this.values.colorTemperature) {
|
@@ -6,16 +6,13 @@
|
|
6
6
|
'use strict'
|
7
7
|
|
8
8
|
const { timeout } = require('homebridge-lib')
|
9
|
-
const { ApiClient } = require('hb-deconz-tools')
|
10
9
|
const DeconzService = require('../DeconzService')
|
11
10
|
|
12
|
-
const { HttpError } = ApiClient
|
13
|
-
|
14
11
|
class LightsResource extends DeconzService {
|
15
12
|
constructor (accessory, resource, params) {
|
16
13
|
super(accessory, resource, params)
|
17
14
|
this.stateKey = resource.rtype === 'groups' ? 'action' : 'state'
|
18
|
-
this.
|
15
|
+
this.statePath = '/' + this.stateKey
|
19
16
|
|
20
17
|
this.updating = 0
|
21
18
|
this.targetState = {}
|
@@ -49,28 +46,25 @@ class LightsResource extends DeconzService {
|
|
49
46
|
}
|
50
47
|
|
51
48
|
async identify () {
|
52
|
-
if (this.capabilities
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
await this.put({ alert: 'stop' })
|
57
|
-
} else {
|
58
|
-
await this.put({ alert: 'select' })
|
59
|
-
}
|
49
|
+
if (this.resource.body?.capabilities?.alerts?.includes('breathe')) {
|
50
|
+
await this.put(this.statePath, { alert: 'breathe' })
|
51
|
+
await timeout(1000)
|
52
|
+
return this.put(this.statePath, { alert: 'finish' })
|
60
53
|
}
|
54
|
+
return this.put(this.statePath, { alert: 'select' })
|
61
55
|
}
|
62
56
|
|
63
|
-
// Collect changes into a combined request.
|
64
|
-
async
|
57
|
+
// Collect state changes into a combined request.
|
58
|
+
async putState (state) {
|
65
59
|
for (const key in state) {
|
66
60
|
this.resource.body[this.stateKey][key] = state[key]
|
67
61
|
this.targetState[key] = state[key]
|
68
62
|
}
|
69
|
-
return this.
|
63
|
+
return this._putState()
|
70
64
|
}
|
71
65
|
|
72
|
-
// Send the request (for the combined changes) to the gateway.
|
73
|
-
async
|
66
|
+
// Send the request (for the combined state changes) to the gateway.
|
67
|
+
async _putState () {
|
74
68
|
try {
|
75
69
|
if (this.platform.config.waitTimeUpdate > 0) {
|
76
70
|
this.updating++
|
@@ -88,7 +82,7 @@ class LightsResource extends DeconzService {
|
|
88
82
|
targetState.transitiontime = this.gateway.transitionTime * 10
|
89
83
|
this.gateway.resetTransitionTime()
|
90
84
|
}
|
91
|
-
if (this.capabilities
|
85
|
+
if (this.resource.body?.capabilities?.transition_block) {
|
92
86
|
if (
|
93
87
|
(
|
94
88
|
targetState.on != null || targetState.bri != null ||
|
@@ -102,16 +96,11 @@ class LightsResource extends DeconzService {
|
|
102
96
|
targetState.transitiontime = 0
|
103
97
|
}
|
104
98
|
}
|
105
|
-
this.
|
106
|
-
await this.client.put(this.rpathState, targetState)
|
99
|
+
await this.put(this.statePath, targetState)
|
107
100
|
this.recentlyUpdated = true
|
108
101
|
await timeout(500)
|
109
102
|
this.recentlyUpdated = false
|
110
|
-
} catch (error) {
|
111
|
-
if (!(error instanceof HttpError)) {
|
112
|
-
this.warn(error)
|
113
|
-
}
|
114
|
-
}
|
103
|
+
} catch (error) { this.warn(error) }
|
115
104
|
}
|
116
105
|
}
|
117
106
|
|
@@ -20,7 +20,7 @@ class Outlet extends DeconzService.LightsResource {
|
|
20
20
|
: this.resource.body.state.all_on
|
21
21
|
}).on('didSet', (value, fromHomeKit) => {
|
22
22
|
if (fromHomeKit) {
|
23
|
-
this.
|
23
|
+
this.putState({ on: value })
|
24
24
|
}
|
25
25
|
})
|
26
26
|
|
@@ -31,7 +31,7 @@ class Outlet extends DeconzService.LightsResource {
|
|
31
31
|
value: this.resource.body.state.any_on
|
32
32
|
}).on('didSet', (value, fromHomeKit) => {
|
33
33
|
if (fromHomeKit) {
|
34
|
-
this.
|
34
|
+
this.putState({ on: value })
|
35
35
|
}
|
36
36
|
})
|
37
37
|
}
|
@@ -42,13 +42,14 @@ class Outlet extends DeconzService.LightsResource {
|
|
42
42
|
value: 1 // Eve interpretes OutletInUse as: device is physically plugged in.
|
43
43
|
})
|
44
44
|
|
45
|
-
this.
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
wallSwitch: false
|
45
|
+
if (this.resource.rtype === 'lights') {
|
46
|
+
this.addCharacteristicDelegate({
|
47
|
+
key: 'wallSwitch',
|
48
|
+
value: false
|
49
|
+
})
|
51
50
|
}
|
51
|
+
|
52
|
+
this.addCharacteristicDelegates()
|
52
53
|
}
|
53
54
|
|
54
55
|
updateState (state) {
|
@@ -63,6 +64,13 @@ class Outlet extends DeconzService.LightsResource {
|
|
63
64
|
this.values.anyOn = value
|
64
65
|
break
|
65
66
|
case 'on':
|
67
|
+
if (this.values.wallSwitch && !state.reachable) {
|
68
|
+
if (this.values.on) {
|
69
|
+
this.log('not reachable: force On to false')
|
70
|
+
}
|
71
|
+
this.values.on = false
|
72
|
+
break
|
73
|
+
}
|
66
74
|
this.values.on = value
|
67
75
|
break
|
68
76
|
default:
|
@@ -8,7 +8,7 @@
|
|
8
8
|
const { ApiClient } = require('hb-deconz-tools')
|
9
9
|
const DeconzService = require('../DeconzService')
|
10
10
|
|
11
|
-
const { dateToString
|
11
|
+
const { dateToString } = ApiClient
|
12
12
|
|
13
13
|
/**
|
14
14
|
* @memberof DeconzService
|
@@ -57,12 +57,6 @@ class SensorsResource extends DeconzService {
|
|
57
57
|
}
|
58
58
|
}
|
59
59
|
|
60
|
-
async identify () {
|
61
|
-
if (this.resource.body.config.alert) {
|
62
|
-
return this.put('/config', { alert: 'select' })
|
63
|
-
}
|
64
|
-
}
|
65
|
-
|
66
60
|
updateState (state) {
|
67
61
|
if (state.lastupdated != null) {
|
68
62
|
this.values.lastUpdated = dateToString(state.lastupdated)
|
@@ -85,13 +79,9 @@ class SensorsResource extends DeconzService {
|
|
85
79
|
}
|
86
80
|
}
|
87
81
|
|
88
|
-
async
|
89
|
-
|
90
|
-
|
91
|
-
} catch (error) {
|
92
|
-
if (!(error instanceof HttpError)) {
|
93
|
-
this.warn(error)
|
94
|
-
}
|
82
|
+
async identify () {
|
83
|
+
if (this.resource.body.config.alert) {
|
84
|
+
return this.put('/config', { alert: 'select' })
|
95
85
|
}
|
96
86
|
}
|
97
87
|
}
|
@@ -5,7 +5,7 @@
|
|
5
5
|
|
6
6
|
'use strict'
|
7
7
|
|
8
|
-
const DeconzService = require('
|
8
|
+
const DeconzService = require('../DeconzService')
|
9
9
|
|
10
10
|
class Switch extends DeconzService.LightsResource {
|
11
11
|
constructor (accessory, resource, params = {}) {
|
@@ -20,7 +20,7 @@ class Switch extends DeconzService.LightsResource {
|
|
20
20
|
: this.resource.body.state.all_on
|
21
21
|
}).on('didSet', (value, fromHomeKit) => {
|
22
22
|
if (fromHomeKit) {
|
23
|
-
this.
|
23
|
+
this.putState({ on: value })
|
24
24
|
}
|
25
25
|
})
|
26
26
|
|
@@ -31,18 +31,19 @@ class Switch extends DeconzService.LightsResource {
|
|
31
31
|
value: this.resource.body.state.any_on
|
32
32
|
}).on('didSet', (value, fromHomeKit) => {
|
33
33
|
if (fromHomeKit) {
|
34
|
-
this.
|
34
|
+
this.putState({ on: value })
|
35
35
|
}
|
36
36
|
})
|
37
37
|
}
|
38
38
|
|
39
|
-
this.
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
wallSwitch: false
|
39
|
+
if (this.resource.rtype === 'lights') {
|
40
|
+
this.addCharacteristicDelegate({
|
41
|
+
key: 'wallSwitch',
|
42
|
+
value: false
|
43
|
+
})
|
45
44
|
}
|
45
|
+
|
46
|
+
this.addCharacteristicDelegates()
|
46
47
|
}
|
47
48
|
|
48
49
|
updateState (state) {
|
@@ -57,6 +58,13 @@ class Switch extends DeconzService.LightsResource {
|
|
57
58
|
this.values.anyOn = value
|
58
59
|
break
|
59
60
|
case 'on':
|
61
|
+
if (this.values.wallSwitch && !state.reachable) {
|
62
|
+
if (this.values.on) {
|
63
|
+
this.log('not reachable: force On to false')
|
64
|
+
}
|
65
|
+
this.values.on = false
|
66
|
+
break
|
67
|
+
}
|
60
68
|
this.values.on = value
|
61
69
|
break
|
62
70
|
default:
|
@@ -19,7 +19,11 @@ class Temperature extends DeconzService.SensorsResource {
|
|
19
19
|
key: 'temperature',
|
20
20
|
Characteristic: this.Characteristics.hap.CurrentTemperature,
|
21
21
|
unit: '°C',
|
22
|
-
props: {
|
22
|
+
props: {
|
23
|
+
minValue: Math.round(resource.body.capabilities?.measured_value?.min ?? -40),
|
24
|
+
maxValue: Math.round(resource.body.capabilities?.measured_value?.max ?? 100),
|
25
|
+
minStep: 0.1
|
26
|
+
},
|
23
27
|
value: 0
|
24
28
|
})
|
25
29
|
|
@@ -47,7 +51,9 @@ class Temperature extends DeconzService.SensorsResource {
|
|
47
51
|
}
|
48
52
|
|
49
53
|
updateState (state) {
|
50
|
-
if (state.
|
54
|
+
if (state.measured_value != null) {
|
55
|
+
this.values.temperature = Math.round(state.measured_value * 10) / 10
|
56
|
+
} else if (state.temperature != null) {
|
51
57
|
this.values.temperature = Math.round(state.temperature / 10) / 10
|
52
58
|
}
|
53
59
|
super.updateState(state)
|
@@ -0,0 +1,125 @@
|
|
1
|
+
// homebridge-deconz/lib/DeconzService/Valve.js
|
2
|
+
// Copyright© 2022-2023 Erik Baauw. All rights reserved.
|
3
|
+
//
|
4
|
+
// Homebridge plugin for deCONZ.
|
5
|
+
|
6
|
+
'use strict'
|
7
|
+
|
8
|
+
const DeconzService = require('../DeconzService')
|
9
|
+
// const timeout = require('homebridge-lib')
|
10
|
+
|
11
|
+
class Switch extends DeconzService.LightsResource {
|
12
|
+
constructor (accessory, resource, params = {}) {
|
13
|
+
params.Service = accessory.Services.hap.Valve
|
14
|
+
super(accessory, resource, params)
|
15
|
+
|
16
|
+
this.addCharacteristicDelegate({
|
17
|
+
key: 'active',
|
18
|
+
Characteristic: this.Characteristics.hap.Active,
|
19
|
+
value: this.capabilities.on
|
20
|
+
? this.resource.body.state.on
|
21
|
+
: this.resource.body.state.all_on
|
22
|
+
}).on('didSet', async (value, fromHomeKit) => {
|
23
|
+
try {
|
24
|
+
if (fromHomeKit) {
|
25
|
+
await this.put(this.statePath, { on: value === this.Characteristics.hap.Active.ACTIVE })
|
26
|
+
}
|
27
|
+
if (this.values.active) {
|
28
|
+
this.values.inUse = this.Characteristics.hap.InUse.IN_USE
|
29
|
+
if (this.values.setDuration > 0) {
|
30
|
+
this.values.remainingDuration = this.values.setDuration
|
31
|
+
this.autoInActive = new Date().valueOf() + this.values.setDuration * 1000
|
32
|
+
this.autoInActiveTimeout = setTimeout(async () => {
|
33
|
+
try {
|
34
|
+
await this.put(this.statePath, { on: false })
|
35
|
+
} catch (error) { this.warn(error) }
|
36
|
+
}, this.values.setDuration * 1000)
|
37
|
+
}
|
38
|
+
} else {
|
39
|
+
this.values.inUse = this.Characteristics.hap.InUse.NOT_IN_USE
|
40
|
+
if (this.autoInActiveTimeout != null) {
|
41
|
+
clearTimeout(this.autoInActiveTimeout)
|
42
|
+
delete this.autoInActiveTimeout
|
43
|
+
delete this.autoInActive
|
44
|
+
this.values.remainingDuration = 0
|
45
|
+
}
|
46
|
+
}
|
47
|
+
} catch (error) { this.warn(error) }
|
48
|
+
})
|
49
|
+
|
50
|
+
this.addCharacteristicDelegate({
|
51
|
+
key: 'inUse',
|
52
|
+
Characteristic: this.Characteristics.hap.InUse,
|
53
|
+
value: this.Characteristics.hap.InUse.NOT_IN_USE
|
54
|
+
})
|
55
|
+
|
56
|
+
this.addCharacteristicDelegate({
|
57
|
+
key: 'remainingDuration',
|
58
|
+
Characteristic: this.Characteristics.hap.RemainingDuration,
|
59
|
+
value: 0,
|
60
|
+
props: {
|
61
|
+
maxValue: 4 * 3600
|
62
|
+
},
|
63
|
+
getter: async () => {
|
64
|
+
const remaining = this.autoInActive - new Date().valueOf()
|
65
|
+
return remaining > 0 ? Math.round(remaining / 1000) : 0
|
66
|
+
}
|
67
|
+
})
|
68
|
+
|
69
|
+
this.addCharacteristicDelegate({
|
70
|
+
key: 'setDuration',
|
71
|
+
Characteristic: this.Characteristics.hap.SetDuration,
|
72
|
+
value: 300,
|
73
|
+
props: {
|
74
|
+
maxValue: 4 * 3600
|
75
|
+
}
|
76
|
+
})
|
77
|
+
|
78
|
+
this.addCharacteristicDelegate({
|
79
|
+
key: 'valveType',
|
80
|
+
Characteristic: this.Characteristics.hap.ValveType,
|
81
|
+
value: this.Characteristics.hap.ValveType.GENERIC_VALVE
|
82
|
+
})
|
83
|
+
|
84
|
+
if (this.resource.rtype === 'lights') {
|
85
|
+
this.addCharacteristicDelegate({
|
86
|
+
key: 'wallSwitch',
|
87
|
+
value: false
|
88
|
+
})
|
89
|
+
}
|
90
|
+
|
91
|
+
this.addCharacteristicDelegates()
|
92
|
+
|
93
|
+
this.values.active = this.Characteristics.hap.Active.INACTIVE
|
94
|
+
}
|
95
|
+
|
96
|
+
updateState (state) {
|
97
|
+
for (const key in state) {
|
98
|
+
const value = state[key]
|
99
|
+
this.resource.body.state[key] = value
|
100
|
+
switch (key) {
|
101
|
+
case 'on':
|
102
|
+
if (this.values.wallSwitch && !state.reachable) {
|
103
|
+
this.log('not reachable: force Active to false')
|
104
|
+
this.values.active = this.Characteristics.hap.Active.INACTIVE
|
105
|
+
this.values.inUse = this.Characteristics.hap.InUse.NOT_IN_USE
|
106
|
+
break
|
107
|
+
}
|
108
|
+
// falls through
|
109
|
+
case 'all_on':
|
110
|
+
this.values.active = value
|
111
|
+
? this.Characteristics.hap.Active.ACTIVE
|
112
|
+
: this.Characteristics.hap.Active.INACTIVE
|
113
|
+
this.values.inUse = value
|
114
|
+
? this.Characteristics.hap.InUse.IN_USE
|
115
|
+
: this.Characteristics.hap.InUse.NOT_IN_USE
|
116
|
+
break
|
117
|
+
default:
|
118
|
+
break
|
119
|
+
}
|
120
|
+
}
|
121
|
+
super.updateState(state)
|
122
|
+
}
|
123
|
+
}
|
124
|
+
|
125
|
+
module.exports = Switch
|
@@ -6,6 +6,7 @@
|
|
6
6
|
'use strict'
|
7
7
|
|
8
8
|
const DeconzService = require('../DeconzService')
|
9
|
+
const { timeout } = require('homebridge-lib')
|
9
10
|
|
10
11
|
class WarningDevice extends DeconzService.LightsResource {
|
11
12
|
constructor (accessory, resource, params = {}) {
|
@@ -33,11 +34,10 @@ class WarningDevice extends DeconzService.LightsResource {
|
|
33
34
|
body = { alert: 'lselect', ontime: onTime }
|
34
35
|
}
|
35
36
|
}
|
36
|
-
this.put(body)
|
37
|
+
await this.put(this.statePath, body)
|
37
38
|
if (value) {
|
38
|
-
|
39
|
-
|
40
|
-
}, onTime * 1000)
|
39
|
+
await timeout(onTime * 1000)
|
40
|
+
this.values.on = false
|
41
41
|
}
|
42
42
|
}
|
43
43
|
})
|
@@ -5,8 +5,8 @@
|
|
5
5
|
|
6
6
|
'use strict'
|
7
7
|
|
8
|
-
const { timeout } = require('homebridge-lib')
|
9
8
|
const DeconzService = require('../DeconzService')
|
9
|
+
const { timeout } = require('homebridge-lib')
|
10
10
|
|
11
11
|
class WindowCovering extends DeconzService.LightsResource {
|
12
12
|
constructor (accessory, resource, params = {}) {
|
@@ -34,7 +34,7 @@ class WindowCovering extends DeconzService.LightsResource {
|
|
34
34
|
return
|
35
35
|
}
|
36
36
|
this.values.targetPosition = Math.round(this.values.targetPosition / 5) * 5
|
37
|
-
|
37
|
+
return this.setPosition()
|
38
38
|
})
|
39
39
|
|
40
40
|
this.addCharacteristicDelegate({
|
@@ -46,8 +46,8 @@ class WindowCovering extends DeconzService.LightsResource {
|
|
46
46
|
this.addCharacteristicDelegate({
|
47
47
|
key: 'holdPosition',
|
48
48
|
Characteristic: this.Characteristics.hap.HoldPosition
|
49
|
-
}).on('didSet', () => {
|
50
|
-
this.put({ stop: true })
|
49
|
+
}).on('didSet', async () => {
|
50
|
+
await this.put(this.statePath, { stop: true })
|
51
51
|
this.values.positionState = this.Characteristics.hap.PositionState.STOPPED
|
52
52
|
})
|
53
53
|
|
@@ -60,7 +60,7 @@ class WindowCovering extends DeconzService.LightsResource {
|
|
60
60
|
return
|
61
61
|
}
|
62
62
|
if (this.values.currentPosition !== 100) {
|
63
|
-
|
63
|
+
return this.setPosition()
|
64
64
|
}
|
65
65
|
})
|
66
66
|
}
|
@@ -80,7 +80,7 @@ class WindowCovering extends DeconzService.LightsResource {
|
|
80
80
|
if (!fromHomeKit) {
|
81
81
|
return
|
82
82
|
}
|
83
|
-
await this.put({ speed: value })
|
83
|
+
await this.put('/config', { speed: value })
|
84
84
|
})
|
85
85
|
}
|
86
86
|
|
@@ -90,7 +90,7 @@ class WindowCovering extends DeconzService.LightsResource {
|
|
90
90
|
Characteristic: this.Characteristics.my.PositionChange
|
91
91
|
}).on('didSet', async (value) => {
|
92
92
|
if (value !== 0) {
|
93
|
-
this.put({ lift_inc: -value })
|
93
|
+
await this.put(this.statePath, { lift_inc: -value })
|
94
94
|
await timeout(this.platform.config.waitTimeReset)
|
95
95
|
this.values.positionChange = 0
|
96
96
|
}
|
@@ -120,7 +120,7 @@ class WindowCovering extends DeconzService.LightsResource {
|
|
120
120
|
? this.Characteristics.hap.PositionState.INCREASING
|
121
121
|
: this.Characteristics.hap.PositionState.DECREASING
|
122
122
|
this.moving = new Date()
|
123
|
-
|
123
|
+
return this.put(this.statePath, { lift })
|
124
124
|
}
|
125
125
|
|
126
126
|
updateState (state) {
|
@@ -8,7 +8,7 @@
|
|
8
8
|
const { ApiClient } = require('hb-deconz-tools')
|
9
9
|
const { ServiceDelegate } = require('homebridge-lib')
|
10
10
|
|
11
|
-
const { dateToString } = ApiClient
|
11
|
+
const { HttpError, dateToString } = ApiClient
|
12
12
|
|
13
13
|
/** Service delegates.
|
14
14
|
* @extends ServiceDelegate
|
@@ -42,6 +42,7 @@ class DeconzService extends ServiceDelegate {
|
|
42
42
|
static get Switch () { return require('./Switch') }
|
43
43
|
static get Temperature () { return require('./Temperature') }
|
44
44
|
static get Thermostat () { return require('./Thermostat') }
|
45
|
+
static get Valve () { return require('./Valve') }
|
45
46
|
static get WarningDevice () { return require('./WarningDevice') }
|
46
47
|
static get WindowCovering () { return require('./WindowCovering') }
|
47
48
|
|
@@ -115,6 +116,17 @@ class DeconzService extends ServiceDelegate {
|
|
115
116
|
}
|
116
117
|
}
|
117
118
|
}
|
119
|
+
|
120
|
+
async put (path, body) {
|
121
|
+
this.debug('PUT %s %j', path, body)
|
122
|
+
try {
|
123
|
+
await this.client.put(this.rpath + path, body)
|
124
|
+
} catch (error) {
|
125
|
+
if (!(error instanceof HttpError)) {
|
126
|
+
this.warn(error)
|
127
|
+
}
|
128
|
+
}
|
129
|
+
}
|
118
130
|
}
|
119
131
|
|
120
132
|
module.exports = DeconzService
|
package/package.json
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
"displayName": "Homebridge deCONZ",
|
5
5
|
"author": "Erik Baauw",
|
6
6
|
"license": "Apache-2.0",
|
7
|
-
"version": "1.0.
|
7
|
+
"version": "1.0.2",
|
8
8
|
"keywords": [
|
9
9
|
"homebridge-plugin",
|
10
10
|
"homekit",
|
@@ -22,13 +22,13 @@
|
|
22
22
|
"ui": "cli/ui.js"
|
23
23
|
},
|
24
24
|
"engines": {
|
25
|
-
"deCONZ": "2.
|
25
|
+
"deCONZ": "2.23.1",
|
26
26
|
"homebridge": "^1.6.1",
|
27
|
-
"node": "
|
27
|
+
"node": "20.7.0||^20||^18"
|
28
28
|
},
|
29
29
|
"dependencies": {
|
30
|
-
"hb-deconz-tools": "~1.0.
|
31
|
-
"homebridge-lib": "~6.
|
30
|
+
"hb-deconz-tools": "~1.0.7",
|
31
|
+
"homebridge-lib": "~6.6.0"
|
32
32
|
},
|
33
33
|
"scripts": {
|
34
34
|
"prepare": "standard && rm -rf out && jsdoc -c jsdoc.json",
|