Building a WiFi-connected weather station with an Android user interface for less than 30 Euro

mdiaconescu's picture

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:

Figure 1: The Complete Breadboard Design for the Weather Station Hardware.

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.

Figure 2: The Complete Schematic Design for Weather Station Hardware.

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..

Figure 3: The 3.3V Power Supply on a Breadboard.
Figure 4: The Schematics for the 3.3V Power Supply.

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, when Vout ~= 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

In the tables below, a summary of the hardware components connection is shown:
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:

  1. The Arduino pins used for the WiFi status LEDs (i.e., defined by the WIFI_ACTIVE_LED_PIN and WIFI_DISABLED_LED_PIN constants) are set to output.
  2. 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 and WIFI_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.
  3. Perform a hardware reset by pulling down the WiFi module RESET pin (set it LOW for about 200ms).
  4. Start UART/Serial communication with the module at 115200 baud rate.
  5. Wait for the WiFi module to boot, which takes two seconds or more.
  6. 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.

Figure 5: Android Application User Interface.

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.

Tags: