homebridge-deconz 0.1.10 → 0.1.11
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/cli/deconz.js +1 -4
- package/homebridge-ui/public/index.old.html +3 -5
- package/lib/Deconz/Resource.js +91 -161
- package/lib/DeconzAccessory/Gateway.js +66 -21
- package/lib/DeconzAccessory/Light.js +3 -3
- package/lib/DeconzAccessory/Sensor.js +23 -18
- package/lib/DeconzAccessory/index.js +26 -38
- package/lib/DeconzService/Humidity.js +3 -0
- package/lib/DeconzService/Label.js +103 -0
- package/lib/DeconzService/Light.js +6 -2
- package/lib/DeconzService/Outlet.js +1 -1
- package/lib/DeconzService/Status.js +25 -1
- package/lib/DeconzService/Switch.js +43 -78
- package/lib/DeconzService/index.js +1 -0
- package/package.json +4 -4
package/cli/deconz.js
CHANGED
@@ -514,10 +514,7 @@ class Main extends CommandLineTool {
|
|
514
514
|
|
515
515
|
this.bridgeid = this.gatewayConfig.bridgeid
|
516
516
|
if (this.clargs.options.apiKey == null) {
|
517
|
-
if (
|
518
|
-
this.gateways[this.bridgeid] != null &&
|
519
|
-
this.gateways[this.bridgeid].apiKey != null
|
520
|
-
) {
|
517
|
+
if (this.gateways[this.bridgeid]?.apiKey != null) {
|
521
518
|
this.clargs.options.apiKey = this.gateways[this.bridgeid].apiKey
|
522
519
|
} else if (process.env.DECONZ_API_KEY != null) {
|
523
520
|
this.clargs.options.apiKey = process.env.DECONZ_API_KEY
|
@@ -32,8 +32,7 @@ async function showFormPluginConfig () {
|
|
32
32
|
console.log('%s: cachedAccessories: %o', config.name, cachedAccessories)
|
33
33
|
const cachedGateways = cachedAccessories.filter((accessory) => {
|
34
34
|
return accessory.plugin === 'homebridge-deconz' &&
|
35
|
-
accessory.context
|
36
|
-
accessory.context.className === 'Gateway'
|
35
|
+
accessory.context?.className === 'Gateway'
|
37
36
|
})
|
38
37
|
const result = {}
|
39
38
|
for (const gateway of cachedGateways) {
|
@@ -207,8 +206,7 @@ async function showFormGateways (gateway) {
|
|
207
206
|
const cachedAccessories = await homebridge.getCachedAccessories()
|
208
207
|
const cachedGateways = cachedAccessories.filter((accessory) => {
|
209
208
|
return accessory.plugin === 'homebridge-deconz' &&
|
210
|
-
accessory.context
|
211
|
-
accessory.context.className === 'Gateway'
|
209
|
+
accessory.context?.className === 'Gateway'
|
212
210
|
})
|
213
211
|
const result = {}
|
214
212
|
for (const gateway of cachedGateways) {
|
@@ -249,7 +247,7 @@ async function showFormGateways (gateway) {
|
|
249
247
|
// layout: null,
|
250
248
|
// form: null
|
251
249
|
// }, {
|
252
|
-
// gateway: gateway
|
250
|
+
// gateway: gateway ?? gateways[0]
|
253
251
|
// }, 'Gateway Settings', 'Homebridge Settings')
|
254
252
|
const form = homebridge.createForm({
|
255
253
|
footerDisplay: 'For a detailed description, see the [wiki](https://github.com/ebaauw/homebridge-deconz/wiki/Configuration).',
|
package/lib/Deconz/Resource.js
CHANGED
@@ -5,13 +5,12 @@
|
|
5
5
|
|
6
6
|
'use strict'
|
7
7
|
|
8
|
-
const {
|
8
|
+
const { OptionParser } = require('homebridge-lib')
|
9
9
|
const Deconz = require('../Deconz')
|
10
10
|
const DeconzAccessory = require('../DeconzAccessory')
|
11
11
|
const DeconzService = require('../DeconzService')
|
12
12
|
|
13
13
|
const { toInstance, toInt, toObject, toString } = OptionParser
|
14
|
-
const { defaultGamut } = Colour
|
15
14
|
const { buttonEvent } = Deconz.ApiClient
|
16
15
|
const { SINGLE, DOUBLE, LONG } = DeconzService.Button
|
17
16
|
const rtypes = ['lights', 'sensors', 'groups']
|
@@ -54,9 +53,9 @@ class Resource {
|
|
54
53
|
toString('uniqueid', uniqueid, true)
|
55
54
|
const a = uniqueid.replace(/:/g, '').toUpperCase().split('-')
|
56
55
|
return {
|
57
|
-
mac: a
|
58
|
-
endpoint: a
|
59
|
-
cluster: a
|
56
|
+
mac: a?.[0],
|
57
|
+
endpoint: a?.[1],
|
58
|
+
cluster: a?.[2]
|
60
59
|
}
|
61
60
|
}
|
62
61
|
|
@@ -130,6 +129,11 @@ class Resource {
|
|
130
129
|
* @type {boolean}
|
131
130
|
*/
|
132
131
|
this.zigbee = true
|
132
|
+
} else if (this.isDeconzClip()) {
|
133
|
+
const a = body.uniqueid.split('-')
|
134
|
+
this.id = gateway.id + '-M' + a?.[0]
|
135
|
+
this.subtype = a?.[1]
|
136
|
+
this.zigbee = false
|
133
137
|
} else {
|
134
138
|
this.subtype = rtype[0].toUpperCase() + rid
|
135
139
|
this.id = gateway.id + '-' + this.subtype
|
@@ -166,58 +170,16 @@ class Resource {
|
|
166
170
|
? body.swversion == null ? '0.0.0' : body.swversion
|
167
171
|
: gateway.values.software
|
168
172
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
ctMax: (body.ctmax != null && body.ctmax !== 0 && body.ctmax !== 65535)
|
180
|
-
? body.ctmax
|
181
|
-
: 500,
|
182
|
-
ctMin: (body.ctmin != null && body.ctmin !== 0)
|
183
|
-
? body.ctmin
|
184
|
-
: 153,
|
185
|
-
xy: body.state.xy !== undefined,
|
186
|
-
gamut: defaultGamut,
|
187
|
-
alert: body.state.alert !== undefined,
|
188
|
-
colorLoop: body.state.effect !== undefined
|
189
|
-
}
|
190
|
-
if (body.capabilities != null) {
|
191
|
-
const cap = body.capabilities
|
192
|
-
if (cap.color != null) {
|
193
|
-
if (cap.color.ct != null) {
|
194
|
-
this.capabilities.ctMax = cap.color.ct.max
|
195
|
-
this.capabilities.ctMin = cap.color.ct.min
|
196
|
-
}
|
197
|
-
if (cap.color.effects != null && cap.color.effects.length > 2) {
|
198
|
-
this.capabilities.effects = cap.color.effects.slice(2)
|
199
|
-
}
|
200
|
-
if (cap.color.xy != null) {
|
201
|
-
this.capabilities.gamut = {
|
202
|
-
r: cap.color.xy.red,
|
203
|
-
g: cap.color.xy.green,
|
204
|
-
b: cap.color.xy.blue
|
205
|
-
}
|
206
|
-
}
|
207
|
-
}
|
208
|
-
}
|
209
|
-
break
|
210
|
-
}
|
211
|
-
case 'WarningDevice':
|
212
|
-
this.capabilities = {}
|
213
|
-
break
|
214
|
-
case 'WindowCovering':
|
215
|
-
this.capabilities = {}
|
216
|
-
break
|
217
|
-
default:
|
218
|
-
this.capabilities = {}
|
219
|
-
break
|
220
|
-
}
|
173
|
+
/** The name of the {@link DeconzService} subclass of the delegate of the
|
174
|
+
* corresponding HomeKit service, or `null` for unsupported and unknown
|
175
|
+
* resources.
|
176
|
+
*
|
177
|
+
* This is derived from the resource type and `type` in the resource body.
|
178
|
+
* @type {string}
|
179
|
+
*/
|
180
|
+
this.serviceName = this._serviceName
|
181
|
+
|
182
|
+
this.capabilities = {}
|
221
183
|
|
222
184
|
const f = 'patch' + this.serviceName
|
223
185
|
if (typeof this[f] === 'function') {
|
@@ -230,8 +192,12 @@ class Resource {
|
|
230
192
|
* @type {integer}
|
231
193
|
*/
|
232
194
|
get prio () {
|
233
|
-
if (this.rtype === 'groups')
|
234
|
-
|
195
|
+
if (this.rtype === 'groups') {
|
196
|
+
return -1
|
197
|
+
}
|
198
|
+
if (this.rtype === 'lights' || this.isDeconzClip()) {
|
199
|
+
return 0xFF - this.endpoint
|
200
|
+
}
|
235
201
|
return sensorsPrios.indexOf(this.serviceName)
|
236
202
|
}
|
237
203
|
|
@@ -242,14 +208,7 @@ class Resource {
|
|
242
208
|
*/
|
243
209
|
get rpath () { return '/' + this.rtype + '/' + this.rid }
|
244
210
|
|
245
|
-
|
246
|
-
* corresponding HomeKit service, or `null` for unsupported and unknown
|
247
|
-
* resources.
|
248
|
-
*
|
249
|
-
* This is derived from the resource type and `type` in the resource body.
|
250
|
-
* @type {string}
|
251
|
-
*/
|
252
|
-
get serviceName () {
|
211
|
+
get _serviceName () {
|
253
212
|
if (this.rtype === 'groups') {
|
254
213
|
return 'Light'
|
255
214
|
} else if (this.rtype === 'lights') {
|
@@ -267,11 +226,11 @@ class Resource {
|
|
267
226
|
// case 'Door Lock': return null
|
268
227
|
// case 'Door Lock Unit': return null
|
269
228
|
case 'Fan': return 'Light'
|
270
|
-
case 'On/Off light switch': return '
|
229
|
+
case 'On/Off light switch': return 'Switch'
|
271
230
|
case 'On/Off light': return 'Light'
|
272
|
-
case 'On/Off output': return '
|
273
|
-
case 'On/Off plug-in unit': return '
|
274
|
-
case 'Smart plug': return '
|
231
|
+
case 'On/Off output': return 'Outlet'
|
232
|
+
case 'On/Off plug-in unit': return 'Outlet'
|
233
|
+
case 'Smart plug': return 'Outlet'
|
275
234
|
case 'Configuration tool': return ''
|
276
235
|
case 'Range extender': return ''
|
277
236
|
case 'Warning device': return 'WarningDevice'
|
@@ -280,55 +239,34 @@ class Resource {
|
|
280
239
|
default: return null
|
281
240
|
}
|
282
241
|
} else { // (this.rtype === 'sensors')
|
283
|
-
|
284
|
-
|
285
|
-
case '
|
286
|
-
case '
|
287
|
-
case '
|
288
|
-
case '
|
289
|
-
case '
|
290
|
-
case '
|
291
|
-
case '
|
292
|
-
case 'ZHACarbonMonoxide':
|
293
|
-
case 'CLIPCarbonMonoxide': return 'CarbonMonoxide'
|
294
|
-
case 'ZHAConsumption':
|
295
|
-
case 'CLIPConsumption': return 'Consumption'
|
296
|
-
// case 'ZHADoorLock':
|
297
|
-
// case 'CLIPDoorLock': return null
|
242
|
+
const type = /^(CLIP|ZHA|ZGP)?([A-Za-z]*)$/.exec(this.body.type)?.[2]
|
243
|
+
switch (type) {
|
244
|
+
case 'AirPurifier': return 'AirPurifier'
|
245
|
+
case 'AirQuality': return 'AirQuality'
|
246
|
+
case 'Alarm': return 'Alarm'
|
247
|
+
case 'Battery': return 'Battery'
|
248
|
+
case 'CarbonMonoxide': return 'CarbonMonoxide'
|
249
|
+
case 'Consumption': return 'Consumption'
|
250
|
+
// case 'DoorLock': return null
|
298
251
|
case 'Daylight': return 'Daylight'
|
299
|
-
case '
|
300
|
-
case '
|
301
|
-
case '
|
302
|
-
case '
|
303
|
-
case '
|
304
|
-
case '
|
305
|
-
case '
|
306
|
-
case '
|
307
|
-
|
308
|
-
|
309
|
-
case '
|
310
|
-
case '
|
311
|
-
case '
|
312
|
-
case '
|
313
|
-
case '
|
314
|
-
case '
|
315
|
-
case '
|
316
|
-
case '
|
317
|
-
case 'ZHARelativeRotary': return 'Switch'
|
318
|
-
case 'ZHASpectral': return ''
|
319
|
-
case 'ZGPSwitch':
|
320
|
-
case 'ZHASwitch':
|
321
|
-
case 'CLIPSwitch': return 'Switch'
|
322
|
-
case 'ZHATemperature':
|
323
|
-
case 'CLIPTemperature': return 'Temperature'
|
324
|
-
case 'ZHAThermostat':
|
325
|
-
case 'CLIPThermostat': return 'Thermostat'
|
326
|
-
case 'ZHATime':
|
327
|
-
case 'CLIPTime': return ''
|
328
|
-
case 'ZHAVibration':
|
329
|
-
case 'CLIPVibration': return 'Motion'
|
330
|
-
case 'ZHAWater':
|
331
|
-
case 'CLIPWater': return 'Leak'
|
252
|
+
case 'Fire': return 'Smoke'
|
253
|
+
case 'GenericFlag': return 'Flag'
|
254
|
+
case 'GenericStatus': return 'Status'
|
255
|
+
case 'Humidity': return 'Humidity'
|
256
|
+
case 'LightLevel': return 'LightLevel'
|
257
|
+
case 'Moisture': return 'Humidity'
|
258
|
+
case 'OpenClose': return 'Contact'
|
259
|
+
case 'Power': return 'Power'
|
260
|
+
case 'Presence': return 'Motion'
|
261
|
+
case 'Pressure': return 'AirPressure'
|
262
|
+
case 'RelativeRotary': return 'Label'
|
263
|
+
case 'Spectral': return ''
|
264
|
+
case 'Switch': return 'Label'
|
265
|
+
case 'Temperature': return 'Temperature'
|
266
|
+
case 'Thermostat': return 'Thermostat'
|
267
|
+
case 'Time': return ''
|
268
|
+
case 'Vibration': return 'Motion'
|
269
|
+
case 'Water': return 'Leak'
|
332
270
|
default: return null
|
333
271
|
}
|
334
272
|
}
|
@@ -389,63 +327,48 @@ class Resource {
|
|
389
327
|
}
|
390
328
|
}
|
391
329
|
|
330
|
+
/** Check whether resource is a CLIP sensor for Homebridge deCONZ.
|
331
|
+
* For these, we use the following conventions:
|
332
|
+
* - Use `uniqueid` to indicate device ID and subtype;
|
333
|
+
* - Use `swversion` to indicate readonly and range for `Flag` and `Status`.
|
334
|
+
*/
|
335
|
+
isDeconzClip () {
|
336
|
+
return this.rtype === 'sensors' &&
|
337
|
+
this.body.type.startsWith('CLIP') &&
|
338
|
+
this.body.modelid === this.body.type && (
|
339
|
+
this.body.manufacturername === 'homebridge-deconz' ||
|
340
|
+
this.body.manufacturername === 'homebridge-hue'
|
341
|
+
)
|
342
|
+
}
|
343
|
+
|
392
344
|
/** Patch a resource corresponding to a `Flag` service.
|
393
|
-
* @param {DeconzAccessory.Gateway} gateway - The gateway.
|
394
345
|
*/
|
395
|
-
patchFlag (
|
396
|
-
if (
|
397
|
-
this.body.manufacturername === 'homebridge-hue' &&
|
398
|
-
this.body.modelid === 'CLIPGenericFlag' &&
|
399
|
-
this.body.swversion === '0'
|
400
|
-
) {
|
346
|
+
patchFlag () {
|
347
|
+
if (this.isDeconzClip() && this.body.swversion === '0') {
|
401
348
|
this.capabilities.readonly = true
|
402
349
|
}
|
403
350
|
}
|
404
351
|
|
405
|
-
/** Patch a resource corresponding to a `
|
406
|
-
* @param {DeconzAccessory.Gateway} gateway - The gateway.
|
352
|
+
/** Patch a resource corresponding to a `Status` service.
|
407
353
|
*/
|
408
|
-
patchStatus (
|
409
|
-
if (
|
410
|
-
this.body.manufacturername === 'homebridge-hue' &&
|
411
|
-
this.body.modelid === 'CLIPGenericStatus'
|
412
|
-
) {
|
354
|
+
patchStatus () {
|
355
|
+
if (this.isDeconzClip()) {
|
413
356
|
const a = this.body.swversion.split(',')
|
414
357
|
const min = parseInt(a[0])
|
415
358
|
const max = parseInt(a[1])
|
416
|
-
const step = parseInt(a[2])
|
417
|
-
// Eve 3.1 displays the following controls, depending on the properties:
|
418
|
-
// 1. {minValue: 0, maxValue: 1, minStep: 1} switch
|
419
|
-
// 2. {minValue: a, maxValue: b, minStep: 1}, 1 < b - a <= 20 down|up
|
420
|
-
// 3. {minValue: a, maxValue: b}, (a, b) != (0, 1) slider
|
421
|
-
// 4. {minValue: a, maxValue: b, minStep: 1}, b - a > 20 slider
|
422
|
-
// Avoid the following bugs:
|
423
|
-
// 5. {minValue: 0, maxValue: 1} nothing
|
424
|
-
// 6. {minValue: a, maxValue: b, minStep: 1}, b - a = 1 switch*
|
425
|
-
// *) switch sends values 0 and 1 instead of a and b;
|
426
359
|
if (min === 0 && max === 0) {
|
427
360
|
this.capabilities.readonly = true
|
428
361
|
} else if (min >= -127 && max <= 127 && min < max) {
|
429
|
-
|
430
|
-
|
431
|
-
this.capabilities.props = { minValue: min, maxValue: max, minStep: 1 }
|
432
|
-
} else if (max - min === 1) {
|
433
|
-
// Workaround Eve bug (case 6 above).
|
434
|
-
this.capabilities.props = { minValue: min, maxValue: max }
|
435
|
-
} else if (step !== 1) {
|
436
|
-
// Default to slider for backwards compatibility.
|
437
|
-
this.capabilities.props = { minValue: min, maxValue: max }
|
438
|
-
} else {
|
439
|
-
this.capabilities.props = { minValue: min, maxValue: max, minStep: 1 }
|
440
|
-
}
|
362
|
+
this.capabilities.min = min
|
363
|
+
this.capabilities.max = max
|
441
364
|
}
|
442
365
|
}
|
443
366
|
}
|
444
367
|
|
445
|
-
/** Patch a resource corresponding to a `
|
368
|
+
/** Patch a resource corresponding to a `Label` service.
|
446
369
|
* @param {DeconzAccessory.Gateway} gateway - The gateway.
|
447
370
|
*/
|
448
|
-
|
371
|
+
patchLabel (gateway) {
|
449
372
|
const buttons = []
|
450
373
|
let dots = false
|
451
374
|
switch (this.manufacturer) {
|
@@ -536,6 +459,15 @@ class Resource {
|
|
536
459
|
buttons.push([3, 'Turn Left', SINGLE])
|
537
460
|
}
|
538
461
|
break
|
462
|
+
case 'SYMFONISK sound remote gen2':
|
463
|
+
buttons.push([1, 'Play', SINGLE])
|
464
|
+
buttons.push([2, 'Plus', SINGLE | LONG, true])
|
465
|
+
buttons.push([3, 'Minus', SINGLE | LONG, true])
|
466
|
+
buttons.push([4, 'Previous', SINGLE])
|
467
|
+
buttons.push([5, 'Next', SINGLE])
|
468
|
+
buttons.push([6, 'One Dot', SINGLE | DOUBLE | LONG])
|
469
|
+
buttons.push([7, 'Two Dots', SINGLE | DOUBLE | LONG])
|
470
|
+
break
|
539
471
|
case 'TRADFRI SHORTCUT Button':
|
540
472
|
buttons.push([1, 'Button', SINGLE | DOUBLE | LONG])
|
541
473
|
break
|
@@ -1124,9 +1056,8 @@ class Resource {
|
|
1124
1056
|
}
|
1125
1057
|
|
1126
1058
|
/** Patch a resource corresponding to a `Thermostat` service.
|
1127
|
-
* @param {DeconzAccessory.Gateway} gateway - The gateway.
|
1128
1059
|
*/
|
1129
|
-
patchThermostat (
|
1060
|
+
patchThermostat () {
|
1130
1061
|
if (this.manufacturer === 'ELKO' && this.model === 'Super TR') {
|
1131
1062
|
this.capabilities.heatValue = 'heat'
|
1132
1063
|
} else {
|
@@ -1135,9 +1066,8 @@ class Resource {
|
|
1135
1066
|
}
|
1136
1067
|
|
1137
1068
|
/** Patch a resource corresponding to a `WindowCovering` service.
|
1138
|
-
* @param {DeconzAccessory.Gateway} gateway - The gateway.
|
1139
1069
|
*/
|
1140
|
-
patchWindowCovering (
|
1070
|
+
patchWindowCovering () {
|
1141
1071
|
if (this.manufacturer === 'LUMI' && this.model === 'lumi.curtain.acn002') {
|
1142
1072
|
this.capabilities.maxSpeed = 2
|
1143
1073
|
this.capabilities.positionChange = true
|
@@ -13,6 +13,12 @@ const Deconz = require('../Deconz')
|
|
13
13
|
const DeconzAccessory = require('../DeconzAccessory')
|
14
14
|
const DeconzService = require('../DeconzService')
|
15
15
|
|
16
|
+
const migration = {
|
17
|
+
name: 'homebridge-deconz',
|
18
|
+
description: 'migration',
|
19
|
+
classid: 1
|
20
|
+
}
|
21
|
+
|
16
22
|
const rtypes = ['lights', 'sensors', 'groups']
|
17
23
|
|
18
24
|
const periodicEvents = [
|
@@ -516,9 +522,7 @@ class Gateway extends AccessoryDelegate {
|
|
516
522
|
}
|
517
523
|
try {
|
518
524
|
try {
|
519
|
-
|
520
|
-
await this.client.delete(this.context.migration)
|
521
|
-
}
|
525
|
+
await this.deleteMigration()
|
522
526
|
await this.client.deleteApiKey()
|
523
527
|
} catch (error) {}
|
524
528
|
this.values.apiKey = null
|
@@ -531,7 +535,6 @@ class Gateway extends AccessoryDelegate {
|
|
531
535
|
this.exposeErrors = {}
|
532
536
|
this.context.settingsById = {}
|
533
537
|
this.context.fullState = null
|
534
|
-
this.context.migration = null
|
535
538
|
this.values.logLevel = 2
|
536
539
|
} catch (error) { this.error(error) }
|
537
540
|
}
|
@@ -669,6 +672,60 @@ class Gateway extends AccessoryDelegate {
|
|
669
672
|
this.deleteService(id)
|
670
673
|
}
|
671
674
|
|
675
|
+
/** Assert that migration resourcelink exists and is valid.
|
676
|
+
*/
|
677
|
+
async checkMigration () {
|
678
|
+
if (this.context.migration != null) {
|
679
|
+
try {
|
680
|
+
const response = await this.client.get(this.context.migration)
|
681
|
+
if (
|
682
|
+
response.name !== migration.name ||
|
683
|
+
response.description !== migration.description ||
|
684
|
+
response.classid !== migration.classid ||
|
685
|
+
response.owner !== this.client.apiKey
|
686
|
+
) {
|
687
|
+
// not my migration resourcelink
|
688
|
+
this.warn('%s: migration resourcelink no longer valid', this.context.migration)
|
689
|
+
this.context.migration = null
|
690
|
+
}
|
691
|
+
} catch (error) {
|
692
|
+
if (error.statusCode === 404) {
|
693
|
+
this.warn('%s: migration resourcelink no longer exists', this.context.migration)
|
694
|
+
this.context.migration = null
|
695
|
+
}
|
696
|
+
}
|
697
|
+
}
|
698
|
+
}
|
699
|
+
|
700
|
+
/** Create or update migration resourcelink.
|
701
|
+
*/
|
702
|
+
async updateMigration () {
|
703
|
+
await this.checkMigration()
|
704
|
+
if (this.context.migration == null) {
|
705
|
+
const response = await this.client.post('/resourcelinks', {
|
706
|
+
name: migration.name,
|
707
|
+
description: migration.description,
|
708
|
+
classid: migration.classid,
|
709
|
+
links: Object.keys(this.accessoryByRpath).sort()
|
710
|
+
})
|
711
|
+
this.context.migration = '/resourcelinks/' + response.success.id
|
712
|
+
} else {
|
713
|
+
await this.client.put(this.context.migration, {
|
714
|
+
links: Object.keys(this.accessoryByRpath).sort()
|
715
|
+
})
|
716
|
+
}
|
717
|
+
}
|
718
|
+
|
719
|
+
/** Delete migration resourcelink.
|
720
|
+
*/
|
721
|
+
async deleteMigration () {
|
722
|
+
await this.checkMigration()
|
723
|
+
if (this.context.migration != null) {
|
724
|
+
await this.client.delete(this.context.migration)
|
725
|
+
this.context.migration = null
|
726
|
+
}
|
727
|
+
}
|
728
|
+
|
672
729
|
// ===========================================================================
|
673
730
|
|
674
731
|
_deviceToMap (id, details = false) {
|
@@ -733,7 +790,7 @@ class Gateway extends AccessoryDelegate {
|
|
733
790
|
return { status: 200, body }
|
734
791
|
}
|
735
792
|
if (path.length === 2) {
|
736
|
-
const id = path[1]
|
793
|
+
const id = path[1].replace(/:/g, '').toUpperCase()
|
737
794
|
if (this.accessoryById[id] == null) {
|
738
795
|
return { status: 404 } // Not Found
|
739
796
|
}
|
@@ -749,7 +806,7 @@ class Gateway extends AccessoryDelegate {
|
|
749
806
|
return { status: 200, body }
|
750
807
|
}
|
751
808
|
if (path.length === 2) {
|
752
|
-
return this._deviceToMap(path[1], true)
|
809
|
+
return this._deviceToMap(path[1].replace(/:/g, '').toUpperCase(), true)
|
753
810
|
}
|
754
811
|
}
|
755
812
|
return { status: 403 } // Forbidden
|
@@ -808,7 +865,7 @@ class Gateway extends AccessoryDelegate {
|
|
808
865
|
return { status: 405 } // Method Not Allowed
|
809
866
|
}
|
810
867
|
if (path.length === 3 && path[2] === 'settings') {
|
811
|
-
const id = path[1]
|
868
|
+
const id = path[1].replace(/:/g, '').toUpperCase()
|
812
869
|
if (this.accessoryById[id] == null) {
|
813
870
|
return { status: 404 } // Not Found
|
814
871
|
}
|
@@ -820,7 +877,7 @@ class Gateway extends AccessoryDelegate {
|
|
820
877
|
return { status: 405 } // Method Not Allowed
|
821
878
|
}
|
822
879
|
if (path.length === 3 && path[2] === 'settings') {
|
823
|
-
const id = path[1]
|
880
|
+
const id = path[1].replace(/:/g, '').toUpperCase()
|
824
881
|
if (this.deviceById[id] == null) {
|
825
882
|
return { status: 404 } // Not Found
|
826
883
|
}
|
@@ -1060,19 +1117,7 @@ class Gateway extends AccessoryDelegate {
|
|
1060
1117
|
}
|
1061
1118
|
|
1062
1119
|
if (changed) {
|
1063
|
-
|
1064
|
-
const response = await this.client.post('/resourcelinks', {
|
1065
|
-
name: 'homebridge-deconz',
|
1066
|
-
description: 'migration',
|
1067
|
-
classid: 1,
|
1068
|
-
links: Object.keys(this.accessoryByRpath).sort()
|
1069
|
-
})
|
1070
|
-
this.context.migration = '/resourcelinks/' + response.success.id
|
1071
|
-
} else {
|
1072
|
-
await this.client.put(this.context.migration, {
|
1073
|
-
links: Object.keys(this.accessoryByRpath).sort()
|
1074
|
-
})
|
1075
|
-
}
|
1120
|
+
await this.updateMigration()
|
1076
1121
|
this.identify()
|
1077
1122
|
}
|
1078
1123
|
}
|
@@ -25,7 +25,7 @@ class Light extends DeconzAccessory {
|
|
25
25
|
|
26
26
|
this.addPropertyDelegate({
|
27
27
|
key: 'serviceName',
|
28
|
-
value: device.resource.
|
28
|
+
value: device.resource.serviceName
|
29
29
|
})
|
30
30
|
|
31
31
|
this.service = this.createService(device.resource, {
|
@@ -65,7 +65,7 @@ class Light extends DeconzAccessory {
|
|
65
65
|
silent: true
|
66
66
|
})
|
67
67
|
}
|
68
|
-
if (this.
|
68
|
+
if (this.servicesByServiceName.Consumption?.[0] != null) {
|
69
69
|
params.totalConsumptionDelegate = this.service.characteristicDelegate('totalConsumption')
|
70
70
|
if (this.service.values.consumption === undefined) {
|
71
71
|
// Power to be computed by history if not exposed by device
|
@@ -75,7 +75,7 @@ class Light extends DeconzAccessory {
|
|
75
75
|
unit: ' W'
|
76
76
|
})
|
77
77
|
}
|
78
|
-
} else if (this.
|
78
|
+
} else if (this.servicesByServiceName.Power?.[0] != null) {
|
79
79
|
params.consumptionDelegate = this.service.characteristicDelegate('consumption')
|
80
80
|
// Total Consumption to be computed by history
|
81
81
|
params.computedTotalConsumptionDelegate = this.service.addCharacteristicDelegate({
|
@@ -3,6 +3,11 @@
|
|
3
3
|
//
|
4
4
|
// Homebridge plugin for deCONZ.
|
5
5
|
|
6
|
+
// Keep separate for Eve History
|
7
|
+
// Switch/Outlet/Lightbulb
|
8
|
+
// Stateless Programmable Switch (Eve button)
|
9
|
+
// Sensors
|
10
|
+
|
6
11
|
'use strict'
|
7
12
|
|
8
13
|
const { ServiceDelegate } = require('homebridge-lib')
|
@@ -51,8 +56,8 @@ class Sensor extends DeconzAccessory {
|
|
51
56
|
}
|
52
57
|
|
53
58
|
const params = {}
|
54
|
-
if (this.
|
55
|
-
const service = this.
|
59
|
+
if (this.servicesByServiceName.Contact?.length === 1) {
|
60
|
+
const service = this.servicesByServiceName.Contact[0]
|
56
61
|
params.contactDelegate = service.characteristicDelegate('contact')
|
57
62
|
params.lastContactDelegate = service.addCharacteristicDelegate({
|
58
63
|
key: 'lastActivation',
|
@@ -66,8 +71,8 @@ class Sensor extends DeconzAccessory {
|
|
66
71
|
silent: true
|
67
72
|
})
|
68
73
|
}
|
69
|
-
if (this.
|
70
|
-
const service = this.
|
74
|
+
if (this.servicesByServiceName.Motion?.length === 1) {
|
75
|
+
const service = this.servicesByServiceName.Motion[0]
|
71
76
|
params.motionDelegate = service.characteristicDelegate('motion')
|
72
77
|
params.lastMotionDelegate = service.addCharacteristicDelegate({
|
73
78
|
key: 'lastActivation',
|
@@ -75,34 +80,34 @@ class Sensor extends DeconzAccessory {
|
|
75
80
|
silent: true
|
76
81
|
})
|
77
82
|
}
|
78
|
-
if (this.
|
79
|
-
const service = this.
|
83
|
+
if (this.servicesByServiceName.LightLevel?.length === 1) {
|
84
|
+
const service = this.servicesByServiceName.LightLevel[0]
|
80
85
|
params.lightLevelDelegate = service.characteristicDelegate('lightLevel')
|
81
86
|
}
|
82
|
-
if (this.
|
83
|
-
const service = this.
|
87
|
+
if (this.servicesByServiceName.Daylight?.length === 1) {
|
88
|
+
const service = this.servicesByServiceName.Daylight[0]
|
84
89
|
params.lightLevelDelegate = service.characteristicDelegate('lightLevel')
|
85
90
|
}
|
86
|
-
if (this.
|
87
|
-
const service = this.
|
91
|
+
if (this.servicesByServiceName.Temperature?.length === 1) {
|
92
|
+
const service = this.servicesByServiceName.Temperature[0]
|
88
93
|
params.temperatureDelegate = service.characteristicDelegate('temperature')
|
89
94
|
}
|
90
|
-
if (this.
|
91
|
-
const service = this.
|
95
|
+
if (this.servicesByServiceName.Humidity?.length === 1) {
|
96
|
+
const service = this.servicesByServiceName.Humidity[0]
|
92
97
|
params.humidityDelegate = service.characteristicDelegate('humidity')
|
93
98
|
}
|
94
|
-
if (this.
|
95
|
-
const service = this.
|
99
|
+
if (this.servicesByServiceName.AirPressure?.length === 1) {
|
100
|
+
const service = this.servicesByServiceName.AirPressure[0]
|
96
101
|
params.airPressureDelegate = service.characteristicDelegate('airPressure')
|
97
102
|
}
|
98
|
-
if (this.
|
99
|
-
const service = this.
|
103
|
+
if (this.servicesByServiceName.AirQuality?.length === 1) {
|
104
|
+
const service = this.servicesByServiceName.AirQuality[0]
|
100
105
|
if (service.characteristicDelegate('vocDensity') != null) {
|
101
106
|
params.vocDensityDelegate = service.characteristicDelegate('vocDensity')
|
102
107
|
}
|
103
108
|
}
|
104
|
-
if (this.
|
105
|
-
const service = this.
|
109
|
+
if (this.servicesByServiceName.Flag != null) {
|
110
|
+
const service = this.servicesByServiceName.Flag[0]
|
106
111
|
params.switchOnDelegate = service.characteristicDelegate('on')
|
107
112
|
params.lastSwitchOnDelegate = service.addCharacteristicDelegate({
|
108
113
|
key: 'lastActivation',
|
@@ -19,7 +19,9 @@ const { SINGLE, DOUBLE, LONG } = DeconzService.Button
|
|
19
19
|
class DeconzAccessory extends AccessoryDelegate {
|
20
20
|
static get Light () { return require('./Light') }
|
21
21
|
static get Gateway () { return require('./Gateway') }
|
22
|
+
static get Outlet () { return DeconzAccessory.Light }
|
22
23
|
static get Sensor () { return require('./Sensor') }
|
24
|
+
static get Switch () { return DeconzAccessory.Light }
|
23
25
|
static get WarningDevice () { return require('./WarningDevice') }
|
24
26
|
static get WindowCovering () { return require('./WindowCovering') }
|
25
27
|
|
@@ -29,7 +31,6 @@ class DeconzAccessory extends AccessoryDelegate {
|
|
29
31
|
* @param {Accessory.Category} category - The HomeKit accessory category.
|
30
32
|
*/
|
31
33
|
constructor (gateway, device, category) {
|
32
|
-
// TODO device settings
|
33
34
|
super(gateway.platform, {
|
34
35
|
id: device.id,
|
35
36
|
name: device.resource.body.name,
|
@@ -44,7 +45,7 @@ class DeconzAccessory extends AccessoryDelegate {
|
|
44
45
|
|
45
46
|
this.serviceByRpath = {}
|
46
47
|
this.serviceBySubtype = {}
|
47
|
-
this.
|
48
|
+
this.servicesByServiceName = {}
|
48
49
|
|
49
50
|
/** The gateway.
|
50
51
|
* @type {DeconzAccessory.Gateway}
|
@@ -140,27 +141,27 @@ class DeconzAccessory extends AccessoryDelegate {
|
|
140
141
|
|
141
142
|
let service
|
142
143
|
if (params.serviceName === 'AirQuality') {
|
143
|
-
service = this.
|
144
|
+
service = this.servicesByServiceName.AirQuality?.[0]
|
144
145
|
if (service != null) {
|
145
146
|
service.addResource(resource)
|
146
147
|
}
|
147
148
|
} else if (params.serviceName === 'Battery') {
|
148
|
-
service = this.
|
149
|
+
service = this.servicesByServiceName.Battery?.[0]
|
149
150
|
} else if (params.serviceName === 'Consumption') {
|
150
|
-
service = this.
|
151
|
-
this.
|
152
|
-
this.
|
151
|
+
service = this.servicesByServiceName.Outlet?.[0] ||
|
152
|
+
this.servicesByServiceName.Light?.[0] ||
|
153
|
+
this.servicesByServiceName.Power?.[0]
|
153
154
|
if (service != null) {
|
154
155
|
service.addResource(resource)
|
155
156
|
}
|
156
157
|
} else if (params.serviceName === 'Power') {
|
157
|
-
service = this.
|
158
|
-
this.
|
159
|
-
this.
|
158
|
+
service = this.servicesByServiceName.Outlet?.[0] ||
|
159
|
+
this.servicesByServiceName.Light?.[0] ||
|
160
|
+
this.servicesByServiceName.Consumption?.[0]
|
160
161
|
if (service != null) {
|
161
162
|
service.addResource(resource)
|
162
163
|
}
|
163
|
-
} else if (params.serviceName === '
|
164
|
+
} else if (params.serviceName === 'Label') {
|
164
165
|
// Default button
|
165
166
|
if (resource.capabilities.buttons == null) {
|
166
167
|
this.warn(
|
@@ -176,9 +177,9 @@ class DeconzAccessory extends AccessoryDelegate {
|
|
176
177
|
resource.capabilities.namespace =
|
177
178
|
this.Characteristics.hap.ServiceLabelNamespace.ARABIC_NUMERALS
|
178
179
|
}
|
179
|
-
service = this.
|
180
|
+
service = this.servicesByServiceName.Label?.[0]
|
180
181
|
if (service == null) {
|
181
|
-
service = new DeconzService.
|
182
|
+
service = new DeconzService.Label(this, resource, {
|
182
183
|
primaryService: params.primaryService
|
183
184
|
})
|
184
185
|
}
|
@@ -191,17 +192,16 @@ class DeconzAccessory extends AccessoryDelegate {
|
|
191
192
|
}
|
192
193
|
this.serviceBySubtype[resource.subtype] = service
|
193
194
|
this.serviceByRpath[resource.rpath] = service
|
194
|
-
if (this.
|
195
|
-
this.
|
195
|
+
if (this.servicesByServiceName[params.serviceName] == null) {
|
196
|
+
this.servicesByServiceName[params.serviceName] = [service]
|
197
|
+
} else {
|
198
|
+
this.servicesByServiceName[params.serviceName].push(service)
|
196
199
|
}
|
197
|
-
if (
|
198
|
-
|
199
|
-
|
200
|
-
) {
|
201
|
-
if (this.serviceByServiceName.Battery == null) {
|
202
|
-
this.serviceByServiceName.Battery = new DeconzService.Battery(this, resource)
|
200
|
+
if (resource.body.config?.battery !== undefined) {
|
201
|
+
if (this.servicesByServiceName.Battery?.[0] == null) {
|
202
|
+
this.servicesByServiceName.Battery = [new DeconzService.Battery(this, resource)]
|
203
203
|
}
|
204
|
-
service.batteryService = this.
|
204
|
+
service.batteryService = this.servicesByServiceName.Battery[0]
|
205
205
|
}
|
206
206
|
return service
|
207
207
|
}
|
@@ -237,12 +237,8 @@ class DeconzAccessory extends AccessoryDelegate {
|
|
237
237
|
multiClip: undefined,
|
238
238
|
multiLight: undefined,
|
239
239
|
logLevel: this.values.logLevel,
|
240
|
-
lowBatteryThreshold: this.
|
241
|
-
|
242
|
-
: this.serviceByServiceName.Battery.values.lowBatteryThreshold,
|
243
|
-
// offset: this.serviceByServiceName.Temperature == null
|
244
|
-
// ? undefined
|
245
|
-
// : this.serviceByServiceName.Temperature.values.offset,
|
240
|
+
lowBatteryThreshold: this.servicesByServiceName?.Battery?.[0].values.lowBatteryThreshold,
|
241
|
+
// offset: this.servicesByServiceName?.Temperature?.[0].values.offset,
|
246
242
|
serviceName: this.values.serviceName,
|
247
243
|
splitLight: undefined,
|
248
244
|
venetianBlind: this.service.values.venetianBlind,
|
@@ -290,21 +286,13 @@ class DeconzAccessory extends AccessoryDelegate {
|
|
290
286
|
responseBody[key] = value
|
291
287
|
continue
|
292
288
|
case 'lowBatteryThreshold':
|
293
|
-
if (this.
|
289
|
+
if (this.servicesByServiceName.Battery?.[0] != null) {
|
294
290
|
value = OptionParser.toInt(key, body[key], 10, 100)
|
295
|
-
this.
|
291
|
+
this.servicesByServiceName.Battery[0].values[key] = value
|
296
292
|
responseBody[key] = value
|
297
293
|
continue
|
298
294
|
}
|
299
295
|
break
|
300
|
-
// case 'offset': // TODO: doesn't work because of fromHomeKit
|
301
|
-
// if (this.serviceByServiceName.Temperature != null) {
|
302
|
-
// const value = OptionParser.toNumber(key, body[key], -5, 5)
|
303
|
-
// this.serviceByServiceName.Temperature.values[key] = value
|
304
|
-
// responseBody[key] = value
|
305
|
-
// continue
|
306
|
-
// }
|
307
|
-
// break
|
308
296
|
case 'serviceName':
|
309
297
|
if (this.values.serviceName != null) {
|
310
298
|
value = OptionParser.toString(key, body[key])
|
@@ -30,6 +30,9 @@ class Humidity extends DeconzService.SensorsResource {
|
|
30
30
|
if (state.humidity != null) {
|
31
31
|
this.values.humidity = Math.round(state.humidity / 10) / 10
|
32
32
|
}
|
33
|
+
if (state.moisture != null) {
|
34
|
+
this.values.humidity = Math.round(state.moisture / 10) / 10
|
35
|
+
}
|
33
36
|
super.updateState(state)
|
34
37
|
}
|
35
38
|
}
|
@@ -0,0 +1,103 @@
|
|
1
|
+
// homebridge-deconz/lib/DeconzService/Label.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('.')
|
9
|
+
|
10
|
+
/**
|
11
|
+
* @memberof DeconzService
|
12
|
+
*/
|
13
|
+
class Label extends DeconzService.SensorsResource {
|
14
|
+
constructor (accessory, resource, params = {}) {
|
15
|
+
params.Service = accessory.Services.hap.ServiceLabel
|
16
|
+
super(accessory, resource, params)
|
17
|
+
|
18
|
+
this.addCharacteristicDelegate({
|
19
|
+
key: 'namespace',
|
20
|
+
Characteristic: this.Characteristics.hap.ServiceLabelNamespace,
|
21
|
+
value: resource.capabilities.namespace
|
22
|
+
})
|
23
|
+
|
24
|
+
this.addCharacteristicDelegates({ noLastUpdated: true })
|
25
|
+
|
26
|
+
this.buttonResources = {}
|
27
|
+
this.buttonServices = {}
|
28
|
+
this.hasRepeat = false
|
29
|
+
}
|
30
|
+
|
31
|
+
createButtonServices (resource) {
|
32
|
+
if (resource.body.type.endsWith('Switch')) {
|
33
|
+
this.buttonResources[resource.rpath] = {
|
34
|
+
buttonEvent: resource.body.state.buttonevent,
|
35
|
+
lastUpdated: '',
|
36
|
+
toButtonEvent: resource.capabilities.toButtonEvent
|
37
|
+
}
|
38
|
+
} else if (resource.body.type.endsWith('RelativeRotary')) {
|
39
|
+
const keys = Object.keys(resource.capabilities.buttons)
|
40
|
+
this.buttonResources[resource.rpath] = {
|
41
|
+
lastUpdated: '',
|
42
|
+
right: keys[0],
|
43
|
+
left: keys[1]
|
44
|
+
}
|
45
|
+
}
|
46
|
+
for (const i in resource.capabilities.buttons) {
|
47
|
+
const { label, events, hasRepeat } = resource.capabilities.buttons[i]
|
48
|
+
if (hasRepeat) {
|
49
|
+
this.hasRepeat = hasRepeat
|
50
|
+
}
|
51
|
+
this.buttonServices[i] = new DeconzService.Button(this.accessoryDelegate, {
|
52
|
+
name: this.name + ' ' + label,
|
53
|
+
button: Number(i),
|
54
|
+
events,
|
55
|
+
hasRepeat
|
56
|
+
})
|
57
|
+
}
|
58
|
+
}
|
59
|
+
|
60
|
+
updateState (state, rpath) {
|
61
|
+
const buttonResource = this.buttonResources[rpath]
|
62
|
+
if (buttonResource.lastUpdated === '') {
|
63
|
+
buttonResource.lastUpdated = state.lastupdated
|
64
|
+
} else if (
|
65
|
+
state.lastupdated != null &&
|
66
|
+
state.lastupdated !== buttonResource.lastUpdated
|
67
|
+
) {
|
68
|
+
buttonResource.lastUpdated = state.lastupdated
|
69
|
+
if (buttonResource.buttonEvent !== undefined) {
|
70
|
+
const oldValue = buttonResource.buttonEvent
|
71
|
+
if (state.buttonevent != null) {
|
72
|
+
buttonResource.buttonEvent = buttonResource.toButtonEvent == null
|
73
|
+
? state.buttonevent
|
74
|
+
: buttonResource.toButtonEvent(state.buttonevent)
|
75
|
+
}
|
76
|
+
// TODO handle repeat
|
77
|
+
const i = Math.floor(buttonResource.buttonEvent / 1000)
|
78
|
+
this.buttonServices[i]?.update(
|
79
|
+
buttonResource.buttonEvent, oldValue,
|
80
|
+
false // this.accessoryDelegate.settingsService.values.repeat
|
81
|
+
)
|
82
|
+
} else {
|
83
|
+
const i = state.expectedrotation >= 0
|
84
|
+
? buttonResource.right
|
85
|
+
: buttonResource.left
|
86
|
+
this.buttonServices[i].updateRotation()
|
87
|
+
}
|
88
|
+
}
|
89
|
+
super.updateState(state)
|
90
|
+
}
|
91
|
+
|
92
|
+
updateConfig (config) {
|
93
|
+
// TODO handle change in devicemode
|
94
|
+
super.updateConfig(config)
|
95
|
+
}
|
96
|
+
|
97
|
+
async identify () {
|
98
|
+
this.debug('hasRepeat: %j', this.hasRepeat)
|
99
|
+
return super.identify()
|
100
|
+
}
|
101
|
+
}
|
102
|
+
|
103
|
+
module.exports = Label
|
@@ -110,7 +110,9 @@ class Light extends DeconzService.LightsResource {
|
|
110
110
|
this.resource.body?.capabilities?.color?.ct?.min == null ||
|
111
111
|
this.resource.body?.capabilities?.color?.ct?.max == null
|
112
112
|
) {
|
113
|
-
this.
|
113
|
+
if (this.capabilities.on) {
|
114
|
+
this.warn('using default ct range')
|
115
|
+
}
|
114
116
|
}
|
115
117
|
const ctMin = this.resource.body?.capabilities?.color?.ct?.min ?? 153
|
116
118
|
const ctMax = this.resource.body?.capabilities?.color?.ct?.max ?? 500
|
@@ -144,7 +146,9 @@ class Light extends DeconzService.LightsResource {
|
|
144
146
|
this.resource.body?.capabilities?.color?.xy?.green == null ||
|
145
147
|
this.resource.body?.capabilities?.color?.xy?.red == null
|
146
148
|
) {
|
147
|
-
this.
|
149
|
+
if (this.capabilities.on) {
|
150
|
+
this.warn('using default xy gamut')
|
151
|
+
}
|
148
152
|
this.capabilities.gamut = defaultGamut
|
149
153
|
} else {
|
150
154
|
this.capabilities.gamut = {
|
@@ -11,6 +11,30 @@ const DeconzService = require('../DeconzService')
|
|
11
11
|
* @memberof DeconzService
|
12
12
|
*/
|
13
13
|
class Status extends DeconzService.SensorsResource {
|
14
|
+
props (caps) {
|
15
|
+
if (caps.min == null || caps.max == null) {
|
16
|
+
return undefined
|
17
|
+
}
|
18
|
+
// Eve 3.1 displays the following controls, depending on the properties:
|
19
|
+
// 1. {minValue: 0, maxValue: 1, minStep: 1} switch
|
20
|
+
// 2. {minValue: a, maxValue: b, minStep: 1}, 1 < b - a <= 20 down|up
|
21
|
+
// 3. {minValue: a, maxValue: b}, (a, b) != (0, 1) slider
|
22
|
+
// 4. {minValue: a, maxValue: b, minStep: 1}, b - a > 20 slider
|
23
|
+
// Avoid the following bugs:
|
24
|
+
// 5. {minValue: 0, maxValue: 1} nothing
|
25
|
+
// 6. {minValue: a, maxValue: b, minStep: 1}, b - a = 1 switch*
|
26
|
+
// *) switch sends values 0 and 1 instead of a and b;
|
27
|
+
if (caps.min === 0 && caps.max === 1) {
|
28
|
+
// Workaround Eve bug (case 5 above).
|
29
|
+
return { minValue: caps.min, maxValue: caps.max, minStep: 1 }
|
30
|
+
}
|
31
|
+
if (caps.max - caps.min === 1) {
|
32
|
+
// Workaround Eve bug (case 6 above).
|
33
|
+
return { minValue: caps.min, maxValue: caps.max }
|
34
|
+
}
|
35
|
+
return { minValue: caps.min, maxValue: caps.max, minStep: 1 }
|
36
|
+
}
|
37
|
+
|
14
38
|
constructor (accessory, resource, params = {}) {
|
15
39
|
params.Service = accessory.Services.my.Status
|
16
40
|
super(accessory, resource, params)
|
@@ -29,7 +53,7 @@ class Status extends DeconzService.SensorsResource {
|
|
29
53
|
this.addCharacteristicDelegate({
|
30
54
|
key: 'status',
|
31
55
|
Characteristic: this.Characteristics.my.Status,
|
32
|
-
props: resource.capabilities
|
56
|
+
props: this.props(resource.capabilities)
|
33
57
|
}).on('didSet', async (value, fromHomeKit) => {
|
34
58
|
if (fromHomeKit) {
|
35
59
|
await this.put('/state', { status: value })
|
@@ -5,101 +5,66 @@
|
|
5
5
|
|
6
6
|
'use strict'
|
7
7
|
|
8
|
-
const DeconzService = require('
|
8
|
+
const DeconzService = require('.')
|
9
9
|
|
10
|
-
|
11
|
-
* @memberof DeconzService
|
12
|
-
*/
|
13
|
-
class Switch extends DeconzService.SensorsResource {
|
10
|
+
class Switch extends DeconzService.LightsResource {
|
14
11
|
constructor (accessory, resource, params = {}) {
|
15
|
-
params.Service = accessory.Services.hap.
|
12
|
+
params.Service = accessory.Services.hap.Switch
|
16
13
|
super(accessory, resource, params)
|
17
14
|
|
18
15
|
this.addCharacteristicDelegate({
|
19
|
-
key: '
|
20
|
-
Characteristic: this.Characteristics.hap.
|
21
|
-
value:
|
16
|
+
key: 'on',
|
17
|
+
Characteristic: this.Characteristics.hap.On,
|
18
|
+
value: this.capabilities.on
|
19
|
+
? this.resource.body.state.on
|
20
|
+
: this.resource.body.state.all_on
|
21
|
+
}).on('didSet', (value, fromHomeKit) => {
|
22
|
+
if (fromHomeKit) {
|
23
|
+
this.put({ on: value })
|
24
|
+
}
|
22
25
|
})
|
23
26
|
|
24
|
-
this.
|
27
|
+
if (this.resource.body.state.on === undefined) {
|
28
|
+
this.addCharacteristicDelegate({
|
29
|
+
key: 'anyOn',
|
30
|
+
Characteristic: this.Characteristics.my.AnyOn,
|
31
|
+
value: this.resource.body.state.any_on
|
32
|
+
}).on('didSet', (value, fromHomeKit) => {
|
33
|
+
if (fromHomeKit) {
|
34
|
+
this.put({ on: value })
|
35
|
+
}
|
36
|
+
})
|
37
|
+
}
|
25
38
|
|
26
|
-
this.
|
27
|
-
this.buttonServices = {}
|
28
|
-
this.hasRepeat = false
|
29
|
-
}
|
39
|
+
this.addCharacteristicDelegates()
|
30
40
|
|
31
|
-
|
32
|
-
|
33
|
-
this.
|
34
|
-
|
35
|
-
lastUpdated: '',
|
36
|
-
toButtonEvent: resource.capabilities.toButtonEvent
|
37
|
-
}
|
38
|
-
} else if (resource.body.type.endsWith('RelativeRotary')) {
|
39
|
-
const keys = Object.keys(resource.capabilities.buttons)
|
40
|
-
this.buttonResources[resource.rpath] = {
|
41
|
-
lastUpdated: '',
|
42
|
-
right: keys[0],
|
43
|
-
left: keys[1]
|
44
|
-
}
|
45
|
-
}
|
46
|
-
for (const i in resource.capabilities.buttons) {
|
47
|
-
const { label, events, hasRepeat } = resource.capabilities.buttons[i]
|
48
|
-
if (hasRepeat) {
|
49
|
-
this.hasRepeat = hasRepeat
|
50
|
-
}
|
51
|
-
this.buttonServices[i] = new DeconzService.Button(this.accessoryDelegate, {
|
52
|
-
name: this.name + ' ' + label,
|
53
|
-
button: Number(i),
|
54
|
-
events,
|
55
|
-
hasRepeat
|
56
|
-
})
|
41
|
+
this.settings = {
|
42
|
+
resetTimeout: this.platform.config.resetTimeout,
|
43
|
+
waitTimeUpdate: this.platform.config.waitTimeUpdate,
|
44
|
+
wallSwitch: false
|
57
45
|
}
|
58
46
|
}
|
59
47
|
|
60
48
|
updateState (state, rpath) {
|
61
|
-
const
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
// TODO handle repeat
|
77
|
-
const i = Math.floor(buttonResource.buttonEvent / 1000)
|
78
|
-
if (this.buttonServices[i] != null) {
|
79
|
-
this.buttonServices[i].update(
|
80
|
-
buttonResource.buttonEvent, oldValue,
|
81
|
-
false // this.accessoryDelegate.settingsService.values.repeat
|
82
|
-
)
|
83
|
-
}
|
84
|
-
} else {
|
85
|
-
const i = state.expectedrotation >= 0
|
86
|
-
? buttonResource.right
|
87
|
-
: buttonResource.left
|
88
|
-
this.buttonServices[i].updateRotation()
|
49
|
+
for (const key in state) {
|
50
|
+
const value = state[key]
|
51
|
+
this.resource.body.state[key] = value
|
52
|
+
switch (key) {
|
53
|
+
case 'all_on':
|
54
|
+
this.values.on = value
|
55
|
+
break
|
56
|
+
case 'any_on':
|
57
|
+
this.values.anyOn = value
|
58
|
+
break
|
59
|
+
case 'on':
|
60
|
+
this.values.on = value
|
61
|
+
break
|
62
|
+
default:
|
63
|
+
break
|
89
64
|
}
|
90
65
|
}
|
91
66
|
super.updateState(state)
|
92
67
|
}
|
93
|
-
|
94
|
-
updateConfig (config) {
|
95
|
-
// TODO handle change in devicemode
|
96
|
-
super.updateConfig(config)
|
97
|
-
}
|
98
|
-
|
99
|
-
async identify () {
|
100
|
-
this.debug('hasRepeat: %j', this.hasRepeat)
|
101
|
-
return super.identify()
|
102
|
-
}
|
103
68
|
}
|
104
69
|
|
105
70
|
module.exports = Switch
|
@@ -27,6 +27,7 @@ class DeconzService extends ServiceDelegate {
|
|
27
27
|
static get Flag () { return require('./Flag') }
|
28
28
|
static get Gateway () { return require('./Gateway') }
|
29
29
|
static get Humidity () { return require('./Humidity') }
|
30
|
+
static get Label () { return require('./Label') }
|
30
31
|
static get Leak () { return require('./Leak') }
|
31
32
|
static get Light () { return require('./Light') }
|
32
33
|
static get LightsResource () { return require('./LightsResource') }
|
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": "0.1.
|
7
|
+
"version": "0.1.11",
|
8
8
|
"keywords": [
|
9
9
|
"homebridge-plugin",
|
10
10
|
"homekit",
|
@@ -21,13 +21,13 @@
|
|
21
21
|
"ui": "cli/ui.js"
|
22
22
|
},
|
23
23
|
"engines": {
|
24
|
-
"deCONZ": "2.
|
24
|
+
"deCONZ": "2.21.2",
|
25
25
|
"homebridge": "^1.6.0",
|
26
26
|
"node": "^18.15.0"
|
27
27
|
},
|
28
28
|
"dependencies": {
|
29
|
-
"homebridge-lib": "~6.3.
|
30
|
-
"ws": "^8.
|
29
|
+
"homebridge-lib": "~6.3.13",
|
30
|
+
"ws": "^8.13.0",
|
31
31
|
"xml2js": "~0.4.23"
|
32
32
|
},
|
33
33
|
"scripts": {
|