DEV Community

Cover image for A Smart Heating System
Graham Trott
Graham Trott

Posted on • Updated on

A Smart Heating System

Photo by Aleksandar Cvetanovic on Unsplash


The cost of domestic heating is currently going through the roof (so to speak). Here in the UK in winter/spring of 2022 we're facing a 50% or more increase in the cost of town gas and electricity, so energy-saving measures are a hot topic (ha!). This piece is about a private project: a DIY hardware/software system for home heating control.

I'd been thinking for a while about building a system to control radiators or other heating types, with electric actuators to replace the thermostatic ones I had at present. Something using Arduinos or Pi Picos, perhaps. Then, a couple of months ago, by lucky chance I discovered the Bulgarian company Shelly™, who sell a range of smart, low-cost relays and sensors, each one having a wifi interface. Since my main interest was in building software, not hardware, this was too good to pass up, so I bought a small kit of parts and set to work.

The aims of the project

The primary aim was to build an energy-saving system that would regulate the temperature in each room independently and turn off the heating in rooms that are not being used, either with timing rules, movement detectors or just manually. Everything would be controlled from a smartphone; not just from inside the house but from anywhere with an Internet connection. And the project would respect Occam's Razor, avoiding complexity wherever possible.

Some requirements and constraints soon became apparent:

  1. The user interface is a browser-based mobile webapp. I don't have the time, experience or inclination to program native apps for Android and iPhone.
  2. The system is set up as a DIY project requiring little more expertise than the ability to wire up a plug. Full documentation is (or will be) provided as part of the UI. It should offer commercial opportunities for contractors to deploy systems to their own customers.
  3. For keen coders, all the software is Open Source except for the Shelly items, which for the more dedicated can be replaced by home-builds if so desired.
  4. The software should be accessible to the widest possible range of interested parties, which means no complex build or deploy toolchains and the absolute minimum of dependencies. This should avoid being held hostage to the future, but also make the software accessible to amateur and part-time programmers as well as full-time professionals.

System description

So with the above aims in mind, this is how I structured the system:

The control hardware is based on Shelly 1 relays (see their on-line store) that control standard 30mm electric radiator valve actuators. They can also be used with direct resistance heating as they are able to switch up to 16A. A Shelly H&T (Humidity and Temperature) unit is provided in each room to report the temperature at regular intervals.

Here's a photo of a "room kit" - all the parts needed to equip a single room with a single radiator, using a standard UK pattress box. The relay is the small blue item inside the box, which also has a neon indicator to show when power is applied to the actuator (right of photo). It plugs into a standard wall outlet and communicates by wifi with the system controller. On the upper left is a Shelly H&T thermometer unit.
Image description

The relays and sensors are controlled by a small computer such as a Raspberry Pi. It doesn't have to be the latest Model 4; I've successfully used a Model 1B, which although very slow is able to do the job and is available at a low cost. It runs a local HTTP server into which the thermometer modules post messages, and a control program that runs through a set of rules for the system to decide when and where heating is needed and turn relays on and off as appropriate. All the Pi software is written in Python (indirectly, as I'll explain later).

The controller reports changes of temperature to an external web server using REST requests. I use standard public shared hosting and that pretty well mandates PHP, but (for those who hate the language) the REST server is only a few hundred lines of PHP and once built can mostly be forgotten.

The user interface is a browser-based webapp written in JavaScript (indirectly - see later) and built as a single-page application (SPA). It gives the user a view of the current state of the system and the ability to add and remove rooms, change the target temperature and set up timing schedules of any complexity. It includes a full help stack (or will eventually) and a demo module running behind the scenes that gives a full simulation of a system without the need to buy any hardware.

How it works

The user's interaction with the webapp creates a system map - a JSON structure with details of the rooms, the sensors and the relays. When any changes occur the map is posted to the server with a flag set to request confirmation. The system controller picks up the map every few seconds and confirms, passing back the current states of the sensors. These are then retrieved by the webapp and displayed to the user.

All the software is held in a GitHub repository. Anyone wishing to contribute to the project will be welcomed, whether it's to work on the software, the user interface or the documentation. The project is currently in a fairly early state of development, with a growing list of things to fix and things to add.

Here are some notes on the component parts of the software:

The system controller

This is Python code running on a headless Raspberry Pi. Any other computer would do just as well but few can match it on price or compactness. The local HTTP server uses Bottle, which can be regarded as a leaner version of Flask, and it has very little to do. Each thermometer unit regularly reports its temperature to the local HTTP server, which writes it to a file whose name is the IP address of the thermometer.

The control program is run as a cron task, once every minute. It downloads the system map from the web server, collects the current temperature values from the appropriate local thermometer files and posts them back to the server. It also uses this information to decide if heating should be turned on or off in any room, and sends commands to the relays, each of which also has a local IP address and is listening on port 80.

As hinted earlier, the controller code is not written directly in Python but in an English-like scripting language that is itself written in Python. The compiler for this language runs on the Pi itself and is supplied with the project as Python source files. High-level source scripts compile on the fly and the compiler output is passed directly to a built-in runtime engine. Here's a snippet to give the general flavour; here, all capitalized words are script variables:

open File Path cat `/sensors/` cat Sensor cat `.txt` for reading
read SensorValues from File
close File
put json SensorValues into SensorValues
put float property `temperature` of SensorValues into Temperature
put property `relays` of Room into Relays
put property `events` of Room into Events
take 1 from the length of Events giving L

! Deal with mode requests
put property `mode` of Room into Mode
if Mode is `off` go to TurnOff

if Mode is `on`
    put float property `target` of Room into Target
    if Temperature is less than Target go to TurnOn
    go to TurnOff
Enter fullscreen mode Exit fullscreen mode

The reason for doing this is for accessibility and maintainability. Changes are simply a matter of editing a text file and writing it back to the Pi. Using English-like script means the code - to a considerable extent - resembles the way you would describe the operation of the system in words. Software professionals commonly assume that everyone shares their ability to read code and understand complex frameworks and build tools, but that's far from the case. The majority - including some quite competent programmers - find it very hard to read conventional code. Our brains are wired for words, not mathematical expressions.

There's a performance price to pay, of course. The control program is little more than 200 lines of script but takes nearly half a second to compile on a Pi Model 1B, though later models slash this down to well under 20ms. However, even on the old model this is quite acceptable; a couple of seconds to get the program up and running is of little importance in a one minute cron cycle.

The user interface

The UI home page is intended for mobile use and currently looks like this:
Image description
As with the system controller, an unusual approach is used here too, in the form of another high-level script compiler and runtime, this one written in JavaScript. It's syntactically very similar to the Python version and designed to facilitate the creation of complex interactive user interfaces without the need to learn any complex programming techniques. In terms of accessability and long-term maintainability there is nothing to beat English. For a native English speaker the learning curve is very short as scripts tend to follow the natural way of describing what's happening. Which is that a DOM is created and the user interacts with it. The compiler/runtime is loaded directly from the repository; all relevant URLs can be found at the end of this article.

Some might argue that all this represents an unnecessary additional layer of complexity. I would counter that most large coding projects today use a complex framework and require an additional build/deploy stage with its own learning requirements. The compiler used here - EasyCoder - is far simpler than products such as React or Angular and is extensively documented in its own website and repository. The ability to run directly from a high-level script greatly speeds development and maintenance, and runtime performance is scarcely an issue; on a modern smartphone Chrome will compile a thousand-line script in well under 50ms.

Additionally, a high-level scripting language makes the underlying JavaScript invisible, except perhaps when tracking down the more obscure bugs. This is great for people who don't live and breathe JavaScript but who would like to know what's happening.

DOM construction

This project also uses an unconventional approach to DOM construction; one that I described in a recent article. Webson is a JSON format; a language for representing DOM trees. In this project, no HTML is present. Instead, all of the screen elements are defined in a series of Webson files, which combine layout and style in a format not dissimilar to CSS. Webson permits - but neither encourages nor discourages - the use of external CSS classes but none are used so far in this project. With screens of this kind the layout is quite closely associated with its appearance so I personally prefer to keep the styles in with the layout elements they affect.

Here's the code for the icon and text at the left-hand end of each room in the UI photo above when the operating mode is set to "on". As you can see, much of it is CSS:

    "#doc": "The icon and text for 'on' mode",
    "#element": "button",
    "@id" : "room-/ROOM/-mode-icon",
    "display": "flex",
    "flex-direction": "row",
    "width": "10em",
    "#": ["$Button", "$Padding", "$Text"],

    "$Button": {
        "#element": "div",
        "margin-right": "0.5em",
        "pointer-events": "none",
        "#": "$Icon",

        "$Icon": {
            "#element": "img",
            "@src": "resources/icon/temperature.png",
            "height": "2em",
            "padding": "0.5em"

    "$Padding": {
        "#element": "div",
        "width": "0.2em",
        "pointer-events": "none"

    "$Text": {
        "#element": "div",
        "display": "flex",
        "flex-direction": "column",
        "flex": 1,
        "height": "100%",
        "text-align": "left",
        "pointer-events": "none",
        "#": "$Inner",

        "$Inner": {
            "#element": "div",
            "display": "inline-block",
            "flex": 1,
            "text-align": "center",
            "padding": "0.2em",
            "#": ["$Content1", "$Content2"],

            "$Content1": {
                "#element": "div",
                "#content": "On",
                "font-size": "1.4em",
                "font-weight": "bold"

            "$Content2": {
                "#element": "div",
                "@id" : "room-/ROOM/-target",
                "font-size": "0.9em",
                "font-weight": "bold"
Enter fullscreen mode Exit fullscreen mode

Using Webson in an EasyCoder script is simple. It's generally 2 lines of code:

rest get MainMenuScript from `/resources/webson/mainmenu.json`
render MainMenuScript in Container
Enter fullscreen mode Exit fullscreen mode

where Container is a script variable that acts for the specific element - usually a div - that's already in the DOM and is due to receive the new content.

Demo mode

The hardware needed for a typical installation costs a couple of hundred pounds/dollars/euros, so I added a "demo mode" for system evaluation. This runs a background script that mimics the effects of heating being turned on and off, and runs in accelerated real time. Users can set up entire systems and watch them perform according to the built-in rules.


Every software product needs good documentation; for system installers, users and maintenance people. There's no better place to put it than in the user interface, where it can't get lost. To make the writing job as simple as possible I chose Markdown, enhanced to support some extra tags and to fit in with the single-page design. This is a modified version of another past project from the same overall repository and is delivered as a single high-level script.

Every screen and every popup dialog has a Help button that leads to a page of help in a cross-referenced stack.


The mobile webapp is at Its documentation can be viewed by navigating from the screen that appears the first time the site is visited, or from the Help button that's present on every screen.

The project repository is at

EasyCoder is at with its repository at

The Webson repository is at

The Storyteller (enhanced Markdown) repository is at This project uses a modified version whose source script is included in the main repository.

Discussion (0)