Request: ADAX thermostat/radiator binding

Hi
I have a number of Adax Wifi radiators in my house and would love to have these integrated into my OpenHAB installation. But, I’m not a developer and have gotten most of my other OpenHAB stuff working based on browsing the forum and kind of copy/paste a lot of solutions. So, my programming knowledge and tech understanding is limited. As far as I know, there’s currently no way to integrate these radiators with existing bindings in OpenHAB.

Since I purchased these radiators, Adax has publicly released an API to be able to integrate with the thermostats - Getting Started with the Adax API

So, my questions are

  1. Is an integration of these radiators already possible with something existing (that I have missed)?
  2. If not, can a binding be created based on this API?
  3. Are you willing to help me do it?

I’m willing to help as much as I can, but I’m afraid I’m somewhat lost when it comes down to this tech level. I know there are guides I could read how to do this - but that’s in theory. In reality I would have a hard time understanding the process without proper (and patient) guidance.

Any help would be appreciated. Thanks.

Submit a request on bountysource to encourage a volunteer developer to develop a binding. That is the current path for non-developers who wish new addons.

Without a publicly released API though chances are slim. Web screen scraping is discouraged for official addons even if it woks short term. There are too many changes that could break things.

Thank you Bruce
I will check out bountysource.

There is a released and publicly available API (as far as I can tell) - the one I linked to in my first post.

Sorry, I somehow misread that. - Not enough :coffee: :frowning:

  1. You could probably use the HTTP binding for this. If not, you can use the Exec binding or executeCommandLine Action to call the example Python scripts provided at that link.

  2. Yes, but it will require finding a volunteer who both has Adax devices and has the desire and ability to create a binding.

  3. I have neither Adax devices nor the desire a and ability to create a binding. But I can help debug rules and to some degree HTTP binding and Exec binding issues.

1 Like

I have now managed to make it work on my OpenHAB 2.5 (openhabian), using python scripts and executeCommandLine. Here is my setup, currently for one room (since I only have one oven) … sorry for the weird mix of English and Norwegian:

items:

Number  AdaxHillevis_Id         "Adax room id [%d]"
Switch  AdaxHillevis_Heating    "Status av ovnen [%s]"  <heating>
Number  AdaxHillevis_RoomTemp   "Romtemperatur  [%.1f °C]"  <temperature>
Number  AdaxHillevis_Setpoint   "Ønsket temp. [%.0f °C]"  <temperature>
Number  AdaxHillevis_AdaxSetPt  "Setpoint Adax cloud [%.0f °C]"  <temperature>

AdaxHillevis_Id includes the Adax API id of the room (temperature control is per room not per device). I set it in a “System started” rule.

adax_get_room_status.py:

#!/usr/bin/python3
import requests
import sanction
import sys
import json

if len(sys.argv) < 2:
    print("Error: needs the room ID as an argument")
    quit()
ROOM_ID = int(sys.argv[1])

# for values, see Adax WiFi app, Account Section
CLIENT_ID = "xxxxxx"
CLIENT_SECRET = "123456789abcd"
API_URL = "https://api-1.adax.no/client-api"

def get_token():
    # Authenticate and obtain JWT token
    oauthClient = sanction.Client(token_endpoint = API_URL + '/auth/token')
    oauthClient.request_token(grant_type = 'password', username = CLIENT_ID, password = CLIENT_SECRET)
    return oauthClient.access_token

def get_room_status(roomId, token):
    headers = { "Authorization": "Bearer " + token }
    response = requests.get(API_URL + "/rest/v1/content/", headers = headers)
    status = response.json()
    for room in status['rooms']:
        if room['id'] == roomId:
            return room
    return {}  # room not found

token = get_token()
status = get_room_status(ROOM_ID, token)
print(status)

adax_set_room_tg_temp.py

#!/usr/bin/python3
import requests
import sanction
import sys
if len(sys.argv) < 3:
    print("Error: needs 2 arguments: room ID and the target temperature")
    quit()
ROOM_ID = int(sys.argv[1])
TG_TEMP = round(float(sys.argv[2]), 2)

# for values, see Adax WiFi app, Account Section
CLIENT_ID = "xxxxxx"
CLIENT_SECRET = "123456789abcd"
API_URL = "https://api-1.adax.no/client-api"

def get_token():
    # Authenticate and obtain JWT token
    oauthClient = sanction.Client(token_endpoint = API_URL + '/auth/token')
    oauthClient.request_token(grant_type = 'password', username = CLIENT_ID, password = CLIENT_SECRET)
    return oauthClient.access_token

def set_room_target_temperature(roomId, temperature, token):
    # Sets target temperature of the room
    headers = { "Authorization": "Bearer " + token }
    json = { 'rooms': [{ 'id': roomId, 'targetTemperature': round(100 * temperature) }] }
    requests.post(API_URL + '/rest/v1/control/', json = json, headers = headers)

token = get_token()
set_room_target_temperature(ROOM_ID, TG_TEMP, token)

Note that one needs to install the sanction package for user openhab, which I solved by installing it system-wide using sudo pip3 install sanction.

rules:

rule "Update Adax status for Hillevi's room from Adax cloud"
when
        Time cron "0 0/10 * * * ?"  // every 10 minutes
then
        val String roomStatus = executeCommandLine("/etc/openhab2/misc/adax_get_room_status.py " + AdaxHillevis_Id.state.toString, 5000)
        logDebug("Adax", "roomStatus = " + roomStatus)

        val roomTemp = Double::parseDouble(transform("JSONPATH", "$.temperature", roomStatus)) / 100
        val Number tgTemp = Double::parseDouble(transform("JSONPATH", "$.targetTemperature", roomStatus)) / 100
        val Boolean enabled = Boolean::parseBoolean(transform("JSONPATH", "$.heatingEnabled", roomStatus))
        val roomName = transform("JSONPATH", "$.name", roomStatus)
        logInfo("Adax", "update for " + roomName + ": temp = " + roomTemp.toString + "°C, set point = " + tgTemp.toString + "°C, heater enabled = " + enabled.toString)

        AdaxHillevis_AdaxSetPt.postUpdate(tgTemp)
        AdaxHillevis_RoomTemp.postUpdate(roomTemp)
        if (enabled) {
                AdaxHillevis_Heating.postUpdate(ON)
        } else {
                AdaxHillevis_Heating.postUpdate(OFF)
        }
        // wait a bit, so _AdaxSetPt is set, to avoid calling adax_set_room_tg_temp.py
        Thread::sleep(200)
        AdaxHillevis_Setpoint.postUpdate(tgTemp)
end

rule "Send new setpoint for Hillevis room to Adax cloud"
when
        Item AdaxHillevis_Setpoint changed
then
        if (newState != AdaxHillevis_AdaxSetPt.state) {
                // send the new setpoint to the Adax cloud and update AdaxHillevis_AdaxSetPt
                logInfo("Adax", "sending updated setpoint " + newState.toString + " to Adax cloud")
                executeCommandLine("/etc/openhab2/misc/adax_set_room_tg_temp.py " + AdaxHillevis_Id.state.toString + " " + newState.toString)
                AdaxHillevis_AdaxSetPt.postUpdate(newState)
        }
end

Note that executeCommandLine() changed syntax in OH3.

This setup needs the _AdaxSetPt to be set before updating _Setpoint, otherwise the second rule triggers a needless update to the Adax cloud. It is not very elegant, but the best I managed…
This should be all…

One question for Rich: this works for one room, but with several rooms, I would have to duplicate the rules - but I am pretty sure there is a way to write rules that can work for more things, provided they have the same naming scheme for items - can you help here?

Moreover, if I had more rooms, I would probably rewrite the ‘get’-script so that i returns the json for all rooms and then try to write a rule that updates all items by parsing the json - but again, this requires better knowledge of the rules language than I have…

1 Like

A small update: the above solution had a problem that when I changed the setpoint using for ex. the Setpoint widget in the Classic UI, it will try to send the update after each change, which was causing problems.
This is solved using a timer, so the update is sent 10 seconds after the last change:

var Timer adaxHillevisSetPtTimer = null

rule "Send new setpoint for Hillevis room to Adax cloud"
when
        Item AdaxHillevis_Setpoint changed
then
        // cancel any planned updates
        if (adaxHillevisSetPtTimer !== null) {
                adaxHillevisSetPtTimer.cancel()
        }
        if (newState != AdaxHillevis_AdaxSetPt.state) {
                logInfo("Adax", "received a new setpoint " + newState.toString + " for Hillevis room")
                // wait for 10 seconds, in case we get a new value
                adaxHillevisSetPtTimer = createTimer(now.plusSeconds(10)) [|
                        // send the new setpoint to the Adax cloud and update AdaxHillevis_AdaxSetPt
                        logInfo("Adax", "sending updated setpoint " + newState.toString + " to Adax cloud")
                        executeCommandLine("/etc/openhab2/misc/adax_set_room_tg_temp.py " + AdaxHillevis_Id.state.toString + " " + newState.toString)
                        AdaxHillevis_AdaxSetPt.postUpdate(newState)
                ]
        }
end
1 Like

I just stumbled over your ADAX implementation as I bought new ADAX-panels and are keen to try your scripts.
In which folder should the python script be copied on a openhabian system (running on RaspBerry Pi)?

As you can see in the examples, the python scripts are in /etc/openhab2/misc/. This was on OH2, running on opanhabian.
I expect this to work on OH3 as well, but only if the DSL scripts are saved to files (like in OH2) - it won’t work in an UI-created rule, since it uses a global timer.

I am currently in a process of slowly re-creating the whole setup in OH3. For the ADAX integration, I want to test HabApp, so I can do it completely in Python…

1 Like

Thanks for your fast answer. I haven’t seen/recognized the file-location in the examples.
As I am also still on OH2, I will start trying in coming weekend. 3 additional ADAX-heaters just arrived yesterday.