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.