Hi all,
I’m about to implement a home automation system that will control the heating system of our house so that it will avoid the expensive hours when electricity costs the most.
I have a programming background but I’m new to OpenHab and would appreciate some guidance on the Right OpenHab Way to do things.
Let me first explain what I’m planning. I’m writing this quite verbosely so that this would benefit other community members in the future.
Background and context: heating system of our house and Nordpool day ahead spot prices
The electricity price follows the spot price of Nordpool. The price / kWh is different for each hour of the day and the prices for the next day are published on the previous evening. See Market data | Nord Pool
The prices have been jumping like crazy. The cheapest hours are like 0.5 c/kWh but the most expensive hours can be 70 c/kWh or even more.
Our house is heated with a ground source heat pump. It heats up water for the underfloor heating and it has an integrated 180 liter tank for hot water. The heat pump is Nibe F-1226 8kW. The heat pump has connectors for external control so it should be quite easy to control.
In addition to the 180 liter integrated water tank in the heat pump, we have a separate 300 liter water heater (Nibe Haato 300, 3kW). This heater has a mechanical thermostat which controls when the heater is on/off.
The plan for the external water heater
The spot prices for each hour of the day can be fetched with an API provided by Entso-E Transparency Platform. Anyone can create an user account to the platform and request API access to be granted, this is free of charge. See the API user guide if you want to learn more.
My plan for the water heater is quite simple.
-
Read the spot prices for next day from Entso-E
-
Analyze the spot prices and find the cheapest 4 hour slot from the day (e.g. 01:00-05:00). 4 hours will be enough to heat the water to the thermostat max temperature. The 300 liters should be easily enough for one day’s hot water consumption.
-
I will not tamper the water heater’s internal logic. I will simply cut the power from it when I don’t want it to be on. This will be done so that I will connect a relay to Raspberry Pi’s GPIO and this relay will control a contactor which is connected to the power cable of the water heater. This will be the same Raspberry that will run my OpenHab.
-
Word of caution at this point: the connections from the relay output onwards involve high voltage and only authorized electricians are allowed to make those connections. Do not touch them yourself. There is a risk of serious injury or death.
Proof of concept status at the moment
I have connected a simple buzzer to the GPIO of my Raspberry Pi. I installed the GPIO binding add-on and I was able to create a Buzzer Thing and Switch Item which toggles the buzzer on and off. Hooray!
The fact that I’m able to control the buzzer via Openhab GPIO binding also means that I will be able to control a relay. From the connections point of view they are exactly the same, the GPIO 3.3. volts output toggles the relay on/off just like it toggles the buzzer on/off.
I was also able to write a proof of concept script which reads the spot prices for next day from the Entso-E Transparency Platform.
The script looks like this:
// Entso-E security token.
val String token = "insert-your-own-token-here";
// @see https://transparency.entsoe.eu/content/static_content/Static%20content/web%20api/Guide.html#_areas
val String area = "10YFI-1--------U";
// @todo: investigate the timezone handling later.
val String start = now().plusDays(1).format(java.time.format.DateTimeFormatter.ofPattern("YYYYMMdd")) + "0000";
val String end = now().plusDays(1).format(java.time.format.DateTimeFormatter.ofPattern("YYYYMMdd")) + "2200";
// Make the API call to Ensto-E.
val String priceXml = sendHttpGetRequest("https://transparency.entsoe.eu/api?securityToken=" + token + "&documentType=A44&in_Domain=" + area + "&out_Domain=" + area + "&periodStart=" + start + "&periodEnd=" + end);
// Convert the response to JSON for easier handling.
val String priceJson = transform("XSLT", "xml2json.xsl", priceXml);
// Read the start time of the response.
val ZonedDateTime startUtc = ZonedDateTime.parse(transform("JSONPATH", "$['Publication_MarketDocument']['period.timeInterval']['start']", priceJson));
// Print hourly prices to log.
for (var i = 0; i < 24; i++) {
// Read the price for this hour.
var String price = transform("JSONPATH", "$['Publication_MarketDocument']['TimeSeries']['Period']['Point'][" + i + "]['price.amount']", priceJson);
// Convert EUR/MWh to c/kWh and add 24% VAT.
var Double priceWithVat = Double::parseDouble(price) * 1.24 / 10;
// Print to log
var ZonedDateTime dt = startUtc.plusHours(i);
logInfo("entsoe", dt.toString + ": " + priceWithVat.toString + " c/kWh");
}
Output of the script:
22:26:13.284 [INFO ] [org.openhab.core.model.script.entsoe ] - 2022-06-10T22:00Z: 3.44968 c/kWh
22:26:13.305 [INFO ] [org.openhab.core.model.script.entsoe ] - 2022-06-10T23:00Z: 2.44528 c/kWh
22:26:13.329 [INFO ] [org.openhab.core.model.script.entsoe ] - 2022-06-11T00:00Z: 1.6938400000000002 c/kWh
22:26:13.350 [INFO ] [org.openhab.core.model.script.entsoe ] - 2022-06-11T01:00Z: 1.6901199999999998 c/kWh
22:26:13.372 [INFO ] [org.openhab.core.model.script.entsoe ] - 2022-06-11T02:00Z: 1.68764 c/kWh
22:26:13.393 [INFO ] [org.openhab.core.model.script.entsoe ] - 2022-06-11T03:00Z: 1.76824 c/kWh
22:26:13.414 [INFO ] [org.openhab.core.model.script.entsoe ] - 2022-06-11T04:00Z: 3.16324 c/kWh
22:26:13.438 [INFO ] [org.openhab.core.model.script.entsoe ] - 2022-06-11T05:00Z: 4.27676 c/kWh
22:26:13.460 [INFO ] [org.openhab.core.model.script.entsoe ] - 2022-06-11T06:00Z: 4.50368 c/kWh
22:26:13.480 [INFO ] [org.openhab.core.model.script.entsoe ] - 2022-06-11T07:00Z: 3.6679199999999996 c/kWh
22:26:13.501 [INFO ] [org.openhab.core.model.script.entsoe ] - 2022-06-11T08:00Z: 2.67096 c/kWh
22:26:13.523 [INFO ] [org.openhab.core.model.script.entsoe ] - 2022-06-11T09:00Z: 1.6752399999999998 c/kWh
22:26:13.547 [INFO ] [org.openhab.core.model.script.entsoe ] - 2022-06-11T10:00Z: 1.3714400000000002 c/kWh
22:26:13.568 [INFO ] [org.openhab.core.model.script.entsoe ] - 2022-06-11T11:00Z: 1.21148 c/kWh
22:26:13.589 [INFO ] [org.openhab.core.model.script.entsoe ] - 2022-06-11T12:00Z: 0.5245200000000001 c/kWh
22:26:13.611 [INFO ] [org.openhab.core.model.script.entsoe ] - 2022-06-11T13:00Z: 1.24992 c/kWh
22:26:13.632 [INFO ] [org.openhab.core.model.script.entsoe ] - 2022-06-11T14:00Z: 1.44832 c/kWh
22:26:13.656 [INFO ] [org.openhab.core.model.script.entsoe ] - 2022-06-11T15:00Z: 1.6479599999999999 c/kWh
22:26:13.677 [INFO ] [org.openhab.core.model.script.entsoe ] - 2022-06-11T16:00Z: 1.6467199999999997 c/kWh
22:26:13.698 [INFO ] [org.openhab.core.model.script.entsoe ] - 2022-06-11T17:00Z: 1.88976 c/kWh
22:26:13.722 [INFO ] [org.openhab.core.model.script.entsoe ] - 2022-06-11T18:00Z: 1.9716 c/kWh
22:26:13.745 [INFO ] [org.openhab.core.model.script.entsoe ] - 2022-06-11T19:00Z: 2.6498800000000005 c/kWh
22:26:13.770 [INFO ] [org.openhab.core.model.script.entsoe ] - 2022-06-11T20:00Z: 2.1575999999999995 c/kWh
22:26:13.792 [INFO ] [org.openhab.core.model.script.entsoe ] - 2022-06-11T21:00Z: 1.45452 c/kWh
A couple of notes about the script.
-
First of all, the proof-of-concept script only works after the day ahead prices have been published for the next day. That is, in the evening. If you want to read the prices for the current day, remove the plusDays(1).
-
Second of all, the timezone handling needs a bit of love. The script currently has an offset-by-one between Finland (EET / EEST) and Central Europe (CET / CEST).
-
The Entso-E API returns the prices in XML format. I first tried to use the XPath Transformation service to read the values from XML, but there is an issue. Despite the fact that the Entso-E XML declares a namespace, the XML elements are in the default namespace. This means that parsing the XML with XPath would turn out to be ugly as hell because I would need to use fully qualified namespaces in the XPath matching.
-
To keep the code readable, I’m first transforming the XML to JSON using witht the XSLT xml2json.xsl which is available here: Convert XML to JSON using XSLT – Bojan Bjelic
-
Credits for the inspiration of using this XML to JSON technique go to this thread: MythTV Upcoming Recordings Widget, with HTTP Bind, XML to JSON via XSLT and pagination display
Summa summarum, conclusions about the proof of concept status:
-
I’m able to read the spot prices from Entso-E API
-
I’m able to control a buzzer via GPIO
Request for guidance from the community
With all the long background from above, I’d appreciate some guidance from the OpenHab community.
Request for guidance 1: About Things, Channels, Items and Persistence
I’ve been reading the conceptual descriptions what Things, Channels and Items are, but they are quite abstract for an OpenHab newbie like me so I’m hesitating a bit.
How would you recommend me to model the hourly prices of one day? It’s essentially a key-value pair where the
- key is the datetime of given hour, e.g. 2022-06-10T22:00Z
- value is the spot price for that hour, e.g. 3.44968
I believe I will have one Thing called “Entso-E”
- … but how would you recommed me to model the Channels and Items so that I would have places where I can update the 24 x hourly spot prices for each hour?
- If I run the script twice, the expected result would be that the latter prices would overwrite the previous values for the exact same datetime keys, not creating duplicate values for the same hour.
- If I run the script again tomorrow, the expected result is that I don’t overwrite the price of the 01:00-02:00 hour of the previous day.
- Do you think InfluxDB or the default RRD4J is better for this persistence use case and why?
- I obviously want to be able to plot the hourly prices for each day I have collected the data for, but the actual plotting should be straight forward based on what I have been reading about Grafana.
This is most probably very basic OpenHab stuff but I’m hesitating what is the Right Way to do this with OpenHab concepts so I would really appreciate some guidance on how to configure these different entities (channels, items) and their persistence.
Request for guidance 2. Decoupling the fetching of the hourly prices and determining the cheap hours for the water heater
The above script only fetches the spot prices and once I figure out the answers to the above things / channels / items question above with the help of the community, I can save them.
I definitely do not want to mix the “find the 4 cheap hours” to the script above. I want to de-couple it to a separate piece of code which gets executed after the spot prices have been fetched. The reason for this is that I will also have the other Thing (the heat pump) that will be using the spot prices.
The logic for determining the on/off times for the heat pump will be significantly more complex than finding the 4 cheap hours for the water heater because the heat pump controlling will need to take the weather forecast into account. But anyway, I do not want to fetch the spot prices again for the heat pump controlling, I want to use the same prices that have already been fetched once for the water heater.
So the question is: does the following sound logical way to decouple the logic in OpenHab:
-
Script 1: Fetches the spot prices and saves the values for each hour with Things / Items / Channels
-
Execute script 1 with cron once per day in the evening after the day ahead prices have been published
-
Script 2: Determines the 4 cheapest hours for the water heater for the next day. Save the results to some other Things / Items / Channels
-
Invoke script 2 either by cron slightly later than script 1 or if it’s possible, after script 1 has done it’s magic. Based on what I’ve been reading, an event based trigger would probably be more OpenHab way to invoke this script, would you agree?
-
Run a rule once per hour with cron at every full hour. If the hour changes from cheap to expensive or from expensive to cheap, toggle the relay switch on/off.
Cheers,
Markus