Heating Control Basic

Aus FHEMWiki

Die Deutsche Version ist hier verfügbar: Heizungskontrolle Einfach

The aim of this project is to control a central heating system using FHEM. The main contribution for the solution described below is from a forum post by borsti. The heating logic is to switch on heating if one or more radiators are open and switch off heating when all of them are closed.


The system is composed of the following components:

  • A central heater with a thermostat input.
  • A server with FHEM installed.
  • CUL USB stick connected to the server.
  • FS20 AS1 radio-controlled switch connected to the thermostat input of the heater.
  • 3x FHT80b thermostats for 3 rooms.
  • As many FHT8V as there are radiators in the 3 rooms above.

FHEM Configuration

Once the hardware set up and FHEM running as described in the manuals, we can start configuring the heating system. The rest of the article describes the contents of the fhem.cfg file step by step. The file can be easily edited from withing FHEM.

define CUL1 CUL /dev/CUL@9600 5643
define room1 FHT 5030
define room2 FHT 235d
define room3 FHT 2d28
define heater FS20 5643 53

The configuration starts with the definition of the hardware components. The CUL is defined first, then the different FHT80b thermostats each with their house codes (which will be different in your installation of course). We then set the retrycount which defines how many times FHEM will try to send commands to the FHT. In the last line we define the FS20 AS1 that is hooked up to the central heater. Note that we don't have to define the actual motors (actuators) mounted on the radiator. These are controlled by the FHT thermostats and FHEM does not need to interfere.

define n_heater notify n_heater {\

This line defines a new notify called n_heater which will hold the code. This basically represents a macro which we can later run whenever we want, using a single command. From now on we are using normal Perl commands. Since the code is embedded in an FHEM command, some special characters need to be repeated (i.e. ;; instead of ; and adding a \ to the end of all lines). Check the reference manual for more information about using Perl inside FHEM scripts.

my $need_heat=0;;\
  my $idle_actuators=0;;\

Here we are defining two variables which we will use to track whether one of the FHT wants to heat and how many FHT agree not to heat.

my $burner_state=$fs20_c2b{ReadingsVal("heater","state","off")};;\

We define another variable representing the current state of the heater. ReadingsVal is a function with three arguments: Device name (heater), reading (state) and default value which will be used if no other value is found (off).

The function will return either on or off. Because programming with text strings is cumbersome, we want to use codes instead. The array fs20_c2b is filled with text string of FS20 devices and their corresponding code. By using on or off as keys, we can look up the codes that corresponds to the text.

This means that in the end, $burner_state will be either 00 instead ofoff and 11 instead of on.

my @fhts=devspec2array("TYPE=FHT");;\

Here we create an array of all the device parameters. FHEM has a built-in function called devspec2array which makes this easy. The only argument we pass is TYPE=FHT which restricts the returned array to only contain FHT devices.

foreach(@fhts) {\
  my $actuator=ReadingsVal($_, "actuator", "101%");;\
  $actuator=(substr($actuator, 0, (length($actuator)-1)));;\
  if ($actuator > 50) {\
  if ($actuator < 20) {\

This is the loop where we check all FHT for their actuator settings. The for-construct loops through the array we created earlier (@fhts). For each of them we read the actuator setting using ReadingsVal as described above. The first parameter $_ is a placeholder for the variable that is looped, in our case the FHT device we're currently checking. Since the acutator value is already numeric, we don't need the $fs20_c2b but can directly continue with the check. We do have to get rid of the percentage sign though e.g. transform 10% to 10. For this we use the substr function to extract a string starting from position 0 to the second but last character (length - 1) of the orginial string. Effectively this chops off the last character, leaving only the number.

Next, we check whether the actuator value is bigger than 50 (i.e. the radiators are more than 50% open). If this is the case, the variable $need_heat is set to 1.

If the actuator is below 20, we increment the $idle_actuators variable. Effectively this means that at the end of the for loop, $need_heat will be 1 if any of the FHT needs heat, and $idle_actuators will contain the number of FHT that don't need heat anymore.

if ($need_heat != 0) {\
  Log(3,"Heating needed. Previous burner status: " . $burner_state);;\
  fhem("set heater on") if ($burner_state == 00)\

Now we're ready for action. If we decided above that heating is needed, we write a short message to the log file indicating the previous state of the heater. The first argument of the Log function is the level. Important messages have level 1, less important ones have higher levels. The second argument is the text to print, in our case a static sentence followed by the heater state that we saved before. The actual order to FHEM is sent using the fhem function. This allows to use normal FHEM commands such as set, get, trigger etc. Here, we simply set the FS20 device heater to on. This is only done if the heater was previously off.

else {\
  if ($idle_actuators == @fhts) {\
   Log(3,"No (more) heating needed. Previous burner status: " . $burner_state);;\
   fhem("set heater off") if ($burner_state == 11)\
   else {\
   Log(3,"Heating request: " . $idle_actuators . " of " . @fhts . " actuators are idle.")\

This last block is executed if there was no need to switch on the heater. At this moment we have to check whether or not we want to switch it off. For this we use the number of FHT that indicated not needing any heat and compare it to the total number of FHT devices (@fhts is the array of FHT devices, using it simply like this will return the size of the array, which is what we need). If the numbers are equal this means that all FHT agree to switch off the heater. We print some log message again and then switch off the FS20 device, but again only if it was previously switched on.

The only case remaining now is when the heater should stay unchanged. We capture this with a last else statement and print a log message indicating the current vote for switching off the heater.

This concludes the notify and we can now use it either by typing trigger n_heater into the command box or as part of a scheduled command.

define a_heater at +*00:10:00 trigger n_heater

To schedule the command every 10 minutes we define an at structure called a_heater. The + indicates that is should happen in 10 minutes (as opposed to 10 past midnight) and the * means that it is to be repeated. Effectively this will result in our macro defined above being executed every 10 minutes. This also defines the minimal time the heater will be turned on. While more frequent executions would improve the reactivity it is probably not a good idea to switch the heater on and off too frequently.


Here's an example of the script turning the heater on and off. You can nicely see the oscillations of the temperature in the three rooms.

Fhem heating example.png