Internet Enabled Heating Controller with Blynk UI

433MHz Radio Controlled Heating Controller with Blynk UI

This is a project to control your boiler using a 433MHz radio module. I have used a simple and cheap OOK radio module from eBay. They have a single pin for Data and turning this pin high or low will enable the radio module to send a high or low signal. I am also using an ESP8266 (Wemos D1 Mini) to enable connectivity to the internet and the Blynk app so I can control my heating using my phone or tablet, even when out of the house.

I have a temperature sensor attached to enable the on/off signals to be sent depending on what target temperature I have chosen.

Connect the I2C pins on the sensor to the I2C pins on your Arduino. This particular sensor runs at 3.3v. For the 433Mhz radio module, connect the VCC and GND pins, connect the Data pin to D3 and a 164mm wire to the antenna pin.

The first job was to decipher the on/off signals coming from my wireless thermostat. I guessed the thermostat was using 433Mhz as most of them do so I used an SDR dongle to listen in on the AM band at that frequency and then turned the temperature settings up and down on the thermostat until I could see the signals being transmitted.

If you don’t have an SDR dongle then you could use a cheap 433MHz receiver module and a sound card as outlined in THIS link.

So, once I had a clean signal I recorded the audio and then imported the file into Audacity. By zooming in on the pulses I could see the on/off signals were being sent 3 times. The encoding was obviously Manchester encoding and the pulses were 500 microseconds long with a 1000 millisecond gap in between the 3 signals.

I then experimented with the Arduino and the 433MHz radio module (a lot of trial and error) until I was able to turn the boiler on and off. I have an image of my replicated signal below and it is actually much cleaner than the original.

Now I had control over the boiler I decided I was going to use Blynk to create a phone app for controlling the heating remotely. Go to the Blynk website to download and install the Blynk Library for your Arduino.

Here is an image of the interface I have created in the Blynk app for controlling the heating.

I have a large Enable/Disable button for turning the system on and off. Once the system is enabled the heating will be turned on and off depending on what target temperature I have set and what the current temperature is. If the current temperature is below the target temperature then the boiler is turned on and the house will start to heat up. Once the house temperature gets over the target temperature it will turn off.

There are displays to show the current temperature and humidity a graph to show historical data, a slider to adjust the target temperature and finally the target temp itself.

This UI is created with 7 widgets in the Blynk app. The top widget is a large button. This is set to be a switch and on Virtual Pin V0.

Next is a large labelled value widget to show the system status. This is set to Virtual Pin V1. The text is centre justified.

Next is a small labelled value box to show the temperature. This is set to Virtual Pin V4. The text is centre justified and formatted to /pin.#/°C to show the temperature with 1 digit after the decimal point and °C after that.

Next is a small labelled value box to show the humidity. This is set to Virtual Pin V5. The text is centre justified and the formatting is set to /pin.#/% to show one digit after the decimal point and then the % sign.

Next is a history graph. This is set to show Virtual Pins V4 and V5 (Temp and Humidity). The graph shows data for the last 1 hour, 6 hours, 1 day, 1 week, 1 month and 3 months.

Next is a small slider control to adjust the temperature. I set the minimum and maximum temperatures to 10°C and 30°C.

Finally, we have another small labeled value box to show the selected temperature. Again, centre justified and formatted to /pin.#/°C

Next we have the code for the ESP8266.

These two defines are just to make things clearer in the code.

#define ON 1
#define OFF 0

The libraries are imported. I am using an ESP8266 (A Wemos D1 Mini to be specific) board with an HTU21D I2C temperature and humidity sensor. I will also be saving data in the EEPROM so I import that library too.

#include <Wire.h>
#include "SparkFunHTU21D.h"
#include <ESP8266WiFi.h>
#include <BlynkSimpleEsp8266.h>
#include <EEPROM.h>

The datapin for the radio transmitter is pin D3.

#define dataPin D3

Then I setup a HTU21D object to communicate with the sensor.

HTU21D myHumidity;

Enter your auth code (from your Blynk account) along with the SSID and Password of your WiFi.

char auth[] = "xxx";
char ssid[] = "xxx";
char pass[] = "xxx";

The on/off signals have to be re-transmitted every 5 minutes to stop the boiler shutting down (failsafe mode) so i will be storing the milliseconds value for times when I have last transmitted the code (Every 5 mins), the last time we updated the UI (Every second) and the last time we checked if the current temperature has reached the target temperature (Every 60 seconds).

unsigned long lastTransmit;
unsigned long lastUpdate;
unsigned long lastTempCheck;

Next we declare and initialise a few variables.

float temp;
int enabledState;
int requiredTemp;

temp is the current temperature, enabledState is set to 1 or 0 depending on the state of the button or the last known state of the system, requiredTemp will store the target temperature.

Now onto the setup() function.

void setup()
{
  EEPROM.begin(3);
  myHumidity.begin();
  //EEPROM.write(2, 0);
  //EEPROM.commit();
  pinMode(dataPin, OUTPUT);
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, LOW);
  Serial.begin(115200);
  Blynk.begin(auth, ssid, pass);
  Serial.println("Initialising....");

  lastTransmit = millis();
  lastUpdate = millis();
  lastTempCheck = millis();
  enabledState = EEPROM.read(1);
  Serial.println("EEPROM: System is  " + String(enabledState ? "ENABLED" : "DISABLED"));
  requiredTemp = EEPROM.read(2);
  Serial.println("EEPROM: Target Temperature is " + String(requiredTemp) + "°C");
}

We will be storing the last known state of the system (Enabled or Disabled) as well as the target temperature in EEPROM in case the system gets reset. We set aside 3 bytes to be used.

  EEPROM.begin(3);

Then we start the HTU21D sensor.

  myHumidity.begin();

If you are running the code for the very first time the system state will not be stored in the EEPROM so remove the comment bars from the following 2 lines to set the stored state to 0 (Disabled). Don’t forget to re-comment these 2 lines and then re-upload your code a second time after.

  //EEPROM.write(2, 0);
  //EEPROM.commit();

The pins for the radio transmitter data line and the internal LED are both set to output. The LED is turned off.

  pinMode(dataPin, OUTPUT);
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, LOW);

Serial comms are used for debugging purposes so we initialise the serial line and then initialise the Blynk library with the Blynk auth code, WiFi SSID and Password.

  Serial.begin(115200);
  Blynk.begin(auth, ssid, pass);
  Serial.println("Initialising....");

The current millis() values are then stored for later use.

  lastTransmit = millis();
  lastUpdate = millis();
  lastTempCheck = millis();

The enabledState is retrieved from EEPROM as well as the target temperature. These are printed to the Serial Monitor window.

  enabledState = EEPROM.read(1);
  Serial.println("EEPROM: System is  " + String(enabledState ? "ENABLED" : "DISABLED"));
  requiredTemp = EEPROM.read(2);
  Serial.println("EEPROM: Target Temperature is " + String(requiredTemp) + "°C");

Once the Blynk library connects to the Blynk cloud servers there are a few housekeeping commands to carry out. The slider and target temperature boxes are set to the stored settings first.

BLYNK_CONNECTED() {
  Serial.println("CONNECTED");  Blynk.run();
  Blynk.virtualWrite(V2, requiredTemp);  Blynk.run();
  Blynk.virtualWrite(V3, requiredTemp);  Blynk.run();

Then the button is set to Enabled or Disabled. Its colour is set to red or green appropriately.

  Blynk.virtualWrite(V0, enabledState);
  Blynk.setProperty(V0, "color", (enabledState ? "#00FF00" : "#FF0000"));  Blynk.run()

Then the status box is changed to “Ready” and green.

Blynk.virtualWrite(V1, "Ready"); Blynk.run();
Blynk.setProperty(V1, "color", "#00FF00"); Blynk.run();

the current temperature and humidity readings are retrieved from the sensor using the getTemps() function.

  getTemps();

The Enabled/Disabled button is set to Virtual Pin V0 in the Blynk app so any time it is pressed the BLYNK_WRITE(V0) function is activated. The button will toggle the system state between ENABLED and DISABLED and then store that in EEPROM in case we lose power or reset.

BLYNK_WRITE(V0)
{
  enabledState = !enabledState;
  EEPROM.write(1, enabledState);
  EEPROM.commit();

We print the system status to the Serial Monitor window and then change the buttons state and colour.

  Serial.println("System is " + String(enabledState ? "ENABLED" : "DISABLED"));
  Blynk.virtualWrite(V0, enabledState);
  Blynk.setProperty(V0, "color", enabledState ? "#00FF00" : "#FF0000");  Blynk.run();

Every time the slider is moved, which is on Virtual Pin V2, the BLYNK_WRITE(V2)function is triggered. We start by retrieving the temperature, as an Integer, from the slider and storing it in the requiredTemp variable.

BLYNK_WRITE(V2)
{
  requiredTemp = param.asInt();  Blynk.run();

Then the target temperature box on Virtual Pin V3 is updated with the slider value and the value is stored in EEPROM.

  Blynk.virtualWrite(V3, requiredTemp);  Blynk.run();
  EEPROM.write(2, requiredTemp);
  EEPROM.commit();
  Serial.println("Target Temperature is " + String(requiredTemp));

The next function is one I have made for sending out the appropriate pulses for the ON or OFF signal to my boiler. You can use the exact same function to control yours but you will need to adjust the binary codes and timings to suit your system. This code will only work with my boiler but you can easily find out what yours is with a 433MHz radio receiver or an SDR dongle (recommended).

void heatingControl(boolean onOff)
{
  Serial.print(millis());
  Serial.println(" Transmitting " + String(onOff ? "ON signal" : "OFF signal"));
  int on[]   = {0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1};
  int off[]  = {0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1};

  for (int repeat = 1; repeat <= 3; repeat++)
  {
    for (int i = 0; i < sizeof( onOff ? on : off  ) / sizeof( int ); i++) {
      switch (onOff ? on[i] : off[i])
      {
        case 0:
          digitalWrite(dataPin, LOW);
          delayMicroseconds(500);
          digitalWrite(dataPin, HIGH);
          delayMicroseconds(500);
          break;
        case 1:
          digitalWrite(dataPin, HIGH);
          delayMicroseconds(500);
          digitalWrite(dataPin, LOW);
          delayMicroseconds(500);
          break;
        case 2:
          digitalWrite(dataPin, LOW);
          delayMicroseconds(500);
          break;
      }
    }
    int waitTime = millis();
    if ((millis() - waitTime) < 1000) {
      Blynk.run();
    }
  }
  Blynk.virtualWrite(V1, String(onOff ? "Heating ON" : "Heating OFF"));  Blynk.run();
  Blynk.setProperty(V1, "color", String(onOff ? "#00FF00" : "#FF0000"));  Blynk.run();
  lastTransmit = millis();
}

The function expects you to pass a boolean value to it when called. A true value will turn the heating on and a false value will turn it off.

void heatingControl(boolean onOff)
{
  Serial.print(millis());
  Serial.println(" Transmitting " + String(onOff ? "ON signal" : "OFF signal"));

The binary codes for the ON and OFF signals are stored in integer arrays. Pulses for the 1 bit is made up of a 500 microsecond HIGH followed by a 500 microsecond LOW. The 0 bit is a 500 microsecond LOW followed by a 500 microsecond HIGH. The signals are Manchester IEE 802.3 as below.

Weirdly, although every bit in the ON/OFF signals was correctly encoded, the 9th bit had an extra 500 microsecond LOW after it for some reason. I therefore had to store this as a 2 in the array. I have no idea why it is encoded that way but the signals won’t work without it.

  int on[]   = {0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1};
  int off[]  = {0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1};

The signals are repeated 3 times with a 1000 millisecond delay in-between. They are sent 3 times to ensure the receiver has a good chance to get an ON/OFF signal if interference is present. We then simply step through the appropriate array and send out the correctly timed pulses for the chosen signal.

  for (int repeat = 1; repeat <= 3; repeat++)
  {
    for (int i = 0; i < sizeof( onOff ? on : off  ) / sizeof( int ); i++) {
      switch (onOff ? on[i] : off[i])
      {
        case 0:
          digitalWrite(dataPin, LOW);
          delayMicroseconds(500);
          digitalWrite(dataPin, HIGH);
          delayMicroseconds(500);
          break;
        case 1:
          digitalWrite(dataPin, HIGH);
          delayMicroseconds(500);
          digitalWrite(dataPin, LOW);
          delayMicroseconds(500);
          break;
        case 2:
          digitalWrite(dataPin, LOW);
          delayMicroseconds(500);
          break;
      }
    }

The Blynk.run() command must be ran continuously for the Blynk library to work. The system disconnects and reconnects if a few hundred milliseconds have passed without a handshake. I have therefore liberally pastedBlynk.run() commands throughout the code where there may be delays and the 1000 millisecond delay after the 3 pulses contains a continuous repeated call to the Blynk.run() function to prevent this.

int waitTime = millis();
if ((millis() - waitTime) < 1000) {
Blynk.run();
}

After sending an ON or OFF signal the status box (Virtual Pin V1) is changed to say “Heating ON” or “Heating OFF” and the colour is changed to either red or green.

  Blynk.virtualWrite(V1, String(onOff ? "Heating ON" : "Heating OFF"));  Blynk.run();
  Blynk.setProperty(V1, "color", String(onOff ? "#00FF00" : "#FF0000"));  Blynk.run();

Finally, we want to ensure we do not transmit the ON/OFF signals too often so the current value of millis() is stored so we can ensure they are only sent every 60 seconds and no less (to prevent damage to the boiler).

  lastTransmit = millis();

Next is the short getTemps() function which gets the temperature and humidity readings from the HTU21D sensor and stored them. These values are then written to the label boxes an Virtual Pins V4 and V5 on the Blynk app. Thus function will be called once every 1000 milliseconds.

void getTemps()
{
  float humd = myHumidity.readHumidity(); Blynk.run();
  temp = myHumidity.readTemperature(); Blynk.run();

  Blynk.virtualWrite(V4, temp); Blynk.run();
  Blynk.virtualWrite(V5, humd); Blynk.run();

}

Our final function before the main loop is the critical one. This function is called once every 60 seconds. It checks the current temperature to see if it is higher or lower then the required temperature, if so, and the system is ENABLED then the ON or OFF signal is sent to the boiler. This will ensure that the desired temperature is maintained in the house.

void checkTemp()
{
  if ((temp > requiredTemp) && (enabledState == ON)) heatingControl(OFF);  
  if ((temp < requiredTemp) && (enabledState == ON)  heatingControl(ON);
  lastTempCheck = millis();
}

We have now reached the main loop function. The Blynk.run() command is repeatedly called to enable the Blynk app to work.

void loop()
{
  Blynk.run();

Then we check if 60 seconds (60000 milliseconds) have passed since we last transmitted a signal. If it has, the ON or OFF signal is re-transmitted. This is to ensure the boiler gets a signal every 5 minutes to prevent it going into fail-safe mode. The ON signal is only transmitted if the system is ENABLED.

 if ((millis() -   lastTransmit) > 60000)
  {
    if ((temp < requiredTemp) && (enabledState == ON)) heatingControl(ON);
    else heatingControl(OFF);
  }

Every 1 second we get the sensor readings, update the display, flash the built in LED (to show the system is running) and store the current value of millis().

 if ((millis() -   lastUpdate) > 1000)
  {
    getTemps();
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
    lastUpdate = millis();
  }

Finally, every 60 seconds we will compare the current temperature with the target temperature and turn the heating ON or OFF if necessary.

  if ((millis() -   lastTempCheck) > 60000) checkTemp();

There you have it, a true IoT project to control my homes heating using a phone or tablet. I can check the temperatures remotely and turn the heating on or off from outside the house. This is a system now similar to Nest or Hive but a fraction of the cost. Parts have cost me less than $10 in total.

This project is only in its infancy and I will be adding further control and display panels within the house as it matures. Follow me for updates.

All the code can be found on my GitHub at :-

https://github.com/thearduinoguy/433MHz-Heating-Control

Mike McRoberts

@TheArduinoGuy

Leave a Reply

Your email address will not be published. Required fields are marked *