Suggestion for controlling my underfloor heating, Roth Touchline

Hope one of your cleaver guys here can come up with a go suggestion.

I have this floor heating system called Roth Touchline+. It has an app that only works half of the time, but fortunately someone has already revers-engineered the API and it is reasonably simple.
Documented here:

Read temperature:
curl -sX POST -H “Content-Type: text/xml” “” --data “<item_list>G5.RaumTemp<


Read set temperature:
curl -sX POST -H “Content-Type: text/xml” “” --data “<item_list>G5.SollTemp<


Set temperature:
curl -s ""

Yes you are reading correctly, it uses POST to READ and GET to SET!!!
So I guess that leaves out the HTTP binding (that and also the fact that the HTTP binding only supports text/plain but I need text/xml).

So the question is: do you have any suggestions as to how I could easiest implement this?
I am still getting started in OpenHAB, but I’m a fairly experienced linux user and I can do a fair bit of shell programming (but only shell).
In OpenHAB 1 I made a shell script I called with the exec binding, but I felt a bit sluggish and did not seem to work to well. But maybe that still is the way to go.

Let me hear your thoughts.


Use the http actions in rules triggered by unbound Items:

Thanks a lot rikoshak,

That sound like the solution I was hoping for.
Now I just have to figure out how to use actions and trigger them by items :open_mouth:

Well I’m sure I will come out on the other side, much more knowledgable :slight_smile:

I will keep you posted about my progress (hopefully).

Create item that are not bound to anything not linked to any channels.

Create rules triggered by commands on those items.

Put the call to the actions in these rules.

1 Like

Hi Morten,

Did you get any progress regarding Roth and getting / setting values?

Hi Kristoffer,

Sorry for the late answer.
No unfortunately I have not made any progress on this. I did gave it a fair try I think, but I didn’t manage to fully figure out how to create and use unbound items as rikoshak suggests.

But if you have better luck then please keep me posted.
Otherwise I will get back if I find time to look into this again.

Whee I finally got the time to look at this again!
Looking at @rlkoshak’s vTimeOfDays rule got me on the track, and I finally figured out what he meant about “unbound items” :slight_smile:
So it is just that: an item with nothing attached, just to store the state (nothing special you just create an item). And then it is updated using some rules.

So to hopefully help others on their way, here is my items and rules files.
Configured for 12 thermostats because thats what I have. Names are read from the controller and injected into the labels, combined with the current temperature.


Number RaumTempG0 "[%.1f °C]" <Temperature>
Number RaumTempG1 "[%.1f °C]" <Temperature>
Number RaumTempG2 "[%.1f °C]" <Temperature>
Number RaumTempG3 "[%.1f °C]" <Temperature>
Number RaumTempG4 "[%.1f °C]" <Temperature>
Number RaumTempG5 "[%.1f °C]" <Temperature>
Number RaumTempG6 "[%.1f °C]" <Temperature>
Number RaumTempG7 "[%.1f °C]" <Temperature>
Number RaumTempG8 "[%.1f °C]" <Temperature>
Number RaumTempG9 "[%.1f °C]" <Temperature>
Number RaumTempG10 "[%.1f °C]" <Temperature>
Number RaumTempG11 "[%.1f °C]" <Temperature>

Number SollTempG0 "[%.1f °C]" <Temperature>
Number SollTempG1 "[%.1f °C]" <Temperature>
Number SollTempG2 "[%.1f °C]" <Temperature>
Number SollTempG3 "[%.1f °C]" <Temperature>
Number SollTempG4 "[%.1f °C]" <Temperature>
Number SollTempG5 "[%.1f °C]" <Temperature>
Number SollTempG6 "[%.1f °C]" <Temperature>
Number SollTempG7 "[%.1f °C]" <Temperature>
Number SollTempG8 "[%.1f °C]" <Temperature>
Number SollTempG9 "[%.1f °C]" <Temperature>
Number SollTempG10 "[%.1f °C]" <Temperature>
Number SollTempG11 "[%.1f °C]" <Temperature>

And rules that match:

    val logName = "rothreadset"
    val String roth_read = ""
    val String roth_set = ""
    val String roth_post = "<body><item_list>

    rule "Update Roth Touchline+ temperatures"
      System started or
      //Time cron "*/10 * * * * ? *" or
      Time cron "23 */10 * * * ? *"
      var String SetPoint_read = sendHttpPostRequest(roth_read, "text/xml", roth_post)

      RaumTempG0.sendCommand(transform("REGEX", "s/(.?.)(..)/$1.$2/g", transform("XPATH", "/body/item_list/i[contains(n,'G0.RaumTemp')]/v", SetPoint_read)))
      RaumTempG1.sendCommand(transform("REGEX", "s/(.?.)(..)/$1.$2/g", transform("XPATH", "/body/item_list/i[contains(n,'G1.RaumTemp')]/v", SetPoint_read)))
      RaumTempG2.sendCommand(transform("REGEX", "s/(.?.)(..)/$1.$2/g", transform("XPATH", "/body/item_list/i[contains(n,'G2.RaumTemp')]/v", SetPoint_read)))
      RaumTempG3.sendCommand(transform("REGEX", "s/(.?.)(..)/$1.$2/g", transform("XPATH", "/body/item_list/i[contains(n,'G3.RaumTemp')]/v", SetPoint_read)))
      RaumTempG4.sendCommand(transform("REGEX", "s/(.?.)(..)/$1.$2/g", transform("XPATH", "/body/item_list/i[contains(n,'G4.RaumTemp')]/v", SetPoint_read)))
      RaumTempG5.sendCommand(transform("REGEX", "s/(.?.)(..)/$1.$2/g", transform("XPATH", "/body/item_list/i[contains(n,'G5.RaumTemp')]/v", SetPoint_read)))
      RaumTempG6.sendCommand(transform("REGEX", "s/(.?.)(..)/$1.$2/g", transform("XPATH", "/body/item_list/i[contains(n,'G6.RaumTemp')]/v", SetPoint_read)))
      RaumTempG7.sendCommand(transform("REGEX", "s/(.?.)(..)/$1.$2/g", transform("XPATH", "/body/item_list/i[contains(n,'G7.RaumTemp')]/v", SetPoint_read)))
      RaumTempG8.sendCommand(transform("REGEX", "s/(.?.)(..)/$1.$2/g", transform("XPATH", "/body/item_list/i[contains(n,'G8.RaumTemp')]/v", SetPoint_read)))
      RaumTempG9.sendCommand(transform("REGEX", "s/(.?.)(..)/$1.$2/g", transform("XPATH", "/body/item_list/i[contains(n,'G9.RaumTemp')]/v", SetPoint_read)))
      RaumTempG10.sendCommand(transform("REGEX", "s/(.?.)(..)/$1.$2/g", transform("XPATH", "/body/item_list/i[contains(n,'G10.RaumTemp')]/v", SetPoint_read)))
      RaumTempG11.sendCommand(transform("REGEX", "s/(.?.)(..)/$1.$2/g", transform("XPATH", "/body/item_list/i[contains(n,'G11.RaumTemp')]/v", SetPoint_read)))

      SollTempG0.sendCommand(transform("REGEX", "s/(.?.)(..)/$1.$2/g", transform("XPATH", "/body/item_list/i[contains(n,'G0.SollTemp')]/v", SetPoint_read)))
      SollTempG1.sendCommand(transform("REGEX", "s/(.?.)(..)/$1.$2/g", transform("XPATH", "/body/item_list/i[contains(n,'G1.SollTemp')]/v", SetPoint_read)))
      SollTempG2.sendCommand(transform("REGEX", "s/(.?.)(..)/$1.$2/g", transform("XPATH", "/body/item_list/i[contains(n,'G2.SollTemp')]/v", SetPoint_read)))
      SollTempG3.sendCommand(transform("REGEX", "s/(.?.)(..)/$1.$2/g", transform("XPATH", "/body/item_list/i[contains(n,'G3.SollTemp')]/v", SetPoint_read)))
      SollTempG4.sendCommand(transform("REGEX", "s/(.?.)(..)/$1.$2/g", transform("XPATH", "/body/item_list/i[contains(n,'G4.SollTemp')]/v", SetPoint_read)))
      SollTempG5.sendCommand(transform("REGEX", "s/(.?.)(..)/$1.$2/g", transform("XPATH", "/body/item_list/i[contains(n,'G5.SollTemp')]/v", SetPoint_read)))
      SollTempG6.sendCommand(transform("REGEX", "s/(.?.)(..)/$1.$2/g", transform("XPATH", "/body/item_list/i[contains(n,'G6.SollTemp')]/v", SetPoint_read)))
      SollTempG7.sendCommand(transform("REGEX", "s/(.?.)(..)/$1.$2/g", transform("XPATH", "/body/item_list/i[contains(n,'G7.SollTemp')]/v", SetPoint_read)))
      SollTempG8.sendCommand(transform("REGEX", "s/(.?.)(..)/$1.$2/g", transform("XPATH", "/body/item_list/i[contains(n,'G8.SollTemp')]/v", SetPoint_read)))
      SollTempG9.sendCommand(transform("REGEX", "s/(.?.)(..)/$1.$2/g", transform("XPATH", "/body/item_list/i[contains(n,'G9.SollTemp')]/v", SetPoint_read)))
      SollTempG10.sendCommand(transform("REGEX", "s/(.?.)(..)/$1.$2/g", transform("XPATH", "/body/item_list/i[contains(n,'G10.SollTemp')]/v", SetPoint_read)))
      SollTempG11.sendCommand(transform("REGEX", "s/(.?.)(..)/$1.$2/g", transform("XPATH", "/body/item_list/i[contains(n,'G11.SollTemp')]/v", SetPoint_read)))

      SollTempG0.label = transform("XPATH", "/body/item_list/i[contains(n,'')]/v", SetPoint_read).toString + " " + RaumTempG0.state as Number + "°C"
      SollTempG1.label = transform("XPATH", "/body/item_list/i[contains(n,'')]/v", SetPoint_read).toString + " " + RaumTempG1.state as Number + "°C"
      SollTempG2.label = transform("XPATH", "/body/item_list/i[contains(n,'')]/v", SetPoint_read).toString + " " + RaumTempG2.state as Number + "°C"
      SollTempG3.label = transform("XPATH", "/body/item_list/i[contains(n,'')]/v", SetPoint_read).toString + " " + RaumTempG3.state as Number + "°C"
      SollTempG4.label = transform("XPATH", "/body/item_list/i[contains(n,'')]/v", SetPoint_read).toString + " " + RaumTempG4.state as Number + "°C"
      SollTempG5.label = transform("XPATH", "/body/item_list/i[contains(n,'')]/v", SetPoint_read).toString + " " + RaumTempG5.state as Number + "°C"
      SollTempG6.label = transform("XPATH", "/body/item_list/i[contains(n,'')]/v", SetPoint_read).toString + " " + RaumTempG6.state as Number + "°C"
      SollTempG7.label = transform("XPATH", "/body/item_list/i[contains(n,'')]/v", SetPoint_read).toString + " " + RaumTempG7.state as Number + "°C"
      SollTempG8.label = transform("XPATH", "/body/item_list/i[contains(n,'')]/v", SetPoint_read).toString + " " + RaumTempG8.state as Number + "°C"
      SollTempG9.label = transform("XPATH", "/body/item_list/i[contains(n,'')]/v", SetPoint_read).toString + " " + RaumTempG9.state as Number + "°C"
      SollTempG10.label = transform("XPATH", "/body/item_list/i[contains(n,'')]/v", SetPoint_read).toString + " " + RaumTempG10.state as Number + "°C"
      SollTempG11.label = transform("XPATH", "/body/item_list/i[contains(n,'')]/v", SetPoint_read).toString + " " + RaumTempG11.state as Number + "°C"
      //logInfo(logName, "return from post" + SetPoint_read)

    rule "Set Roth Touchline+ temperatures room 0"
      Item SollTempG0 changed 
      sendHttpGetRequest(roth_set + "?G0.SollTemp=" + (SollTempG0.state as Number * 100).intValue())

    rule "Set Roth Touchline+ temperatures room 1"
      Item SollTempG1 changed 
      sendHttpGetRequest(roth_set + "?G1.SollTemp=" + (SollTempG1.state as Number * 100).intValue())

    rule "Set Roth Touchline+ temperatures room 2"
      Item SollTempG2 changed 
      sendHttpGetRequest(roth_set + "?G2.SollTemp=" + (SollTempG2.state as Number * 100).intValue())

    rule "Set Roth Touchline+ temperatures room 3"
      Item SollTempG3 changed 
      sendHttpGetRequest(roth_set + "?G3.SollTemp=" + (SollTempG3.state as Number * 100).intValue())

    rule "Set Roth Touchline+ temperatures room 4"
      Item SollTempG4 changed 
      sendHttpGetRequest(roth_set + "?G4.SollTemp=" + (SollTempG4.state as Number * 100).intValue())

    rule "Set Roth Touchline+ temperatures room 5"
      Item SollTempG5 changed 
      sendHttpGetRequest(roth_set + "?G5.SollTemp=" + (SollTempG5.state as Number * 100).intValue())

    rule "Set Roth Touchline+ temperatures room 6"
      Item SollTempG6 changed 
      sendHttpGetRequest(roth_set + "?G6.SollTemp=" + (SollTempG6.state as Number * 100).intValue())

    rule "Set Roth Touchline+ temperatures room 7"
      Item SollTempG7 changed 
      sendHttpGetRequest(roth_set + "?G7.SollTemp=" + (SollTempG7.state as Number * 100).intValue())

    rule "Set Roth Touchline+ temperatures room 8"
      Item SollTempG8 changed 
      sendHttpGetRequest(roth_set + "?G8.SollTemp=" + (SollTempG8.state as Number * 100).intValue())

    rule "Set Roth Touchline+ temperatures room 9"
      Item SollTempG9 changed 
      sendHttpGetRequest(roth_set + "?G9.SollTemp=" + (SollTempG9.state as Number * 100).intValue())

    rule "Set Roth Touchline+ temperatures room 10"
      Item SollTempG10 changed 
      sendHttpGetRequest(roth_set + "?G10.SollTemp=" + (SollTempG10.state as Number * 100).intValue())

    rule "Set Roth Touchline+ temperatures room 11"
      Item SollTempG11 changed 
      sendHttpGetRequest(roth_set + "?G11.SollTemp=" + (SollTempG11.state as Number * 100).intValue())

    //logInfo(logName, "GET request" + roth_set + "?G0.SollTemp=" + (SollTempG0.state as Number * 100).intValue())

Finally what I have in my sites file:

    Text label="Gulvvarme" icon="temperature" {
      //Setpoint item=SollTempG0 valuecolor=[>22="red",>21="green",>20="orange",<=20="blue"]
      Setpoint item=SollTempG0 minValue=5 maxValue=30 step=0.5
      Setpoint item=SollTempG1 minValue=5 maxValue=30 step=0.5
      Setpoint item=SollTempG2 minValue=5 maxValue=30 step=0.5
      Setpoint item=SollTempG3 minValue=5 maxValue=30 step=0.5
      Setpoint item=SollTempG4 minValue=5 maxValue=30 step=0.5
      Setpoint item=SollTempG5 minValue=5 maxValue=30 step=0.5
      Setpoint item=SollTempG6 minValue=5 maxValue=30 step=0.5
      Setpoint item=SollTempG7 minValue=5 maxValue=30 step=0.5
      Setpoint item=SollTempG8 minValue=5 maxValue=30 step=0.5
      Setpoint item=SollTempG9 minValue=5 maxValue=30 step=0.5
      Setpoint item=SollTempG10 minValue=5 maxValue=30 step=0.5
      Setpoint item=SollTempG11 minValue=5 maxValue=30 step=0.5

It’s neither perfect nor finished, but it works for now.
The current temperature is part of the label to put it all on one line, that also means the current temperature jumps back and forth depending on the label length:

If someone has a suggestion how to adjust that (possibly justify it on the right side), please let me know.
Also I will see if I can find time to implement vacation mode and error reporting.

Let me know if this is of any use for you, or if you have any troubles.

1 Like

Hi Morten,

Great work - took just a few minutes to implement!

Thanks a lot for sharing your solution.

So glad to hear it was useful to you.

I would like it, instead of being 12 lines, to be a loop, where the number of iterations is detrmined from the number of thermostats read from the heating controller.
If anyone have any idea how to make that, I’m all ears.

Also when stepping up or down quick, there is a tendency for it to stop or hang.
I suppose it is waiting for feedback from the http actions. Here we don’t need realtime updates, so is there a way to make OpenHAB receive the updates but wait with the execution of the rules for a few milliseconds until the user is finished stepping up or down?

Design Pattern: Associated Items will be a good place to start.

Set a timer that does the action. When the rule triggers see if there is a timer running. If there is, reschedule it which will put off executing the actions until the UI stops sending commands for how ever long you set the timer for.

Thanks a lot rikoshak,
Quick and precise as usual!

I must look in to the loop suggestion, seems doable but will require a bit of effort.

I did implement the Timer idea (so now I know about timers also, I’m learning every day :slight_smile:), but unfortunately i did not seem to change anything. So the “sluggines” must be in the iOS app interface.
Idead I don’t see that kind of behaviour in the browser.
Anyway it’s nice to know, and it saves OpenHAB from sending a bunch of http requests in a row.

I just realised I had a small error in my implementation:
It did not take single digit temperatures in to account…

Just returned from vacation while our house has been Airbnb’ed, and some of the thermostats was set to 5℃, which my rule read as 500℃ ! :wink:

No we don’t have airconditioning so it make no sense to set it to 5℃, especially since the heating is turned off anyway…

Anyway the fix is to add a question mark after the fist dot in the regex to make the first digit optional:
“REGEX”, “s/(.?.)(…)/$1.$2/g”
I have edited the original post to reflect the change.

Yes I realise that the regex should be tuned to only match digits, but it works okay for now.
Better yet: the whole thing should be rewritten to use a real parser for XML, well “be my guest”… :slight_smile:


Complete noob user here, I wanted to try out OpenHab simply because I too have a Roth heating system, with bad app, and I wanted to see if I could get Neeo and/or HomeKit to talk to it instead. I’m using OpenHab 2.3.0

Anyway, I set up OpenHab and I think I installed all the neccessary stuff, and then I created the item and rules files, and a default sitemap and put the contents above in them. I also set the default sitemap for the basicui, and now up pops a view with my five thermostats. Not bad. However, there is just nothing useful info in them.

Looking at the openhab.log I can see that it kinda works, it manages to talk to the TouchLine controller, but at the same time something is terribly wrong. I get many entries similar to this:

2018-08-13 21:10:23.472 [WARN ] [rthome.model.script.actions.BusEvent] - Cannot convert '<b.ody>.<>
  .  <i.><n>.G0.n.ame<./n><.v></.v></.i><i.><n>.G0.R.aumT.emp<./n><.v>25.11</.v></.i><i.><n>.G0.S.ollT.emp<./n><.v>24.00</.v></.i>
  .  <i.><n>.G1.n.ame<./n><.v></.v></.i><i.><n>.G1.R.aumT.emp<./n><.v>24.10</.v></.i><i.><n>.G1.S.ollT.emp<./n><.v>18.00</.v></.i>
  .  <i.><n>.G2.n.ame<./n><.v>St.ue o.g kj.økke.n</v.></i.><i>.<n></.n><v.>267.2</v.></i.><i>.<n></.n><v.>200.0</v.></.i>
  .  <i.><n>.G3.n.ame<./n><.v>To.alet.t</v.></i.><i>.<n></.n><v.>246.4</v.></i.><i>.<n></.n><v.>220.0</v.></.i>
  .  <i.><n>.G4.n.ame<./n><.v></.v></.i><i.><n>.G4.R.aumT.emp<./n><.v>25.76</.v></.i><i.><n>.G4.S.ollT.emp<./n><.v>20.00</.v></.i>
  .  </.item._lis.t></.bod.y>' to a command type which item 'SollTempG0' accepts: [DecimalType, QuantityType, RefreshType].

Any ideas?

I had to restart openhab after fixing a certificate related to iCloud, and now it works! Not sure why, but it does. Only had to convert the files I created earlier to UTF-8 to make the degrees symbol show up correctly.

Love it - thanks for your work here! :slight_smile:

Glad you got it working :slight_smile:

I was just looking and discovered I forgot to mention you need the “RegEx Transformation” and “XPath Transformation” transformations installed.
Not sure if they are by default, but they can be found in Paper UI -> Add-Ons -> Transformations.

Yup I figured that out from looking at your code, no problem!

Sadly, though, it seems like it is only working part time. I get many timeouts in the log. This might have to do with my peculiar network configuration, but I have strung together a small python program (on the same computer) that connects to the Roth and reads out the info pretty reliably. I tried adding a longer timeout (20000ms) to the http requests in the rules file, but it didn’t help.

Strange, I have no timeout errors in neither openhab.log or events.log
(that relates to Roth Touchline+ any way :slight_smile:)

I too have a script that reads out the temperatures of all thermostats every 15min and push them to a influxdb.
It’s not running on the same machine though (it’s on a OpenWRT router that also controls my 1-wire network to read temperatures from the district heating system, water consumption etc.)

Could it be because the two reads collide? (not sure how the Roth controller handles two concurrent requests).

Hi, first thanks to @MortenVinding for posting this code, I was really happy when I found out that I can have my Roth heating in openhab.

But somehow I can only get it to work for 5 out of 8 thermostats, when I add the 6th, I get the same gibberish that @henningnt mentioned:

This does not depend on the ID of the thermostat, if I add one that worked earlier at the 6th position in the roth_post-item_list, the post seems to break.

Does anybody have a hint on this?
@henningnt mentioned a “particular network configuration” - I have the Roth gateway connected via a ethernet-over-powerline connection, might this maybe cause some limitations?

Hope you guys in this thread still are reading this! :slight_smile:

Hi, i’m also interested in same topic as i’m in the process of implementing Roth touchline. Only 4 channels to start with, but shall be upgraded to 12 and hope that the limit Christian sees is a local issue. Any news Christian?

Besides, i’m also curious to get to know if it’s possible to read out the battery status of the thermostats? With 20+ battery devices in my home, it’s really convinient to let Openhab monitor and create an alarm when it’s time to recharge. My wife and kids complaints every time a radiator is either cold or too wam becase the battery was not changed in due time :slight_smile:

I found this page with usefull codes, but sadly battery status is not mentioned. Anyone who can confirm if there will be a code anyway?

Just for the record:
I finally had time to come back to this, uncommented my 7. thermostat in the items and rules files, and suddently it works. No more gibberish as in the post from Nov 2019, all thermostats reporting temperatures correctly.

So sorry, no more hints on why this happens, but at least confirmation that 7 thermostats can be controlled from OH.
But I really hope @OJEP did get his 12 thermostats to work in the meantime!

Reading again through my post from Nov 2019, I see that I wrote that I tried to control 8 thermostats - which makes me wonder whether that just was a typo or me actually trying to communicate with a nonexistent thermostat. Last case might produce the scrambled answer, but I don’t dare to test this now.