homebridge-velux
Version:
Homebridge plugin for Velux Integra KLF 200 gateway
210 lines (198 loc) • 6.91 kB
JavaScript
// homebridge-velux/lib/VeluxAccessory/Gateway.js
// Copyright © 2025 Erik Baauw. All rights reserved.
//
// Homebridge plugin for Velux Integra KLF 200 gateway.
import { timeout, toHexString } from 'homebridge-lib'
import { AccessoryDelegate } from 'homebridge-lib/AccessoryDelegate'
import { VeluxClient } from 'hb-velux-tools/VeluxClient'
import { VeluxAccessory } from './index.js'
import { VeluxService } from '../VeluxService/index.js'
import '../VeluxService/Gateway.js'
class Gateway extends AccessoryDelegate {
constructor (platform, params = {}) {
super(platform, params)
this.nodeIdList = []
this.accessories = {}
this.service = new VeluxService.Gateway(this, {
name: params.name
})
this.manageLogLevel(this.service.characteristicDelegate('logLevel'))
this.on('heartbeat', this.heartbeat)
this.client = new VeluxClient({
host: params.host,
password: params.password,
timeout: platform.config.timeout
})
this.client
.on('connecting', (host) => { this.log('connecting to %s...', host) })
.on('connect', (host) => { this.log('connected to %s', host) })
.on('disconnect', (host) => { this.log('disconnected from %s', host) })
.on('error', (error) => {
if (error.request == null) {
this.error('error: %s', error)
return
}
if (error.request.params == null) {
this.log('request %d: %s', error.request.id, error.request.cmdName)
} else {
this.log(
'request %d: %s %j', error.request.id, error.request.cmdName,
error.request.params
)
}
this.warn('request %d: error: %s', error.request.id, error)
})
.on('warning', (error) => {
if (error.request == null) {
this.warn('error: %s', error)
return
}
if (error.request.params == null) {
this.log('request %d: %s', error.request.id, error.request.cmdName)
} else {
this.log(
'request %d: %s %j', error.request.id, error.request.cmdName,
error.request.params
)
}
this.warn('request %d: error: %s', error.request.id, error)
})
.on('request', (request) => {
if (request.params == null) {
this.debug('request %d: %s', request.id, request.cmdName)
} else {
this.debug(
'request %d: %s %j', request.id, request.cmdName, request.params
)
}
if (request.data == null) {
this.vdebug(
'request %d: %s [%s]', request.id, request.cmdName,
toHexString(request.cmd, 4)
)
} else {
this.vdebug(
'request %d: %s [%s]: %s', request.id, request.cmdName,
toHexString(request.cmd, 4), toHexString(request.data)
)
}
})
.on('response', (response) => {
if (response.response == null) {
this.debug('request %d: ok', response.request.id)
} else {
this.debug(
'request %d: response: %j', response.request.id, response.response
)
}
})
.on('notification', (notification) => {
const req = notification.request != null
? 'request ' + notification.request.id + ': '
: ''
if (notification.data == null) {
this.vdebug(
'%s%s [%s]', req, notification.cmdName,
toHexString(notification.cmd, 4)
)
} else {
this.vdebug(
'%s%s [%s]: %s', req, notification.cmdName,
toHexString(notification.cmd, 4), toHexString(notification.data)
)
}
if (notification.payload == null) {
this.debug('%s%s', req, notification.cmdName)
} else {
this.debug('%s%s: %j', req, notification.cmdName, notification.payload)
}
this.accessories?.[notification?.payload?.nodeId]?.update(notification?.payload)
})
.on('send', (data) => {
this.vvdebug('send %s', toHexString(data))
})
.on('data', (data) => {
this.vvdebug('received %s', toHexString(data))
})
}
async init () {
try {
this.log('connecting...')
await this.client.connect()
const { softwareVersion } = await this.client.request(VeluxClient.commands.GW_GET_VERSION_REQ)
this.values.firmware = softwareVersion
const { api } = await this.client.request(VeluxClient.commands.GW_GET_PROTOCOL_VERSION_REQ)
const nodes = {}
let nodeList = await this.client.request(VeluxClient.commands.GW_CS_GET_SYSTEMTABLE_DATA_REQ)
for (const node of nodeList) {
nodes[node.nodeId] = node
}
nodeList = await this.client.request(VeluxClient.commands.GW_GET_ALL_NODES_INFORMATION_REQ)
for (const node of nodeList) {
Object.assign(nodes[node.nodeId], node)
}
this.log('KLF 200 v%s, api v%s: %d nodes', softwareVersion, api, Object.keys(nodes).length)
for (const id in nodes) {
this.nodeIdList.push(id)
const node = nodes[id]
const params = {
name: node.name,
id: node.serialNumber,
manufacturer: node.manufacturer,
model: node.model,
firmware: '' + node.buildNumber,
payload: node
}
switch (node.actuatorType) {
case 0x0280:
if (VeluxAccessory.WindowCovering == null) {
await import('./WindowCovering.js')
}
this.accessories[id] = new VeluxAccessory.WindowCovering(this, params)
break
default:
this.warn(
'%s [%s]: ignoring unsupported node type %d', node.name, node.serialNumber,
node.actuatorType
)
continue
}
}
this.heartbeatEnabled = true
this.initialised = true
this.debug('initialised')
this.emit('initialised')
} catch (error) {
if (!(error instanceof VeluxClient.Error)) {
this.warn(error)
}
await this.client.disconnect()
await timeout(600 * 1000)
return this.init()
}
}
async heartbeat (beat) {
try {
if (!this.client.connected) {
if (beat % 60 !== 0) {
return
}
await this.client.connect()
}
if (beat % 600 === 0) {
await this.client.request(VeluxClient.commands.GW_HOUSE_STATUS_MONITOR_ENABLE_REQ)
}
if (beat % this.service.values.heartrate === 0 && this.nodeIdList.length > 0) {
await this.client.request(VeluxClient.commands.GW_STATUS_REQUEST_REQ, {
nodeIds: this.nodeIdList
})
}
} catch (error) {
this.warn('heartbeat error %s', error)
}
}
async shutdown () {
return this.client.disconnect()
}
}
VeluxAccessory.Gateway = Gateway