My underfloor heating (hot water) was previously controlled with Busch-Jäger bimetal thermostats. That went well, but there was no option for night setback or absence mode. As a SmartHome freak, I want to control everything from a tablet.

Several experiments with finished electronic thermostats followed, with MAX! and HomeMatic, but nothing was really satisfying - sometimes very expensive. For a long time I have been using Shellies for the light switches in the house, which are connected via WiFi and fit into a flush-mounted box. These are inexpensive, reliable and can be integrated into OpenHAB via MQTT.

I do not use the Shelly HT because it is a standalone device with battery operation and therefore only very rarely sends temperature updates to OpenHAB. Since Allterco has offered a temperature sensor add-on for the Shelly 1, I've toyed with the idea of using this duo for the thermostat.



The Shelly 1 with the add-on runs as a two-point controller via the "Temperature Actions". This also works if the WLAN or OpenHAB should not work. The switching thresholds are given to the Shelly 1 via HTTP commands from OpenHAB. MQTT does not work for these parameters, possibly once in a later firmware.

The Shelly returns all measured values ​​and the relay position via MQTT to OpenHAB so that they can be displayed there in the sitemap. The relay controls my hot water valves in the heating distributor.

There are a few global virtual switches for all thermostatic valves:

  • Heating on / off (summer / winter)
    Day / night switching via CRON. Lower temperatures are used at night.
    Hysteresis: this is the distance in ° C between switching the thermostats on and off. Since the Shelly only measures to an accuracy of 0.1 ° C, at least 0.2 ° C should be set

Unfortunately, my underfloor heating pump is not electronically controlled, which is why it used to run all winter. The pump has got its own Shelly 1, which is controlled by the group of all thermostatic valves. If at least one valve is switched on, the pump also runs. The pump is only switched off when all valves are off.


You have to buy

So the total cost of a thermostat is € 19.

On the left the Shelly 1 and on the right the temperature sensor add-on. The add-on is plugged into the programming socket of the Shelly.

The temperature sensor DS18B20 in the TO92 housing


3D printed housing

There is no suitable thermostat housing from Busch Jäger, so I designed one myself - it consists of the wall plate, the Shelly adapter and the cover for the sensor. You can download it from Thingiverse:

Above the round Shelly adapter, below the wall plate and the cover.


Software for the Shelly

The Shelly 1 is equipped with the current Shelly firmware 1.8.0.

When setting up via the web interface, you have to enter the access data of your own WLAN and the MQTT server in the Shelly. The MQTT server Mosquitto is located with OpenHAB on the Raspberry Pi.


The Shelly adapter is attached to the wall plate with M3x12 screws. The screw in the middle is a M3x16 for screwing the cover.
The M3 nuts are pressed into the PLA housing with a soldering iron. It only takes a few seconds.
The Shelly 1 is now pressed into the adapter from above until it sits on the screw connection. The adapter is a few millimeters away from the wall plate. This reduces heat transfer from the Shelly to the wall plate behind which the sensor is located.
The temperature sensor add-on is now plugged into the programming socket of the Shelly. The three wires are fed through the hole in the wall plate.

The three wires are led out on the front. The Busch Jäger frame fits very well on the wall plate.

The wires are shortened a little and then soldered to the DS18B20. Shrink tubing prevents a short circuit. The assignment:

Wire Addon Function DS18B20
black GND  Pin 1
red +3,3V  Pin 3
yellow Data  Pin 2

It is essential to switch off the fuse and check whether the wires are voltage-free!

After removing the old thermostat, the 230V wires are now connected to the Shelly.

After tightening and checking the clamps, the add-on is attached.
The module is pushed into the flush-mounted box and the wall plate is fastened with two screws.
The soldered-on temperature sensor is clamped into the groove in the cover. If needed, secure with some hot glue.

Now you put the Busch Jäger frame on and screw the cover with the central screw.

Finished! Now you can switch the fuse on again and test whether the Shelly 1 can be reached via the web interface.


OpenHAB Things

Each thermostat is its own thing with two channels each: temperature and relay. Creates a file things/mqtt.things with the following content:

// Shelly 1 mit Temperature Addon for WZ-Thermostat
Thing mqtt:topic:HZ_WZ_FloorThermostat "Th-HZ-WZ-FloorThermostat" {
		Type number : chTemp 	"Temperatur" [ 
		    stateTopic="shellies/shelly1-A4CF12F3E44D/ext_temperature/0" ]
		Type switch : chRelais  "Ventilausgang" [ 
		    on="on", off="off", 
		    on="on", off="off" ]

Den MQTT-Pfad zum Shelly findet man am Besten mit dem "MQTT-Explorer" (Windows) heraus:

You can download it from the Windows Store for Windows 10.


OpenHAB Items

For each thing (= thermostat) two items must be created. Creates a file items/mqtt.items with the following content:

// shelly1-A4CF12F3E44D
Number It_WZ_ActTemperature "Wohnzimmer Temperatur [%.1f °C]" 	                 	(gPersistMapDB, Thermostat)
    { channel="mqtt:topic:HZ_WZ_FloorThermostat:chTemp", alexa="TemperatureSensor.temperature" }

Switch It_WZ_RelaisThermostat "Wohnzimmer Thermostatventil [MAP(]"  	(gPersistMapDB, gHeatingValves)
    { channel="mqtt:topic:HZ_WZ_FloorThermostat:chRelais" }


In addition, virtual items are required for the temperatures and day / night or summer / winter settings:

// Fußbodenheizung mit Shelly 1 & Temperatur-Addon:
// Globale Werte:
Group:Switch:OR(ON, OFF)           gHeatingValves  "Heizventile [MAP(]"             (Home)   
Number vIt_HZ_Hysterese		  "Fußbodenheizung Hysterese [%.1f °C]" 		(gPersistMapDB)
Switch vIt_HZ_OnOff		  "Fußbodenheizung [MAP(]" 		(gPersistMapDB)
Switch vIt_HZ_DayNight		  "Fußbodenheizung Programm [MAP(]" 	(gPersistMapDB)
Group Thermostat "Wohnzimmer"     {alexa="Endpoint.Thermostat"}

// Thermostat GF_LivingRoom:
Number vIt_HZ_WZ_SollTag_upper	  "Wohnzimmer Solltemperatur Tag [%.1f °C]" 		(gPersistMapDB, Thermostat)	{alexa="ThermostatController.upperSetpoint"}
Number vIt_HZ_WZ_SollTag_lower	  "Wohnzimmer Solltemperatur Tag Start [%.1f °C]" 	(gPersistMapDB)
Number vIt_HZ_WZ_SollNacht_upper  "Wohnzimmer Solltemperatur Nacht [%.1f °C]" 		(gPersistMapDB)
Number vIt_HZ_WZ_SollNacht_lower  "Wohnzimmer Solltemperatur Nacht Start [%.1f °C]" 	(gPersistMapDB)


OpenHAB Rules

Creates a rules/heating.rules file with the following content:

// Global Thermostat Rules:
// Nightly temperature reduction for all thermostats:
// Night reduction starting everyday at 19:30
rule "FloorHeating_NightReduction"
    Time cron "0 30 19 1/1 * ? *"
	vIt_HZ_DayNight.sendCommand( 'OFF' )
	logInfo(	"FloorHeating_NightReduction", "Rule FloorHeating_NightReduction ...")

// Daily temperature operation for all thermostats:
// Day operation starting everyday at 07:00
rule "FloorHeating_DayOperation"
    Time cron "0 0 7 1/1 * ? *"
	vIt_HZ_DayNight.sendCommand( 'ON' )
	logInfo(	"FloorHeating_DayOperation", "Rule FloorHeating_DayOperation ...")

// Floor Heating Pump: shellies/shelly1-A4CF12F48853
rule "FL_FloorHeatingPump"
	Item gHeatingValves received update
		// Switch ON=pump running
		It_FL_Pump.sendCommand( "ON" ) 
		logInfo( "FL_FloorHeatingPump", "Heating ON" )
	} else {
		// Switch OFF=pump not running
		It_FL_Pump.sendCommand( "OFF" ) 
		logInfo( "FL_FloorHeatingPump", "Heating OFF" )

// Thermostat Wohnzimmer:
// Calculate lower DAY setpoint if hysterese or upper setpoint have changed
rule "WZ_Thermostat_LowerSetpointDay"
    Item vIt_HZ_Hysterese changed or
	Item vIt_HZ_WZ_SollTag_upper changed
	var Double UpperSetpoint = ( vIt_HZ_WZ_SollTag_upper.state as Number ).doubleValue()
	var Double Hysterese     = ( vIt_HZ_Hysterese.state as Number ).doubleValue()
	var Double LowerSetpoint = UpperSetpoint - Hysterese

	vIt_HZ_WZ_SollTag_lower.sendCommand( LowerSetpoint ) 

    logInfo( "WZ_Thermostat_LowerSetpointDay", "Rule WZ_Thermostat_LowerSetpointDay: " + "TagLower= " + LowerSetpoint )

// Calculate lower NIGHT setpoint if hysterese or upper setpoint have changed
rule "WZ_Thermostat_LowerSetpointNight"
    Item vIt_HZ_Hysterese changed or
	Item vIt_HZ_WZ_SollNacht_upper changed
	var Double UpperSetpoint = ( vIt_HZ_WZ_SollNacht_upper.state as Number ).doubleValue()
	var Double Hysterese     = ( vIt_HZ_Hysterese.state as Number ).doubleValue()
	var Double LowerSetpoint = UpperSetpoint - Hysterese
	vIt_HZ_WZ_SollNacht_lower.sendCommand( LowerSetpoint )
	logInfo( "WZ_Thermostat_LowerSetpointNight", "Rule WZ_Thermostat_LowerSetpointNight: " + " NachtLower= " + LowerSetpoint )

// Send new values to the Shelly via HTTP if one of the values has been switched or
// The switch DAY/NIGHT has changed
rule "WZ_Thermostat_SetShelly"
    Item vIt_HZ_WZ_SollTag_upper changed or
	Item vIt_HZ_WZ_SollTag_lower changed or
	Item vIt_HZ_WZ_SollNacht_upper changed or
	Item vIt_HZ_WZ_SollNacht_lower changed or
	Item vIt_HZ_DayNight changed
	var Double UpperSetpoint 
	var Double LowerSetpoint
		// Switch ON=Day setpoints
		UpperSetpoint = ( vIt_HZ_WZ_SollTag_upper.state as Number ).doubleValue()
		LowerSetpoint = ( vIt_HZ_WZ_SollTag_lower.state as Number ).doubleValue()
	} else {
		// Switch OFF=Night setpoints
		UpperSetpoint = ( vIt_HZ_WZ_SollNacht_upper.state as Number ).doubleValue()
		LowerSetpoint = ( vIt_HZ_WZ_SollNacht_lower.state as Number ).doubleValue()
	// Send http to Shelly:
	var String httpString = ""
	httpString = "" + String::format("%.2f",UpperSetpoint)
	logInfo( "WZ_Thermostat_SetShelly", "Rule WZ_Thermostat_SetShelly: " + httpString )
	httpString = "" + String::format("%.2f",LowerSetpoint)
    logInfo( "WZ_Thermostat_SetShelly", "Rule WZ_Thermostat_SetShelly: " + httpString )

//----------------------------------------------------------------------------------EVERY Thermostat
// Switch Shelly ON/OFF for SUMMER/WINTER switch
rule "WZ_Thermostat_HeatingOnOff"
	Item vIt_HZ_OnOff changed
	var String httpString = ""
		// Switch ON=heating running
		httpString = ""
		logInfo( "WZ_Thermostat_HeatingOnOff", "Heating ON: " + httpString )
		httpString = ""
		logInfo( "WZ_Thermostat_HeatingOnOff", "Heating ON: " + httpString )
	} else {
		// Switch OFF=heating switched off
		httpString = ""
		logInfo( "WZ_Thermostat_HeatingOnOff", "Heating ON: " + httpString )
		httpString = ""
		It_WZ_RelaisThermostat.sendCommand( OFF )
		logInfo( "WZ_Thermostat_HeatingOnOff", "Heating ON: " + httpString )


OpenHAB Map

Creates a file transform/ with the following content:



OpenHAB Sitemap

Creates a file sitemaps/heating.sitemap with the following content:

sitemap heating label="Fußbodenheizung" {
    Frame label="Globale Einstellung Fußbodenheizung" {
		Setpoint item=vIt_HZ_Hysterese minValue=0.1 maxValue=4.0 step=0.1
		Switch item=vIt_HZ_OnOff
		Switch item=vIt_HZ_DayNight
		Default item=gHeatingValves
		Default item=It_FL_Pump
	Frame label="Heizung Wohnzimmer" {
		Text item=It_WZ_ActTemperature
		Switch item=It_WZ_RelaisThermostat
		Setpoint item=vIt_HZ_WZ_SollTag_upper minValue=4.5 maxValue=35 step=0.5
		Text item=vIt_HZ_WZ_SollTag_lower
		Setpoint item=vIt_HZ_WZ_SollNacht_upper minValue=4.5 maxValue=35 step=0.5
		Text item=vIt_HZ_WZ_SollNacht_lower

The result in the browser:

The icons used for valves and pumps are not included in OpenHAB, you have to use your own.


Conclusion and outlook

The project with its five thermostats kept me busy for a few days, especially the design and 3D printing. After installation and configuration, it works flawlessly. Now only winter has to come - that is the hard practical test. I'm currently trying to control the temperatures via Alexa, but that's another topic.

What could be improved?

  • Use separate times for each individual thermostat
    Absence of the house residents for lowering also during the day
    Use Alexa for the settings