{"id":447,"date":"2021-01-18T22:05:20","date_gmt":"2021-01-18T21:05:20","guid":{"rendered":"http:\/\/techblog.vindvejr.dk\/?p=447"},"modified":"2021-01-18T22:10:42","modified_gmt":"2021-01-18T21:10:42","slug":"velux-active-kix-300-integration","status":"publish","type":"post","link":"https:\/\/techblog.vindvejr.dk\/?p=447","title":{"rendered":"Velux Active KIX 300 integration"},"content":{"rendered":"\n<p>It&#8217;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 <a href=\"https:\/\/community.openhab.org\/t\/connecting-velux-active-kix-300\/75696\" data-type=\"URL\" data-id=\"https:\/\/community.openhab.org\/t\/connecting-velux-active-kix-300\/75696\">openHAB Community thread<\/a>, so I no longer had the excuse that I didn&#8217;t want to spend time reverse engineering the APK for Android. Now it was just a matter of implementing an integration.<\/p>\n\n\n\n<p>Since I didn&#8217;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 <a rel=\"noreferrer noopener\" href=\"https:\/\/github.com\/nougad\/velux-cli\" target=\"_blank\">https:\/\/github.com\/nougad\/velux-cli<\/a>.<\/p>\n\n\n\n<p>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:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\n#!\/usr\/bin\/python3\n\nimport getpass\nimport requests\n\nURL=&#039;https:\/\/app.velux-active.com\/oauth2\/token&#039;\n\n# From Android app reverse engineering\nCLIENT_ID=&#039;&#039;\nCLIENT_SECRET=&#039;&#039;\n\n# My account\nUSERNAME=&#039;veluxaccount@email.address&#039;\n\ntry:\n        password = getpass.getpass()\nexcept Exception as error:\n        print(&#039;Error:&#039;, error)\n\ndata = {\n        &#039;grant_type&#039;: &#039;password&#039;,\n        &#039;client_id&#039;: CLIENT_ID,\n        &#039;client_secret&#039;: CLIENT_SECRET,\n        &#039;username&#039;: USERNAME,\n        &#039;password&#039;: password,\n        &#039;user_prefix&#039;: &#039;velux&#039;\n}\n\nresponse = requests.post(URL, data=data)\n\nif response.status_code == 200:\n        file = open(&#039;token.json&#039;, &#039;w&#039;)\n        file.write(response.text)\n        file.close()\nelse:\n        print(&#039;Error:&#039;, response)\n\n<\/pre><\/div>\n\n\n<p>A file &#8216;token.json&#8217; will be saved in current directory. Next script will refresh the token, so this must be run once per every few hours. I don&#8217;t remember exact expiration period, but it&#8217;s in the response.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\n#!\/usr\/bin\/python3\n\nimport json\nimport requests\n\nURL=&#039;https:\/\/app.velux-active.com\/oauth2\/token&#039;\n\n# From Android app reverse engineering\nCLIENT_ID=&#039;&#039;\nCLIENT_SECRET=&#039;&#039;\n\nfile = open(&#039;token.json&#039;, &#039;r&#039;)\nbody = file.read()\nfile.close()\n\ntoken = json.loads(body)\n\ndata = {\n        &#039;grant_type&#039;: &#039;refresh_token&#039;,\n        &#039;refresh_token&#039;: token&#x5B;&#039;refresh_token&#039;],\n        &#039;client_id&#039;: CLIENT_ID,\n        &#039;client_secret&#039;: CLIENT_SECRET\n}\n\nresponse = requests.post(URL, data=data)\n\nif response.status_code == 200:\n        file = open(&#039;token.json&#039;, &#039;w&#039;)\n        file.write(response.text)\n        file.close()\nelse:\n        print(&#039;Error:&#039;, response)\n\n<\/pre><\/div>\n\n\n<p>And finally this last script is run every 5 minutes which seems to be the frequency for new measurements:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\n#!\/usr\/bin\/python3\n\nimport json\nimport requests\nimport paho.mqtt.client as mqtt\n\nURL=&#039;https:\/\/app.velux-active.com\/api\/homestatus&#039;\nMQTT_BROKER=&#039;192.168.0.236&#039;\nHOME_ID=&#039;your_home_id&#039;\n\nfile = open(&#039;token.json&#039;, &#039;r&#039;)\nbody = file.read()\nfile.close()\n\ntoken = json.loads(body)\n\ndata = {\n        &#039;access_token&#039;: token&#x5B;&#039;access_token&#039;],\n        &#039;home_id&#039;: HOME_ID\n}\n\nresponse = requests.post(URL, data=data)\n\nif response.status_code != 200:\n        print(&#039;Error:&#039;, response)\n        exit()\n\ndata = json.loads(response.text)\nhome = data&#x5B;&#039;body&#039;]&#x5B;&#039;home&#039;]\nrooms = home.get(&#039;rooms&#039;)\n\nif rooms:\n        room = rooms&#x5B;0]\nelse:\n        print(&#039;Unexpected response:&#039;, response.text)\n        exit()\n\nclient = mqtt.Client()\nclient.connect(MQTT_BROKER)\n\nclient.publish(&#039;velux\/hallway\/co2&#039;, payload=room&#x5B;&#039;co2&#039;], qos=0, retain=False)\nclient.publish(&#039;velux\/hallway\/humidity&#039;, payload=room&#x5B;&#039;humidity&#039;], qos=0, retain=False)\nclient.publish(&#039;velux\/hallway\/temperature&#039;, payload=(room&#x5B;&#039;temperature&#039;]\/10), qos=0, retain=False)\nclient.publish(&#039;velux\/hallway\/lux&#039;, payload=room&#x5B;&#039;lux&#039;], qos=0, retain=False)\n\nfor module in data&#x5B;&#039;body&#039;]&#x5B;&#039;home&#039;]&#x5B;&#039;modules&#039;]:\n        if module&#x5B;&#039;type&#039;] == &#039;NXD&#039;: # Departure switch\n                client.publish(&#039;velux\/departure_switch\/battery_percent&#039;, payload=module&#x5B;&#039;battery_percent&#039;], qos=0, retain=False)\n        if module&#x5B;&#039;type&#039;] == &#039;NXS&#039;: # Sensor\n                client.publish(&#039;velux\/sensor1\/battery_percent&#039;, payload=module&#x5B;&#039;battery_percent&#039;], qos=0, retain=False)\n\n<\/pre><\/div>\n\n\n<p>In openHAB I have configured a thing to receive these MQTT updates, so I have them persisted in MySQL, shown in sitemaps, etc.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>It&#8217;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 &hellip; <a href=\"https:\/\/techblog.vindvejr.dk\/?p=447\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[10,13],"tags":[],"class_list":["post-447","post","type-post","status-publish","format-standard","hentry","category-iot","category-openhab"],"_links":{"self":[{"href":"https:\/\/techblog.vindvejr.dk\/index.php?rest_route=\/wp\/v2\/posts\/447","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/techblog.vindvejr.dk\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/techblog.vindvejr.dk\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/techblog.vindvejr.dk\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/techblog.vindvejr.dk\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=447"}],"version-history":[{"count":5,"href":"https:\/\/techblog.vindvejr.dk\/index.php?rest_route=\/wp\/v2\/posts\/447\/revisions"}],"predecessor-version":[{"id":452,"href":"https:\/\/techblog.vindvejr.dk\/index.php?rest_route=\/wp\/v2\/posts\/447\/revisions\/452"}],"wp:attachment":[{"href":"https:\/\/techblog.vindvejr.dk\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=447"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/techblog.vindvejr.dk\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=447"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/techblog.vindvejr.dk\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=447"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}