homebridge-velux 0.0.1 → 0.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/README.md +3 -2
- package/config.schema.json +12 -0
- package/lib/{VeluxGateway.js → VeluxAccessory/Gateway.js} +54 -34
- package/lib/VeluxAccessory/WindowCovering.js +20 -0
- package/lib/{VeluxAccessory.js → VeluxAccessory/index.js} +8 -16
- package/lib/VeluxPlatform.js +13 -12
- package/lib/VeluxService/Gateway.js +30 -0
- package/lib/{VeluxService.js → VeluxService/WindowCovering.js} +5 -19
- package/lib/VeluxService/index.js +10 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
[](https://www.npmjs.com/package/homebridge-velux)
|
|
8
8
|
[](https://www.npmjs.com/package/homebridge-velux)
|
|
9
9
|
[](https://discord.gg/bXmnUwXQR9)
|
|
10
|
-
|
|
10
|
+
[](https://github.com/homebridge/homebridge/wiki/Verified-Plugins)
|
|
11
11
|
|
|
12
12
|
[](https://github.com/ebaauw/homebridge-velux/issues)
|
|
13
13
|
[](https://github.com/ebaauw/homebridge-velux/pulls)
|
|
@@ -30,7 +30,8 @@ It provides the following features:
|
|
|
30
30
|
- Support for multiple KLF 200 gateways;
|
|
31
31
|
- Includes `velux` command-line utility for troubleshooting.
|
|
32
32
|
|
|
33
|
-
Homebridge Velux exposes an accessory for each connected device.
|
|
33
|
+
Homebridge Velux exposes an accessory for each gateway and one for each connected device.
|
|
34
|
+
The gateway accessory is used to control the polling rate and the log level.
|
|
34
35
|
<!-- See the [Wiki](https://github.com/ebaauw/homebridge-velux/wiki/Velux-Accessory) for details. -->
|
|
35
36
|
|
|
36
37
|
### Prerequisites
|
package/config.schema.json
CHANGED
|
@@ -26,6 +26,18 @@
|
|
|
26
26
|
"type": "string",
|
|
27
27
|
"required": true
|
|
28
28
|
},
|
|
29
|
+
"id": {
|
|
30
|
+
"title": "ID",
|
|
31
|
+
"description": "A unique ID for the gateway, preferably its MAC address. Default: the hostname.",
|
|
32
|
+
"type": "string",
|
|
33
|
+
"required": true
|
|
34
|
+
},
|
|
35
|
+
"name": {
|
|
36
|
+
"title": "Name",
|
|
37
|
+
"description": "The name for the gateway. Default: the hostname.",
|
|
38
|
+
"type": "string",
|
|
39
|
+
"required": false
|
|
40
|
+
},
|
|
29
41
|
"password": {
|
|
30
42
|
"title": "Password",
|
|
31
43
|
"description": "The WiFi assword of the FLF 200 gateway.",
|
|
@@ -1,30 +1,55 @@
|
|
|
1
|
-
// homebridge-velux/lib/
|
|
1
|
+
// homebridge-velux/lib/VeluxAccessory/Gateway.js
|
|
2
2
|
// Copyright © 2025 Erik Baauw. All rights reserved.
|
|
3
3
|
//
|
|
4
4
|
// Homebridge plugin for Velux Integra KLF 200 gateway.
|
|
5
5
|
|
|
6
6
|
import { timeout, toHexString } from 'homebridge-lib'
|
|
7
|
-
import {
|
|
7
|
+
import { AccessoryDelegate } from 'homebridge-lib/AccessoryDelegate'
|
|
8
8
|
|
|
9
9
|
import { VeluxClient } from 'hb-velux-tools/VeluxClient'
|
|
10
10
|
|
|
11
|
-
import { VeluxAccessory } from './
|
|
11
|
+
import { VeluxAccessory } from './index.js'
|
|
12
|
+
import { VeluxService } from '../VeluxService/index.js'
|
|
13
|
+
import '../VeluxService/Gateway.js'
|
|
12
14
|
|
|
13
|
-
class
|
|
15
|
+
class Gateway extends AccessoryDelegate {
|
|
14
16
|
constructor (platform, params = {}) {
|
|
15
|
-
super(platform, params
|
|
17
|
+
super(platform, params)
|
|
16
18
|
this.nodeIdList = []
|
|
17
19
|
this.accessories = {}
|
|
18
20
|
|
|
21
|
+
this.service = new VeluxService.Gateway(this, {
|
|
22
|
+
name: params.name
|
|
23
|
+
})
|
|
24
|
+
this.manageLogLevel(this.service.characteristicDelegate('logLevel'))
|
|
25
|
+
|
|
26
|
+
this.on('heartbeat', this.heartbeat)
|
|
27
|
+
|
|
19
28
|
this.client = new VeluxClient({
|
|
20
29
|
host: params.host,
|
|
21
30
|
password: params.password,
|
|
22
31
|
timeout: platform.config.timeout
|
|
23
32
|
})
|
|
24
33
|
this.client
|
|
25
|
-
.on('
|
|
26
|
-
.on('
|
|
34
|
+
.on('connecting', (host) => { this.log('connecting to %s...', host) })
|
|
35
|
+
.on('connect', (host) => { this.log('connected to %s', host) })
|
|
36
|
+
.on('disconnect', (host) => { this.log('disconnected from %s', host) })
|
|
27
37
|
.on('error', (error) => {
|
|
38
|
+
if (error.request == null) {
|
|
39
|
+
this.error('error: %s', error)
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
if (error.request.params == null) {
|
|
43
|
+
this.log('request %d: %s', error.request.id, error.request.cmdName)
|
|
44
|
+
} else {
|
|
45
|
+
this.log(
|
|
46
|
+
'request %d: %s %j', error.request.id, error.request.cmdName,
|
|
47
|
+
error.request.params
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
this.warn('request %d: error: %s', error.request.id, error)
|
|
51
|
+
})
|
|
52
|
+
.on('warning', (error) => {
|
|
28
53
|
if (error.request == null) {
|
|
29
54
|
this.warn('error: %s', error)
|
|
30
55
|
return
|
|
@@ -103,6 +128,7 @@ class VeluxGateway extends Delegate {
|
|
|
103
128
|
this.log('connecting...')
|
|
104
129
|
await this.client.connect()
|
|
105
130
|
const { softwareVersion } = await this.client.request(VeluxClient.commands.GW_GET_VERSION_REQ)
|
|
131
|
+
this.values.firmware = softwareVersion
|
|
106
132
|
const { api } = await this.client.request(VeluxClient.commands.GW_GET_PROTOCOL_VERSION_REQ)
|
|
107
133
|
const nodes = {}
|
|
108
134
|
let nodeList = await this.client.request(VeluxClient.commands.GW_CS_GET_SYSTEMTABLE_DATA_REQ)
|
|
@@ -127,6 +153,9 @@ class VeluxGateway extends Delegate {
|
|
|
127
153
|
}
|
|
128
154
|
switch (node.actuatorType) {
|
|
129
155
|
case 0x0280:
|
|
156
|
+
if (VeluxAccessory.WindowCovering == null) {
|
|
157
|
+
await import('./WindowCovering.js')
|
|
158
|
+
}
|
|
130
159
|
this.accessories[id] = new VeluxAccessory.WindowCovering(this, params)
|
|
131
160
|
break
|
|
132
161
|
default:
|
|
@@ -137,7 +166,7 @@ class VeluxGateway extends Delegate {
|
|
|
137
166
|
continue
|
|
138
167
|
}
|
|
139
168
|
}
|
|
140
|
-
this.
|
|
169
|
+
this.heartbeatEnabled = true
|
|
141
170
|
this.initialised = true
|
|
142
171
|
this.debug('initialised')
|
|
143
172
|
this.emit('initialised')
|
|
@@ -151,33 +180,24 @@ class VeluxGateway extends Delegate {
|
|
|
151
180
|
}
|
|
152
181
|
}
|
|
153
182
|
|
|
154
|
-
get logLevel () { return this._logLevel }
|
|
155
|
-
|
|
156
|
-
updateLogLevel () {
|
|
157
|
-
let logLevel = 0
|
|
158
|
-
for (const id in this.accessories) {
|
|
159
|
-
logLevel = Math.max(logLevel, this.accessories[id].logLevel)
|
|
160
|
-
}
|
|
161
|
-
this._logLevel = logLevel
|
|
162
|
-
}
|
|
163
|
-
|
|
164
183
|
async heartbeat (beat) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
184
|
+
try {
|
|
185
|
+
if (!this.client.connected) {
|
|
186
|
+
if (beat % 60 !== 0) {
|
|
187
|
+
return
|
|
188
|
+
}
|
|
189
|
+
await this.client.connect()
|
|
171
190
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
191
|
+
if (beat % 600 === 0) {
|
|
192
|
+
await this.client.request(VeluxClient.commands.GW_HOUSE_STATUS_MONITOR_ENABLE_REQ)
|
|
193
|
+
}
|
|
194
|
+
if (beat % this.service.values.heartrate === 0 && this.nodeIdList.length > 0) {
|
|
195
|
+
await this.client.request(VeluxClient.commands.GW_STATUS_REQUEST_REQ, {
|
|
196
|
+
nodeIds: this.nodeIdList
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
} catch (error) {
|
|
200
|
+
this.warn('heartbeat error %s', error)
|
|
181
201
|
}
|
|
182
202
|
}
|
|
183
203
|
|
|
@@ -186,4 +206,4 @@ class VeluxGateway extends Delegate {
|
|
|
186
206
|
}
|
|
187
207
|
}
|
|
188
208
|
|
|
189
|
-
|
|
209
|
+
VeluxAccessory.Gateway = Gateway
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// homebridge-velux/lib/VeluxAccessory/WindowCovering.js
|
|
2
|
+
// Copyright © 2025 Erik Baauw. All rights reserved.
|
|
3
|
+
//
|
|
4
|
+
// Homebridge plugin for Velux Integra KLF 200 gateway.
|
|
5
|
+
|
|
6
|
+
import { VeluxAccessory } from './index.js'
|
|
7
|
+
import '../VeluxService/WindowCovering.js'
|
|
8
|
+
|
|
9
|
+
class WindowCovering extends VeluxAccessory {
|
|
10
|
+
constructor (gateway, params = {}) {
|
|
11
|
+
params.ServiceName = 'WindowCovering'
|
|
12
|
+
super(gateway, params)
|
|
13
|
+
|
|
14
|
+
setImmediate(() => {
|
|
15
|
+
this.emit('initialised')
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
VeluxAccessory.WindowCovering = WindowCovering
|
|
@@ -1,37 +1,29 @@
|
|
|
1
|
-
// homebridge-velux/lib/VeluxAccessory.js
|
|
1
|
+
// homebridge-velux/lib/VeluxAccessory/index.js
|
|
2
2
|
// Copyright © 2025 Erik Baauw. All rights reserved.
|
|
3
3
|
//
|
|
4
4
|
// Homebridge plugin for Velux Integra KLF 200 gateway.
|
|
5
5
|
|
|
6
6
|
import { AccessoryDelegate } from 'homebridge-lib/AccessoryDelegate'
|
|
7
|
+
import { VeluxService } from '../VeluxService/index.js'
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class WindowCovering extends AccessoryDelegate {
|
|
11
|
-
constructor (gateway, params = {}) {
|
|
9
|
+
class VeluxAccessory extends AccessoryDelegate {
|
|
10
|
+
constructor (gateway, params) {
|
|
12
11
|
super(gateway.platform, params)
|
|
13
12
|
this.gateway = gateway
|
|
14
13
|
this.client = gateway.client
|
|
15
14
|
this.log(
|
|
16
15
|
'%s [%d]: %s %s [%s] at %d%%', params.name, params.payload.nodeId,
|
|
17
16
|
params.manufacturer, params.model, params.payload.actuatorType,
|
|
18
|
-
params.payload.currentPosition
|
|
17
|
+
100 - params.payload.currentPosition
|
|
19
18
|
)
|
|
20
|
-
this.service = new VeluxService.
|
|
21
|
-
this.manageLogLevel(this.service.characteristicDelegate('logLevel'))
|
|
22
|
-
|
|
23
|
-
setImmediate(() => {
|
|
24
|
-
this.emit('initialised')
|
|
25
|
-
})
|
|
19
|
+
this.service = new VeluxService[params.ServiceName](this, params)
|
|
26
20
|
}
|
|
27
21
|
|
|
22
|
+
get logLevel () { return this.gateway?.logLevel ?? 2 }
|
|
23
|
+
|
|
28
24
|
update (payload) {
|
|
29
25
|
this.service.update(payload)
|
|
30
26
|
}
|
|
31
27
|
}
|
|
32
28
|
|
|
33
|
-
class VeluxAccessory {
|
|
34
|
-
static get WindowCovering () { return WindowCovering }
|
|
35
|
-
}
|
|
36
|
-
|
|
37
29
|
export { VeluxAccessory }
|
package/lib/VeluxPlatform.js
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
import { OptionParser } from 'homebridge-lib/OptionParser'
|
|
9
9
|
import { Platform } from 'homebridge-lib/Platform'
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import { VeluxAccessory } from './VeluxAccessory/index.js'
|
|
12
|
+
import './VeluxAccessory/Gateway.js'
|
|
12
13
|
|
|
13
14
|
class VeluxPlatform extends Platform {
|
|
14
15
|
constructor (log, configJson, homebridge) {
|
|
@@ -19,7 +20,6 @@ class VeluxPlatform extends Platform {
|
|
|
19
20
|
|
|
20
21
|
this
|
|
21
22
|
.once('heartbeat', this.init)
|
|
22
|
-
.on('heartbeat', this.heartbeat)
|
|
23
23
|
.on('shutdown', this.shutdown)
|
|
24
24
|
}
|
|
25
25
|
|
|
@@ -49,7 +49,8 @@ class VeluxPlatform extends Platform {
|
|
|
49
49
|
const optionParser = new OptionParser(config, true)
|
|
50
50
|
optionParser
|
|
51
51
|
.hostKey()
|
|
52
|
-
.stringKey('
|
|
52
|
+
.stringKey('id', true)
|
|
53
|
+
.stringKey('name', true)
|
|
53
54
|
.stringKey('password')
|
|
54
55
|
.on('userInputError', (error) => {
|
|
55
56
|
this.warn('config.json: hosts[%d]: %s', i, error)
|
|
@@ -62,6 +63,7 @@ class VeluxPlatform extends Platform {
|
|
|
62
63
|
validHosts.push({
|
|
63
64
|
host: config.hostname + ':' + config.port,
|
|
64
65
|
name: config.name ?? config.hostname,
|
|
66
|
+
id: config.id ?? config.hostname,
|
|
65
67
|
password: config.password
|
|
66
68
|
})
|
|
67
69
|
} catch (error) {
|
|
@@ -80,7 +82,14 @@ class VeluxPlatform extends Platform {
|
|
|
80
82
|
|
|
81
83
|
for (const host of this.config.hosts) {
|
|
82
84
|
try {
|
|
83
|
-
const gateway = new
|
|
85
|
+
const gateway = new VeluxAccessory.Gateway(this, {
|
|
86
|
+
name: host.name,
|
|
87
|
+
id: host.id,
|
|
88
|
+
manufacturer: 'VELUX',
|
|
89
|
+
model: 'KLF 200',
|
|
90
|
+
host: host.host,
|
|
91
|
+
password: host.password
|
|
92
|
+
})
|
|
84
93
|
jobs.push(gateway.init())
|
|
85
94
|
this.gateways[host] = gateway
|
|
86
95
|
} catch (error) {
|
|
@@ -94,14 +103,6 @@ class VeluxPlatform extends Platform {
|
|
|
94
103
|
this.emit('initialised')
|
|
95
104
|
}
|
|
96
105
|
|
|
97
|
-
async heartbeat (beat) {
|
|
98
|
-
try {
|
|
99
|
-
for (const id in this.gateways) {
|
|
100
|
-
this.gateways[id].heartbeat(beat)
|
|
101
|
-
}
|
|
102
|
-
} catch (error) { this.warn('heartbeat error %s', error) }
|
|
103
|
-
}
|
|
104
|
-
|
|
105
106
|
async shutdown () {
|
|
106
107
|
for (const id in this.gateways) {
|
|
107
108
|
await this.gateways[id].shutdown()
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// homebridge-deconz/lib/VeluxService/Gateway.js
|
|
2
|
+
// Copyright © 2025 Erik Baauw. All rights reserved.
|
|
3
|
+
//
|
|
4
|
+
// Homebridge plugin for Velux Integra KLF 200 gateway.
|
|
5
|
+
|
|
6
|
+
import { VeluxService } from './index.js'
|
|
7
|
+
|
|
8
|
+
class Gateway extends VeluxService {
|
|
9
|
+
constructor (accessory, params) {
|
|
10
|
+
params.Service = accessory.Services.my.DeconzGateway
|
|
11
|
+
super(accessory, params)
|
|
12
|
+
this.client = accessory.client
|
|
13
|
+
|
|
14
|
+
this.addCharacteristicDelegate({
|
|
15
|
+
key: 'heartrate',
|
|
16
|
+
Characteristic: this.Characteristics.my.Heartrate,
|
|
17
|
+
value: 30,
|
|
18
|
+
unit: 's',
|
|
19
|
+
props: { minValue: 0, maxValue: 120, minStep: 1 }
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
this.addCharacteristicDelegate({
|
|
23
|
+
key: 'logLevel',
|
|
24
|
+
Characteristic: this.Characteristics.my.LogLevel,
|
|
25
|
+
value: 2
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
VeluxService.Gateway = Gateway
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
// homebridge-deconz/lib/VeluxService.js
|
|
1
|
+
// homebridge-deconz/lib/VeluxService/WindowCovering.js
|
|
2
2
|
// Copyright © 2025 Erik Baauw. All rights reserved.
|
|
3
3
|
//
|
|
4
4
|
// Homebridge plugin for Velux Integra KLF 200 gateway.
|
|
5
5
|
|
|
6
6
|
import { timeout } from 'hb-lib-tools'
|
|
7
7
|
|
|
8
|
-
import { ServiceDelegate } from 'homebridge-lib/ServiceDelegate'
|
|
9
|
-
|
|
10
8
|
import { VeluxClient } from 'hb-velux-tools/VeluxClient'
|
|
11
9
|
|
|
12
|
-
|
|
10
|
+
import { VeluxService } from '../VeluxService/index.js'
|
|
11
|
+
|
|
12
|
+
class WindowCovering extends VeluxService {
|
|
13
13
|
constructor (accessory, params) {
|
|
14
14
|
params.Service = accessory.Services.hap.WindowCovering
|
|
15
15
|
super(accessory, params)
|
|
@@ -86,16 +86,6 @@ class WindowCovering extends ServiceDelegate {
|
|
|
86
86
|
})
|
|
87
87
|
this.values.positionChange = 0
|
|
88
88
|
|
|
89
|
-
this.addCharacteristicDelegate({
|
|
90
|
-
key: 'logLevel',
|
|
91
|
-
Characteristic: this.Characteristics.my.LogLevel,
|
|
92
|
-
value: accessory.logLevel
|
|
93
|
-
}).on('didSet', (value, fromHomeKit) => {
|
|
94
|
-
if (fromHomeKit) {
|
|
95
|
-
accessory.gateway.updateLogLevel()
|
|
96
|
-
}
|
|
97
|
-
})
|
|
98
|
-
|
|
99
89
|
this.update(params.payload)
|
|
100
90
|
}
|
|
101
91
|
|
|
@@ -120,8 +110,4 @@ class WindowCovering extends ServiceDelegate {
|
|
|
120
110
|
}
|
|
121
111
|
}
|
|
122
112
|
|
|
123
|
-
|
|
124
|
-
static get WindowCovering () { return WindowCovering }
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export { VeluxService }
|
|
113
|
+
VeluxService.WindowCovering = WindowCovering
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// homebridge-velux/lib/VeluxService/index.js
|
|
2
|
+
// Copyright © 2025 Erik Baauw. All rights reserved.
|
|
3
|
+
//
|
|
4
|
+
// Homebridge plugin for Velux Integra KLF 200 gateway.
|
|
5
|
+
|
|
6
|
+
import { ServiceDelegate } from 'homebridge-lib/ServiceDelegate'
|
|
7
|
+
|
|
8
|
+
class VeluxService extends ServiceDelegate {}
|
|
9
|
+
|
|
10
|
+
export { VeluxService }
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"ebaauw"
|
|
8
8
|
],
|
|
9
9
|
"license": "Apache-2.0",
|
|
10
|
-
"version": "0.0.
|
|
10
|
+
"version": "0.0.2",
|
|
11
11
|
"keywords": [
|
|
12
12
|
"homebridge-plugin",
|
|
13
13
|
"homekit",
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"klf200": "3.14"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"hb-velux-tools": "~0.0.
|
|
31
|
-
"homebridge-lib": "~7.1.
|
|
30
|
+
"hb-velux-tools": "~0.0.4",
|
|
31
|
+
"homebridge-lib": "~7.1.3"
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
34
|
"prepare": "standard",
|