Submitted by mdiaconescu on
Building a WiFi-Connected Weather Station
Download Code and Documentation
In this tutorial we show how to build a WiFi-connected weather station with the help of an Arduino Pro Mini (or, alternatively, a Nano, UNO, or MEGA2560), an ESP8266 WiFi module, a DHT22 temperature and humidity sensor and an Android device (smartphone, tablet, watch, etc) for configuring the weather station and for observing the sensor data. The total cost of this project, excluding the Android device, is less than 30 €.
The design goals of our project are:
- simplicity: the solution must be simple and easy to build even for a beginner;
- usability: the temperature (in the range [-30, 50]°C) and humidity (in the range [10 - 100]%) values can be read over WiFi and visualized by using an Android device (smartphone, tablet, watch, etc);
- low cost: the target is 30€ or less, excluding the Android device;
- maintainability: must be possible to add new sensors and functionality when needed, and we should be able to repair it if something goes wrong.
One can buy devices with similar features, but the price range starts at about 75 EUR and more. Also, these devices cannot be easily modified, most of them using SMD (surface mount devices) and integrated ICs, thus a possible improvement or repair when needed is either hard or impossible.
In addition, because it is easy to do and it costs nothing (except a few MCU cycles and a few minutes of programming), we can easily add a few other features:
- a voltmeter for the 5V power supply - it is fairly easy because the ATmega 328 (but also ATmega 168 and some other) MCU comes with a builtin voltmeter (voltage sensor), which has a relatively low accurracy if not calibrated, but provides valuable information with no additional costs;
- the amount of free RAM for the Arduino MCU - mainly used for debugging reasons, it provides useful information for later improvements. Same as for the voltmeter, no additional hardware is required, and it only costs a few MCU cycles with respect to MCU usage;
- compute the average temperature and humidity - this is done in the software, and provides useful information for the weather station user.
Disclaimer: working with electricity is dangerous. For this project we use 5V and 3.3V voltage levels, which is safe for the human body under any environment conditions. However, the low voltage is obtained from a mains connected power brick, and therefore we highly recommend you to take safety precautions and to use a trustful brand for the power supply. We cannot be held responsible for any caused damage! Do it at your own risk and/or ask help from an electronics engineer.
Hardware configuration
The hardware components required for this project are presented below. These components were mostly bought from eBay and Amazon, and the listed prices include postage costs within Germany (for some also in the EU). If you are willing to wait between two and five weeks to receive the items, you can also buy them from China online shops (Aliexpress, eBay, and so on), for less than half of the prices we show further.
Hardware Component | Estimative Price | Description |
---|---|---|
Arduino Pro Mini | 3 - 5 EUR | We use a clone, but it has the same functionality as the original device. While the quality of the original Arduino Pro Mini is higher, so it is the price (two to four times higher). |
ESP8266-02 WiFi module | 6 - 8 EUR | Most of these modules have a 4Mb (512KB) flash memory, allowing to use AT firmware version below 1.1.0 ( released on June 2015). New modules are now available, and the SPI flash IC was updated to an 8Mb (1MB) one, allowing them to use the latest AT firmware, which provides more and improved AT commands. We strongly recommend to use an ESP8266 module with 1MB flash and latest AT firmware. |
Logic Converter module | 1 - 3 EUR | It is used to convert 5V to 3.3V required for the UART communication between the Arduino board and the ESP8266 WiFi module. Some of these devices support also 2.5V and 1.8V levels. |
DHT22 temperature and humidity sensor | 4 - 6 EUR | May be replaced with DHT11 sensor module if negative temperatures are not required and its lower accuracy is ok for you. It contains a temperature and relative air humidity sensor in one package, and uses a 1-Wire digital custom interface for data communication. |
LM317 Step-Down Linear Voltage regulator | approx. 0.5 EUR per piece, 2 - 3 EUR for a set of 10 | It requires two additional resistors (or one resistor and one potentiometer) and two additional capacitors, with a total cost of about 1 EUR. We use it to lower the voltage down to 3.3V but it can be adjusted for values between 1.25 and 37V. This device is capable to supply maximum 1.5A current with proper cooling (not required for our case). |
One red/green Duo LED, or two separate red and green LEDs | approx. 0.5 EUR per piece of 2 EUR for a set of 10 | A common cathode (GND in our case) LED is a good choice for the DUO LED. Two separate LEDs of any size and color may be used as long as they require less than 20-25mA and have a forward voltage less than 5V (supplied by the Arduino I/O pins). |
[email protected] regulated power brick | 3-5 EUR | A ±5% variation is allowed for the regulated power brick. Notice that cheap and low quality power supplies are not recommended since often they have a bad regulation and usually out of the ±5% error range, so it may produce damage to the other components or even put your live in danger. A power brick with a higher Ampere value can be used, but usually these are more expensive. In addition, one can use an USB port as power supply for our device, that is able to supply 500mA or more current (USB ports of some laptops are not appropriate). |
Weather Station Hardware Design
There are applications one can use to design electronic schematics, but most of the time, these are not easy to understand for beginners. We choose to use Fritzing, which allows to draw nice looking breadboard oriented designs, but also schematics and PCB layouts. It also provides a builtin environment which integrates with the Arduino Software and allows to write the Arduino code, compile it and deploy it on the Arduino board. The hardware prototype on a breadboard is shown in Figure 1:
The hardware is divided in a few blocks:
- the power supply: Arduino Pro Mini board needs 5-12V (obtained from a [email protected] power brick), but the ESP8266 WiFi module requires 3.3V regulated voltage which we obtain by using a step-down linear regulator.
- the sensor node controller: the Arduino Pro Mini board represents the controller board, which runs all the logic for this project;
- an WiFi communication module: we have used the cheap and easy to use ESP8266 module for the WiFi communication between the sensor node and the Android device;
- the sensor(s): temperature and humidity values are obtained from a DHT22 sensor;
- logic level voltage converter: allows a voltage safe Serial/UART communication between the Arduino board which uses 5V signals and the ESP8266 module which requires 3.3V signals;
- WiFi status LEDs: a DUO, common cathode green/red LED, is used to visually indicate the WiFi state.
Note: many of the breadboards we are aware of, does not have a default connection between the GND lines for the two power grids, therefore you need to add a connection wire between the two GND lines, as shown in Figure 1. Also, some breadboards have every power grid splited in two halfs, so you have to use a jumper wire if this is the case.
For the breadboard schematic, red wires were used for +5V, orange for 3.3V and black for GND connections. For communication lines, we use green and blue for RX/TX lines, and yellow for data pin of DHT22 sensor. In Figure 2, we show the full electronics schematic, of our weather station sensor node.
The ESP8266 3.3V Power Supply
Our sensor node is powered by a regulated [email protected] power brick. The Arduino Pro Mini board and most of components can directly use the 5V rail comming from the power brick. The regulation down to 3.3V, required by the ESP8266 WiFi module, is obtained by using the LM317T IC, an adjustable step-down linear voltage regulator. It only requires a few additional components: two resistors and two capacitors. It is able to provide up to 1.5A current, with appropriate cooling. However, for our example cooling is not needed, the average current being under 250mA (going up to 500mA but only for very short amounts of time, when the ESP8266 module transmits data). Figure 3 shows the 3.3V power supply built on a breadboard, and Figure 4 shows the corresponding electronics schematic..
The values of the R1 and R2 resistors are chosen according with the following rules:
- the value of R1 must be in the range 100 - 1000Ω, as specified in the LM317 IC datasheet;
- both, R1 and R2 are standard resistor values, so easily available in any electronics shop;
- the equation, provided by the LM317 IC datasheet,
Vout = 1.25 ( 1 + R2 / R1)
is verified, whenVout ~= 3.3
.
The real output voltage, for R1 = 330Ω
and
R2 = 560Ω
is Vout ~= 3.37V
, so the maximum
±5% error is within specifications. C1
is a ceramic
capacitor with a value of 0.1µF
( 100nF
),
being used to filter high frequency spikes and to prevent internal
oscillation for the LM317T
IC. C2
is an
electrolytic capacitor with a value of 1µF
(higher
values, up to about 1000µF
can be used), and its purpose
is to smooth the output voltage (3.3V line). The designed voltage of
C2
must be higher than 3.3V
, and it is very
important to mount the capacitor with the correct polarity.
Electrolytic capacitors are very prone to small explosions if
missused, such as mounting them in reverse polarity or use them with
voltages over their specification.
Note: instead of the fixed value R2 resistor, a 1KΩ potentiometer can be used. In this case, a more precise output voltage is obtained if (and when) really needed. This way you can also use a lower value for R1, and a good choice is 240Ω, as recommended in the LM317 datasheet.
Serial Communication via Voltage Lever Converter
Arduino Pro Mini board is powered by a 5V power supply, so it allows to use 5V on the UART RX and TX communication lines. ESP8266 Wifi module on the other hand uses 3.3V power supply, and it is not 5V tolerant for the VCC, RX, TX or any of its other available I/O lines. Therefore a direct connection between the RX/TX lines of the Arduino board and the RX/TX lines of the ESP8266 module is not possible, and ignoring this warning may very likely result in damaging the ESP8266 module. A simple solution for this case is to use a readily available logic converter module. Its provides 3.3V lines (some also allows 1.8V, 2.5V and 2.8V) as well as 5V communication lines. Notice that such a module can provide very low current (in the range of a few mA) on each of the lines, therefore it must be used only for data communication and not as a voltage regulator. We use a four channel module, so it provides four low voltage (3.3V) lines with four corresponding high voltage (5V) lines. Two lines are used for UART communication and the other two are used for connecting the CH_PD and RESET lines of the ESP8266 module with the pin 2 and respectively 3 of the Arduino board.
It is important to notice that the RX/TX line of the Arduino Pro Mini board have to be cross-connected with the RX/TX lines of the ESP8266 module. This means: the RX line of the Arduino board connects to the TX line of the ESP8266 module and the TX line of the Arduino board connects to the RX line of the ESP8266 module. These connection is made via the logic level converter module as already explained above and also shown in Figure 1 and Figure 2.
The CH_PD (channel power down) and RESET pins of the ESP8266 module needs to be connected to VCC (+3.3V) line via 3.3KΩ resistors. Normally, a 10KΩ resistor can be used, but we observed periodical resets of our WiFi module (we tested a few of them). Using test and trial method, we found that the 3.3KΩ resistor works the best for all our ESP8266 modules.
Note: the ESP8266-01 module is shown in the schematic, since this was available as Fritzing component, but actually in the real board we use an ESP8266-02 module. This should make no difference, and in general we should be able to use any version of these modules. There are about thirteen versions of ESP8266 modules, which differes in form, size and available I/O, but all have the same WiFi functionality.
DHT22 Sensor
The DHT22 sensor allows to obtain temperature readings in the range
[-40, 80]°C
with a typical accuracy of
±0.5°C
and a resolutin of 0.1 units (°C)
.
However, in real tests, the accuracy we could observe is closer to
±1°C
. The sensor can also read humidity values in the
range [0, 100]%
, with a typical accuracy of
±2 units (%)
and a resolution of 0.1 units
.
In our real tests, the accuracy was about ±5%.
The DHT22 sensor can be connected to either 3.3V line or to 5V line, and we decided to use the 5V line. The data pin of the sensor (check the datasheet for more details) is connected to 5V rail via one 10KΩ resistor, to avoid communication errors (this line must stay HIGH when the sensor does not communicate via the data pin). The sensor data pin is connected to digital pin 9 of the Arduino board.
WiFi Status LEDs
During the tests, we found that it is good to know the actual state of the ESP8266 module, specially because the module becomes unresponsive sometimes. We have used a DUO red/green LED, which is actually composed of one green and one red LEDs in a single physical package, and which share a common cathode (GND) connection. The green LED anode was connected to pin 8 of the Arduino Pro Mini board via a 560Ω resistor, thus allowing about 5mA current passing through the LED. The red LED anode was connected to pin 7 of the Arduino Pro Mini board via a 560Ω resistor, thus allowing about 6mA current passing through the LED.
A LED should never be directly connected between the VCC and GND
lines (or directly to I/O pins of an MCU) with the exception of
special LEDs which are designed for this, on which case they have a
builtin resistor or use other current limiting design. Instead one
must use a series resistor to limit the current passing through the
LED. Using Ohm's
Law we can compute the value of the resistor by knowing the LED
forward voltage (the voltage to which the LED starts conducting and
emmiting light), the used supply voltage and the current we want to
allow passing through the LED (which controls the LED brightness). A
standard red LED has about 1.7V forward voltage, a green or orange LED
has about 2V forward voltage, a white or blue led has about 2.7V
forward voltage. Since we only need the LEDs to provide visual
indication, they don't have to lighting very bright. After testing our
DUO LED, we decided that a value of 6mA is desired for the red LED and
a value of 5mA for the green LED (green light is more visible for
human eye than other colors). Applying Ohm's Law, we have
V = IR
, so R = V / I
. For our case, V is the
difference between the power supply voltage (+5V) and the LED forward
voltage. Solving the equation for the red LED we have
R = (5 - 1.7) / 0.006
, which give us
R = 550Ω
. Since 550Ω is not a standard value, the next
available standard resistor value can be used, that being 560Ω. As
homework, you can do the computations for the green LED.
Note: if you don't have a DUO LED, just use two normal LEDs. Also, you can use other LED colors if you like, and even different resistor values to obtain different brightness levels (use Ohm's Law to find the appropriate resistor value). A standard 3mm or 5mm LED usually stands up to 20mA of current, after this point it gets too warm and it is very likely that it gets burned. However some LEDs can stand much more current, but they need special drivers. Also, notice that you should not draw more than 20mA from each of the Arduino pins, or you may damage the board.
Hardware Summary Overview
Arduino Pro Mini Pin | Connects to |
---|---|
RX | Logic Level Converter first RX HV pin |
TX | Logic Level Converter first TX HV pin |
2 | Logic Level Converter second RX HV pin |
3 | Logic Level Converter second TX HV pin |
7 | Red anode of the DUO LED via R4 (560Ω) resistor |
8 | Green anode of the DUO LED via R4 (560Ω) resistor |
9 | Data pin of the DHT22 sensor |
VCC | VCC (+5V) rail of the power supply |
GND | GND rail of the power supply |
Logic Level Converter PIN | Connects to |
---|---|
First TX LV | RX pin of the ESP8266 module |
First RX LV | TX pin of the ESP8266 module |
First TX HV | TX pin of the Arduino Pro Mini board |
First RX HV | RX pin of the Arduino Pro Mini board |
Second TX LV | RESET pin of the ESP8266 module |
Second RX LV | CH_PD pin of the ESP8266 module |
Second TX HV | Pin 3 of the Arduino Pro Mini board |
Second RX HV | Pin 2 of the Arduino Pro Mini board |
LV GND | GND rail of the power supply |
HV GND | GND rail of the power supply |
LV | 3.3V rail, obtained from the LM317 IC output pin |
HV | 5V rail of the power supply |
ESP8266 Module Pin | Connects to |
---|---|
RX | First LV TX pin of the Logic Converter Module |
TX | First LV RX pin of the Logic Converter Module |
RESET | Second LV TX pin of the Logic Converter Module |
CH_PD | Second LV RX pin of the Logic Converter Module |
GND | GND rail of the power supply |
VCC | 3.3V rail, obtained from LM317 output pin |
LM317 IC Pin | Connects to |
---|---|
Input | 5V rail of the power supply |
Output | 3.3V rail |
Adj | Output 3.3V rail via R1 and to GND rail via R2 |
Software Configuration
The Arduino Code
The Arduino code can be written, compiled and deployed to the Arduino board by either using Fritzing or the Arduino IDE (this may be preferred in some cases, because it usually works out of the box). For our project, the following Arduino libraries are needed:
- Arduino-ESP8266, used for the UART communication between the ESP8266 module and the Arduino board. Clone the library repository, rename the folder to ESP8266 and copy it to libraries subfolder, part of the Arduino Software installation folder.
- DHTLib, used for the communication with the DHT22 sensor. This library supports DHT11 sensors as well, in case you use it as a replacement for DHT22. Clone the library repository, rename it to DHT and copy it to libraries subfolder, part of the Arduino Software installation folder.
An Arduino program (also known as sketch) has the following minimal structure:
// 1. constants definition (optional if no constant is needed) // 2. include headers for used libraries ( optional if no library is used) // 3. define the global variables (optional if no global variable is required) // program initialization void setup() { // write here the setup code. } // infinite loop cycle void loop() { // the code from this method loops as long as the Arduino board is powered. }
In the setup
method we write initialization code, which
is executed only once, when the Arduino is powered or after a software
or hardware reset. The loop
method contains the code
which loops in the Arduino MCU as long as the board receives power.
Constants Definition for Arduino Pins Configuration
First we define the constants representing the used Arduino board pins. It is highly recommended to use constants instead of using the pin numbers all over the code. This way, one can easily change the constant value instead of trying to find all the places where the pin number was used, if you decide that another pin has to be used.
// Arduino pin number used for the communication with DHT22 sensor. #define DHT22_PIN 9 // pins number for WiFi disabled LED #define WIFI_DISABLED_LED_PIN 7 // pins number for WiFi enabled/active LED #define WIFI_ACTIVE_LED_PIN 8 // arduino pin used to connect to the CH_PD (Power Down) WiFi module pin #define WIFI_CH_PD_PIN 2 // arduino pin used to connect to the RESET WiFi module pin #define WIFI_RESET_PIN 3
Pin 9 is used for data communication with the DHT22 sensor, pins 7 and 8 are used for the red and green LEDs (WiFi status LEDs), pin 2 allows to put the WiFi into a sleep mode (and to wake it up) and pin 3 allows us perform a hardware reset of the WiFi module, which requires to pull it down (set pin to LOW) for at least 200ms.
Import Required Libraries for DHT22 and ESP8266 Modules
We need to specify the libraries used by our program. This is done
in the standard C/C++ style by using the #include
directive:
#include <dht.h> #include <ESP8266.h>
When using this directive, <>
tells to the
compiler and linker to look in the libraries subfolder of
the Arduino IDE installation, while using double quotes means the
library files are in the your arduino skecth folder.
Define Program Global Variables
As in many (if not all) Arduino programs, we use some global variables:
// DHT22 sensor controller class dht DHT; // ESP8266 WiFi module controller ESP8266 esp( Serial); // store the average temperature and humidity // values, starting with last system reset float avgTemperature = 0; float avgHumidity = 0; // utility variable used to compute the averate temperature value unsigned long avgDhtStep = 1; // data template for sensors const char SENSORS_DATA_TEMPLATE[] PROGMEM = "{\"temperature\": %s, \"avgTemperature\": %s, \"humidity\": %s, \"avgHumidity\": %s, \"voltage\": %s, \"freeRam\": %d}";
The dht22
variable represents an instance of the
library which controls the communication with the DHT22 sensor. The
esp
variable represents an instance of the ESP8266
library used to communicate with the WiFi module. As parameter for
the constructor we provide the Serial
object, so the
communication is made on the UART0 port of the Arduino board. When
using Arduino Pro Mini (also Nano or UNO) board, this is the only
serial port available. However, the library is designed to work with
all the Arduino boards, and some of them have up to four UART ports,
accessed via Serial
, Serial1
,
Serial2
and Serial3
global objects.
Since we like to know the average temperature and humidity values
measured by our Weather Station, we define the
avgTemperature
, avgHumidity
and
avgDhtStep
variables. The first two are used to store
the average temperature and humidity values, while the latter is
used to count how many time the temperature value was read, so the
correct average value can be computed according with the formula:
avg = (avg * (n - 1) + newValue) / n
;
The SENSORS_DATA_TEMPLATE
variable stores the template
(JSON structure) used to communicate with the Android application.
The special PROGMEM
variable modifier enforces the
storage of the value in the flash memory instead of RAM, thus
freeing up about 120Bytes of RAM (about 6% of the total RAM of the
ATmega328P MCU, used by the Arduino Pro Mini, Nano and UNO boards).
Initializing the ESP8266 WiFi Module
The ESP8266 module communicates with the Arduino MCU via the UART protocol. A hardware reset for the WiFi module is recommended to ensure a correct module state before starting the UART communication.
void setupWiFi() { // STEP 1: // Set pins used for WiFi status LEDs as OUTPUT. pinMode( WIFI_ACTIVE_LED_PIN, OUTPUT); pinMode( WIFI_DISABLED_LED_PIN, OUTPUT); // STEP 2: // Arduino pin connected to ESP8266 CH_PD pin is set to OUTPUT. // To keep the module active, this pin must be kept HIGH. pinMode( WIFI_CH_PD_PIN, OUTPUT); digitalWrite( WIFI_CH_PD_PIN, HIGH); // Arduino pin connected to ESP8266 RESET pin is set to OUTPUT. // To avoid random resents, we keep this HIGH. pinMode( WIFI_RESET_PIN, OUTPUT); digitalWrite( WIFI_RESET_PIN, HIGH); // STEP 3: // perform a hardware reset (ESP8266 RESET pin goes LOW) digitalWrite( WIFI_RESET_PIN, LOW); delay( 200); // allow ESP8266 module to boot digitalWrite( WIFI_RESET_PIN, HIGH); // STEP 4: // baud 115200, communication with ESP8266 module Serial.begin( 115200); // STEP 5: // wait for the ESP8266 module to start, after the forced hardware reset. // We check the wifi state once a second, until the ESP8266 WiFi module responds. while( !checkWiFi()) { delay( 1000); }; // STEP 6: // start UDP connection - wait on all ports esp.atCipstartUdp(); }
The WiFi module related initialization requires the following steps:
- The Arduino pins used for the WiFi status LEDs (i.e., defined by
the
WIFI_ACTIVE_LED_PIN
andWIFI_DISABLED_LED_PIN
constants) are set to output. - The Arduino pins used to control the CH_PD and RESET lines of
the ESP8266 module (i.e., defined by the
WIFI_CH_PD_PIN
andWIFI_RESET_PIN constants
) are set to OUTPUT, so we can set them LOW or HIGH depending on the case. These two pins needs to stay HIGH during the normal operation. - Perform a hardware reset by pulling down the WiFi module RESET pin (set it LOW for about 200ms).
- Start UART/Serial communication with the module at 115200 baud rate.
- Wait for the WiFi module to boot, which takes two seconds or more.
- Start UDP communications, and wait for incomming data on all the ports. We could have used only one specific port, but we like to be flexible.
UDP communication is used for the WiFi data transmission between
the Android device and our Weather Station sensor node. The
checkWiFi
method used to check if the WiFi module is in
active state (communicates via UART lines) is shown below:
boolean checkWiFi() { if( esp.at() == ESP8266::Error::NONE) { digitalWrite( WIFI_DISABLED_LED_PIN, LOW); digitalWrite( WIFI_ACTIVE_LED_PIN, HIGH); return true; } else { digitalWrite( WIFI_ACTIVE_LED_PIN, LOW); digitalWrite( WIFI_DISABLED_LED_PIN, HIGH); return false; } }
This method returns true
if the ESP8266 module
responds to AT
command, and false
otherwise. The AT
command is used to check if the
module is active, and it does not represents a real command for the
module. In addition, the checkWiFi
method enables (or
disables) the red/green LED, providing visual indication of the
current WiFi state.
Since the WiFi setup must run only once, when the hardware powers
up, we call the setupWiFi
module inside the Arduino
specific setup
method:
void setup() { // setup WiFi - ESP8266 module setupWiFi(); // add other code here... }
Reading Data from the DHT22 Temperature and Humidity Sensor
The DHT22 sensor provides temperature and humidity values for our sensor node. It has a refresh rate of 0.5Hz, meaning that we can't read data from this sensor faster than once every two seconds. Reading data is fairly easy, because of all the hard code is hidden by the DHTLib library. We write a method to obtain these values and compute the temperature and humidity averaged values.
void updateDHTData() { if ( dht22.read22( DHT22_PIN) == DHTLIB_OK) { avgTemperature = ( avgTemperature * (avgDhtStep - 1) + dht22.temperature) / (float)avgDhtStep; avgHumidity = ( avgHumidity * (avgDhtStep - 1) + dht22.humidity) / (float)avgDhtStep; } }
The latest temperature and humidity values are available by reading
the temperature
and humidity
properties of
the dht22
object. The read22
method is
used to perform a reading when DHT22 sensor is used, but instead,
one can use read11
to get the same effect when the
DHT11 sensor is used. The method returns DHTLIB_OK
when
the reading was successful, and various error codes if problems were
encountered. For simplicity reasons, we ignore the possible errors,
but in a further tutorial we discuss also how to solve such possible
problems.
Reading 5V Supply Voltage Value by Using the Secret Builtin Arduino Voltmeter
Some ATmega MCUs, such as ATmega328/168(p) have a builtin voltage sensor, which can be accessed by the code. This sensor is not very accurate (accuracy is within ±10% range). It uses the builtin 1.1V voltage reference available for these MCUs (some other ATmega MCUs also have a 2.56V internal voltage reference). The following code allows to read the AVcc line voltage, which by default is connected to VCC line of the Arduino board:
float getVcc() { long result; // read 1.1V reference against AVcc ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); // wait for Vref to settle delay(2); // convert ADCSRA |= _BV(ADSC); while (bit_is_set(ADCSRA,ADSC)); result = ADCL; result |= ADCH<<8; // Back-calculate AVcc in mV result = 1103700L / result; // return response in volts return result / 1000.0; }
While this looks complicated, it actually uses a few MCU registers and some bits operations to read the VCC value agains the builtin 1.1V voltage reference, which has a pretty good stability once calibrated. By using a good quality multimeter and a stable voltage source, it is possible to "software calibrate" the internal voltage reference for individual MCUs when needed.
Computing the Free Amounf of RAM
Note: a complete guide related to RAM usage optimization for Arduino MCUs as well as details on how to get the free amount of RAM are described on our blog article Optimize Arduino Memory Usage
One last piece of data we like to collect is the amount of free RAM
available on our Arduino MCU. This can be achieved by calling the
getFreeMCUMemory
method, available as part of the
ESP8266 library. It returns an integer representing the number of
RAM bytes which are not used by the MCU at the moment of calling the
method.
WiFi Communication with the Android Device
All the sensor data we collect needs to be sent to the Android device. The first step in this direction is to listen for data requests from the Android device (periodical data requests are initiated). For this we use the loop method and wait for incomming data:
void loop() { char data[10] = {0}, *ipdData = data; // Incomming data from ESP8266 module // Lengt must be greater than 7 bytes ("+IPD,n:") if ( Serial.available() > 7 && esp.ipd( ipdData) == ESP8266::Error::NONE) { // process the request processRequest( ipdData); } // a small delay before checking againd for WiFi incomming data delay( 25); }
The ipd
method, part of the ESP8266 library is used to
split the received data over WiFi, and retain only the important
parts. The WiFi module sends data in the following format:
+IPD,n,id:ipdData
, where n
is the
length
of the ipdData
, id
is
the communication link ID (an integer between 0 and 4), and
ipdData
represents the relevant data bytes. The first
parameter of the ipd
method is a reference parameter, a
pointer to the received databytes. This method has additional
optional parameters, providing information about the number of
databytes and the connection id. Further, a method named
processRequest
is used to decode the data and perform
required actions.
Since we expect data in the +IPD,n:
format, (the link
id is not used), it makes sense to process data only after receiving
at least 7 bytes. In addition, for this simple project, we expect
only a single databyte, representing the data update request. In a
further version of this project we like to support much more
commands, therefore we use this generic form. Also, for the same
reasons, we define an enumeration which defines the list of the
available commands:
enum class Command { GET_ALL_SENSORS_DATA = 97 };
The data we receive via TX line of the ESP8266 module (so RX line
of our Arduino board) is: +IPD,1:a
. The ascii
representation for 97 (the
Command::GET_ALL_SENSORS_DATA
enumeration literal) is
the char a
.
The processRequest
method code is shown below:
void processRequest( char *data) { char progmemData[150] = {0}; char *pData = progmemData; // first char represents the command char cmd = *(data); switch ( (Command)cmd) { case Command::GET_ALL_SENSORS_DATA: createSensorsDataFromTemplate( pData); esp.atCipsend( pData); break; default: // nothing to do ... break; } }
Its main purpose is to decode the received command and to respond
with the required data. As discussed earlier, we only have one
command, so also only one action case, but this will be extended, so
we use the general structure even for this simplest case. The relevant
code regards the call to createSensorsDataFromTemplate
method. It uses the JSON based data template, replaces the
placeholders with real data and send the response to the Android
device by calling atCipsend
method, part of the ESP8266
library.
void createSensorsDataFromTemplate( char *&data) { char buffTemp[7] = {0}, buffAvgTemp[7] = {0}, buffAvgHum[7] = {0}, buffHum[7] = {0}, buffVcc[5] = {0}, tmpl[140] = {0}; char *pTmpl = tmpl; uint8_t templateLen = -1; // read template from PROGMEM getPMData( SENSORS_DATA_TEMPLATE, pTmpl, templateLen); // create data string from template by replacing // parameters with their actual values from sensors sprintf( data, pTmpl, dtostrf( dht22.temperature, 6, 1, buffTemp), dtostrf( avgTemperature, 6, 1, buffAvgTemp), dtostrf( dht22.humidity, 6, 1, buffHum), dtostrf( avgHumidity, 6, 1, buffAvgHum), dtostrf( getVcc(), 4, 2, buffVcc), getFreeMCUMemory()); }
Using the getPMData
utility method (also part of the
ESP8266 library), the data template string is read from the flash
memory. Replacing the parameters with real values is made by using the
standard sprintf
method. While for a fully fledged C/C++
environment one will use %x.yf
syntax with
sprintf
for floating points numbers, this will not work
with Arduino code. Instead we use dtostrf
to format the
temperature and humidity values (we like values with just one digit
after the decimal point).
Program the Arduino Board
Important: the latest version of the Android
software (v1.6.6+) is required for being able to compile and build
the code for this project. One reason is that it uses C++11 specific
constructs, such as enum class
, and the older Arduino
Software versions does not support C++11. While it can be done also
with older Arduino Software version, this requires to alter some
configuration files, so it may create additional issues.
We need to chose the right Arduino board by using the Tools > Board selection list (within the Arduino IDE). If Arduino Pro Mini board was used, as discussed in this tutorial, we have to choose Arduino Pro or Pro Mini. In addition, one needs to select the communication port (COM port) used for programming the Arduino board, available under Tools > Port menu. Last, click on the arrow button located in the top-left corner to start the compile-and-deploy process.
Note: The Arduino Pro Mini board does not have a builtin auto-reset feature, as found in Arduino Nano, UNO, MEGA2560, and other boards. This means we need to manually push the reset button on the board as soon as the Arduion IDE says uploading. It may take a few trials to get used with the right moment to push the button, but it must be shortly after the IDE says uploading. In addition, you have to disconnect the ESP8266 TX line during the Arduino programming time, otherwise this operation fails. Since we have to do this on every code update, a jumper or a switch can be used to make task easier.
The Android Code
An Android application is used to observe the Weather Station sensor node data. For the development of our Android application, IntelliJ IDEA Ultimate was used. A free community version of this IDE is also available and can be used for our project. As an alternative, one cau use Android Studion.
Android Security Configuration
The first thing you need to do after creating an empty Android
application by using your preferred IDE, is to edit the application
security settings. These can be found in the
AndroidManifest.xml
file Since we need to use WiFi
communication, the following two parameters need to be added:
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.INTERNET"/>
In addition, we like to disable the auto-lock feature of the Android device, as long as this application is active, which requires to set the following permission:
<uses-permission android:name="android.permission.WAKE_LOCK" />
The same AndroidManifest.xml
file defines the Android OS
version required to run this application. We have tested this
application with Android 4.3.1 (API 18) and 4.4.2 (API 19) and 5.0.1
(API 21). Therefore it is safe to set the miminum require version to
API 18 by using the following parameter:
<uses-sdk android:minSdkVersion="18" android:targetSdkVersion="21"/>
While this application may run on Android OS older than 4.3.1 (API 18), our tests with Android 2.3.3 (API 10) failed.
Create the Android User Interface
Creating the Android application user interface can be done by using
the UI editor, or, if you are already familiar with Android, directly
editing the layout file. In our case this file is named
main.xml
and it is located under res/layout
folder.
We use a ScrollView
container as the user interface
parent, to support also small screen devices, with lower physical
resolutions. As layout template, we use TableLayout
, with
two columns: label and value plus measurement unit.
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:fillViewport="true"> <TableLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:id="@+id/main"> <TableRow android:layout_width="fill_parent" android:layout_height="wrap_content"> <!-- table row content --> </TableRow> <!-- more table rows... --> </TableLayout> </ScrollView>
For example, the row used to show the current temperature value is shown below:
<TableRow android:layout_width="fill_parent" android:layout_height="wrap_content"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:text=" Temperature: " android:id="@+id/temperatureLabel"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:text="N/A°C" android:id="@+id/temperatureValueTextView"/> </TableRow>
Each UI element has an android:id
attribute, with an
unique value, used to access the UI element from the Android Java
code. The result user interface is shown in Figure 5.
Write the Android Java Code
We use an Android activity to implement our class. This is the simplest way to create an Android application, specially this is your first Android application.
public class MainActivity extends Activity { // ...here come all the properties and methods... }
For the UDP communication with the Weather Station sensor node we use
Java DatagramSocket
, and initialize it for port 1024
(other ports, starting with 1025, can be used as well). This code
should execute before trying to send any UDP packet over the network.
In this scenario, we request sensor data from the Weather Station
node, once every 10 seconds. Feel free to modify it to another value
if you like:
public class MainActivity extends Activity { int udpPort = 1025; DatagramSocket socket; // other properties.... @Override public void onCreate( Bundle savedInstanceState) { // here are some other initializations (new Thread(new Runnable() { @Override public void run() { try { socket = new DatagramSocket(udpPort); while (true) { if (appInBackground) continue; try { sendUdpData( Commands.GET_ALL_SENSORS_DATA, null); Thread.sleep( 10000); } catch (Exception e) { e.printStackTrace(); } } } catch (Exception e) { e.printStackTrace(); } } })).start(); } }
An anonymous Java thread is used to request periodical data updates.
The 10 seconds delay is obtained by using the Thread.sleep
static method, and the data update request is performed by the
sendUdpData
method.
void sendUdpData( Commands cmd, byte[] params) { try { final DatagramPacket packet; int paramsLength = ( params != null ? params.length : 0); byte data[] = new byte[paramsLength + 1]; byte command[] = new byte[1]; command[0] = cmd.getValue(); System.arraycopy( command, 0, data, 0, command.length); if ( params != null) System.arraycopy(params, 0, data, 1, params.length); packet = new DatagramPacket( data, data.length, getBroadcastAddress(), udpPort); socket.send( packet); } catch( IOException e){ e.printStackTrace(); } }
The sendUdpData
method takes two parameters: the command
to send and its optional parameters. Remember, for the Arduino code, an
enumeration was used to define all the available commands (well, just
one command for now, but this will change later). Now, the same applies
for the Android application: using a Java enumeration, we define the
GET_ALL_SENSORS_DATA
command with the same command code,
97
(this is what the Arduino application expects).
enum Commands { GET_ALL_SENSORS_DATA ( (byte)97); private final byte id; Commands( byte id) { this.id = id; } public byte getValue() { return id; } }
Further, a DatagramPacket
is created, and the command (and
when is the case, the command parameters too) is provided as an array of
bytes (as required by the DatagramPacket
constructor). The
UDP packet is then sent to the Weather Station sensor node. In response,
the sensor node provides a JSON object containing the sensor data required
to update the user interface. Since the UDP communication is asynchronous
(we don't know how long it takes for the request to reach the sensor node
and how long it takes until a response is received), a thread is used to
continuously listen for incoming UDP packets.
public void onCreate( Bundle savedInstanceState) { (new Thread(new Runnable() { @Override public void run() { while (true) { DatagramPacket udpPacket = receiveUdpData( udpPort); if (udpPacket == null) continue; String udpPacketData = new String( udpPacket.getData()); try { JSONObject jsonObj = new JSONObject(udpPacketData); updateUserInterface( jsonObj); } catch ( JSONException e) { e.printStackTrace(); } } } })).start(); } DatagramPacket receiveUdpData( int port) { try { byte[] data = new byte[1024]; DatagramPacket packet = new DatagramPacket( data, data.length); if ( socket == null) return null; socket.receive(packet); return packet; } catch( IOException e){ e.printStackTrace(); return null; } }
The received UDP data (stream of bytes) is converted to a JSON object and
passed to updateUserInterface
method which is responsible to
extract the sensor values and show them in the user interface. We show
only the code which deals with the temperature value, but the same applies
also for humidity, voltage, and the other values (see the full source
code).
void updateUserInterface( final JSONObject jsonObj) { try { final double temperature = jsonObj.getDouble("temperature"); temperatureValueTextView.post(new Runnable() { public void run() { temperatureValueTextView.setText(String.valueOf(temperature) + "°C"); } }); } catch (JSONException e) { e.printStackTrace(); } }
Due to Android API restrictions, a UI component object can be modified
only by the thread who created it. In our case, main application thread is
the one which creates the GUI component objects, while the
updateUserInterface
is invoked by the thread which listen for
UDP data. In such case, the post
method can be used, thus
being able to update the sensor values in the user interface components.
Getting reference to the user interface components is made by using the
corresponding implementation class (e.g. TextView
) and
invoking the findViewById
method, as shown below.
public class MainActivity extends Activity { // some other properties... TextView sensorsDataReceivedTimeTextView; public void onCreate( Bundle savedInstanceState) { sensorsDataReceivedTimeTextView = (TextView) findViewById(R.id.sensorsDataReceivedTimeTextView); } // some other methods... }
One special requirement in our application is to provide a "disable
auto-sleep feature" for the Android device, as long as the application
runs. This means, the device screen stays on as long as the application is
active. To obtain this behavior, we use the addFlags
method
of the window
object (inherited from the
Activity
super class), and provide the corresponding
parameter. In our case, these parameters are defined as literalos of the
WindowManager.LayoutParams
enumeration.
window.addFlags( WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); window.addFlags( WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
Remember, this requires to enable the
android.permission.WAKE_LOCK
permission, by editing the
AndroidManifest.xml
file as shows earlier on this tutorial.
Download the Full Application Code
The Arduino and Android code, as well as the documentation (including the fritzing project and diagrams) are available as open source on github. Feel free to copy or modify them as you like. Please provide a link to our tutorial page if you like to support us.
Further improvements
We can extend our project by considering the following improvements:
- SMS alerts by using a GSM module (costs about 15€). We can receiveSMS alerts on a mobile phone if some measured parameters are not in the predefined limits. For example, if the temperature goes below 0°C, we like to receive an SMS, because this may indicate an issue with the home heating system.
- Improve the code by considering various problematic cases:
- WiFi module does not respond - check the module periodically and perform a hardware reset when needed.
- DHT22 sensor errors - provide a robust code which alert us if the temperature and humidity sensor becomes unstable or is damaged.
- Use the Arduino EEPROM memory to store configuration parameters.
- Improve the hardware design so it allows to be battery powered. This includes to replace our linear voltage regulator for the 3.3V line with a more power consumption efficient one ( a switch mode based one).
- Allow to use solar energy to charge the batteries for Weather Station nodes (specially for the ones used outside, under direct sunlight).
- Add soil moisture sensors, providing indication about the right moment to water our plants.
- Add a light intensity sensor, providing a better way to create statistics related to temperature values with respect to day and night. Altough this can be done in the Android software, it requires to have your phone connected with the node for most of the time, which is general is not the case.
- Add a real time clock (RTC) for our node, further improving the various statistics about measured environment characteristics.
Stay tuned! All these improvements are discussed in our further tutorials.