Rademacher Homepilot (DuoFern) via http-Binding

Great, thanks !! It works now.


For everybody, don’t do my mistake and install the Nashorn Javascript Version - this doesn’t work.

1 Like

Hi, thanks for these detailed descriptions!

While I am currently setting up openHAB 4.0.3 (openHABian on a Raspi 4 / 8GB) for my first time, I have quite some experience with scripting and smart home, maybe that’s why the roller shutter code as well as the evironmental sensor (UWS) worked within a day :smile:

For controlling our floor heating, I have several Rademacher 9485 wall thermostats / controllers installed.
API-wise (when looking at the JSON code the HomePilot (or smarthome-box, as it’s called since the last update) these guys look very similar to the radiator thermostats from the example - not too surprising I guess.

Correct me if I’m wrong, but the code of the example only reads the values (actual and setpoint temperature etc.) from the thermostat, it is not able to actually set values - or is it?

Did anyone already try to implement setting temperature values or activating one of the thermostat’s temperature presets? I would be curious to know! :slight_smile: :wink:

1 Like

Hi @Hundertvolt1 Nico,
thanks for your feedback and I am happy to hear that roller shutters are working for you.

Yes, you are right.
I also have some thermostats and would like to send commands from openHAB - but I didn’t had the time to implement it yet. I guess when it gets colder outside I will have more “pressure” to work on it :crazy_face:.
If you are faster we would be happy if you could share your code for sending commands to thermostats.

best, Kai

Worth a try, if I find time myself :smiley:

What item type would you suggest for the temperature setting?

I was totally surprised that openhab does not yet seem to natively have an item concept for thermostat controllers (as for e. g. dimmers and rollershutters). Or was I blindfolded?

Ideally it should provide a mixture between rollershutter and dimmer as commands - UpDown, Decimal, Refresh. I am thinking of a nice slider on the GUI which has adjustable upper and lower limits, the Rademacher devices have a range between 4 and 40 degrees, as far as I know.

Or is there a nice way to do so “just” using number:dimension?

for the position of a rollershutter we used number format in items file. So I assume for thermostats that should be fine too.

The current position of my Rademacher thermostats are readable and shown in openHAB from 40-280 (that seems to be the temperature deviced by 10 → 4,0°C - 28,0°C).
Position 40 means 4°C.

Reading of the current status is already possible, just the commandTransformation part is still missing:

Type number : Thermostat_Position [
   commandTransformation="JS:ThermostatPositionJSON.js", <-- this is still missing

When we check the device via http://homePilot-IP/v4/devices/55 I can see:

   Manuellbetrieb	0
   Position	        40
   acttemperatur	212
visible	true
deviceNumber	    "35003xxx"
uid	                "e10xxx"
voiceControlledBy	""
origin	            "HomePilot"
batteryStatus	    28
batteryLow	        false
posMin	            40
posMax	            280
   0	            "DIRECT_FUNCTION_CMD"
   1	            "TARGET_TEMPERATURE_CFG"
   2	            "AUTO_MODE_CFG"

Means to me the new ThermostatPositionJSON.js (which you will work on :innocent:) need some adjustments. Instead of “GOTO_POS_CMD” (in RollerShutterPositionJSON.js) there need to be “TARGET_TEMPERATURE_CFG”, right?


        var obj = {name: "GOTO_POS_CMD", value: parseInt(position)};
        return (JSON.stringify(obj));
    } catch(e) {
        return null;

That need to be tested.

and during this conversation with @Hundertvolt1 I obviously solved it. My thermostat is now controllable with OH.
You need to send the target temperature (instead of the position) to the thermostat.

I try to summarize again:

item (in director /items):

Number Thermostat  "Thermostat"  {channel="http:url:HomePilot1:Thermostat"}

Things (in directory /things)

Type number : Thermostat [

ThermostatJSON.js (in directory /transform)

        var obj = {name: "TARGET_TEMPERATURE_CFG", value: parseInt(position)};
        return (JSON.stringify(obj));
    } catch(e) {
        return null;

rule (in directory /rule)

Thermostat.sendCommand(19)     // =  19,0°C 

→ Rademacher thermostat can be controlled via openHAB too.
Does this work for you as well, @Hundertvolt1 ?

Haha, way too cool :smiley: thanks a lot already!

Have to try this as soon as possible. I’ll keep you updated!

Just one remark from looking at it - maybe the parseInt needs to be a bit adapted as the thermostats don’t just work in integer, but in 0.5deg increments.

Hey @April_Wexler,

I more or less copy-pasted your suggestions and did the adaptation to my system and can confirm it’s working fine for at least one of my wall thermo controllers. :smiley: :+1:

Somewhat funny that the sensor readings are formatted in degrees * 10 while the commands are “just” degrees. I wonder how a “half degree” (e.g. 20.5) may be commanded to the device, that was my thought in the previous post already…

Nevertheless, thanks for the really quick response and for making it work!!

On my course to make things work…

One more small question about switches. Also got tons of them installed :sweat_smile:
In your example (“Kueche_Abzugshaube”) I only see the command implementation, seems like there is no actual value reading so far.

I suppose a stateExtension and a stateTransformation are required here… in my HomePilot, I found the values at the usual places and tried an implementation:

       Type switch : Ventilator_Waschraum "Ventilator Waschraum" [  // Relay switch  DID=1010***
            commandExtension="/devices/1010***", //

but so far this does not work correctly and I am getting an “UNDEF” as current status.
The values of statusesMap.Position is “0” for off and “100” for on. I hoped that OH would interpret 0 as off and >0 as on, but obviously it expects something else :wink:
Grateful for a little hint ^

Same as for shutters there are two things needed (at least it is working with that in my OH).
Reason is that the Status is a number, and the command is a switch:

Type switch : Abzugshaube [
Type number : AbzugshaubeStatus [

I can see in my openHAB Log that Status changes from 0 (off) to 100 (on).
You are right, the commandTransformation is not working as it should for the hood in kitchen (German: “Abzugshaube”).
But for me it was still ok just to see status 0 or 100 - and can use that staus in rules.

2023-09-18 11:20:20.138 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'AbzugshaubeStatus' changed from 0 to 100

Yes, the type (switch) and the 0 / 100 scheme are not compatible as such.
I think it should not be a real issue to convert the 0 / 100 thing into whatever the switch type needs, is it?

The only thing I did not find out is exactly what the switch type actually expects for the status (number in another range, boolean, string (with a certain content)…)

Do you by any chance know this or where I could find this information? Then it should be easy to write a script :wink:

Well… productive evening. :star_struck:

Maybe this is also interesting for you - I changed the ThermostatJSON.js in such a way that you can set values in 0.5 step quantization and it guarantees that any numerical input is put into this scheme:

        var obj = {name: "TARGET_TEMPERATURE_CFG", value: (Math.round(2.0 * parseFloat(position)) * 0.5)};
        return (JSON.stringify(obj));
    } catch(e) {
        return null;

Next thing which makes the values look nicer - currently, the Rademacher thermostat devices output the desired and actual measurement value in integer “degrees * 10”, which looks awkward in the GUI and makes value comparisons look strange. I solved this with concatenated transformations - the Thing definition:

        Type number : Heizung_Arbeitszimmer_Istwert "Heizung Arbeitszimmer Istwert" [   //DID=<myDID>
        Type number : Heizung_Arbeitszimmer_Sollwert "Heizung Arbeitszimmer Sollwert" [

and with the toDegrees.js as such:

        return (x * 0.1).toFixed(1);
    } catch(e) {
        return null;

and, tadaa, we have degrees Celsius with one decimal place allover :smiley:

1 Like

Now, last for today, a partial success with the fan relay.

I finally found the definition of the on / off status here.

So I took the switch Thing and added a transformation as such:

       Type switch : Ventilator_Waschraum "Ventilator Waschraum" [  // Relay switch  DID=<myDID>
            commandExtension="/devices/<myDID>", //

under the assumption that this will integrate the status and the control element into ONE thing.

The script toSwitch.js goes as such:

        if (x == 0) {
            return (OnOffType.from(false));
        return ((OnOffType.from(true)));
    } catch(e) {
        return null;

…which works because the enum is automatically imported in the openHAB scope :partying_face:

I see that it’s working because in difference to before, the GUI element stays in the state I switch (instead of jumping back to UNDEF after one or two seconds), and there are no errors in the console log.

BUT: the switch state seems not to be updated with a regular interval, but only each time the switch element in the GUI is manually actuated, and only in this case there is a console log about a changed state. (Just to be sure I tried it without the concatenated transformation, which did not change anything about the updating behavior, but brought back the UNDEF state).

This is, uhm, not very nice, because I am using the internal timer function (“Treppenhausfunktion”) of the actuator, which automatically switches it off after 15 minutes. And OH would never notice this. :stuck_out_tongue_closed_eyes:

In two separate Things as suggested before this is not an issue, well, but this would again decouple the OH internal switch state variable from the real hardware, so no real win.

Now already being so close, I would be very happy to hear if anyone has an idea of how to convince OH to update the switch status on a regular poll (as all the other statuses…)

1 Like

thanks for that great work! I will check (and copy it to my OH) next weekend :slight_smile:

Hi, looking forward to your test results :slight_smile:

One more very important thing to note, especially if you have a very “full” Homepilot / smarthomebox.
If many values are requested from many locations (e.g. updating the set-point values of thermostats, which all have individual local URLs, and as they can change anytime triggered by different sources it absolutely makes sense to keep them up to date), the GET requests quickly get lost and end up in a timeout, as the Homepilot is simply overloaded.

I tried IOBroker some time before, and it happened there as well. Even in the documentation of their adapter, they claim to have some reading intervals hardcoded (!) there. :scream: No changes possible.

And they are updating any value of the HomePilot, so gigatons of requests in my case (80+ physical devices and 200+ virtual devices).
Actually, this disqualified IOBroker for my Homepilot.

Here, the “delay” setting in the Thing definition at the very beginning does the magic.

Thing http:url:HomePilot "HomePilot" [
    baseURL="",  //your HomePilot IP

Note the “delay=100” at the end. 100ms were just a first guess, probably you could even go shorter, but at least with this, the number of HTTP timeouts dropped from several hundred per minute to zero. Without any other drawbacks noted so far. :smiley:

One more “goodie” to go: The Thing definition for Rademacher Window and Door Contacts (9431).

        Type string : Fensterkontakt_Arbeitszimmer "Fensterkontakt Arbeitszimmer"[
        Type number : Fensterkontakt_Arbeitszimmer_Batt "Fensterkontakt Arbeitszimmer Batteriestatus"[

This gives you the battery status (number 0…100) and the opening status as string with possible values “closed”, “tilted” and “open” (all lowercase).

Hi again :slight_smile:
I still have issues with timing - Although I have “only” 52 different stateExtensions at the moment, I get the error that 133 channels take way too long to refresh. I posted this here in another topic. Yet, I don’t know how it counts 133 channels. The channel count is over 250, the stateExtensions 52, as mentioned (but still growing). I have the impression that something is a bit awkward with the caching… but maybe my fault at another place. Help is appreciated :slight_smile:

Anyhow, maybe another kind of handling the issue with less HTTP requests.
I found that in addition to


there is the address


I thought this would be working as I found devtype=Actuator in various other forums.
Actually, meanwhile I found that only devtype=Sensor really yields a different result. Anything else, e.g. just http://<HomePilotIP>/v4/devices or even http://<HomePilotIP>/v4/devices?devtype=Foo has the same response from HomePilot. Unless somebody knows something else.

Nevertheless, this does not change what I mentioned before - requesting http://<HomePilotIP>/v4/devices gives you an overview of all actuators states, including setpoint and measurement values of the thermostats and seems to be very useful!

Update 2
Now I really found how to get the scenes: http://<HomePilotIP>/v4/scenes. :smiley:

Update 3 - breaking news!!
Following an idea, I checked what happens if I open the HomePilot configuration website and see what WireShark logs.

  • So first of all the news is: The config website is acually using the REST API as well meaning you can use the API for doing any action you can do on the config site!!!

  • If you configure Wireshark to filter for <HomePilotIP> and http you can see which addresses are used and which commands are being sent pretty easily when you just execute the action of your choice on the config website. With this, you can do anything on the HomePilot!

  • Some address extensions I found (some of which I have never seen documented at any place before):
    /v4/devices?devtype=Actuator (Actuators like switches etc.)
    /v4/devices?devtype=Camera (HomePilot supports cameras, but I don’t have one, so I can’t tell much about it.
    /v4/devices?devtype=Sensor This we knew already :slight_smile:
    /v4/devices?devtype=Transmitter (Transmitters with momentary events. I did not find a way to get actual events over the API, so this might still be interesting…)
    /v4/scenes mentioned before, the list of scenes incĺuding the Scene IDs (“SID”)
    /v4/groups/devicegroups (The list of device groups inside the HomePilot. Can’t tell much about it as I don’t use this feature).
    /automation/v2 The list of all automations, meaning triggers (“Auslöser”) including conditions and actions

  • with all this background, I found that it is actually possible to trigger a scene by a command, which gives you quite more control about the HomePilot system compared to controlling just the devices.
    It needs a certain JSON command, and other than device commands (which use PUT) it’s using the POST method. As the scenes are stateless, it does not really make sense to add them as Thing, but rather to use a script to trigger them.
    So, how to?
    – Look up the SID of your scene inside /v4/scenes.
    – Put it into this script:

var HTTP = Java.type("org.openhab.core.model.script.actions.HTTP");
var url = "http://<HomePilotIP>/scenes/<mySID>/actions";
var content = "application/json";
var cmd = "{\"request_type\":\"EXECUTESCENE\", \"trigger_event\":\"TRIGGER_SCENE_MANUALLY_EVT\"}"
var result = HTTP.sendHttpPostRequest(url, content, cmd, 10*1000);

…and watch it go! :smiley:

By the way: The scenes support two more trigger events (apart from TRIGGER_SCENE_MANUALLY_EVT, which is the manual trigger as you do by activating the scene in the config website):
SCENE_MODE_CMD (Not really sure what it is)
AUTOMATION_MODE_CMD (Probably the mode which is used by an internal trigger event).

In this short thread, we found a solution to automate the triggering of the scenes with just one rule and one item and you can call these rules by their clear names used in the HomePilot.

For having it all here, this is how it goes:

  • Create an String Item, e.g. HomePilot_Scene_Trigger.
  • Create a DSL rule which triggers on any update of this item:
val HomePilotIP = "";
val timeout = 3000;
var sceneName = HomePilot_Scene_Trigger.state.toString;
var searchString = "$.scenes[?(@.name==\"" + sceneName + "\")].sid";
var scene_url = "http://" + HomePilotIP + "/v4/scenes";
var JsonSceneMap = sendHttpGetRequest(scene_url, timeout);
if (JsonSceneMap == null) {
  logError("homepilot.scenetrigger", "HTTP GET request to {} failed!", scene_url);
} else {
  var SID = transform("JSONPATH", searchString, JsonSceneMap);
  if (SID == "NULL") {
    logWarn("homepilot.scenetrigger", "Scene Name \'{}\' was not found!", sceneName);
  } else {
    logInfo("homepilot.scenetrigger", "Scene Name \'{}\' found --> SID {}.",sceneName, SID);
    var cmd_url = "http://" + HomePilotIP + "/scenes/" + SID + "/actions";
    var cmd = "{\"request_type\":\"EXECUTESCENE\", \"trigger_event\":\"TRIGGER_SCENE_MANUALLY_EVT\"}"
    var result = sendHttpPostRequest(cmd_url, "application/json", cmd, timeout);
    if (result == null) {
      logError("homepilot.scenetrigger", "HTTP POST request to {} failed!", cmd_url);
    } else {
      if (result == "{\"error_description\":\"OK\",\"error_code\":0}") {
        logInfo("homepilot.scenetrigger", "Scene trigger for \'{}\' was successful.",sceneName);
      } else {
        logError("homepilot.scenetrigger", "Scene trigger for \'{}\' failed, error message: {}.",sceneName, result);
  • Anytime you update the Item with a scene name, it will lookup the SID and trigger the scene.
  • The only configuration to be done: the first two lines - set the IP of your HomePilot and the desired HTTP timeout. :slight_smile:
  • little update to the first version: now with handling of typical errors and console logging.

Found some more interesting sites of the HomePilot API:

  • http://<HomePilotIP>/automation/v2/options seems to be a list of capabilities and possible commands to many devices and actuators
  • http://<HomePilotIP>/service/system/twilight returns a JSON with the current Civil Dawn / Civil Dusk used by HomePilot as Unix Timestamps
  • http://<HomePilotIP>/automation as already mentioned, the whole dataset of automations and triggers. Most important, it contains the name strings of the automations and the automation IDs (it’s just called “ID” at this place).
  • http://<HomePilotIP>/automation/v2/<ID> the individual special information for the automation with the selected ID

Have an automation with manual trigger (offers more options than scenes, esp. timed and ordered commands!) and want to start this from OH? Here you go:


with usually being a 4-digit number. The automation must be set to be triggered manually, otherwise it won’t work.

Similar to the scene execution by name, here is manual automation (“Auslöser”) execution by name.

val HomePilotIP = "";
val timeout = 3000;
var automationName = HomePilot_Automation_Trigger.state.toString;
var searchString = "$.automation[?(@.automation.name==\"" + automationName + "\")]automation.id";
var isManualString = "$.automation[?(@.automation.name==\"" + automationName + "\")]is_manual_executable";
var automation_url = "http://" + HomePilotIP + "/automation/v2";
var JsonAutomationMap = sendHttpGetRequest(automation_url, timeout);
if (JsonAutomationMap == null) {
  logError("homepilot.automationtrigger", "HTTP GET request to {} failed!", automation_url);
} else {
  var ID = transform("JSONPATH", searchString, JsonAutomationMap);
  if (ID == "NULL") {
    logWarn("homepilot.automationtrigger", "Automation Name \'{}\' was not found!", automationName);
  } else {
    logInfo("homepilot.automationtrigger", "Automation Name \'{}\' found --> ID {}.",automationName, ID);
    var isManual = transform("JSONPATH", isManualString, JsonAutomationMap);
    if (isManual != "true") {
      logWarn("homepilot.automationtrigger", "Automation \'{}\' is not manually executable!", automationName);
    } else {
      var cmd_url = "http://" + HomePilotIP + "/automation/v2/" + ID + "/actions";
      var cmd = "{\"request_type\":\"EXECUTEAUTOMATION\"}";
      var result = sendHttpPostRequest(cmd_url, "application/json", cmd, timeout);
      if (result == null) {
        logError("homepilot.automationtrigger", "HTTP POST request to {} failed!", cmd_url);
      } else {
        if (result == "{\"error_description\":\"OK\",\"error_code\":0}") {
          logInfo("homepilot.automationtrigger", "Automation trigger for \'{}\' was successful.",automationName);
        } else {
          logError("homepilot.automationtrigger", "Automation trigger for \'{}\' failed, error message: {}.",automationName, result);

All you need to do is to create a string item HomePilot_Automation_Trigger, use updates on this item to trigger the above script.
It will look up the automation name, check if it’s actually manually executable and then trigger it. The most common errors are handled and appear in the console.

I just tried reading everything from http://<HomePilotIP>/v4/devices for a thermostat.
Generally it works, therefore having the potential of saving many HTTP requests for individual sensors.

        Type number : Heizung_Arbeitszimmer_Istwert "Heizung Arbeitszimmer Istwert" [   //DID=<myDID>

reading the actual value works great.

        Type number : Heizung_Arbeitszimmer_Sollwert "Heizung Arbeitszimmer Sollwert" [
            // stateExtension="/v4/devices/<myDID>",
            // stateTransformation="JSONPATH:$.device.statusesMap.Position∩JS:toDegrees.js",

This is the previous (commented and known-working) and the current configuration. Unfortunately, I encountered a problem I knew before from the switches (see here) - it seems like if the stateExtension is different from the commandExtension, the state is no longer updated, at least unless a command is being sent. But no regular updates anymore.

I can’t imagine that this is intended, so either I did something wrong (help appreciated :slight_smile: ) or it may be worth investigating the OH code there :wink:

So much has happened since my last post: thank you so much Nico for your excellent work.

Thanks for fixing the thermostatJSON.js script. Great that you made it possible that we can send 0.5° steps now.

I took your suggestion to configure a delay. However, I never had the problems you describe, as I don’t have that many Rademacher objects. My homepilot has (so far) not reported an overload.
I will continue to monitor this.

Rergading the thermostats: I prefer the conversion into a separate item(s). I have rules that runs, when the acttemperatur value changes - it divides the Temperature by 10 and writes it to a different item:

   Item HeizungAktTemp changed
   val abc = (HeizungAktTemp.state as Number)
   val converted = (abc / 10) 
   val converted2 = ((((converted as Number)*100).intValue)/100.0) // rounding 

This has the advantage when using persistence (influxDB & Grafana) that no “wrong” (non-converted values) are written into the database.

Again: thanks for your great work! I highly appreciate our great step forward!

Best, Kai

I have more good news - must be switchcraft :wink: - Meanwhile, after some cool testing session in this thread, it is possible to fully integrate a switch (relay, integrated relay in motion sensor, etc.).

This means that it is not only possible to send commands, but also the OH-internal status is updated according to the relay’s actual status. You do not need a separate channel for the status anymore! :smiley:

Here is the Thing definition:

        Type switch : Schalter_NL_Flur_2 "Schalter Nachtlicht Flur 2" [  // Relay switch  DID=<myDID>

and here is the associated toSwitchCmd.js:

  try {
        if (x == "0") {
            return ("{\"name\":\"TURN_OFF_CMD\"}");
        return ("{\"name\":\"TURN_ON_CMD\"}");
  } catch(e) {
    console.error('toSwitchCmd.js encountered an error: ' + e);
    return null;

And, believe it or not, I just thought I have some minutes to play around with the same idea with the rollershutters. And it almost immediately worked (with a little trick in the script). :smiley:

Here’s the Thing:

        Type rollershutter : Rolladen_Arbeitszimmer "Rolladen Arbeitszimmer" [   //DID=<myDID>

and here’s the RollerShutterControlAndPos.js script:

        if(x == "STOP") {
            return ("{\"name\":\"STOP_CMD\"}");
        if(x == "DOWN") {
            return ("{\"name\":\"POS_DOWN_CMD\"}");
        if(x == "UP") {
            return ("{\"name\":\"POS_UP_CMD\"}");
        // if it's no keyword, assume it is the position number
        var obj = {name: "GOTO_POS_CMD", value: parseInt(x)};
        return (JSON.stringify(obj));
    } catch(e) {
        console.error('RollerShutterControlAndPos.js encountered an error: ' + e);
        return null;

OH just pushes through what you command it - in case of the control buttons it’s the content of the up/down/stop value definition, and if it’s a percentage position, it’s just a number.
So the trick was to make the script distinguish between button commands via keywords, and to assume that if it’s not a keyword it must be a percentage number.

With this setup, the GUI buttons work, as well as a pure number command from a rule, and the current position is displayed in the rollershutter object itself. :wink:

One more comment about the query load of the HomePilot.

At this moment, I can deal with the whole magic just using two HTTP requests.

There is no need to query the current positions of rollershutters and thermostats with http://<HomePilotIP>/devices/<individualDID>.

You can get any actuator status, actual value, setpoint etc. using stateExtension="/v4/devices" and parsing through the cache allover again with a scheme like stateTransformation="JSONPATH:$.devices[?(@.did==<myDID>)].statusesMap.Position".

This exhaustive list just does not contain pure sensor devices (like remote switches, UWS, door and window sensors, sun sensors or the like). As mentioned quite early (way before my time :slight_smile: ) in this thread, they can be found in stateExtension="/v4/devices?devtype=Sensor", parsing goes very similar in a stateTransformation="JSONPATH:$.meters[?(@.did==<myDID>)].readings.contact_state" scheme.

With this, you can do all the cyclic reading with only two HTTP requests, and no more individual DID addresses.

This (temporarily) fixed the issue with the delay setting - currently, HP counts the pure number of channels, multiplies with the delay and extends the polling period if it’s too short. It does not check if they all have the same stateExtension and will be cached. No way known yet to circumvent this, but seems like the HomePilot can bear two requests without any delay. See ongoing discussion in this thread.

The individual DID addresses are still and only required for issuing commands. But in contrast to periodical readings, commands issued from time to time normally do not have the risk of driving the HomePilot into a denial-of-service :sweat_smile: