homebridge-deconz 0.0.12 → 0.0.15
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/README.md +6 -4
- package/config.schema.json +2 -2
- package/{homebridge-deconz.png → homebridge-ui/public/homebridge-deconz.png} +0 -0
- package/homebridge-ui/public/index.html +488 -8
- package/homebridge-ui/server.js +40 -25
- package/lib/Deconz/Resource.js +5 -4
- package/lib/DeconzAccessory/Gateway.js +74 -29
- package/lib/DeconzPlatform.js +69 -4
- package/lib/DeconzService/Light.js +41 -27
- package/lib/DeconzService/Thermostat.js +2 -2
- package/lib/DeconzService/WindowCovering.js +37 -21
- package/lib/DeconzService/index.js +3 -0
- package/package.json +5 -6
package/README.md
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
<p align="center">
|
2
|
-
<img src="homebridge-deconz.png" height="200px">
|
3
|
-
</p
|
2
|
+
<img src="homebridge-ui/public/homebridge-deconz.png" height="200px">
|
3
|
+
</p>
|
4
|
+
<span align="center">
|
4
5
|
|
5
6
|
# Homebridge deCONZ
|
6
7
|
[](https://www.npmjs.com/package/homebridge-deconz)
|
7
8
|
[](https://www.npmjs.com/package/homebridge-deconz)
|
8
|
-
[](https://discord.gg/
|
9
|
+
[](https://discord.gg/zUhSZSNb4P)
|
10
|
+
[](https://github.com/homebridge/homebridge/wiki/Verified-Plugins)
|
9
11
|
|
10
12
|
[](https://github.com/ebaauw/homebridge-deconz/issues)
|
11
13
|
[](https://github.com/ebaauw/homebridge-deconz/pulls)
|
@@ -22,7 +24,7 @@ See [Future Development of Homebridge Hue](https://github.com/ebaauw/homebridge-
|
|
22
24
|
Homebridge deCONZ is still under development.
|
23
25
|
See [Releases](https://github.com/ebaauw/homebridge-deconz/releases) for more details.
|
24
26
|
|
25
|
-
If you have a question, please post a message to the **#
|
27
|
+
If you have a question, please post a message to the **#deconz** channel of the Homebridge community on [Discord](https://discord.gg/zUhSZSNb4P).
|
26
28
|
|
27
29
|
### Introduction
|
28
30
|
This [Homebridge](https://github.com/homebridge/homebridge) plugin exposes to Apple's [HomeKit](http://www.apple.com/ios/home/) ZigBee devices (lights, plugs, sensors, switches, ...) and virtual devices on a deCONZ gateway by dresden elektronik.
|
package/config.schema.json
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
"pluginAlias": "deCONZ",
|
3
3
|
"pluginType": "platform",
|
4
4
|
"singular": true,
|
5
|
-
"customUi":
|
5
|
+
"customUi": false,
|
6
6
|
"headerDisplay": "Homebridge plugin for deCONZ",
|
7
7
|
"footerDisplay": "For a detailed description, see the [wiki](https://github.com/ebaauw/homebridge-deconz/wiki/Configuration).",
|
8
8
|
"schema": {
|
@@ -77,7 +77,7 @@
|
|
77
77
|
}
|
78
78
|
}
|
79
79
|
},
|
80
|
-
"
|
80
|
+
"layout": [
|
81
81
|
"name",
|
82
82
|
{
|
83
83
|
"key": "hosts",
|
File without changes
|
@@ -6,19 +6,499 @@ Copyright © 2022 Erik Baauw. All rights reserved.
|
|
6
6
|
-->
|
7
7
|
|
8
8
|
<link rel="stylesheet" href="style.css">
|
9
|
+
<p align="center">
|
10
|
+
<a href="https://github.com/ebaauw/homebridge-deconz/wiki/Configuration" target="_blank">
|
11
|
+
<img src="homebridge-deconz.png" height="200px">
|
12
|
+
</a>
|
13
|
+
</p>
|
9
14
|
|
10
15
|
<script>
|
11
|
-
|
12
|
-
|
16
|
+
|
17
|
+
async function showFormPluginConfig () {
|
18
|
+
homebridge.showSpinner()
|
19
|
+
const pluginConfig = await homebridge.getPluginConfig()
|
20
|
+
console.log('pluginConfig: %o', pluginConfig)
|
21
|
+
// const pluginConfigSchema = await homebridge.getPluginConfigSchema()
|
22
|
+
// console.log('pluginConfigSchema: %o', pluginConfigSchema)
|
23
|
+
// const cachedAccessories = await homebridge.getCachedAccessories()
|
24
|
+
// console.log('cachedAccessories: %o', cachedAccessories)
|
25
|
+
// const discoveredGateways = await homebridge.request('discover')
|
26
|
+
// console.log('discovered gateways: %o', discoveredGateways)
|
27
|
+
for (const config of pluginConfig) {
|
28
|
+
if (config._bridge != null) {
|
29
|
+
const cachedAccessories = await homebridge.request('cachedAccessories', {
|
30
|
+
username: config._bridge.username
|
31
|
+
})
|
32
|
+
console.log('%s: cachedAccessories: %o', config.name, cachedAccessories)
|
33
|
+
const cachedGateways = cachedAccessories.filter((accessory) => {
|
34
|
+
return accessory.plugin === 'homebridge-deconz' &&
|
35
|
+
accessory.context != null &&
|
36
|
+
accessory.context.className === 'Gateway'
|
37
|
+
})
|
38
|
+
const result = {}
|
39
|
+
for (const gateway of cachedGateways) {
|
40
|
+
if (gateway.context.uiPort == null) {
|
41
|
+
continue
|
42
|
+
}
|
43
|
+
const pong = await homebridge.request(
|
44
|
+
'get', { uiPort: gateway.context.uiPort, path: '/ping' }
|
45
|
+
)
|
46
|
+
if (pong === 'pong') {
|
47
|
+
result[gateway.context.host] = gateway.context
|
48
|
+
}
|
49
|
+
}
|
50
|
+
const gateways = Object.keys(result).sort()
|
51
|
+
console.log('%s: gateways: %j',config.name, gateways)
|
52
|
+
}
|
53
|
+
}
|
54
|
+
homebridge.hideSpinner()
|
55
|
+
|
56
|
+
const form = homebridge.createForm(
|
57
|
+
{
|
58
|
+
schema: {
|
59
|
+
type: 'object',
|
60
|
+
properties: {
|
61
|
+
config: {
|
62
|
+
title: 'Gateways',
|
63
|
+
description: 'Configure a child bridge per deCONZ gateway. See <a href="https://github.com/ebaauw/homebridge-deconz/wiki/Configuration" target="_blank">wiki</a> for details.',
|
64
|
+
type: 'array',
|
65
|
+
disabled: true,
|
66
|
+
items: {
|
67
|
+
type: 'object',
|
68
|
+
properties: {
|
69
|
+
host: {
|
70
|
+
description: 'Gateway hostname and port.',
|
71
|
+
default: 'localhost:80',
|
72
|
+
type: 'string',
|
73
|
+
required: true
|
74
|
+
},
|
75
|
+
name: {
|
76
|
+
description: 'Homebridge log plugin name.',
|
77
|
+
default: 'deCONZ',
|
78
|
+
type: 'string'
|
79
|
+
},
|
80
|
+
_bridge: {
|
81
|
+
type: 'object',
|
82
|
+
required: true,
|
83
|
+
properties: {
|
84
|
+
name: {
|
85
|
+
type: 'string',
|
86
|
+
required: true
|
87
|
+
},
|
88
|
+
username: {
|
89
|
+
type: 'string',
|
90
|
+
pattern: '^([A-F0-9]{2}:){5}[A-F0-9]{2}$',
|
91
|
+
placeholder: 'AA:BB:CC:DD:EE:FF',
|
92
|
+
required: true
|
93
|
+
},
|
94
|
+
port: {
|
95
|
+
type: 'integer',
|
96
|
+
minimum: 1025,
|
97
|
+
// maximum: 65535,
|
98
|
+
required: true
|
99
|
+
},
|
100
|
+
manufacturer: {
|
101
|
+
type: 'string',
|
102
|
+
enabled: false,
|
103
|
+
required: true
|
104
|
+
},
|
105
|
+
model: {
|
106
|
+
type: 'string',
|
107
|
+
required: true
|
108
|
+
}
|
109
|
+
}
|
110
|
+
}
|
111
|
+
}
|
112
|
+
}
|
113
|
+
}
|
114
|
+
}
|
115
|
+
},
|
116
|
+
// layout: null
|
117
|
+
layout: [
|
118
|
+
{
|
119
|
+
type: 'tabarray',
|
120
|
+
title: '{{ value.name }}',
|
121
|
+
items: [
|
122
|
+
{
|
123
|
+
type: 'fieldset',
|
124
|
+
title: 'Gateway Settings',
|
125
|
+
key: 'config[]',
|
126
|
+
items: [
|
127
|
+
{
|
128
|
+
type: 'flex',
|
129
|
+
'flex-flow': 'row',
|
130
|
+
items: [
|
131
|
+
'config[].host',
|
132
|
+
'config[].name',
|
133
|
+
]
|
134
|
+
}
|
135
|
+
]
|
136
|
+
},
|
137
|
+
{
|
138
|
+
type: 'flex',
|
139
|
+
'flex-flow': 'row',
|
140
|
+
key: 'config[]',
|
141
|
+
items: [
|
142
|
+
{
|
143
|
+
type: 'button',
|
144
|
+
title: 'Connect',
|
145
|
+
key: 'config[].connect'
|
146
|
+
},
|
147
|
+
{
|
148
|
+
type: 'button',
|
149
|
+
title: 'Get API Key',
|
150
|
+
key: 'config[].getApiKey'
|
151
|
+
},
|
152
|
+
{
|
153
|
+
type: 'submit',
|
154
|
+
title: 'Configure',
|
155
|
+
key: 'config[].configure'
|
156
|
+
}
|
157
|
+
]
|
158
|
+
},
|
159
|
+
{
|
160
|
+
type: 'fieldset',
|
161
|
+
key: 'config[]._bridge',
|
162
|
+
// expandable: true,
|
163
|
+
title: 'Child Bridge Accessory Settings',
|
164
|
+
items: [
|
165
|
+
{
|
166
|
+
type: 'flex',
|
167
|
+
'flex-flow': 'row',
|
168
|
+
items: [
|
169
|
+
'config[]._bridge.username',
|
170
|
+
'config[]._bridge.port'
|
171
|
+
]
|
172
|
+
},
|
173
|
+
'config[]._bridge.name',
|
174
|
+
{
|
175
|
+
type: 'flex',
|
176
|
+
'flex-flow': 'row',
|
177
|
+
items: [
|
178
|
+
'config[]._bridge.manufacturer',
|
179
|
+
'config[]._bridge.model'
|
180
|
+
]
|
181
|
+
}
|
182
|
+
]
|
183
|
+
}
|
184
|
+
]
|
185
|
+
}
|
186
|
+
]
|
187
|
+
}, {
|
188
|
+
config: pluginConfig,
|
189
|
+
},
|
190
|
+
'Gateway Settings',
|
191
|
+
'Homebridge Settings'
|
192
|
+
)
|
193
|
+
form.onChange(async (form) => {
|
194
|
+
console.log('change: %o', form)
|
195
|
+
})
|
196
|
+
form.onSubmit(async (form) => {
|
197
|
+
console.log('submit: %o', form)
|
198
|
+
})
|
199
|
+
form.onCancel(async (form) => {
|
200
|
+
console.log('cancel: %o', form)
|
201
|
+
})
|
202
|
+
|
203
|
+
}
|
204
|
+
|
205
|
+
async function showFormGateways (gateway) {
|
206
|
+
homebridge.showSpinner()
|
207
|
+
const cachedAccessories = await homebridge.getCachedAccessories()
|
208
|
+
const cachedGateways = cachedAccessories.filter((accessory) => {
|
209
|
+
return accessory.plugin === 'homebridge-deconz' &&
|
210
|
+
accessory.context != null &&
|
211
|
+
accessory.context.className === 'Gateway'
|
212
|
+
})
|
213
|
+
const result = {}
|
214
|
+
for (const gateway of cachedGateways) {
|
215
|
+
if (gateway.context.uiPort == null) {
|
216
|
+
continue
|
217
|
+
}
|
218
|
+
const pong = await homebridge.request(
|
219
|
+
'get', { uiPort: gateway.context.uiPort, path: '/ping' }
|
220
|
+
)
|
221
|
+
if (pong === 'pong') {
|
222
|
+
result[gateway.context.host] = gateway.context
|
223
|
+
}
|
224
|
+
}
|
225
|
+
const gateways = Object.keys(result).sort()
|
226
|
+
homebridge.hideSpinner()
|
227
|
+
if (gateways.length === 0) {
|
13
228
|
homebridge.showSchemaForm()
|
229
|
+
return
|
230
|
+
}
|
231
|
+
// const form = homebridge.createForm({
|
232
|
+
// schema: {
|
233
|
+
// type: 'object',
|
234
|
+
// properties: {
|
235
|
+
// gateway: {
|
236
|
+
// title: 'Connected Gateways',
|
237
|
+
// type: 'string',
|
238
|
+
// oneOf: gateways.map((name) => {
|
239
|
+
// const config = result[name].context.config
|
240
|
+
// return {
|
241
|
+
// title: `${name}: dresden elektronik ${config.modelid} gateway v${config.swversion} / ${config.devicename} ${config.bridgeid}`,
|
242
|
+
// enum: [name]
|
243
|
+
// }
|
244
|
+
// }),
|
245
|
+
// required: true
|
246
|
+
// }
|
247
|
+
// }
|
248
|
+
// },
|
249
|
+
// layout: null,
|
250
|
+
// form: null
|
251
|
+
// }, {
|
252
|
+
// gateway: gateway != null ? gateway : gateways[0]
|
253
|
+
// }, 'Gateway Settings', 'Homebridge Settings')
|
254
|
+
const form = homebridge.createForm({
|
255
|
+
footerDisplay: 'For a detailed description, see the [wiki](https://github.com/ebaauw/homebridge-deconz/wiki/Configuration).',
|
256
|
+
schema: {
|
257
|
+
type: 'object',
|
258
|
+
properties: {
|
259
|
+
name: {
|
260
|
+
description: 'Plugin name as displayed in the Homebridge log.',
|
261
|
+
type: 'string',
|
262
|
+
required: true,
|
263
|
+
default: 'deCONZ'
|
264
|
+
},
|
265
|
+
gateways: {
|
266
|
+
title: 'Gateways',
|
267
|
+
type: 'array',
|
268
|
+
disabled: true,
|
269
|
+
items: {
|
270
|
+
type: 'object',
|
271
|
+
properties: {
|
272
|
+
host: {
|
273
|
+
description: 'Hostname and port of the deCONZ gateway.',
|
274
|
+
type: 'string'
|
275
|
+
},
|
276
|
+
expose: {
|
277
|
+
description: 'Expose gateway to HomeKit.',
|
278
|
+
type: 'boolean'
|
279
|
+
}
|
280
|
+
}
|
281
|
+
}
|
282
|
+
|
283
|
+
}
|
284
|
+
}
|
285
|
+
} //,
|
286
|
+
// layout: [
|
287
|
+
// 'name',
|
288
|
+
// {
|
289
|
+
// key: 'gateways',
|
290
|
+
// type: 'array',
|
291
|
+
// buttonText: 'Add Gateway',
|
292
|
+
// items: [
|
293
|
+
// {
|
294
|
+
// type: 'section',
|
295
|
+
// htmlClass: 'row',
|
296
|
+
// items: [
|
297
|
+
// {
|
298
|
+
// type: 'section',
|
299
|
+
// htmlClass: 'col',
|
300
|
+
// items: [
|
301
|
+
// 'gateways[].host'
|
302
|
+
// ]
|
303
|
+
// },
|
304
|
+
// {
|
305
|
+
// type: 'section',
|
306
|
+
// htmlClass: 'col',
|
307
|
+
// items: [
|
308
|
+
// {
|
309
|
+
// key: 'gateways[].expose',
|
310
|
+
// disabled: true
|
311
|
+
// }
|
312
|
+
// ]
|
313
|
+
// }
|
314
|
+
// ]
|
315
|
+
// }
|
316
|
+
// ]
|
317
|
+
// }
|
318
|
+
// ]
|
319
|
+
}, {
|
14
320
|
|
15
|
-
|
16
|
-
|
17
|
-
|
321
|
+
}, 'Gateway Settings', 'Homebridge Settings')
|
322
|
+
form.onChange(async (form) => {
|
323
|
+
// showFormGatewaySettings(result[form.gateway])
|
324
|
+
})
|
325
|
+
form.onSubmit(async (form) => {
|
326
|
+
await showFormGatewaySettings(result[form.gateways])
|
327
|
+
})
|
328
|
+
form.onCancel(() => { homebridge.showSchemaForm() })
|
329
|
+
}
|
330
|
+
|
331
|
+
async function showFormGatewaySettings (gateway, device) {
|
332
|
+
homebridge.showSpinner()
|
333
|
+
const data = await homebridge.request(
|
334
|
+
'get', {
|
335
|
+
uiPort: gateway.uiPort,
|
336
|
+
path: '/gateways/' + gateway.id
|
337
|
+
}
|
338
|
+
)
|
339
|
+
const values = {}
|
340
|
+
for (const rtype in data.deviceByRidByRtype) {
|
341
|
+
values[rtype] = []
|
342
|
+
for (const rid in data.deviceByRidByRtype[rtype]) {
|
343
|
+
const device = data.deviceByRidByRtype[rtype][rid]
|
344
|
+
values[rtype].push({
|
345
|
+
title: ['', rtype, rid].join('/') + ': ' +
|
346
|
+
device.resourceBySubtype[device.primary].body.name,
|
347
|
+
enum: [device.id]
|
348
|
+
})
|
349
|
+
}
|
350
|
+
}
|
351
|
+
data.lightsDevice = values.lights[0].enum[0]
|
352
|
+
data.sensorsDevice = values.sensors[0].enum[0]
|
353
|
+
data.groupsDevice = values.groups[0].enum[0]
|
354
|
+
homebridge.hideSpinner()
|
355
|
+
const form = homebridge.createForm({
|
356
|
+
schema: {
|
357
|
+
type: 'object',
|
358
|
+
properties: {
|
359
|
+
expose: {
|
360
|
+
title: 'Expose',
|
361
|
+
type: 'boolean'
|
362
|
+
},
|
363
|
+
lights: {
|
364
|
+
title: 'Lights',
|
365
|
+
type: 'boolean',
|
366
|
+
},
|
367
|
+
sensors: {
|
368
|
+
title: 'Sensors',
|
369
|
+
type: 'boolean',
|
370
|
+
},
|
371
|
+
groups: {
|
372
|
+
title: 'Groups',
|
373
|
+
type: 'boolean',
|
374
|
+
},
|
375
|
+
schedules: {
|
376
|
+
title: 'Schedules',
|
377
|
+
type: 'boolean',
|
378
|
+
},
|
379
|
+
logLevel: {
|
380
|
+
title: 'Log Level',
|
381
|
+
type: 'string',
|
382
|
+
oneOf: ['0', '1', '2', '3'].map((level) => { return { title: level, enum: [level] } }),
|
383
|
+
required: true,
|
384
|
+
condition: {
|
385
|
+
functionBody: 'return model.expose'
|
386
|
+
}
|
387
|
+
},
|
388
|
+
lightsDevice: {
|
389
|
+
title: 'Device',
|
390
|
+
type: 'string',
|
391
|
+
oneOf: values.lights,
|
392
|
+
required: true
|
393
|
+
},
|
394
|
+
sensorsDevice: {
|
395
|
+
title: 'Device',
|
396
|
+
type: 'string',
|
397
|
+
oneOf: values.sensors,
|
398
|
+
required: true
|
399
|
+
},
|
400
|
+
groupsDevice: {
|
401
|
+
title: 'Device',
|
402
|
+
type: 'string',
|
403
|
+
oneOf: values.groups,
|
404
|
+
required: true
|
405
|
+
}
|
406
|
+
}
|
407
|
+
},
|
408
|
+
layout: [
|
409
|
+
{
|
410
|
+
type: 'fieldset',
|
411
|
+
title: `${gateway.context.host} Gateway Settings`
|
412
|
+
},
|
413
|
+
'expose',
|
414
|
+
'logLevel',
|
415
|
+
{
|
416
|
+
type: 'flex',
|
417
|
+
'flex-flow': 'row',
|
418
|
+
title: 'Automatically Expose New',
|
419
|
+
items: [
|
420
|
+
'lights',
|
421
|
+
'sensors',
|
422
|
+
'groups',
|
423
|
+
'schedules'
|
424
|
+
],
|
425
|
+
condition: {
|
426
|
+
functionBody: 'return model.expose'
|
427
|
+
}
|
428
|
+
},
|
429
|
+
{
|
430
|
+
type: 'fieldset',
|
431
|
+
items: [
|
432
|
+
{
|
433
|
+
type: 'tabs',
|
434
|
+
tabs: [
|
435
|
+
{
|
436
|
+
title: 'Lights',
|
437
|
+
items: [
|
438
|
+
'lightsDevice'
|
439
|
+
]
|
440
|
+
},
|
441
|
+
{
|
442
|
+
title: 'Sensors',
|
443
|
+
items: [
|
444
|
+
'sensorsDevice'
|
445
|
+
]
|
446
|
+
},
|
447
|
+
{
|
448
|
+
title: 'Groups',
|
449
|
+
items: [
|
450
|
+
'groupsDevice'
|
451
|
+
]
|
452
|
+
}
|
453
|
+
]
|
454
|
+
}
|
455
|
+
],
|
456
|
+
condition: {
|
457
|
+
functionBody: 'return model.expose'
|
458
|
+
}
|
459
|
+
}
|
460
|
+
]
|
461
|
+
}, data, 'Device Settings', 'Done')
|
462
|
+
form.onChange((form) => {})
|
463
|
+
form.onSubmit((form) => {
|
464
|
+
showFormDeviceSettings(gateway, form.lightsDevice)
|
465
|
+
})
|
466
|
+
form.onCancel((form) => {
|
467
|
+
showFormGateways(gateway.context.host)
|
468
|
+
})
|
469
|
+
}
|
18
470
|
|
19
|
-
|
20
|
-
|
21
|
-
|
471
|
+
async function showFormDeviceSettings (gateway, device) {
|
472
|
+
homebridge.showSpinner()
|
473
|
+
homebridge.hideSpinner()
|
474
|
+
const form = homebridge.createForm({
|
475
|
+
schema: {
|
476
|
+
type: 'object',
|
477
|
+
properties: {
|
478
|
+
gateway: {
|
479
|
+
type: 'string'
|
480
|
+
},
|
481
|
+
device: {
|
482
|
+
type: 'string'
|
483
|
+
}
|
484
|
+
}
|
485
|
+
}
|
486
|
+
}, {
|
487
|
+
gateway: gateway.context.host,
|
488
|
+
device: device
|
489
|
+
}, 'OK', 'Cancel')
|
490
|
+
form.onChange((form) => {})
|
491
|
+
form.onSubmit((form) => {
|
492
|
+
showFormGatewaySettings(gateway)
|
493
|
+
})
|
494
|
+
form.onCancel((form) => {
|
495
|
+
showFormGatewaySettings(gateway)
|
496
|
+
})
|
497
|
+
}
|
498
|
+
|
499
|
+
(async () => {
|
500
|
+
try {
|
501
|
+
await showFormPluginConfig()
|
22
502
|
} catch (error) {
|
23
503
|
console.error(error)
|
24
504
|
}
|
package/homebridge-ui/server.js
CHANGED
@@ -5,39 +5,54 @@
|
|
5
5
|
|
6
6
|
'use strict'
|
7
7
|
|
8
|
-
const {
|
9
|
-
|
10
|
-
} = require('@homebridge/plugin-ui-utils')
|
8
|
+
const { UiServer } = require('homebridge-lib')
|
9
|
+
const Deconz = require('../lib/Deconz')
|
11
10
|
|
12
|
-
class
|
11
|
+
class DeconzUiServer extends UiServer {
|
13
12
|
constructor () {
|
14
13
|
super()
|
15
14
|
|
16
|
-
this.onRequest('
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
accessory.context != null &&
|
22
|
-
accessory.context.className === 'Gateway'
|
15
|
+
this.onRequest('discover', async (params) => {
|
16
|
+
if (this.discovery == null) {
|
17
|
+
this.discovery = new Deconz.Discovery({
|
18
|
+
// forceHttp: this.config.forceHttp,
|
19
|
+
// timeout: this.config.timeout
|
23
20
|
})
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
21
|
+
this.discovery
|
22
|
+
.on('error', (error) => {
|
23
|
+
this.log(
|
24
|
+
'%s: request %d: %s %s', error.request.name,
|
25
|
+
error.request.id, error.request.method, error.request.resource
|
26
|
+
)
|
27
|
+
this.warn(
|
28
|
+
'%s: request %d: %s', error.request.name, error.request.id, error
|
29
|
+
)
|
30
|
+
})
|
31
|
+
.on('request', (request) => {
|
32
|
+
this.debug(
|
33
|
+
'%s: request %d: %s %s', request.name,
|
34
|
+
request.id, request.method, request.resource
|
35
|
+
)
|
36
|
+
})
|
37
|
+
.on('response', (response) => {
|
38
|
+
this.debug(
|
39
|
+
'%s: request %d: %d %s', response.request.name,
|
40
|
+
response.request.id, response.statusCode, response.statusMessage
|
41
|
+
)
|
42
|
+
})
|
43
|
+
.on('found', (name, id, address) => {
|
44
|
+
this.debug('%s: found %s at %s', name, id, address)
|
45
|
+
})
|
46
|
+
.on('searching', (host) => {
|
47
|
+
this.debug('upnp: listening on %s', host)
|
48
|
+
})
|
49
|
+
.on('searchDone', () => { this.debug('upnp: search done') })
|
36
50
|
}
|
51
|
+
const configs = await this.discovery.discover()
|
52
|
+
return configs
|
37
53
|
})
|
38
|
-
|
39
54
|
this.ready()
|
40
55
|
}
|
41
56
|
}
|
42
57
|
|
43
|
-
new
|
58
|
+
new DeconzUiServer() // eslint-disable-line no-new
|
package/lib/Deconz/Resource.js
CHANGED
@@ -656,7 +656,7 @@ class Resource {
|
|
656
656
|
buttons.push([5, 'Next', SINGLE | LONG])
|
657
657
|
break
|
658
658
|
case 'TRADFRI wireless dimmer':
|
659
|
-
if (this.
|
659
|
+
if (this.body.mode === 1) {
|
660
660
|
buttons.push([1, 'Turn Right', SINGLE | LONG])
|
661
661
|
buttons.push([2, 'Turn Left', SINGLE | LONG])
|
662
662
|
} else {
|
@@ -1187,9 +1187,9 @@ class Resource {
|
|
1187
1187
|
*/
|
1188
1188
|
patchThermostat (gateway) {
|
1189
1189
|
if (this.manufacturer === 'ELKO' && this.model === 'Super TR') {
|
1190
|
-
this.
|
1190
|
+
this.capabilities.heatValue = 'heat'
|
1191
1191
|
} else {
|
1192
|
-
this.
|
1192
|
+
this.capabilities.heatValue = 'auto'
|
1193
1193
|
}
|
1194
1194
|
}
|
1195
1195
|
|
@@ -1197,7 +1197,8 @@ class Resource {
|
|
1197
1197
|
* @param {DeconzAccessory.Gateway} gateway - The gateway.
|
1198
1198
|
*/
|
1199
1199
|
patchWindowCovering (gateway) {
|
1200
|
-
if (this.manufacturer === '
|
1200
|
+
if (this.manufacturer === 'LUMI' && this.model === 'lumi.curtain.acn002') {
|
1201
|
+
this.capabilities.maxSpeed = 2
|
1201
1202
|
this.capabilities.positionChange = true
|
1202
1203
|
}
|
1203
1204
|
}
|
@@ -157,7 +157,7 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
|
|
157
157
|
this.heartbeatEnabled = true
|
158
158
|
this
|
159
159
|
.on('identify', this.identify)
|
160
|
-
.once('heartbeat', this.
|
160
|
+
.once('heartbeat', (beat) => { this.initialBeat = beat })
|
161
161
|
.on('heartbeat', this.heartbeat)
|
162
162
|
.on('shutdown', this.shutdown)
|
163
163
|
}
|
@@ -182,6 +182,12 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
|
|
182
182
|
this.values.manufacturer, this.values.model, this.values.software,
|
183
183
|
this.nAccessories, this.nDevices, this.nResourcesMonitored
|
184
184
|
)
|
185
|
+
if (this.context.migration != null) {
|
186
|
+
this.log(
|
187
|
+
'migration: %s: %d resources',
|
188
|
+
this.context.migration, this.nResourcesMonitored
|
189
|
+
)
|
190
|
+
}
|
185
191
|
if (this.logLevel > 2) {
|
186
192
|
this.vdebug(
|
187
193
|
'%d gateway resouces: %j', this.nResources,
|
@@ -210,28 +216,24 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
|
|
210
216
|
}
|
211
217
|
}
|
212
218
|
|
213
|
-
/** Initialise the gateway delegate.
|
214
|
-
*/
|
215
|
-
async init (beat) {
|
216
|
-
try {
|
217
|
-
this.debug('initialising...')
|
218
|
-
this.initialBeat = beat
|
219
|
-
await this.connect()
|
220
|
-
this.initialised = true
|
221
|
-
this.debug('initialised')
|
222
|
-
this.emit('initialised')
|
223
|
-
} catch (error) { this.error(error) }
|
224
|
-
}
|
225
|
-
|
226
219
|
/** Update properties from gateway announcement.
|
227
220
|
* @param {string} host - The gateway hostname or IP address and port.
|
228
221
|
* @param {Object} config - The response body of an unauthenticated
|
229
222
|
* GET `/config` (from {@link DeconzDiscovery#config config()}.
|
230
223
|
*/
|
231
|
-
found (host, config) {
|
232
|
-
|
233
|
-
|
234
|
-
|
224
|
+
async found (host, config) {
|
225
|
+
try {
|
226
|
+
this.context.host = host
|
227
|
+
this.values.host = host
|
228
|
+
this.context.config = config
|
229
|
+
this.values.software = config.swversion
|
230
|
+
if (!this.initialised) {
|
231
|
+
this.debug('initialising...')
|
232
|
+
await this.connect()
|
233
|
+
}
|
234
|
+
} catch (error) {
|
235
|
+
this.error(error)
|
236
|
+
}
|
235
237
|
}
|
236
238
|
|
237
239
|
async shutdown () {
|
@@ -412,8 +414,8 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
|
|
412
414
|
for (const id in this.exposeErrorById) {
|
413
415
|
this.resetExposeError(id)
|
414
416
|
}
|
415
|
-
this.context.fullState = null
|
416
417
|
this.pollNext = true
|
418
|
+
this.pollFullState = true
|
417
419
|
} catch (error) {
|
418
420
|
if (
|
419
421
|
error instanceof Deconz.ApiError && error.type === 101 && retry < 8
|
@@ -627,6 +629,45 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
|
|
627
629
|
|
628
630
|
// ===========================================================================
|
629
631
|
|
632
|
+
async onUiGet (a) {
|
633
|
+
this.debug('ui request: GET %s', a.join('/'))
|
634
|
+
if (a.length === 0) {
|
635
|
+
return {
|
636
|
+
status: 200,
|
637
|
+
body: {
|
638
|
+
expose: this.service.values.expose,
|
639
|
+
groups: this.service.values.groups,
|
640
|
+
heartrate: this.service.values.heartrate,
|
641
|
+
lights: this.service.values.lights,
|
642
|
+
logLevel: this.service.values.logLevel,
|
643
|
+
schedules: this.service.values.schedules,
|
644
|
+
sensors: this.service.values.sensors
|
645
|
+
// deviceByRidByRtype: this.deviceByRidByRtype
|
646
|
+
}
|
647
|
+
}
|
648
|
+
}
|
649
|
+
if (a[0] !== 'accessories') {
|
650
|
+
return { status: 403 } // Forbidden
|
651
|
+
}
|
652
|
+
if (a.length === 1) {
|
653
|
+
return { status: 200, body: this.deviceByRidByRtype }
|
654
|
+
}
|
655
|
+
if (this.deviceById[a[1]] == null) {
|
656
|
+
return { status: 404 } // Not Found
|
657
|
+
}
|
658
|
+
if (a.length === 2) {
|
659
|
+
return { status: 200, body: this.deviceById[a[1]] }
|
660
|
+
}
|
661
|
+
return { status: 403 } // Forbidden
|
662
|
+
}
|
663
|
+
|
664
|
+
async onUiPut (a, body) {
|
665
|
+
this.debug('ui request: PUT %s %j', a.join('/'), body)
|
666
|
+
return { status: 501 } // Not Implented
|
667
|
+
}
|
668
|
+
|
669
|
+
// ===========================================================================
|
670
|
+
|
630
671
|
/** Poll the gateway.
|
631
672
|
*
|
632
673
|
* Periodically get the gateway full state and call
|
@@ -639,9 +680,11 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
|
|
639
680
|
try {
|
640
681
|
this.polling = true
|
641
682
|
this.vdebug('%spolling...', this.pollNext ? 'priority ' : '')
|
642
|
-
if (this.context.fullState == null) {
|
643
|
-
|
644
|
-
|
683
|
+
if (this.context.fullState == null || this.pollFullState) {
|
684
|
+
const fullState = await this.client.get('/')
|
685
|
+
fullState.groups[0] = await this.client.get('/groups/0')
|
686
|
+
this.context.fullState = fullState
|
687
|
+
this.pollFullState = false
|
645
688
|
} else {
|
646
689
|
const config = await this.client.get('/config')
|
647
690
|
if (config.bridgeid === this.id && config.UTC == null) {
|
@@ -673,6 +716,11 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
|
|
673
716
|
this.vdebug('polling done')
|
674
717
|
this.pollNext = false
|
675
718
|
this.polling = false
|
719
|
+
if (!this.initialised) {
|
720
|
+
this.initialised = true
|
721
|
+
this.debug('initialised')
|
722
|
+
this.emit('initialised')
|
723
|
+
}
|
676
724
|
}
|
677
725
|
}
|
678
726
|
|
@@ -857,6 +905,7 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
|
|
857
905
|
}
|
858
906
|
|
859
907
|
this.nAccessories = Object.keys(this.accessoryById).length
|
908
|
+
this.nResourcesMonitored = Object.keys(this.accessoryByRpath).length
|
860
909
|
this.nExposeErrors = Object.keys(this.exposeErrorById).length
|
861
910
|
if (this.nExposeErrors === 0) {
|
862
911
|
this.vdebug('%d accessories', this.nAccessories)
|
@@ -867,24 +916,20 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
|
|
867
916
|
}
|
868
917
|
|
869
918
|
if (changed) {
|
870
|
-
this.nResourcesMonitored = Object.keys(this.accessoryByRpath).length
|
871
|
-
this.identify()
|
872
919
|
if (this.context.migration == null) {
|
873
920
|
const response = await this.client.post('/resourcelinks', {
|
874
921
|
name: 'homebridge-deconz',
|
875
922
|
description: 'migration',
|
876
923
|
classid: 1,
|
877
|
-
links: Object.keys(this.
|
924
|
+
links: Object.keys(this.accessoryByRpath).sort()
|
878
925
|
})
|
879
926
|
this.context.migration = '/resourcelinks/' + response.success.id
|
880
927
|
} else {
|
881
928
|
await this.client.put(this.context.migration, {
|
882
|
-
links: Object.keys(this.
|
929
|
+
links: Object.keys(this.accessoryByRpath).sort()
|
883
930
|
})
|
884
931
|
}
|
885
|
-
this.
|
886
|
-
'migration: %s: %d resources', this.context.migration, this.nResources
|
887
|
-
)
|
932
|
+
this.identify()
|
888
933
|
}
|
889
934
|
}
|
890
935
|
|
package/lib/DeconzPlatform.js
CHANGED
@@ -45,6 +45,7 @@ class DeconzPlatform extends homebridgeLib.Platform {
|
|
45
45
|
.stringKey('name')
|
46
46
|
.stringKey('platform')
|
47
47
|
.boolKey('forceHttp')
|
48
|
+
.stringKey('host')
|
48
49
|
.arrayKey('hosts')
|
49
50
|
.boolKey('noResponse')
|
50
51
|
.intKey('parallelRequests', 1, 30)
|
@@ -60,6 +61,9 @@ class DeconzPlatform extends homebridgeLib.Platform {
|
|
60
61
|
|
61
62
|
try {
|
62
63
|
optionParser.parse(configJson)
|
64
|
+
if (this.config.host != null) {
|
65
|
+
this.config.hosts.push(this.config.host)
|
66
|
+
}
|
63
67
|
this.discovery = new Deconz.Discovery({
|
64
68
|
forceHttp: this.config.forceHttp,
|
65
69
|
timeout: this.config.timeout
|
@@ -106,7 +110,8 @@ class DeconzPlatform extends homebridgeLib.Platform {
|
|
106
110
|
host: host
|
107
111
|
})
|
108
112
|
}
|
109
|
-
this.gatewayMap[id].found(host, config)
|
113
|
+
await this.gatewayMap[id].found(host, config)
|
114
|
+
await events.once(this.gatewayMap[id], 'initialised')
|
110
115
|
this.emit('found')
|
111
116
|
}
|
112
117
|
|
@@ -124,17 +129,25 @@ class DeconzPlatform extends homebridgeLib.Platform {
|
|
124
129
|
async init () {
|
125
130
|
try {
|
126
131
|
const jobs = []
|
127
|
-
this.debug('job %d: find at least one gateway', jobs.length)
|
128
|
-
jobs.push(events.once(this, 'found'))
|
129
132
|
if (this.config.hosts.length > 0) {
|
130
133
|
for (const host of this.config.hosts) {
|
131
134
|
this.debug('job %d: find gateway at %s', jobs.length, host)
|
132
135
|
jobs.push(this.findHost(host))
|
133
136
|
}
|
134
137
|
} else {
|
138
|
+
this.debug('job %d: find at least one gateway', jobs.length)
|
139
|
+
jobs.push(events.once(this, 'found'))
|
135
140
|
for (const id in this.gatewayMap) {
|
141
|
+
const gateway = this.gatewayMap[id]
|
142
|
+
const host = gateway.context.host
|
136
143
|
this.debug('job %d: find gateway %s', jobs.length, id)
|
137
|
-
jobs.push(
|
144
|
+
jobs.push(events.once(gateway, 'initialised'))
|
145
|
+
try {
|
146
|
+
const config = await this.discovery.config(host)
|
147
|
+
await this.foundGateway(host, config)
|
148
|
+
} catch (error) {
|
149
|
+
this.warn('%s: %s', id, error)
|
150
|
+
}
|
138
151
|
}
|
139
152
|
}
|
140
153
|
|
@@ -150,9 +163,61 @@ class DeconzPlatform extends homebridgeLib.Platform {
|
|
150
163
|
|
151
164
|
this.log('%d gateways', Object.keys(this.gatewayMap).length)
|
152
165
|
this.emit('initialised')
|
166
|
+
const dumpInfo = {
|
167
|
+
config: this.config,
|
168
|
+
gatewayMap: {}
|
169
|
+
}
|
170
|
+
for (const id in this.gatewayMap) {
|
171
|
+
const gateway = this.gatewayMap[id]
|
172
|
+
dumpInfo.gatewayMap[id] = gateway.context
|
173
|
+
}
|
174
|
+
await this.createDumpFile(dumpInfo)
|
153
175
|
} catch (error) { this.error(error) }
|
154
176
|
}
|
155
177
|
|
178
|
+
async onUiRequest (method, url, body) {
|
179
|
+
const a = url.split('/').slice(1)
|
180
|
+
if (a.length < 1) {
|
181
|
+
return { status: 403 } // Forbidden
|
182
|
+
}
|
183
|
+
if (a[0] === 'gateways') {
|
184
|
+
if (a.length === 1) {
|
185
|
+
if (method === 'GET') {
|
186
|
+
// const gatewayByHost = await this.discovery.discover()
|
187
|
+
const gatewayByHost = {}
|
188
|
+
for (const id in this.gatewayMap) {
|
189
|
+
const gateway = this.gatewayMap[id]
|
190
|
+
gatewayByHost[gateway.context.host] = {
|
191
|
+
config: gateway.context.config,
|
192
|
+
host: gateway.context.host,
|
193
|
+
id: id
|
194
|
+
}
|
195
|
+
}
|
196
|
+
return {
|
197
|
+
status: 200,
|
198
|
+
body: Object.keys(gatewayByHost).sort().map((host) => {
|
199
|
+
return gatewayByHost[host]
|
200
|
+
})
|
201
|
+
}
|
202
|
+
}
|
203
|
+
return { status: 405 } // Method Not Allowed
|
204
|
+
}
|
205
|
+
const gateway = this.gatewayMap[a[1]]
|
206
|
+
const path = a.slice(2)
|
207
|
+
if (gateway == null) {
|
208
|
+
return { status: 404 } // Not Found
|
209
|
+
}
|
210
|
+
if (method === 'GET') {
|
211
|
+
return gateway.onUiGet(path)
|
212
|
+
}
|
213
|
+
if (method === 'PUT') {
|
214
|
+
return gateway.onUiPut(path, body)
|
215
|
+
}
|
216
|
+
return { status: 405 } // Method Not Allowed
|
217
|
+
}
|
218
|
+
return { status: 403 } // Forbidden
|
219
|
+
}
|
220
|
+
|
156
221
|
async heartbeat (beat) {
|
157
222
|
try {
|
158
223
|
if (beat % 300 === 5 && this.config.hosts.length === 0) {
|
@@ -140,7 +140,7 @@ class Light extends DeconzService.LightsResource {
|
|
140
140
|
unit: '°'
|
141
141
|
}).on('didSet', (value, fromHomeKit) => {
|
142
142
|
if (fromHomeKit) {
|
143
|
-
const hue = Math.round(this.
|
143
|
+
const hue = Math.round(this.values.hue * 65535.0 / 360.0)
|
144
144
|
this.put({ hue: hue })
|
145
145
|
this.values.colormode = 'hs'
|
146
146
|
}
|
@@ -151,7 +151,7 @@ class Light extends DeconzService.LightsResource {
|
|
151
151
|
unit: '%'
|
152
152
|
}).on('didSet', (value, fromHomeKit) => {
|
153
153
|
if (fromHomeKit) {
|
154
|
-
const sat = Math.round(this.
|
154
|
+
const sat = Math.round(this.values.saturation * 254.0 / 100.0)
|
155
155
|
this.put({ sat: sat })
|
156
156
|
this.values.colormode = 'hs'
|
157
157
|
}
|
@@ -236,31 +236,8 @@ class Light extends DeconzService.LightsResource {
|
|
236
236
|
}
|
237
237
|
|
238
238
|
if (this.resource.rtype === 'groups') {
|
239
|
-
this.sceneServices =
|
240
|
-
|
241
|
-
const service = new homebridgeLib.ServiceDelegate(accessory, {
|
242
|
-
name: this.resource.body.name + ' ' + scene.name,
|
243
|
-
Service: this.Services.hap.Switch,
|
244
|
-
subtype: this.subtype + '-S' + scene.id
|
245
|
-
})
|
246
|
-
service.addCharacteristicDelegate({
|
247
|
-
key: 'on',
|
248
|
-
Characteristic: this.Characteristics.hap.On,
|
249
|
-
value: false
|
250
|
-
}).on('didSet', async (value, fromHomeKit) => {
|
251
|
-
this.checkAdaptiveLighting()
|
252
|
-
if (fromHomeKit && value) {
|
253
|
-
try {
|
254
|
-
const path = this.resource.rpath + '/scenes/' + scene.id + '/recall'
|
255
|
-
this.debug('PUT %s', path)
|
256
|
-
await this.client.put(path)
|
257
|
-
} catch (error) { this.error(error) }
|
258
|
-
await timeout(this.platform.config.waitTimeReset)
|
259
|
-
service.values.on = false
|
260
|
-
}
|
261
|
-
})
|
262
|
-
this.sceneServices.push(service)
|
263
|
-
}
|
239
|
+
this.sceneServices = {}
|
240
|
+
this.updateScenes(this.resource.body.scenes)
|
264
241
|
}
|
265
242
|
|
266
243
|
if (this.capabilities.effects != null) {
|
@@ -412,6 +389,43 @@ class Light extends DeconzService.LightsResource {
|
|
412
389
|
super.updateState(state)
|
413
390
|
}
|
414
391
|
|
392
|
+
updateScenes (scenes) {
|
393
|
+
const sceneById = {}
|
394
|
+
for (const scene of scenes) {
|
395
|
+
sceneById[scene.id] = scene
|
396
|
+
if (this.sceneServices[scene.id] == null) {
|
397
|
+
const service = new homebridgeLib.ServiceDelegate(this.accessoryDelegate, {
|
398
|
+
name: this.resource.body.name + ' ' + scene.name,
|
399
|
+
Service: this.Services.hap.Switch,
|
400
|
+
subtype: this.subtype + '-S' + scene.id
|
401
|
+
})
|
402
|
+
service.addCharacteristicDelegate({
|
403
|
+
key: 'on',
|
404
|
+
Characteristic: this.Characteristics.hap.On,
|
405
|
+
value: false
|
406
|
+
}).on('didSet', async (value, fromHomeKit) => {
|
407
|
+
this.checkAdaptiveLighting()
|
408
|
+
if (fromHomeKit && value) {
|
409
|
+
try {
|
410
|
+
const path = this.resource.rpath + '/scenes/' + scene.id + '/recall'
|
411
|
+
this.debug('PUT %s', path)
|
412
|
+
await this.client.put(path)
|
413
|
+
} catch (error) { this.error(error) }
|
414
|
+
await timeout(this.platform.config.waitTimeReset)
|
415
|
+
service.values.on = false
|
416
|
+
}
|
417
|
+
})
|
418
|
+
this.sceneServices[scene.id] = service
|
419
|
+
}
|
420
|
+
}
|
421
|
+
for (const id in this.scenesServices) {
|
422
|
+
if (sceneById[id] == null) {
|
423
|
+
this.scenesSerices[id].destroy()
|
424
|
+
delete this.scenesService[id]
|
425
|
+
}
|
426
|
+
}
|
427
|
+
}
|
428
|
+
|
415
429
|
initAdaptiveLighting () {
|
416
430
|
if (this.adaptiveLighting == null) {
|
417
431
|
this.adaptiveLighting = new homebridgeLib.AdaptiveLighting(
|
@@ -12,7 +12,7 @@ const DeconzService = require('../DeconzService')
|
|
12
12
|
*/
|
13
13
|
class Thermostat extends DeconzService.SensorsResource {
|
14
14
|
constructor (accessory, resource, params = {}) {
|
15
|
-
params.Service = accessory.Services.hap.
|
15
|
+
params.Service = accessory.Services.hap.Thermostat
|
16
16
|
super(accessory, resource, params)
|
17
17
|
|
18
18
|
this.addCharacteristicDelegate({
|
@@ -65,7 +65,7 @@ class Thermostat extends DeconzService.SensorsResource {
|
|
65
65
|
}
|
66
66
|
}).on('didSet', async (value, fromHomeKit) => {
|
67
67
|
if (fromHomeKit) {
|
68
|
-
await this.put('/
|
68
|
+
await this.put('/config', {
|
69
69
|
mode: value === this.Characteristics.hap.TargetHeatingCoolingState.OFF
|
70
70
|
? 'off'
|
71
71
|
: this.capabilities.heatValue
|
@@ -37,10 +37,6 @@ class WindowCovering extends DeconzService.LightsResource {
|
|
37
37
|
key: 'positionState',
|
38
38
|
Characteristic: this.Characteristics.hap.PositionState,
|
39
39
|
value: this.Characteristics.hap.PositionState.STOPPED
|
40
|
-
}).on('didSet', (value) => {
|
41
|
-
if (value === this.Characteristics.hap.PositionState.STOPPED) {
|
42
|
-
this.values.targetPosition = this.values.currentPosition
|
43
|
-
}
|
44
40
|
})
|
45
41
|
|
46
42
|
this.addCharacteristicDelegate({
|
@@ -63,6 +59,25 @@ class WindowCovering extends DeconzService.LightsResource {
|
|
63
59
|
})
|
64
60
|
}
|
65
61
|
|
62
|
+
if (resource.capabilities.maxSpeed != null) {
|
63
|
+
this.addCharacteristicDelegate({
|
64
|
+
key: 'motorSpeed',
|
65
|
+
Characteristic: this.Characteristics.my.MotorSpeed,
|
66
|
+
unit: '',
|
67
|
+
props: {
|
68
|
+
unit: '',
|
69
|
+
minValue: 0,
|
70
|
+
maxValue: resource.capabilities.maxSpeed,
|
71
|
+
minStep: 1
|
72
|
+
}
|
73
|
+
}).on('didSet', async (value, fromHomeKit) => {
|
74
|
+
if (!fromHomeKit) {
|
75
|
+
return
|
76
|
+
}
|
77
|
+
await this.put({ speed: value })
|
78
|
+
})
|
79
|
+
}
|
80
|
+
|
66
81
|
if (resource.capabilities.positionChange) {
|
67
82
|
this.addCharacteristicDelegate({
|
68
83
|
key: 'positionChange',
|
@@ -80,13 +95,10 @@ class WindowCovering extends DeconzService.LightsResource {
|
|
80
95
|
this.addCharacteristicDelegates()
|
81
96
|
|
82
97
|
this.update(resource.body, resource.rpath)
|
98
|
+
this.values.targetPosition = this.values.currentPosition
|
83
99
|
}
|
84
100
|
|
85
|
-
setPosition () {
|
86
|
-
if (this.timer != null) {
|
87
|
-
clearTimeout(this.timer)
|
88
|
-
delete this.timer
|
89
|
-
}
|
101
|
+
async setPosition () {
|
90
102
|
let lift = 100 - this.values.targetPosition // % closed --> % open
|
91
103
|
if (this.venetianBlind) {
|
92
104
|
if (this.values.closeUpwards) {
|
@@ -101,14 +113,11 @@ class WindowCovering extends DeconzService.LightsResource {
|
|
101
113
|
this.values.targetPosition > this.values.currentPosition
|
102
114
|
? this.Characteristics.hap.PositionState.INCREASING
|
103
115
|
: this.Characteristics.hap.PositionState.DECREASING
|
104
|
-
this.
|
105
|
-
this.
|
106
|
-
this.values.positionState =
|
107
|
-
this.Characteristics.hap.PositionState.STOPPED
|
108
|
-
}, 15000)
|
116
|
+
this.moving = new Date()
|
117
|
+
await this.put({ lift: lift })
|
109
118
|
}
|
110
119
|
|
111
|
-
updateState (state
|
120
|
+
updateState (state) {
|
112
121
|
if (state.lift != null) {
|
113
122
|
let position = Math.round(state.lift / 5) * 5
|
114
123
|
let closeUpwards
|
@@ -121,16 +130,23 @@ class WindowCovering extends DeconzService.LightsResource {
|
|
121
130
|
}
|
122
131
|
}
|
123
132
|
position = 100 - position // % open -> % closed
|
124
|
-
if (
|
125
|
-
position === this.values.targetPosition &&
|
126
|
-
(closeUpwards == null || closeUpwards === this.targetCloseUpwards)
|
127
|
-
) {
|
128
|
-
this.values.positionState = this.Characteristics.hap.PositionState.STOPPED
|
129
|
-
}
|
130
133
|
this.values.currentPosition = position
|
131
134
|
if (closeUpwards != null) {
|
132
135
|
this.values.closeUpwards = closeUpwards
|
133
136
|
}
|
137
|
+
if (
|
138
|
+
this.moving == null || new Date() - this.moving >= 30000 || (
|
139
|
+
position === this.values.targetPosition &&
|
140
|
+
(closeUpwards == null || closeUpwards === this.targetCloseUpwards)
|
141
|
+
)
|
142
|
+
) {
|
143
|
+
this.moving = null
|
144
|
+
this.values.targetPosition = position
|
145
|
+
this.values.positionState = this.Characteristics.hap.PositionState.STOPPED
|
146
|
+
}
|
147
|
+
}
|
148
|
+
if (state.speed != null) {
|
149
|
+
this.values.motorSpeed = state.speed
|
134
150
|
}
|
135
151
|
super.updateState(state)
|
136
152
|
}
|
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.0.
|
7
|
+
"version": "0.0.15",
|
8
8
|
"keywords": [
|
9
9
|
"homebridge-plugin",
|
10
10
|
"homekit",
|
@@ -22,13 +22,12 @@
|
|
22
22
|
"engines": {
|
23
23
|
"deCONZ": "2.14.1",
|
24
24
|
"homebridge": "^1.4.0",
|
25
|
-
"node": "^16.14.
|
25
|
+
"node": "^16.14.2"
|
26
26
|
},
|
27
27
|
"dependencies": {
|
28
|
-
"
|
29
|
-
"
|
30
|
-
"
|
31
|
-
"ws": "^8.4.2",
|
28
|
+
"homebridge-lib": "~5.4.0",
|
29
|
+
"semver": "^7.3.7",
|
30
|
+
"ws": "^8.5.0",
|
32
31
|
"xml2js": "~0.4.23"
|
33
32
|
},
|
34
33
|
"scripts": {
|