PLCHome / velux-klf200-api

Provide node to Veluxl® KLF-200 io-homecontrol® Gateway API
42 stars 7 forks source link

velux-klf200-api

this is an Node for the API on the Velux© KLF-200 io-homecontrol© Gateway. (klf-200 provided by Velux©. I'm not affiliated.)

This implementation is based on the API documentation https://www.velux.com/api/klf200. It works only with the version 0.2.0.0.71.

Up to the version 0.1.1.0.45 it was possible to access the KLF-200 via the LAN interface. This does not work in version 0.2.0.0.71 anymore. Velux shared on demand with the Lan interface can only be addressed via the API.

Take a look at the technical specification for klf 200 api.pdf

For the latest updates see the CHANGELOG.md

Install

npm install velux-klf200-api

Currently tested with API 3.18 from 12.10.2019 version 0.2.0.0.71


Current problems

GW_NODE_STATE_POSITION_CHANGED_NTF contains an incorrect timestamp. The lowest 2 bytes of the 4 bytes are sent to the higher 2 bytes and the lowest 2 bytes are 0. In response to the "GW_GET_ALL_NODES_INFORMATION_REQ" command, the correct time stamp is sent: 5be8d806 - 2018-11-12T01: 31: 50.000Z 15 sec Later a "GW_NODE_STATE_POSITION_CHANGED_NTF" hits. This contains a timestamp: d8160000 correct was here 5be8d816

If there is no communication with the KLF every 10 minutes to 15 minutes, the connection will be disconnected as described in the manual. If this happens when the home monitor "GW_HOUSE_STATUS_MONITOR_ENABLE_REQ" is activated, the KLF200 is no longer reachable. The KLF200 no longer sends the TLS command "Change Cipher Spec." on TLS start. This means that TLS encryption can no longer be initiated. I saw these in the wireshark. Should anyone else notice this error, it would be nice if that this someone also reports to the VELUX hotline.

The KLF200 needs more than 3 seconds to connect. It needs more then a second to send the key and more then a second for the TLS command "Change Cipher Spec.".


The connect password is the WLAN-Password not the web config password


Requirements

In case that your connection doesn't work due to a different certificate you have to extract the certificate from your box with the following command:

$ echo -n | openssl s_client -connect <ip adress of your KLF-200>:51200 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > velux-cert.pem

After extracting the certificate you have to generate the fingerprint with the following command:

$ openssl x509 -noout -fingerprint -sha1 -inform pem -in velux-cert.pem

This will print a fingerprint like 02:8C:23:A0:89:2B:62:98:C4:99:00:5B:D2:E7:2E:0A:70:3D:71:6A.

The fingerprint is not checked by default. But you can check it if you set the fingerprint variable. You can exchange the certificate via the CA variable.

'use strict'
const velux = require('velux-klf200-api')
const fs = require('fs')
velux.fingerprint = '02:8C:23:A0:89:2B:62:98:C4:99:00:5B:D2:E7:2E:0A:70:3D:71:6A'
veluc.CA = fs.readFileSync('velux-cert.pem')

If the fingerprint is not set, it can be queried via velux.fingerprint.


Promise

This API works with and without promise. You can use a callback function. If there is no callback function in the call the API create an promise object.

API without Promise

every API callback function is called first with an error object and then eventually a data object. If there is no error, the first parameter is null.

function callback(error,data) {}
'use strict'
const velux = require('velux-klf200-api')

step1()
function step1() {
  velux.connect('10.10.10.15',{},step2)
}
function step2(err) {
  if (err) {
    step5(err)
  }  else {
    velux.login('<some password>',step3)
  }
}
function step3(err,data) {
  if (err) {
    step5(err)
  } else {
    velux.sendCommand({ api: velux.API.GW_GET_VERSION_REQ},step4)
  }
}
function step4(err,data) {
  if (err) {
    step5(err)
  } else {
    console.log('step3:',data)
    velux.sendCommand({ api: velux.API.GW_GET_PROTOCOL_VERSION_REQ},step5)
  }
}
function step5(err,data) {
  if (err) {
    console.log(err)
  } else {
    console.log('step4:',data)
  }
  velux.end(step6)
}
function step6(err) {
  if (err) {
    console.log(err)
  }
}

API with Promise

'use strict'
const velux = require('velux-klf200-api')

velux.connect('192.168.2.15',{})
.then(()=>{
  return velux.login('<some password>')
})
.then((data)=>{
  return velux.sendCommand({ api: velux.API.GW_GET_VERSION_REQ})
})
.then((data)=>{
  console.log('step3:',data)
  return velux.sendCommand({ api: velux.API.GW_GET_PROTOCOL_VERSION_REQ})
})
.then((data)=>{
  console.log('step4:',data)
  return velux.end()
})
.catch((err)=>{
  console.log(err)
  return velux.end()
})

in the API documentation I write the functions like that

(data)=>{}

it's the same as

function (data){}
this is the output if all is correct
step3: { id: 0,
  api: 9,
  apiText: 'GW_GET_VERSION_CFM',
  softwareVersion: [ 0, 2, 0, 0, 71, 0 ],
  hardwareVersion: 6,
  productGroup: 14,
  productType: 3 }
step4: { id: 0,
  api: 11,
  apiText: 'GW_GET_PROTOCOL_VERSION_CFM',
  majorVersion: 3,
  minorVersion: 14 }

API functions

connect (host, options ,cb)
login (password, cb)
sendCommand (data, cb)
end (cb)
on (eventName, listener)
off (eventName, listener)
once (eventName, listener)

function: connect

The connect function establishes the connection to the KLF. According to VELUX documentation, only 2 connections are permitted for the KLF.

It returns an true in data if the API is connected.

function: login

this function executes the login. this must happen before calling another API.

this function corresponds to the API call:

sendCommand({ 'api': velux.API.GW_PASSWORD_ENTER_REQ,
              'password': password})

function: sendCommand

This function sends a command to the KLF200 and waits for the answer The request and the answer are always objects. Only API commands that end with 'REQ' can be sent. The answer is always the appropriate confirm. This always ends with 'CFM'

The construction of the request object

{ api: velux.API.<command>_REQ,
  <dataName>: <value>}

or

{ apiText: '<command>_REQ',
  <dataName>: <value>}

The construction of the answer object

{ id: 0,
  api: <commandNumber>,
  apiText: '<command>_CFM',
  <data>: <value>}

see examples and API below

function: end

The end function closes the connection to the KLF.

functions: on off once

These are the functions for the event emitter. The KLF responds to each request with a confirm. Many data are also sent as a notification. These can only be retrieved via the events. For more information read this

functions
parameter

When a notification arrives, the emitter is called once with 'NTF' and once with the API name. This allows you to react to each notification separately.

current list of notifications

Lists all nodes

'use strict'
const velux = require('velux-klf200-api')

velux.on('NTF',(data)=>{
  console.log(data)
})

velux.connect('192.168.2.15',{})
.then(()=>{
  return velux.login('<some password>')
})
.then((data)=>{
  return velux.sendCommand({ api: velux.API.GW_GET_ALL_NODES_INFORMATION_REQ })
})
.then((data)=>{
  console.log(data)
})
.catch((err)=>{
  console.log(err)
  return velux.end()
})

Let a shutter with node ID 0 wink

'use strict'
const velux = require('velux-klf200-api')

velux.connect('192.168.2.15',{})
.then(()=>{
  return velux.login('<some password>')
})
.then((data)=>{
  return velux.sendCommand({ api: velux.API.GW_WINK_SEND_REQ,
      commandOriginator: 1,
      priorityLevel: 2,
      winkStat: true,
      winkTime: 10,
      indexArrayCount: 1,
      indexArray: [0]
  })
})
.then((data)=>{
  console.log(data)
})
.catch((err)=>{
  console.log(err)
  return velux.end()
})

Moves a shutter with node ID 0 to 100%

'use strict'
const velux = require('velux-klf200-api')

velux.connect('192.168.2.15',{})
.then(()=>{
  return velux.login('<some password>')
})
.then((data)=>{
  return velux.sendCommand({ api: velux.API.GW_COMMAND_SEND_REQ,
      commandOriginator: 1,
      priorityLevel: 2,
      parameterActive: 0,
      functionalParameterMP: {valueType:'RELATIVE', value:100},
      /* functionalParameterMP: 100, */
      indexArrayCount: 1,
      indexArray: [0],
      priorityLevelLock: false,
      lockTime: 0
  })
})
.then((data)=>{
  console.log(data)
})
.catch((err)=>{
  console.log(err)
  return velux.end()
})

The value Object

The value is an Object, you can set the value to an explicit position or to an calculated position. when you set the value you can use either rawValue or value and valueType. It's also possible to use an number instead an Object. Then the API use Relative.

Access Method name for |Description                                   |Range (Hex)   |Size (Dec)
Standard Parameter     |                                              |              |
valueType              |value                                         |rawValue      |

RELATIVE               |Relative value (0 – 100%)                     |0x0000–0xC800 |51201
RELATIVE               |No feed-back value known                      |0xF7FF        |1
PERCENT_PM             |Percentage point plus or minus (-100% – 100%) |0xC900-0xD0D0 |2001
TARGET                 |The target value for the parameter            |0xD100        |1
CURRENT                |The current value for the parameter           |0xD200        |1
DEFAULT                |The default value for the parameter           |0xD300        |1
IGNORE                 |Ignore the parameter field where this         |0xD400        |1
                       |Access Method is written                      |              |

License (MIT)

Copyright (c) 2018 Chris Traeger

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.