Introduction: IoT Made Easy: Capturing Remote Weather Data: UV and Air Temperature & Humidity

About: Engineer, writer and forever student. Passionate to share knowledge of electronics with focus on IoT and robotics.

On this tutorial, we will capture remote data as UV (Ultra-Violet radiation), air temperature and humidity. Those data will be very important and will be used in a future complete Weather Station.

The block Diagram shows what we will get at the end.

Step 1: BoM - Bill of Material

NodeMCU (ESP8266-12E) - USD 9.00

Humidity and Temperature Sensor (DHT22) - USD10.00

UV Sensor - USD 4.00

OLED USD 12.00

Breadboard - USD1.00

Step 2: The Analog UV Sensor

This UV sensor generates an analog output proportional to Ultra-Violet radiation found on the light-sensing spectrum. It uses a UV photodiode (based on Gallium Nitride), which can detect the 240-370nm range of light (which covers UVB and most of UVA spectrum). The signal level from the photodiode is very small, in the nano-ampere level, so the module has embedded an operational amplifier to amplify the signal to a more readable volt-level (0 to 1V).

The sensor and op-amp can be powered, by connecting VCC to 3.3VDC (or 5VDC) and GND to power ground. The analog signal can be gotten from the OUT pin.

Its Output will be in millivolts and will be read by the Analog Input of our NodeMCU. Once read, we should "convert" (or " map") it for values to be better handled by the code. We can do it with the function readSensorUV():

/* Read UV Sensor in mV and call UV index calculation */
void readSensorUV()
{                    
  byte numOfReadings = 5;
  dataSensorUV = 0;
  for (int i=0; i< numOfReadings; i++) 
  {
    dataSensorUV += analogRead(sensorUVPin);
    delay (200);
  }
  dataSensorUV /= numOfReadings;
  dataSensorUV = (dataSensorUV * (3.3 / 1023.0))*1000;
  Serial.println(dataSensorUV);
  indexCalculate();
}

Once we have the UV data we can easily calculate the UV index as defined in the above table. The function indexCalculate() will do it for us:

/*  UV Index calculation */
void indexCalculate()
{
    if (dataSensorUV < 227) indexUV = 0;
    else if (227 <= dataSensorUV && dataSensorUV < 318) indexUV = 1;
    else if (318 <= dataSensorUV && dataSensorUV < 408) indexUV = 2;
    else if (408 <= dataSensorUV && dataSensorUV < 503) indexUV = 3;
    else if (503 <= dataSensorUV && dataSensorUV < 606) indexUV = 4;    
    else if (606 <= dataSensorUV && dataSensorUV < 696) indexUV = 5;
    else if (696 <= dataSensorUV && dataSensorUV < 795) indexUV = 6;
    else if (795 <= dataSensorUV && dataSensorUV < 881) indexUV = 7; 
    else if (881 <= dataSensorUV && dataSensorUV < 976) indexUV = 8;
    else if (976 <= dataSensorUV && dataSensorUV < 1079) indexUV = 9;  
    else if (1079 <= dataSensorUV && dataSensorUV < 1170) indexUV = 10;
    else indexUV = 11;       
}

Step 3: Installing a Display: OLED

For test purposes, we will include an OLED on our UV meter (This step is completely optional).

It is OK during tests, to use the Serial Monitor, but what's happening when you are using your prototypes far from your PC in a stand-alone mode? For that, let's install an OLED display, the SSD1306, which main characteristics are:

  • Display size: 0.96"
  • I2C IIC SPI Serial
  • 128X64
  • White OLED LCD LED

Follow the electrical diagram and connect the 4 pins of our OLED:

  • VCC goes to 3.3V
  • GND goes to ground
  • SCL goes to NodeMCU (GPIO 2) ==>D4
  • SDA goes to NodeMCU (GPIO 0) ==>D3

Once we have connected the display, let's download and install its library on our Arduino IDE: the "ESP8266 OLED Driver for SSD1306 display" developed by Daniel Eichhorn (Make sure that you use Version 3.0.0 or bigger!).

Install the library on your Arduino IDE, that can be found on SSD1306Wire.h

Once you restarted the IDE, the library should be already installed.

The library supports I2C protocol to access the OLED display using the built-in Wire.h library:

/* OLED */
#include "SSD1306Wire.h"
#include "Wire.h"
const int I2C_DISPLAY_ADDRESS = 0x3c;
const int SDA_PIN = 0;
const int SCL_PIN = 2;
SSD1306Wire display(I2C_DISPLAY_ADDRESS, SDA_PIN, SCL_PIN);

Let's list some important API that will be used with our OLED display. The complete list can be found at the GITHub provided above.

A. Display Control:

void init(); // Initialise the display
void displayOn(void); // Turn the display on
void displayOff(void); // Turn the display offs
void clear(void); // Clear the local pixel buffer
void flipScreenVertically(); // Turn the display upside down

B. Text Operations:

void drawString(int16_t x, int16_t y, String text); // (xpos, ypos, "Text")
void setFont(const char* fontData);  // Sets the current font. 
<p>Available default fonts: </p><p><ul><li>ArialMT_Plain_10, <li>ArialMT_Plain_16, <li>ArialMT_Plain_24</ul></p>

Once the both the OLED itself and its Library are installed, let's write a simple program to test it. Enter with bellow code on your IDE, the result should be a display as shown in the above photo:

* OLED */
#include "SSD1306Wire.h"
#include "Wire.h"
const int I2C_DISPLAY_ADDRESS = 0x3c;
const int SDA_PIN = 0;
const int SCL_PIN = 2;
SSD1306Wire display(I2C_DISPLAY_ADDRESS, SDA_PIN, SCL_PIN);

void setup () 
{
  Serial.begin(115200); 
  displaySetup();
}

void loop() 
{ 
}

/* Initiate and display setup data on OLED */
void displaySetup()
{
  display.init();         // initialize display
  display.clear();        // Clear display
  display.flipScreenVertically(); // Turn the display upside down
  display.display();      // Put data on display
  
  Serial.println("Initiating Display Test");
  
  display.setFont(ArialMT_Plain_24);
  display.drawString(30, 0, "OLED");  // (xpos, ypos, "Text")
  display.setFont(ArialMT_Plain_16);
  display.drawString(18, 29, "Test initiated");
  display.setFont(ArialMT_Plain_10);
  display.drawString(10, 52, "Serial BaudRate:");
  display.drawString(90, 52, String(11500));
  display.display();  // Put data on display
  delay (3000);
}

The above program can be downloaded from my GitHub:

NodeMCU_OLED_Test

Step 4: A Local UV Meter

Now, with the OLED display installed, we can connect a battery and do some remote tests using our "UV Meter"

#define SW_VERSION "UV_Sensor_V.1" 

/* UV Sensor */
#define sensorUVPin A0
int dataSensorUV = 0;
int indexUV = 0;

/* OLED */
#include "SSD1306Wire.h"
#include "Wire.h"
const int I2C_DISPLAY_ADDRESS = 0x3c;
const int SDA_PIN = 0;
const int SCL_PIN = 2;
SSD1306Wire display(I2C_DISPLAY_ADDRESS, SDA_PIN, SCL_PIN);

void setup()
{
  Serial.begin(115200);
  displaySetup();
}

void loop()
{                    
  readSensorUV();
  displayUV();
  delay (1000);
}

/* Initiate and display setup data on OLED */
void displaySetup()
{
  display.init();                 // initialize display
  display.clear();                // Clear display
  display.flipScreenVertically(); // Turn the display upside down
  display.display();              // Put data on display

  Serial.println("Initiating UV Sensor Test");
  display.setFont(ArialMT_Plain_24);
  display.drawString(10, 0, "MJRoBot");  
  display.setFont(ArialMT_Plain_16);
  display.drawString(0, 29, "UV Sensor Test");
  display.setFont(ArialMT_Plain_10);
  display.drawString(0, 52, "SW Ver.:");
  display.drawString(45, 52, SW_VERSION);
  display.display();
  delay (3000);
}

/* Read UV Sensor in mV and call UV index calculation */
void readSensorUV()
{                    
  byte numOfReadings = 5;
  dataSensorUV = 0;
  for (int i=0; i< numOfReadings; i++) 
  {
    dataSensorUV += analogRead(sensorUVPin);
    delay (200);
  }
  dataSensorUV /= numOfReadings;
  dataSensorUV = (dataSensorUV * (3.3 / 1023.0))*1000;
  Serial.println(dataSensorUV);
  indexCalculate();
}

/*  UV Index calculation */
void indexCalculate()
{
    if (dataSensorUV < 227) indexUV = 0;
    else if (227 <= dataSensorUV && dataSensorUV < 318) indexUV = 1;
    else if (318 <= dataSensorUV && dataSensorUV < 408) indexUV = 2;
    else if (408 <= dataSensorUV && dataSensorUV < 503) indexUV = 3;
    else if (503 <= dataSensorUV && dataSensorUV < 606) indexUV = 4;    
    else if (606 <= dataSensorUV && dataSensorUV < 696) indexUV = 5;
    else if (696 <= dataSensorUV && dataSensorUV < 795) indexUV = 6;
    else if (795 <= dataSensorUV && dataSensorUV < 881) indexUV = 7; 
    else if (881 <= dataSensorUV && dataSensorUV < 976) indexUV = 8;
    else if (976 <= dataSensorUV && dataSensorUV < 1079) indexUV = 9;  
    else if (1079 <= dataSensorUV && dataSensorUV < 1170) indexUV = 10;
    else indexUV = 11;       
}

/*  Display UV Values on local OLED*/
void displayUV()
{
  display.clear();
  display.setFont(ArialMT_Plain_16);
  display.drawString(20, 0, "UV Sensor");
  display.drawString(0, 23, "UV (mV):" );
  display.drawString(80, 23, String(dataSensorUV));
  display.drawString(0, 48, "UV Index:" );
  display.setFont(ArialMT_Plain_24);
  display.drawString(82, 42, String(indexUV));
  display.display();
}

The above code can be downloaded from my GitHun: NodeMCU_UV_Sensor_OLED.ino

Step 5: Installing a DHT22 for Air Temperature and Humidity Measurements

One of the most used sensors for capturing weather data is the DHT22 (or it's brother DHT11), a digital relative humidity and temperature sensor. It uses a capacitive humidity sensor and a thermistor to measure the surrounding air and spits out a digital signal on the data pin (no analog input pins needed).

The sensor should be powered between 3.3V and 5V and will work from -40oC to +80oC with an accuracy of +/- 0.5oC for temperature and +/-2% for relative Humidity. It is also important to have in mind that its sensing period is in average 2 seconds (minimum time between readings). The site of Adafruit provides a lot of information about both, DHT22 and its brother DHT11. For more details, please visit DHT22/11 Tutorial page .

The DHT22 has 4 pins (facing the sensor, pin 1 is the most left) :

  1. VCC (we will connect to 3.3V from NodeMCU);
  2. Data out;
  3. Not connected and
  4. Ground.

Once usually you will use the sensor on distances less than 20m, a 10K resistor should be connected between Data and VCC pins. The Output pin will be connected to NodeMCU pin D3 (see the diagram above). Once the sensor is installed at our module, download the DHT library from Adafruit GitHub repository and install it in your Arduino's Library file. Once you reload your Arduino IDE, the "DHT sensor library" should be installed.

At beginning of the code, we must include the lines:

/* DHT22*/
#include "DHT.h"
#define DHTPIN D2  
#define DHTTYPE DHT22 
DHT dht(DHTPIN, DHTTYPE);
float hum = 0;
float temp = 0;

A new function will be created to read the sensor:

/* Get DHT data */
void getDhtData(void)
{
  float tempIni = temp;
  float humIni = hum;
  temp = dht.readTemperature();
  hum = dht.readHumidity();
  if (isnan(hum) || isnan(temp))   // Check if any reads failed and exit early (to try again).
  {
    Serial.println("Failed to read from DHT sensor!");
    temp = tempIni;
    hum = humIni;
    return;
  }
}

The complete code including the UV and DHT sensors can be downloaded from my GitHub: NodeMCU_UV_DHT_Sensor_OLED

Step 6: Sending Data to ThingSpeak.com

So far, we have only used the NodeMCU ESP12-E as a regular and ordinary Arduino board. Of course, we have only "scratched" the real potential of this spectacular little chip and now is the time to take-off to heaven! Or better to the stars! Ehr...to the cloud! ;-)

Let's begin!

  1. First, you must have an account at ThinkSpeak.com
  2. Follow the instructions to create a Channel and take note of your Channel ID and Write API Key
  3. Update the below code with your WiFi network and Thinkspeak credentials
  4. Run the program on IDE

Let's comment the code most important parts:

First, let's call the ESP8266 library, define the WiFi client and define your local Router and Thinkspeak credentials:

/* ESP12-E & Thinkspeak*/
#include <ESP8266WiFi.h>
WiFiClient client;
const char* MY_SSID = "YOUR SSD ID HERE";
const char* MY_PWD = "YOUR PASSWORD HERE";
const char* TS_SERVER = "api.thingspeak.com";
String TS_API_KEY ="YOUR CHANNEL WRITE API KEY";

Second, let's include a very important library for IoT projects: SimpleTimer.h:

/* TIMER */
#include <SimpleTimer.h>
SimpleTimer timer;

Third, during setup(), we will initiate serial communication, call the function connectWiFi() and define the timers. Note that the line of code: timer.setInterval(60000L, sendDataTS); will call the function sendDataTS() every 60 seconds, in order to upload data to ThinkSpeak channel.

void setup() 
{
  ...
  Serial.begin(115200);
  delay(10);
  ...
  connectWifi();
  timer.setInterval(60000L, sendDataTS);
  ...
}

At last but not least, during the loop(), the only command needed is to initiate the timer and that's it!

void loop() 
{
  ...
  timer.run(); // Initiates SimpleTimer
}

Below, you can see the two important functions used to handle Thinkspeak communication:

ESP12-E connection with your WiFi network:

/***************************************************
 * Connecting WiFi
 **************************************************/
void connectWifi()
{
  Serial.print("Connecting to "+ *MY_SSID);
  WiFi.begin(MY_SSID, MY_PWD);
  while (WiFi.status() != WL_CONNECTED) 
  {
    delay(1000);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi Connected");
  Serial.println("");  
}

ESP12-E sending data to ThinkSpeak:

/***************************************************
 * Sending Data to Thinkspeak Channel
 **************************************************/
void sendDataTS(void)
{
   if (client.connect(TS_SERVER, 80)) 
   { 
     String postStr = TS_API_KEY;
     postStr += "&field1=";
     postStr += String(dataSensorUV);
     postStr += "&field2=";
     postStr += String(indexUV);
     postStr += "&field3=";
     postStr += String(temp);
     postStr += "&field4=";
     postStr += String(hum);

     postStr += "\r\n\r\n";
   
     client.print("POST /update HTTP/1.1\n");
     client.print("Host: api.thingspeak.com\n");
     client.print("Connection: close\n");
     client.print("X-THINGSPEAKAPIKEY: " + TS_API_KEY + "\n");
     client.print("Content-Type: application/x-www-form-urlencoded\n");
     client.print("Content-Length: ");
     client.print(postStr.length());
     client.print("\n\n");
     client.print(postStr);
     delay(1000); 
   }
   sent++;
   client.stop();
}

The complete code can be found on my GitHub: NodeMCU_UV_DHT_Sensor_OLED_TS_EXT

Once you have the code uploaded to your NodeMCU. Let's connect an external battery and do some measurement under the sun. I put the Remote Station on the roof and start capturing data on ThingSpeak.com as shown in above photos.

Step 7: Conclusion

As always, I hope this project can help others find their way into the exciting world of electronics!

For details and final code, please visit my GitHub depository: RPi-NodeMCU-Weather-Station

For more projects, please visit my blog: MJRoBot.org

Stay tuned! Next tutorial we will send data from a remote weather station to a central one, based on a Raspberry Pi Web server:

Saludos from the south of the world!

See you in my next instructable!

Thank you,

Marcelo