homebridge-velux 0.0.0 → 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.
@@ -0,0 +1,15 @@
1
+ # These are supported funding model platforms
2
+
3
+ github: [ebaauw]
4
+ patreon: # Replace with a single Patreon username
5
+ open_collective: # Replace with a single Open Collective username
6
+ ko_fi: # Replace with a single Ko-fi username
7
+ tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8
+ community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9
+ liberapay: # Replace with a single Liberapay username
10
+ issuehunt: # Replace with a single IssueHunt username
11
+ lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
12
+ polar: # Replace with a single Polar username
13
+ buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
14
+ thanks_dev: # Replace with a single thanks.dev username
15
+ custom: ["https://www.paypal.me/ebaauw/EUR"]
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  [![Downloads](https://img.shields.io/npm/dt/homebridge-velux)](https://www.npmjs.com/package/homebridge-velux)
8
8
  [![Version](https://img.shields.io/npm/v/homebridge-velux)](https://www.npmjs.com/package/homebridge-velux)
9
9
  [![Homebridge Discord](https://img.shields.io/discord/432663330281226270?color=728ED5&logo=discord&label=discord)](https://discord.gg/bXmnUwXQR9)
10
- <!-- [![verified-by-homebridge](https://badgen.net/badge/homebridge/verified/purple)](https://github.com/homebridge/homebridge/wiki/Verified-Plugins) -->
10
+ [![verified-by-homebridge](https://badgen.net/badge/homebridge/verified/purple)](https://github.com/homebridge/homebridge/wiki/Verified-Plugins)
11
11
 
12
12
  [![GitHub issues](https://img.shields.io/github/issues/ebaauw/homebridge-velux)](https://github.com/ebaauw/homebridge-velux/issues)
13
13
  [![GitHub pull requests](https://img.shields.io/github/issues-pr/ebaauw/homebridge-velux)](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
@@ -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/VeluxGateway.js
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 { Delegate } from 'homebridge-lib/Delegate'
7
+ import { AccessoryDelegate } from 'homebridge-lib/AccessoryDelegate'
8
8
 
9
9
  import { VeluxClient } from 'hb-velux-tools/VeluxClient'
10
10
 
11
- import { VeluxAccessory } from './VeluxAccessory.js'
11
+ import { VeluxAccessory } from './index.js'
12
+ import { VeluxService } from '../VeluxService/index.js'
13
+ import '../VeluxService/Gateway.js'
12
14
 
13
- class VeluxGateway extends Delegate {
15
+ class Gateway extends AccessoryDelegate {
14
16
  constructor (platform, params = {}) {
15
- super(platform, params.name)
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('connect', (host) => { this.debug('connected to %s', host) })
26
- .on('disconnect', (host) => { this.debug('disconnected from %s', host) })
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.updateLogLevel()
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
- if (!this.initialised) {
166
- return
167
- }
168
- if (!this.client.connected) {
169
- if (beat % 60 !== 0) {
170
- return
184
+ try {
185
+ if (!this.client.connected) {
186
+ if (beat % 60 !== 0) {
187
+ return
188
+ }
189
+ await this.client.connect()
171
190
  }
172
- await this.client.connect()
173
- }
174
- if (beat % 600 === 0) {
175
- await this.client.request(VeluxClient.commands.GW_HOUSE_STATUS_MONITOR_ENABLE_REQ)
176
- }
177
- if (beat % 10 === 0 && this.nodeIdList.length > 0) {
178
- await this.client.request(VeluxClient.commands.GW_STATUS_REQUEST_REQ, {
179
- nodeIds: this.nodeIdList
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
- export { VeluxGateway }
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
- import { VeluxService } from './VeluxService.js'
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.WindowCovering(this, params)
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 }
@@ -8,7 +8,8 @@
8
8
  import { OptionParser } from 'homebridge-lib/OptionParser'
9
9
  import { Platform } from 'homebridge-lib/Platform'
10
10
 
11
- import { VeluxGateway } from './VeluxGateway.js'
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('name')
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 VeluxGateway(this, host)
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
- class WindowCovering extends ServiceDelegate {
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
- class VeluxService {
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.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.2",
31
- "homebridge-lib": "~7.1.2"
30
+ "hb-velux-tools": "~0.0.4",
31
+ "homebridge-lib": "~7.1.3"
32
32
  },
33
33
  "scripts": {
34
34
  "prepare": "standard",