Icy thermostat in OH2

I recently switched to OH2 and, since there is no binding for the Icy thermostat, I created the following solution for getting the temperature and getting/setting the setpoint. I hope someone else can use it.

I started with the following python scripts, based on info I found on various sites. It requires python3 and the requests package.

#--- scripts/icythermostat.py ---
"""Module for interacting with the ICY thermostat. (Essent slimme thermostaat)."""
import sys
import requests

class IcyThermostat(object):
    """Class to get temperature and get/set setpoint via ICY portal."""
    LOGIN_URL = 'https://portal.icy.nl/login'
    DATA_URL = 'https://portal.icy.nl/data'

    def __init__(self, username, password):
        self.__login = {'username':username, 'password':password}
        self.__reset_values()
        self.__get_token_and_serial()

    def __reset_values(self):
        self.__headers = None
        self.__serial = None
        self.setpoint = 0.0
        self.temperature = 0.0

    def __get_token_and_serial(self):
        response = requests.post(self.LOGIN_URL, data=self.__login).json()
        if response["status"]["message"] != 'OK':
            print("Failed to get token and serial.", file=sys.stderr)
            print(response, file=sys.stderr)
            self.__reset_values()
        else:
            self.__headers = {'Session-token':response["token"]}
            self.__serial = response["serialthermostat1"]

    def get_temperatures_json(self):
        """Retreive json status from Icy portal"""
        response = None
        if self.__headers is None:
            self.__get_token_and_serial()
        if self.__headers is not None:
            response = requests.get(self.DATA_URL, headers=self.__headers).json()
        return response

    def get_temperatures(self):
        """Retreives current temperature and setpoint"""
        response = self.get_temperatures_json()
        if response != None:
            if response["status"]["message"] != 'OK':
                print("Failed to get temperature", file=sys.stderr)
                print(response, file=sys.stderr)
                self.__reset_values()
            else:
                self.setpoint = response["temperature1"]
                self.temperature = response["temperature2"]

    def set_setpoint(self, setpoint):
        """Update setpoint"""
        if self.__headers is None:
            self.__get_token_and_serial()
        if self.__headers is not None and self.__serial is not None:
            data = {'uid':self.__serial, 'temperature1':setpoint}
            response = requests.post(self.DATA_URL, headers=self.__headers, data=data).json()
            if response["status"]["message"] != 'OK':
                print("Failed to set new setpoint", file=sys.stderr)
                print(response, file=sys.stderr)
#--- scripts/icytemperature.py ---
import icythermostat

thermo = icythermostat.IcyThermostat('<username>', '<password>')
thermo.get_temperatures()
print(thermo.temperature)
#--- scripts/icysetpoint.py ---
import sys
import icythermostat

thermo = icythermostat.IcyThermostat('<username>', '<password>')

if len(sys.argv) == 2:
    new_setpoint=sys.argv[1]
    thermo.set_setpoint(new_setpoint)

thermo.get_temperatures()
print(thermo.setpoint)

The two small scripts are used to poll for both the current temperature and setpoint. Unfortunately, these had to be two separate polling calls, because I couldnā€™t find out how to update two values using one exec call. Next to that I had to create an additional Thing, because I was unable to combine the polling of the current setpoint and setting a new one via basic UI. A rule is used to update.

Things (icythermo.things)

Thing exec:command:icytemperature [command="python3 /etc/openhab2/scripts/icytemperature.py", interval=30, timeout=8 ]
Thing exec:command:icysetpoint [command="python3 /etc/openhab2/scripts/icysetpoint.py %2$s", interval=0, timeout=8, autorun=true]
Thing exec:command:icygetsetpoint [command="python3 /etc/openhab2/scripts/icysetpoint.py", interval=30, timeout=8 ]

To get nice graphs in influxdb/grafana I needed to add a proxy _number for the Temperature, because the exec binding only supports strings. Probably Iā€™ll add one for the setpoint as well. (Thanks to this post: Convert String item to Number item in OH2 )

Items (icythermo.items)

String IcyThermostat_Temperature "Temperature [%s Ā°C]"  <temperature> { channel="exec:command:icytemperature:output" }
Number IcyThermostat_Temperature_number "Temperature [%.1f Ā°C]" <temperature> 
String IcyThermostat_Setpoint "Setpoint [%s Ā°C]" <temperature> { channel="exec:command:icysetpoint:input" }
String IcyThermostat_SetpointOutput "Setpoint [%s Ā°C]" <temperature> { channel="exec:command:icygetsetpoint:output" }

Rules (icythermo.rules)

val logName = "icythermo"

rule "Update setpoint"
when
    System started or
    Item IcyThermostat_SetpointOutput changed
then
    if (IcyThermostat_Setpoint.state != IcyThermostat_SetpointOutput.state) {
        logInfo(logName, "Updating setpoint to " + IcyThermostat_SetpointOutput)
        IcyThermostat_Setpoint.postUpdate(IcyThermostat_SetpointOutput.state)
    }
end

rule "Convert temperature to number"
when
    System started or
    Item IcyThermostat_Temperature changed
then
    IcyThermostat_Temperature_number.postUpdate(Float::parseFloat(String::format("%s", IcyThermostat_Temperature.state).replace(' ','')))
end

Sitemap

Default item=IcyThermostat_Temperature_number label="Living"
Setpoint item=IcyThermostat_Setpoint label="Living setpoint" minValue=15 maxValue=25 step=0.5

Looking back at this I do think I had to jump through some hoops to get it working. Due to limitations of both the thermostat and the exec binding. But now I get some nice graphs and can take over the silly ā€œprogrammingā€ that the thermostat provides. Looking forward to your feedback on the above.

A better solution will be a binding that only exposes a temperature and setpoint and hides the polling for updates. This will also eliminate the exec bindings issues. Unfortunately I donā€™t have the time to dive into the inner workings of OpenHab, setting up a development environment, and creating it myself. Maybe the above will spark someones interest.

1 Like

A halfway measure would be to set up your Python script as a daemon and have it report the numbers to OH using REST or MQTT. You can see how I managed that here:

This eliminates the Exec issues and the need for the Proxy Item and associated rules, but requires more work on the Python side.

I was thinking about that, but then using Rust as a language to toy with. Unfortunately, when I started with OH, my backlog of things Iā€™d like to achieve is growing on a daily basis :).

So, for now, Iā€™ll consider it functioningā€¦

Hello Maarten,
Iā€™m trying to get your scripts to work but Iā€™m failing :frowning:
When I reboot the system and look at the log file I see the error "Error during the execution of startup rule ā€˜Convert temperature to numberā€™: For input string: ā€œNULLā€

Can you help me ?

Kind regards,

Dirk-Jan

Hi Dirk-Jan,

I had to really dig deep in my brain on how these scripts are glued together :slight_smile:
Not sure why it doesnā€™t work for you, here theyā€™re happily running since I posted this message.

The error you see actually means that the Item IcyThermostat_Temperature has no value (yet). This is tied to the exec Thing exec:command:icytemperature. My suggestion is to first get the python scripts to work via a shell, without the Things and Items. You should be able to execute them and have the current temperature or setpoint printed. (using the commands as specified in the exec Things.)

If that works you should be able to work your way to getting the values to string Items, once this is successful add the rules for conversion to numbers. Just comment out the line items youā€™re not interested in at that time.

To make it more easy you can also start with first just reading the Temperature, then reading of the Setpoint, and as final step close the loop with adjusting the setpoint.

Hope this helps and let me know if you got it working.

ā€“
MD

Okay, step by step :wink: if I now enter the exec commands in a shell Iā€™m getting a reply and it works for input and output, I even see the changes on the Essent website. My mistake was that I didnā€™t install requests package in the right way :wink:

I think the setpoint part works well in openhab and that the last bump is the error message "Error during the execution of startup rule ā€˜Convert temperature to numberā€™: For input string: ā€œNULLā€ for the rule.

Iā€™ll keep you updated

That last error indicates that the Itemā€™s state is NULL. All Items initialize to NULL when OH starts up or when a .items file is changed. NULL cannot be cast to a Number.

Your Rule need to check for NULL it you need to use persistence with restoreOnStartup to populate the Items with the most recent state in the database instead of NULL.