Introduction: Easy IOT - Low Power Wireless Temperature Sensors

This tutorial is part of a series of tutorials, building a network of devices that can be controlled via a radio link from a central hub device. For the tutorial series overview please follow this link:

In this tutorial we will build an ultra-low power temperature node to work with our ESP32 Hub system.

We will be using two of the most popular temperature sensors, the DS18B20 and the DHT22 Temperature and Humidity sensor.

The node is based on a modified Arduino Pro Mini 3.3V (Clone) and is powered by a single rechargeable 18650 Lithium Ion Battery. The node works by periodically transmitting the temperature back to the hub, then going into a deep sleep for a period of time. Depending on how regularly the sensor transmits, the battery has the potential of lasting over a year on a single charge.

As the Arduino Pro Mini does not have a USB connector on it, we will also need an FTDI Serial Programmer.


  1. FTDI USB Serial Programmer
  2. 18650 Battery and Holder (and a charger for the battery)
  3. Assembled HC-12 Module
  4. DS18B20 and/or DHT22 Temperature sensor
  5. 4.7K Resistor (For the DS18B20)
  6. Female Dupont Header socket

Step 1: Pro Mini Modifications

An unmodified Pro Mini 3.3V board draws around 5mA and the HC-12 module up to 16mA in idle (not transmitting). This is a significant power consumption and would drain the battery pretty quickly. In order to maximise battery life, we can do several things:

· Remove the voltage regulator on the Arduino Pro Mini

· Remove the power LED on the Arduino Pro Mini

· Put the Arduino into deep sleep mode when not being used

· Put the HC-12 module into sleep mode when not being used.

After this, the power consumption while sleeping should be reduced to <30uA

First remove the regulator and LED shown in the image, some of the Pro Mini Boards are slightly different layouts, but all should have these two components.

For more information about these power mods go to:

Next, solder the female Dupont header sockets onto the 6 pins at the end of the board, marked with BLK and GRN at each end. This is for the FTDI Programmer to plug into. You can also carefully bend these pins downward so that they lie flat to save space.

Step 2: Assembling the Node - HC-12 Module

As in the previous tutorials, you will need an assembled HC-12 module.

See step 1 of this tutorial if you do not already have an assembled HC-12 module:

You will also need the modified Arduino Pro Mini, the temperature sensor, an 18650 battery and holder.

First connect your HC-12 Module to the Arduino as follows:



RX = A3

TX = A2

SET = A1

You can either use the Dupont wires to connect to the included header pins that will need soldering in, or to save space, you can solder the wires directly onto the Arduino.

Next solder the battery holder wires onto the back of the Arduino VCC and GND pins (RED to VCC, BLACK to GND) shown in the second image:

Step 3: Assembling the Node - Temperature Sensors

Now we need to connect our temperature sensors. In the image below, we will connect both the DS18B20 and the DHT22 to the Arduino, you have the choice of connecting one or the other or both.

If both are used, the module could be placed on a window sill with the DS18B20 temperature module placed outside the window to give readings for both indoor temperature and humidity and outdoor temperature.

Connect the DHT22 as follows (the module can be soldered directly into the Arduino if necessary):

+ = 2

OUT = 3

- = 4

Because we want to make sure that the temperature sensors are not drawing power when the Arduino is sleeping, we use IO pins as the VCC for the sensors, so that when they are not being used, they can be switched off.

The DS18B20 requires
a 4.7K resistor to be soldered between the positive line and the signal line, so we will solder the resistor between pins 9 and 10 on the Arduino and then solder on the temperature sensor wires as shown in the image (there is a convenient GND at the end of the pro mini board):

RED (VCC) = 10



Step 4: Assembling the Node - Connecting FTDI Programmer

Here is our finished module ready to be programmed.

Insert your FTDI programmer into the header pins on the arduino. If you arent sure, check out this post:

We also built a smaller module with only the DS18B20 module, that including the battery can fit inside this small enclosure:

Step 5: Arduino Code - Programming a New ID

We will continue editing the node code used in the previous tutorials in order to build one program that is used to control multiple different types of node. How the program behaves is dependent on the Node ID that we set in the EEPROM Memory. The first two characters of the code represent different nodes for example: TP = temperature sensor or RM = relay module.

Like before, we will use our ID programming sketch to give the Pro Mini an ID. We will use the ID TP0001. Find your ID programming sketch and change the ID to TP0001 or something similar, or if you didn’t save it, create a new sketch and copy the following code in:

char progID[7] = "TP0003"; // Device ID char sID[7]; // Buffer to store read ID String nodeId; // String to store ID

void setup() { // Start serial port to computer Serial.begin(9600); //For each character of the ID write to EEPROM position 0-5 for (int i=0; i<6; i++) { EEPROM.write(i,progID[i]); }

//Read back ID from EEPROM for (int i=0; i<6; i++) { sID[i] =; }

//Save read ID to string and print to Serial nodeId = String(sID); Serial.println(nodeId); }

// Do nothing. void loop() {}

Once you have done this, you can upload your program to the
Arduino. Connect your FTDI programmer to your PC with a USB cable and then plug it into the header we soldered onto the Arduino. Press upload, and once complete, check that it worked in the serial monitor.

Step 6: Arduino Code - Sensor Libraries

Next we need to modify our sensor node code to allow us to control and read data from temperature sensors as well as operate the relay node we built in the previous tutorial. We will need some new libraries for the two types of sensor we will be using, the DS18B20 and DHT22.

These libraries can be found in the library manager:

The first is called “DallasTemperature” and the second is “DHT sensor library” by Adafruit. Install both of these. We will also need to include the Onewire library for the DS18B20 sensor. The images show the libraries we used.

Next we need to ‘include’ and set up these libraries at the beginning of our program. The new section should look like this and should be placed between the “EEPROM.h” include and the “SoftwareSerial HC12(A2,A3)” line:

// Temperature Node Setup /********************************************************************/ //DS18B20 Setup #include #include #define ONE_WIRE_BUS 9 OneWire oneWire(ONE_WIRE_BUS); DallasTemperature sensors(&oneWire);

//DHT22 Setup #include ; #define DHTPIN 3 // what pin we're connected to #define DHTTYPE DHT22 // DHT 22 (AM2302) DHT dht(DHTPIN, DHTTYPE); //// Initialize DHT sensor for normal 16mhz Arduino


Step 7: Arduino Code - Setup Function, Main Loop and Functions

We will then need to add to the setup() function, to set the ‘pin modes’ of the pins we are using to power the sensors. For both sensors, the pins either side of the signal pin are used as the VCC and GND, giving us the option to completely disable the device when the Arduino is sleeping in order to save power.

The new setup() function should look like this:

void setup()
{ //Set pin 5 as output and pull high (relay is active low). pinMode(5, OUTPUT); digitalWrite(5, HIGH); //DHT21 pinMode(2, OUTPUT); pinMode(4, OUTPUT); digitalWrite(2, HIGH); digitalWrite(4, LOW);

//DS18B20 pinMode(10, OUTPUT); digitalWrite(10, HIGH); dht.begin();

//Pull the HC12 SET pin high (LOW for AT Commands). pinMode(A1, OUTPUT); digitalWrite(A1, HIGH);

//Start serial ports. Serial.begin(9600); HC12.begin(9600); //Read 7 digit ID from EEPROM. for (int i=0; i<6; i++) { sID[i] =; } //Save read ID to string and print to Serial. nodeId = String(sID); if(nodeId.startsWith("TP")) sensors.begin();

Serial.print("Node Starting..."); Serial.println(nodeId); }

Next we will create a new function to handle sensors that will be called from the main loop:

//Main program (loops forever).
void loop() { //Check for messages bool gotMessage = checkRadio();

//If message is received and mode is "CMD" if( gotMessage ) { if( msgMode == "CMD") HandleCommand(); }

HandleSensor(); }

//Process commands. void HandleCommand() { if( nodeId.startsWith("RM")) RelayControl(); }

//Read from sensors and transmit data. void HandleSensor() { if(nodeId.startsWith("TP")) { LowPowerDSDHCombi(); if(sleepTime > 0) Sleep(); } }

Currently we only have once sensor, but in future more could be added to this function. Once the sensor data has been processed, the HandleSensor() function then triggers the Sleep() function.

The function that handles reading the sensor data and then sending it to the Hub is called LowPowerDSDHCombi(). This will read from both the DS18B20 and the DHT22 sensor as well as read the battery voltage and the sleep time before sending the message to the Hub.

void LowPowerDSDHCombi()
{ //Read from DS18B20 sensors.requestTemperatures(); float tempOut = sensors.getTempCByIndex(0);

//Read from DHT22 float hum = dht.readHumidity(); float tempIn = dht.readTemperature(); float batt = readVcc();

//Send message to hub in format: //ID|Mode|Temp In|Temp Out|Humidity|Battery|Sleep time\r HC12.print(sID); HC12.print("|"); HC12.print("PTX"); HC12.print("|"); HC12.print(tempIn); HC12.print("|"); HC12.print(tempOut); HC12.print("|"); HC12.print(hum); HC12.print("|"); HC12.print(batt/1000); HC12.print("|"); HC12.println(sleepTime); delay(100); }

Finally we need to add two more functions, one to read the
battery voltage (because we removed the regulator, we can read the internal voltage of the microprocessor to find this) and one to put the Arduino and HC-12 module to sleep when not being used to save power.

These look like this:

//Reads internal input voltage.
long readVcc() { long result; // Read 1.1V reference against AVcc ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); delay(2); // Wait for Vref to settle ADCSRA |= _BV(ADSC); // Convert while (bit_is_set(ADCSRA,ADSC)); result = ADCL; result |= ADCH<<8; result = 1125300L / result; // Back-calculate AVcc in mV return result; }

//Puts 328p into deep sleep and shuts down HC-12. void Sleep() { //Send HC12 to sleep digitalWrite(A1, LOW); delay(100); HC12.print("AT+SLEEP"); delay(100); digitalWrite(A1, HIGH); //Loop for x times (48 seconds between temprature transissions) for (int i = 0; i < int(sleepTime/8); i++) { LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF); }

//Wake up HC12 module digitalWrite(A1, LOW); delay(100); digitalWrite(A1, HIGH); }

The full arduino sketch .ino can be downloaded.

Step 8: DroidScript Code

Now we are starting to build a collection of nodes to work with our wireless IOT system, we should make the android application for controlling and reading data from the nodes neater and easier to expand.

The easiest way to do this is to introduce tabs for each type of node, and to break the code into separate ‘.js’ files for each tab. This simplifies the main program and makes it easier to understand and navigate the program.

Open the DroidScript web editor and create a new app. We will be using parts of the app we wrote in the previous tutorial but rearranging the layout into separate files.

Click the “+” new file button and give the file a name. Create 3 files, for the three tabs we will use, one for temperature, one for the relay module and one for settings (setting the IP address).

Inside these files, we need to create functions to draw the layouts for each tab that can be called by the OnStart() function.

In the Relay.js file write:

function RelayTabSetup()



In the Settings.js file:

function SettingsTabSetup()


In the Temperature.js file:

function TempTabSetup()


Now in the main program file (Tutorial 3.js) delete the section that writes “Hello” and then under the app.AddLayout() line, add the tabs layout so that your program now looks like this:

app.Script( "Relay.js" );
app.Script( "Temperature.js" ); app.Script( "Settings.js" );

//Called when application is started. function OnStart() { //Create a layout with objects vertically centered. lay = app.CreateLayout( "linear", "VCenter,FillXY" ); //Add layout to app. app.AddLayout( lay ); //Create tabs. tabs = app.CreateTabs( "Temp,Relay,Settings", 1, 1, "VCenter" ); lay.AddChild( tabs ); RelayTabSetup(); TempTabSetup(); SettingsTabSetup(); }

You should be able to test the app now and if everything is correct, it will load the three empty tabs.

Step 9: DroidScript Code - Functions

Now we need to add back in the functionality to our app so we can control our relay module still and read the temperatures from our new node.

We’ll start with the relay tab, adding back in the text and buttons we had in our previous version of the app, but now inside the Relay.js file so that it now looks like this:

function RelayTabSetup()
{ layRelay = tabs.GetLayout( "Relay" ); //Create a button to send request. btnRelayOn = app.CreateButton( "Relay On", 0.3 ); btnRelayOn.SetMargins( 0, 0.03, 0, 0 ); btnRelayOn.SetOnTouch( btnRelayOn_OnTouch ); layRelay.AddChild( btnRelayOn ); //Create a button to send request. btnRelayOff = app.CreateButton( "Relay Off", 0.3 ); btnRelayOff.SetMargins( 0, 0.01, 0, 0 ); btnRelayOff.SetOnTouch( btnRelayOff_OnTouch ); layRelay.AddChild( btnRelayOff ); }

//Send relay on command for hub to transmit. function btnRelayOn_OnTouch() { //Send request to remote server. var cmd = "txnow=RM0002:CMD:RELAYON"; app.HttpRequest( "get", url, "/tx", cmd , HandleReply ); }

//Send relay off command for hub to transmit. function btnRelayOff_OnTouch() { //Send request to remote server. var cmd = "txnow=RM0002:CMD:RELAYOFF"; app.HttpRequest( "get", url, "/tx", cmd, HandleReply ); }

In the settings tab:

function SettingsTabSetup()
{ laySettings = tabs.GetLayout( "Settings" ); //Create a text label and add it to layout. txt = app.CreateText( "HUB IP:" ); txt.SetTextSize( 16 ); laySettings.AddChild( txt ); //Create an text edit box. var txt = app.ReadFile( ipfile ); edtIP = app.CreateTextEdit( txt, 0.6 ); edtIP.SetMargins( 0, 0.02, 0, 0 ); laySettings.AddChild( edtIP ); //Create a button to send IP. btnIPSave = app.CreateButton( "Save", 0.3, 0.1 ); btnIPSave.SetMargins( 0, 0.05, 0, 0 ); btnIPSave.SetOnTouch( btnIPSave_OnTouch ); laySettings.AddChild( btnIPSave ); //Create a text label. txtLabel = app.CreateText( "HUB STATUS: " ); txtLabel.SetTextSize( 15 ); txtLabel.SetMargins(0, 0.03); laySettings.AddChild( txtLabel ); //Create text to show if connected or not. txtStatus = app.CreateText( "" ); txtStatus.SetTextSize( 15 ); txtStatus.SetMargins(0, 0.01); txtStatus.SetTextColor("red"); txtStatus.SetText("Disconnected"); laySettings.AddChild( txtStatus ); }

//Saves current hub IP to text file and checks for connection. function btnIPSave_OnTouch() { //Get IP from text box and save to file. var s = edtIP.GetText(); app.WriteFile( ipfile, s ); app.ShowPopup("Saved: " + app.ReadFile( ipfile )); url = "http://" + app.ReadFile( ipfile ); //Show progress wheel until response received //then check status after half a second. app.ShowProgress(); setTimeout(HubStatus, 500); }

If you try to run the app at this point, it won’t work as the main program isn’t complete.

Before we start working on getting temperature readings, let’s get the app working again. We need to add the functions to handle hub messages and check the connection back in. Add these until your main program looks like this:

app.Script( "Relay.js" );
app.Script( "Temperature.js" ); app.Script( "Settings.js" );

var url = ""; //variable to store var ipfile = "/sdcard/hubip.txt";

//Called when application is started. function OnStart() { //Get last hub IP saved from text file. url = "http://" + app.ReadFile( ipfile ); //Create a layout with objects vertically centered. lay = app.CreateLayout( "linear", "VCenter,FillXY" ); app.AddLayout( lay ); //Create tabs. tabs = app.CreateTabs( "Temp,Relay,Settings", 1, 1, "VCenter" ); tabs.SetOnChange( tabs_OnChange ); lay.AddChild( tabs ); RelayTabSetup(); TempTabSetup(); SettingsTabSetup(); }

//Check if Hub is connected by asking for status. function HubStatus() { //Send request to remote server. var path = "/status"; app.HttpRequest( "get", url, path, "", HandleReply ); }

//Handle the hub's webserver reply.
function HandleReply( error, response ) { console.log(response); //Splits message on "|" into array. var res = response.split("|"); //If timeout or other error show disconnected. if (error) { console.log(error); txtStatus.SetTextColor("red"); txtStatus.SetText("Disconnected"); } //If message reads "HUB|OK" show connected else show disconnected. else if (res[0] == "HUB" ) { if (res[1] == "OK") { txtStatus.SetTextColor("green"); txtStatus.SetText("Connected"); } else { txtStatus.SetTextColor("red"); txtStatus.SetText("Disconnected"); } } //Response received so hide progress wheel. app.HideProgress(); }

//Handle tab selection. function tabs_OnChange( name ) { if (name=="Settings") HubStatus(); }

We have also changed when the app checks the status of the Hub, instead of it being checked every 30s, we can now detect when the user has selected the settings tab and send the status request then instead.

You should now be able to test your app again. Under the settings tab, check the IP of your hub is correct and that it is showing “Connected”. If so, you can now test your relay control to check it is still working.

Step 10: DroidScript Code - Temperature Sensor Tab

Next we will add the ability to read from our new temperature sensors. To do this, first we will add the text fields to the Temperature tab for the DS18B20 sensor. We can also display the other information the temperature node is sending, including its battery level and sleep period.

function TempTabSetup()
{ layTemp = tabs.GetLayout( "Temp" ); txtLabel = app.CreateText( "Indoor Temperature:" ); txtLabel.SetTextSize( 18 ); layTemp.AddChild( txtLabel ); txtTP_temIn = app.CreateText( "Null" ); txtTP_temIn.SetTextSize( 22 ); layTemp.AddChild( txtTP_temIn ); txtLabel = app.CreateText( "Indoor Humidity:" ); txtLabel.SetMargins(0, 0.05); txtLabel.SetTextSize( 18 ); layTemp.AddChild( txtLabel ); txtTP_hum = app.CreateText( "Null" ); txtTP_hum.SetTextSize( 22 ); layTemp.AddChild( txtTP_hum ); txtLabel = app.CreateText( "Outdoor Temperature:" ); txtLabel.SetMargins(0, 0.05); txtLabel.SetTextSize( 18 ); layTemp.AddChild( txtLabel ); txtTP_temOut = app.CreateText( "Null" ); txtTP_temOut.SetTextSize( 22 ); layTemp.AddChild( txtTP_temOut ); txtLabel = app.CreateText( "Battery:" ); txtLabel.SetMargins(0, 0.05); txtLabel.SetTextSize( 18 ); layTemp.AddChild( txtLabel ); txtTP_bat = app.CreateText( "Null" ); txtTP_bat.SetTextSize( 22 ); layTemp.AddChild( txtTP_bat ); txtLabel = app.CreateText( "Sleep Time:" ); txtLabel.SetMargins(0, 0.05); txtLabel.SetTextSize( 18 ); layTemp.AddChild( txtLabel ); txtTP_slp = app.CreateText( "Null" ); txtTP_slp.SetTextSize( 22 ); layTemp.AddChild( txtTP_slp ); }

function GetLastTP() { //Send request to remote server. var path = "/rx"; app.HttpRequest( "get", url, path, "", HandleReply ); }

The function at the end sends a request to the hub for its last temperature reading. The last part we need to add is the function that periodically checks for new temperature readings and updates the text under the temperature tab.

At the end of the OnStart() function add the following lines:

//Check for connection to hub, then again every 10s.
//HubStatus(); Update(); setInterval(Update ,30000);

Then after the OnStart() function:

function Update()
{ GetLastTP(); }

Finally update the HandleReply() function as follows, this sets the text under the temp tab when a reply is received from the ESP32 Hub.

//Handle the hub's webserver reply.
function HandleReply( error, response ) { console.log(response); //Splits message on "|" into array. var res = response.split("|"); //If timeout or other error show disconnected. if (error) { console.log(error); txtStatus.SetTextColor("red"); txtStatus.SetText("Disconnected"); } //If message reads "HUB|OK" show connected else show disconnected. else if (res[0] == "HUB" ) { if (res[1] == "OK") { txtStatus.SetTextColor("green"); txtStatus.SetText("Connected"); } else { txtStatus.SetTextColor("red"); txtStatus.SetText("Disconnected"); } } else if (res[0] == "TP0003") { txtTP_temOut.SetText(res[2] + "°C"); txtTP_temIn.SetText(res[3] + "°C"); txtTP_hum.SetText(res[4] + "°C"); txtTP_bat.SetText(res[5] + "V"); txtTP_slp.SetText(res[6] + "S"); } //Response received so hide progress wheel. app.HideProgress(); }

Step 11: Testing

Launch your app, and try out the different tabs, they should look like the first three images. Next make sure your ESP32 Hub and the temperature node is powered and that you have the correct IP address in your app. The Status should change to connected and within 30s or so you should see your temperature readings.

The full SPK can be downloaded here: