Velux Active KIX 300 integration

It’s been a while since my last post, so just a short one this time. I invested in VELUX ACTIVE with NETATMO in 2019 for controlling my skylights. I knew it was a cloud-based solution with all its cons, and even without any API for integration. This obviously sucks, but suddenly ClientID and ClientSecret was published in an openHAB Community thread, so I no longer had the excuse that I didn’t want to spend time reverse engineering the APK for Android. Now it was just a matter of implementing an integration.

Since I didn’t have experience with Python yet, I decided I wanted to try to use that for an implementation. It was my impression that it had everything I needed: HTTPS, JSON and MQTT. And I was right, so after 90 minutes of google-coding, this is my implementation for fetching my data from my sensor switch (CO2, temperature and humidity). Implementation is based on https://github.com/nougad/velux-cli.

First script is run to get a token which will be used for authentication. Script will ask for the password for the corresponding Velux account:

#!/usr/bin/python3

import getpass
import requests

URL='https://app.velux-active.com/oauth2/token'

# From Android app reverse engineering
CLIENT_ID=''
CLIENT_SECRET=''

# My account
USERNAME='veluxaccount@email.address'

try:
        password = getpass.getpass()
except Exception as error:
        print('Error:', error)

data = {
        'grant_type': 'password',
        'client_id': CLIENT_ID,
        'client_secret': CLIENT_SECRET,
        'username': USERNAME,
        'password': password,
        'user_prefix': 'velux'
}

response = requests.post(URL, data=data)

if response.status_code == 200:
        file = open('token.json', 'w')
        file.write(response.text)
        file.close()
else:
        print('Error:', response)

A file ‘token.json’ will be saved in current directory. Next script will refresh the token, so this must be run once per every few hours. I don’t remember exact expiration period, but it’s in the response.

#!/usr/bin/python3

import json
import requests

URL='https://app.velux-active.com/oauth2/token'

# From Android app reverse engineering
CLIENT_ID=''
CLIENT_SECRET=''

file = open('token.json', 'r')
body = file.read()
file.close()

token = json.loads(body)

data = {
        'grant_type': 'refresh_token',
        'refresh_token': token['refresh_token'],
        'client_id': CLIENT_ID,
        'client_secret': CLIENT_SECRET
}

response = requests.post(URL, data=data)

if response.status_code == 200:
        file = open('token.json', 'w')
        file.write(response.text)
        file.close()
else:
        print('Error:', response)

And finally this last script is run every 5 minutes which seems to be the frequency for new measurements:

#!/usr/bin/python3

import json
import requests
import paho.mqtt.client as mqtt

URL='https://app.velux-active.com/api/homestatus'
MQTT_BROKER='192.168.0.236'
HOME_ID='your_home_id'

file = open('token.json', 'r')
body = file.read()
file.close()

token = json.loads(body)

data = {
        'access_token': token['access_token'],
        'home_id': HOME_ID
}

response = requests.post(URL, data=data)

if response.status_code != 200:
        print('Error:', response)
        exit()

data = json.loads(response.text)
home = data['body']['home']
rooms = home.get('rooms')

if rooms:
        room = rooms[0]
else:
        print('Unexpected response:', response.text)
        exit()

client = mqtt.Client()
client.connect(MQTT_BROKER)

client.publish('velux/hallway/co2', payload=room['co2'], qos=0, retain=False)
client.publish('velux/hallway/humidity', payload=room['humidity'], qos=0, retain=False)
client.publish('velux/hallway/temperature', payload=(room['temperature']/10), qos=0, retain=False)
client.publish('velux/hallway/lux', payload=room['lux'], qos=0, retain=False)

for module in data['body']['home']['modules']:
        if module['type'] == 'NXD': # Departure switch
                client.publish('velux/departure_switch/battery_percent', payload=module['battery_percent'], qos=0, retain=False)
        if module['type'] == 'NXS': # Sensor
                client.publish('velux/sensor1/battery_percent', payload=module['battery_percent'], qos=0, retain=False)

In openHAB I have configured a thing to receive these MQTT updates, so I have them persisted in MySQL, shown in sitemaps, etc.