ESP32 based Advanced Weather Station with BME280 and Live Weather data

ESP32 based Advanced Weather Station

In this project, we will learn how to create a weather station, which will display reading from a BME280 module and live weather data from OpenWeatherMap API in a webserver. The device will get temperature, humidity, barometric pressure, and altitude from the BME280 sensor and external temperature, humidity, weather condition, and sunrise and sunset from OpenWeatherMap API. We can see those reading in a web browser.

 

BME280 Temperature, Humidity & Pressure Sensor

The BME280 is an integrated environmental sensor developed specifically for mobile applications and wearables where size and low power consumption are key design parameters. The unit combines high linearity and high accuracy sensors and is perfectly feasible for low current consumption and long-term stability. The BME280 sensor offers an extremely fast response time and therefore supports performance requirements for emerging applications such as context awareness, and high accuracy over a wide temperature range.

BME280 Pinout

This sensor can measure relative humidity from 0 to 100% with ±3% accuracy, barometric pressure from 300Pa to 1100 hPa with ±1 hPa absolute accuracy, and temperature from -40°C to 85°C with ±1.0°C accuracy.

The BME280 supports I2C and SPI interfaces. The module we are using supports a voltage range of 1.7 – 3.3V. The BME280 has an average current consumption of 3.6µA when measuring all the parameters at a refresh rate of 1Hz. The sensor can be operated in three power modes: Sleep, normal, and forced mode.

One thing to keep in mind while buying a BME280 module is that a similar-looking module also comes with a BMP280 sensor which lacks the humidity measurement capability, which makes it way cheaper than the BME280. The best way to identify between BME280 and BMP280 sensors is to look at the product code. BME280 sensor will have a product code starting with ‘U’, e.g., UP, UN, etc. While the BMP280 will have a product code starting with “K”, e.g., KN, KP, etc. Another visual difference is that BME280 is somewhat square while BMP280 is rectangular.

BME280 and BMP280

 

OpenWeatherMap API

OpenWeatherMap is an online service, that provides global weather data via API, including current weather data, forecasts, nowcasts, and historical weather data for any geographical location. Through their API we can access current weather data for any location on Earth including over 200,000 cities! They collect and process weather data from different sources such as global and local weather models, satellites, radars, and a vast network of weather stations. Data is available in JSON, XML, or HTML format. We will be using HTTP GET to request the JSON data for this project.

 

Weather Station Circuit Diagram

Weather Station Circuit Diagram

Connections are fairly simple. Follow the connection diagram above. We have added a 18650 battery and TP4056 charging module with protection for the backup. Start by connecting the negative of the battery to the B- pin of the TP4056 module and the positive to the B+ pin of the module. Now connect the OUT+ of the module to the VIN pin of ESP32 Devkit and B- to the GND pin. The TP4056 module also contains the DW01 protection chip which will protect the battery from over-discharge and short circuits.

Now let’s connect the BME280 module to the ESP32 Devkit. Connect the VCC pin of BME280 to the 3.3V pin and GND to the GND pin. The module supports both SPI and I2C. We will be using the I2C interface for communication. For that connect the SDA pin to D22 and the SCL pin to D21 of the ESP32 Devkit.

ESP32 based Weather Station

 

Code Explanation and Arduino IDE Installing Libraries

For this project, we will need to install several Arduino libraries and it can be done directly via the library manager. To install the libraries, navigate to the Arduino IDE > Sketch > Include Library > Manage Libraries Wait for the Library Manager to download the libraries index and update the list of installed libraries. Then search and install the following libraries.

  • Adafruit BME280 Library
  • Adafruit Unified Sensor
  • Arduino_JSON Library

Install the following libraries using the zip file.

 

Installing ESP32 Filesystem Uploader in Arduino IDE

We are using SPIFFS for hosting the webserver files. WE can use the ESP32 flash storage like a normal file system just like in an SD card or USB mass storage with the help of SPIFFS To upload these files to the filesystem we will need a tool called ESP32 filesystem uploader. To install this tool to your Arduino IDE please follow the following steps

  • Go to the GitHub releases page and download the ESP32FS-1.0.zip.
  • Extract the zip file to the Tools folder in the Arduino IDE directory. After extracting the path would look like this “C:\Users\Username\Documents\Arduino\tools\ESP32FS\tool\ esp32fs.jar”
  • Restart the Arduino IDE and you can use this tool by clicking on the Tools Menu.

Installing ESP32 Filesystem Uploader in Arduino IDE

 

Webserver File Structure

As we already said we will be hosting the webserver using the ESP32 file system. The file system structure will look like this. All the data that needed to be loaded to the filesystem are stored in a folder called data, within the sketch folder.

Webserver File Structure

 

HTML File

The index.html file is the main file loaded when the server gets a request. It contains all the structural programming for the webpage. Here is the code for the HTML file.

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="icon" type="image/png" href="icons/favicon.png">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
    <link rel="stylesheet" href="css/style.css">
    <link rel="stylesheet" href="fonts/quartz.ttf">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/107/three.min.js"></script>
</head>
<body>
    <div id="bg_mob">
        <div class="mob_inner">
            <div class="sec1">
                <div class="weather_img">
                    <img id= "weather_icon" src="icons/unknown.png" />
                </div>
                <div class="weather_text">
                    <p>Sunrise <span class="font_fam"id="Sunrise"></span></p>
                    <p>Sunset &nbsp<span class="font_fam"id="Sunset"></span></p>
                </div>
                <div class="clr"></div>
            </div>

            <article class="sec2">
                <div class="fancy">
                    <p class="subtitle">
                        <span>Outdoor<br>Temperature</span>
                    </p>
                    <div class="temp_num">
                        <div>
                            <span class="wob_t" id="out_temperature"></span><span class="point_para">. </span><span class="point_para" id="out_temperatureD"></span><span class="celsius"> &#8451;</span>
                            <!-- <span class="wob_per" aria-disabled="true" role="button">°F</span> -->
                        </div>
                    </div>
                </div>
                <div class="fancy">
                    <p class="subtitle">
                        <span>Outdoor<br>Humidty</span>
                    </p>
                    <div class="temp_num">
                        <div>
                            <span class="wob_t" id="out_humidity"></span><span class="wob_per" aria-label="" aria-disabled="true" role="button">%</span>
                            <!-- <span class="wob_per" aria-label="°Celsius" aria-disabled="true" role="button">%</span> -->
                        </div>
                    </div>
                </div>
                <div class="clr"></div>
            </article>

            <article class="sec3">
                <div class="fancy fancy2_sec">
                    <p class="subtitle">
                        <span>Indoor</span>
                    </p>
                    <div class="temp_num temp_num2">
                        <div>
                            <span class="wob_t" id="in_temperature"></span><span class="point_para">. </span><span class="point_para" id="in_temperatureD"></span><span class="celsius"> &#8451;</span>
                            <!-- <span class="wob_per" aria-disabled="true" role="button">°F</span> -->
                        </div>
                    </div>
                </div>
                <div class="fancy fancy2_sec">
                    <p class="subtitle">
                        <span>Humidity</span>
                    </p>
                    <div class="temp_num temp_num2">
                        <div>
                            <span class="wob_t" id="in_humidity"></span><span class="wob_per" aria-label="" aria-disabled="true" role="button">%</span>
                            <!-- <span class="wob_per" aria-label="°Celsius" aria-disabled="true" role="button">%</span> -->
                        </div>
                    </div>
                </div>
                <div class="clr"></div>
            </article>

            <article class="sec4">
                <div class="fancy fancy2_sec">
                    <p class="subtitle">
                        <span>Pressure</span>
                    </p>
                    <div class="temp_num temp_num3">

                        <div>
                            <span class="wob_t" id="in_presssure"></span>
                            <!-- <span class="wob_per" aria-disabled="true" role="button">°F</span> -->
                        </div>
                        <div class="pre_alt"><span>Pa</span></div>
                    </div>
                </div>
                <div class="fancy fancy2_sec">
                    <p class="subtitle">
                        <span>Altitude</span>
                    </p>
                    <div class="temp_num temp_num3">
                        <div>
                            <span class="wob_t" id="in_altitude"></span>
                            <!-- <span class="wob_per" aria-disabled="true" role="button">°F</span> -->
                        </div>
                        <div class="pre_alt"><span>m</span></div>
                    </div>
                </div>
                <div class="clr"></div>
            </article>
        </div>
    </div>
    <script src="script.js"></script>
</body>
</html>

 

CSS File

The style.css file is used to define a style for the HTML page. With CSS, we can control the colour, font, text size, spacing between elements, how elements are positioned and laid out, what background images or background colours are to be used, different displays for different devices and screen sizes, and much more!

body{
    margin:0;
    padding: 0;
    color:white;
    font-family: roboto;
    position: relative;
}
p{margin:0;padding:0;}

@font-face {
  font-family: quartz;
  src: url(fonts/quartz.ttf);
}

#bg_mob{
    background-color: black;
    max-width: 540px;
    margin:0 auto;
    height:100vh;
}

.mob_inner{
    padding: 15px;
}
.sec1,.sec2,.sec3,.sec4{
  width: 100%;margin: 15px 0 0;
}
.weather_img,.weather_text{
    float: left;
}
.weather_img{
  width: 36%;
}
.weather_img img{
  width: 100%;
  max-width: 120px;
}
.weather_text{
    text-align: right;
    width: 64%;
    font-size: 22px;
    margin-top: 19px;
    line-height: 26px;
}
.clr{
    clear:both;
}
.fancy {
    text-align: center;
    width: 50%;
    float: left;
}
.fancy2_sec .subtitle{
    line-height: 32px;
}
.fancy p span {
    display: inline-block;
    position: relative;
    color: #ababab;
}
.fancy p span:before {
    right: 100%;
    margin-right: 15px;
}
.fancy p span:after {
    left: 100%;
    margin-left: 15px;
}
.fancy p span:before, .fancy p span:after {
    content: "";
    position: absolute;
    height: 15px;
    border-bottom: 2px solid #ababab;
    top: 0;
    width: 35px;
}
.wob_t{
    font-family: quartz;
    font-size:100px;
}
.pre_alt{
    color: #ababab;
}
.pre_alt span{
    font-family: roboto;
    font-size: 15px;
}
.font_fam{
    font-family: quartz;
    margin-left: 8px;
    text-align: left;
}
.temp_num,.temp_num2,.temp_num3{margin-top:0px;}

.temp_num3 .wob_t{font-size: 32px;}

.point_para{font-size: 32px;letter-spacing: -4px;font-family: quartz;}

.wob_per{font-size: 22px;font-family: roboto;}

.celsius{
    vertical-align: top;
    margin-left: -16px;
    margin-top: 22px;
    display: inline-block;
}

 

JavaScript File

The Scirpt.js file is responsible for image manipulation, JSON decoding, and dynamic changes in content. This helps us to update the values on the webpage. It controls the event listeners for data updates.

// Create events for the sensor readings
if (!!window.EventSource) {
  var source = new EventSource('/events');

  source.addEventListener('open', function(e) {
    console.log("Events Connected");
  }, false);

  source.addEventListener('error', function(e) {
    if (e.target.readyState != EventSource.OPEN) {
      console.log("Events Disconnected");
    }
  }, false);

  source.addEventListener('BME_readings', function(e) {
    //console.log("gyro_readings", e.data);
    var obj = JSON.parse(e.data);
    document.getElementById("in_temperature").innerHTML = obj.in_temperature;
    document.getElementById("in_temperatureD").innerHTML = obj.in_temperatureD;
    document.getElementById("in_humidity").innerHTML = obj.in_humidity;
    document.getElementById("in_presssure").innerHTML = obj.in_presssure;
    document.getElementById("in_altitude").innerHTML = obj.in_altitude;

  }, false);
  source.addEventListener('OWM_readings', function(e) {
    //console.log("gyro_readings", e.data);
    var obj = JSON.parse(e.data);
    document.getElementById("out_temperature").innerHTML = obj.out_temperature;
    document.getElementById("out_temperatureD").innerHTML = obj.out_temperatureD;
    document.getElementById("out_humidity").innerHTML = obj.out_humidity;
    document.getElementById("weather_icon").src = obj.weather_icon;
    document.getElementById("Sunset").innerHTML = timeConverter(obj.Sunset);
    document.getElementById("Sunrise").innerHTML = timeConverter1(obj.Sunrise);

  }, false);
}

    //block.setAttribute("style", "text-align:center");
function resetPosition(element){
  var xhr = new XMLHttpRequest();
  xhr.open("GET", "/"+element.id, true);
  console.log(element.id);
  xhr.send();
}

function timeConverter(UNIX_timestamp){
  var a = new Date(UNIX_timestamp * 1000);
  var hour = a.getHours();
  var min = a.getMinutes();
  var time = hour + ':' + min;
  return time;
}
function timeConverter1(UNIX_timestamp){
  var a = new Date(UNIX_timestamp * 1000);
  var hour = a.getHours();
  var min = a.getMinutes();
  var time = '0'+hour + ':' + min;
  return time;
}

 

Arduino Sketch

Here is the Sketch for the ESP32 weather station.

#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <ESPmDNS.h>
#include <Arduino_JSON.h>
#include "SPIFFS.h"
#include <Adafruit_Sensor.h>
#include <Adafruit_BME80.h>
// Create a sensor object
Adafruit_BME280 bme; // use I2C interface
// Replace with your network credentials
const char* ssid = "SKYNET 4G";
const char* password = "jobitjos";
// OPENWEATHER API CONFIGURATION
String openWeatherMapApiKey = "02f3dcd78793160081a1667f73ffcc4d";
String temperature_unit = "metric";
// Replace with your country code and city
String city = "Kannur";
String endpoint;
float OutTemperature ;
int OutHumidity;
String Icon_;
int Sun_rise;
int Sun_set;
String jsonBuffer;
// Create AsyncWebServer object on port 80
AsyncWebServer server(80);
// Create an Event Source on /events
AsyncEventSource events("/events");
// Json Variable to Hold Sensor Readings
JSONVar readings;
JSONVar owmreadings;
// Timer variables
unsigned long lastTime = 0;
unsigned long lastTimeTemperature = 0;
unsigned long lastTimeAcc = 0;
unsigned long SensorDelay = 10;
unsigned long OWMapDelay = 1000;
float in_temperature, in_presssure, in_altitude, in_humidity;
float out_temperature, out_humidity, out_weathe;
int in_temperatureD , out_temperatureD;
sensors_event_t a, g, temp;
// Initialize Sensor
void initSensor() {
  Serial.println(F("BME280 Sensor event test"));
  unsigned status;
  status = bme.begin(0x76);
  if (!status) {
    Serial.println(F("Could not find a valid BME280 sensor, check wiring or "
                     "try a different address!"));
    while (1) delay(10);
  }
}
void initSPIFFS() {
  if (!SPIFFS.begin()) {
    Serial.println("An error has occurred while mounting SPIFFS");
  }
  Serial.println("SPIFFS mounted successfully");
}
// Initialize WiFi
void initWiFi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.println("");
  Serial.print("Connecting to WiFi...");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(1000);
  }
  if (!MDNS.begin("esp32")) {
    Serial.println("Error starting mDNS");
    return;
  }
  MDNS.addService("http", "tcp", 80);
  Serial.println("");
  Serial.println(WiFi.localIP());
  Serial.print("Local Address");
  Serial.println("esp32.local");
}
String httpGETRequest(const char* serverName) {
  WiFiClient client;
  HTTPClient http;
  // Your Domain name with URL path or IP address with path
  http.begin(client, serverName);
  // Send HTTP POST request
  int httpResponseCode = http.GET();
  String payload = "{}";
  if (httpResponseCode > 0) {
    Serial.print("HTTP Response code: ");
    Serial.println(httpResponseCode);
    payload = http.getString();
  }
  else {
    Serial.print("Error code: ");
    Serial.println(httpResponseCode);
  }
  // Free resources
  http.end();
  return payload;
}
String getReadings() {
  int sensetemp;
  sensetemp = bme.readTemperature();
  readings["in_temperature"] = String(sensetemp);
  sensetemp = bme.readTemperature() * 10;
  readings["in_temperatureD"] = String(sensetemp % 10);
  sensetemp = bme.readHumidity();
  readings["in_humidity"] = String(sensetemp);
  readings["in_presssure"] = String(bme.readPressure());
  readings["in_altitude"] = String(bme.readAltitude(1013.25));
  String jsonString = JSON.stringify(readings);
  return jsonString;
}
String getOWMReadings() {
  if (WiFi.status() == WL_CONNECTED) {
    endpoint = "http://api.openweathermap.org/data/2.5/weather?q=" + city + "&units=" + temperature_unit  + "&appid=" + openWeatherMapApiKey;
    jsonBuffer = httpGETRequest(endpoint.c_str());
    JSONVar myObject = JSON.parse(jsonBuffer);
    // JSON.typeof(jsonVar) can be used to get the type of the var
    if (JSON.typeof(myObject) == "undefined") {
      Serial.println("Parsing input failed!");
    }
    Icon_ = myObject["weather"][0]["icon"];
    OutTemperature = double(myObject["main"]["temp"]);
    OutHumidity    = int(myObject["main"]["humidity"]);
    Sun_rise     = int(myObject["sys"]["sunrise"]);
    Sun_set      = int(myObject["sys"]["sunset"]);
  }
  else {
    Serial.println("WiFi Disconnected");
  }
  int sensetemp;
  sensetemp = OutTemperature;
  owmreadings["out_temperature"] = String(sensetemp);
  sensetemp = OutTemperature * 10;
  owmreadings["out_temperatureD"] = String(sensetemp % 10);
  owmreadings["out_humidity"] = String(OutHumidity);
  owmreadings["weather_icon"] = String("icons/" + Icon_ + ".png");
  owmreadings["Sunset"] = String(Sun_set);
  owmreadings["Sunrise"] = String(Sun_rise);
  owmreadings["City"] = String(city);
  owmreadings["Key"] = String(openWeatherMapApiKey);
  String jsonString = JSON.stringify(owmreadings);
  Serial.println(jsonString);
  return jsonString;
}
void setup() {
  Serial.begin(115200);
  initWiFi();
  initSPIFFS();
  initSensor();

  // Handle Web Server
  server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
    request->send(SPIFFS, "/index.html", "text/html");
  });
  server.serveStatic("/", SPIFFS, "/");
  server.on("/reset", HTTP_GET, [](AsyncWebServerRequest * request) {
    in_temperature = 0;
    in_temperatureD = 0;
    in_humidity = 0;
    in_presssure = 0;
    in_altitude = 0;
    out_temperature = 0;
    out_temperatureD = 0;
    out_humidity = 0;
    request->send(200, "text/plain", "OK");
  });
  // Handle Web Server Events
  events.onConnect([](AsyncEventSourceClient * client) {
    if (client->lastId()) {
      Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
    }
    // send event with message "CircuitDigest", id current millis
    // and set reconnect delay to 1 second
    client->send("CircuitDigest!", NULL, millis(), 10000);
  });
  server.addHandler(&events);
  server.begin();
}
void loop() {
  if ((millis() - lastTime) > SensorDelay) {
    // Send Events to the Web Server with the Sensor Readings
    events.send(getReadings().c_str(), "BME_readings", millis());
    lastTime = millis();
  }
  if ((millis() - lastTimeAcc) > OWMapDelay) {
    // Send Events to the Web Server with the Sensor Readings
    events.send(getOWMReadings().c_str(), "OWM_readings", millis());
    lastTimeAcc = millis();
  }
}

Let’s have a look at how the code is get reading from the sensor, gets weather data from the API, and sends it to the webserver.

 

Included Libraries

The WiFi, HTTPClient, AsyncTCP, and ESPAsyncWebServer libraries are used for the wifi communication and web server creation and maintenance.

#include <WiFi.h>
#include <HTTPClient.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

The SPIFFS library is used for the file system management and the Arduino_JSON library is for handling the JSON strings.

#include <Arduino_JSON.h>
#include "SPIFFS.h"

And finally, the Adafruit Sensor library and Adafruit BME280 library are for handling the BME280 sensor.

#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

 

User Changeable Parameters

In the code, there are a few values you need to change to make it work. Replace "Your WiFi SSID" and "Your WiFi Password" with your WiFi SSID and password. Also, change the City code and temperature measurement unit as per yours.

// Replace with your network credentials
const char* ssid = "Your WiFi SSID";
const char* password = "Your WiFi Password";
// OPENWEATHER API CONFIGURATION
String temperature_unit = "metric";
// Replace with your city
String city = "Kannur";
// Replace with your OpenWeatherMap API KEY
String openWeatherMapApiKey = "02f3dcd78459680081a1477f73bbdd4d";

The next and most important parameter is the API key. This Key is unique and this is our authentication key to pull the data from OpenWeatherMap servers. The procedure to get your API key is very simple. Go to the OpenWeatherMap website, and create an account. If you already have an account login into it. And go to the following link  https://home.openweathermap.org/api_keys. On that page, you can see your default API key. If you want you can create your keys later. Copy the API key and paste it into the code. That’s it you are ready to get the data from the OpenWeatherMap server.

OpenWeatherMap Webpage

 

Getting BME280 Readings

The following function is used to get the readings from the BME280.

String getReadings() {
  int sensetemp;
  sensetemp = bme.readTemperature();
  readings["in_temperature"] = String(sensetemp);
  sensetemp = bme.readTemperature() * 10;
  readings["in_temperatureD"] = String(sensetemp % 10);
  sensetemp = bme.readHumidity();
  readings["in_humidity"] = String(sensetemp);
  readings["in_presssure"] = String(bme.readPressure());
  readings["in_altitude"] = String(bme.readAltitude(1013.25));
  String jsonString = JSON.stringify(readings);
  return jsonString;
}

In this function, each reading is taken from the BME280 sensor encoded into a JSON string and, returns the JSON string to the event handler. The event handler will send this data to the web page when the event is triggered.

 

Getting Weather Data from OpenWeatherMap API

For this, we have created another function. Which will get the weather data from the API in the JSON format, decode it, arrange it into the format that we need. Then similar to the BME280 reading function, this function will code these into a JSON string and will send it to the server when the particular event is triggered. HTTP GET is used to get the data from the server.

String getOWMReadings() {
 
  if (WiFi.status() == WL_CONNECTED) {
    endpoint = "http://api.openweathermap.org/data/2.5/weather?q=" + city + "&units=" + temperature_unit  + "&appid=" + openWeatherMapApiKey;
    jsonBuffer = httpGETRequest(endpoint.c_str());
    JSONVar myObject = JSON.parse(jsonBuffer);
    // JSON.typeof(jsonVar) can be used to get the type of the var
    if (JSON.typeof(myObject) == "undefined") {
      Serial.println("Parsing input failed!");
    }
    Icon_ = myObject["weather"][0]["icon"];
    OutTemperature = double(myObject["main"]["temp"]);
    OutHumidity    = int(myObject["main"]["humidity"]);
    Sun_rise     = int(myObject["sys"]["sunrise"]);
    Sun_set      = int(myObject["sys"]["sunset"]); 
  }
  else
{
    Serial.println("WiFi Disconnected");
  }
  int sensetemp;
  sensetemp = OutTemperature;
  owmreadings["out_temperature"] = String(sensetemp);
  sensetemp = OutTemperature * 10;
  owmreadings["out_temperatureD"] = String(sensetemp % 10);
  owmreadings["out_humidity"] = String(OutHumidity);
  owmreadings["weather_icon"] = String("icons/"+Icon_+".png");
  owmreadings["Sunset"] = String(Sun_set);
  owmreadings["Sunrise"] = String(Sun_rise);
  owmreadings["City"] = String(city);
  owmreadings["Key"] = String(openWeatherMapApiKey);
  String jsonString = JSON.stringify(owmreadings);
  Serial.println(jsonString);
  return jsonString; 
}

 

Web Setup and Event request Handlings 

The following section in the setup function will create and set up the web server and also handle the event request when a web page is loading. The first event handler will send the webpage data when a request to load the web page is received. The second event handler will listen for reset requests and will rest all the variables displayed on the page to zero. The third even handler is to check the client connectivity and if the client disconnects the server will try to re-establish the connection in a one-second interval.

// Handle Web Server
  server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
    request->send(SPIFFS, "/index.html", "text/html");
  });
  server.serveStatic("/", SPIFFS, "/");
  server.on("/reset", HTTP_GET, [](AsyncWebServerRequest * request) {
    in_temperature = 0;
    in_temperatureD = 0;
    in_humidity = 0;
    in_presssure = 0;
    in_altitude = 0;
    out_temperature = 0;
    out_temperatureD = 0;
    out_humidity = 0;
    request->send(200, "text/plain", "OK");
  });
  // Handle Web Server Events
  events.onConnect([](AsyncEventSourceClient * client) {
    if (client->lastId()) {
      Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
    }
    // send event with message "CircuitDigest", id current millis
    // and set reconnect delay to 1 second
    client->send("CircuitDigest!", NULL, millis(), 10000);
  });
  server.addHandler(&events);
  server.begin();

In the loop function, you can also see two other event handlers. These handlers will not listen to any requests. These will send the data periodically at a set time. This will refresh the data on the web page. The data from the BME280 is refreshed more rapidly than the Open Weather Map data.

void loop() {
  if ((millis() - lastTime) > SensorDelay) {
    // Send Events to the Web Server with the Sensor Readings
    events.send(getReadings().c_str(), "BME_readings", millis());
    lastTime = millis();
  }
  if ((millis() - lastTimeAcc) > OWMapDelay) {
    // Send Events to the Web Server with the Weather data
    events.send(getOWMReadings().c_str(), "OWM_readings", millis());
    lastTimeAcc = millis();
  }
}

 

Uploading the Code and Files

Uploading the code is straightforward. Just like any other Arduino board connect the ESP32 Devkit to the PC. Select the correct Board and Port, click upload. That’s it. To upload the Web Server files, make sure that all the files are in the folder named data, which is in the same folder as the .ino file. Go to Tools -> ESP32 Sketch Data Upload. The IDE will upload the files and reboot the ESP32. While uploading the files, make sure that the serial monitor is not opened. If it is opened you will get an error. If so, just close the serial monitor and upload the files again. You can download the sketch and webserver files from the following link.

 

Accessing Web Server

Now once the code is compiled and programmed into the ESP32, you can either check the serial monitor which will print out the IP address of ESP32 once it’s connected to the WiFi network.

Accessing Web Server

Or you can check your router and what’s the IP, that’s allocated to the ESP32. This will vary depending on your router. Once you got the IP you can access the webserver by simply entering this address in the web browser and going to the page. And here is the result.

DIY Weather Station with Live Weather Data                            

The weather condition icon will change according to the data received from the API. The sunrise and sunset time are also pulled from the OWM API.

Code

Here is the Sketch for the ESP32 weather station.

#include <Arduino.h>

#include <WiFi.h>

#include <HTTPClient.h>

#include <AsyncTCP.h>

#include <ESPAsyncWebServer.h>

#include <ESPmDNS.h>

#include <Arduino_JSON.h>

#include "SPIFFS.h"

#include <Adafruit_Sensor.h>

#include <Adafruit_BME80.h>

// Create a sensor object

Adafruit_BME280 bme; // use I2C interface

// Replace with your network credentials

const char* ssid = "SKYNET 4G";

const char* password = "jobitjos";

// OPENWEATHER API CONFIGURATION

String openWeatherMapApiKey = "02f3dcd78793160081a1667f73ffcc4d";

String temperature_unit = "metric";

// Replace with your country code and city

String city = "Kannur";

String endpoint;

float OutTemperature ;

int OutHumidity;

String Icon_;

int Sun_rise;

int Sun_set;

String jsonBuffer;

// Create AsyncWebServer object on port 80

AsyncWebServer server(80);

// Create an Event Source on /events

AsyncEventSource events("/events");

// Json Variable to Hold Sensor Readings

JSONVar readings;

JSONVar owmreadings;

// Timer variables

unsigned long lastTime = 0;

unsigned long lastTimeTemperature = 0;

unsigned long lastTimeAcc = 0;

unsigned long SensorDelay = 10;

unsigned long OWMapDelay = 1000;

float in_temperature, in_presssure, in_altitude, in_humidity;

float out_temperature, out_humidity, out_weathe;

int in_temperatureD , out_temperatureD;

sensors_event_t a, g, temp;

// Initialize Sensor

void initSensor() {

  Serial.println(F("BME280 Sensor event test"));

  unsigned status;

  status = bme.begin(0x76);

  if (!status) {

    Serial.println(F("Could not find a valid BME280 sensor, check wiring or "

                     "try a different address!"));

    while (1) delay(10);

  }

}

void initSPIFFS() {

  if (!SPIFFS.begin()) {

    Serial.println("An error has occurred while mounting SPIFFS");

  }

  Serial.println("SPIFFS mounted successfully");

}

// Initialize WiFi

void initWiFi() {

  WiFi.mode(WIFI_STA);

  WiFi.begin(ssid, password);

  Serial.println("");

  Serial.print("Connecting to WiFi...");

  while (WiFi.status() != WL_CONNECTED) {

    Serial.print(".");

    delay(1000);

  }

  if (!MDNS.begin("esp32")) {

    Serial.println("Error starting mDNS");

    return;

  }

  MDNS.addService("http", "tcp", 80);

  Serial.println("");

  Serial.println(WiFi.localIP());

  Serial.print("Local Address");

  Serial.println("esp32.local");

}

String httpGETRequest(const char* serverName) {

  WiFiClient client;

  HTTPClient http;

  // Your Domain name with URL path or IP address with path

  http.begin(client, serverName);

  // Send HTTP POST request

  int httpResponseCode = http.GET();

  String payload = "{}";

  if (httpResponseCode > 0) {

    Serial.print("HTTP Response code: ");

    Serial.println(httpResponseCode);

    payload = http.getString();

  }

  else {

    Serial.print("Error code: ");

    Serial.println(httpResponseCode);

  }

  // Free resources

  http.end();

  return payload;

}

String getReadings() {

  int sensetemp;

  sensetemp = bme.readTemperature();

  readings["in_temperature"] = String(sensetemp);

  sensetemp = bme.readTemperature() * 10;

  readings["in_temperatureD"] = String(sensetemp % 10);

  sensetemp = bme.readHumidity();

  readings["in_humidity"] = String(sensetemp);

  readings["in_presssure"] = String(bme.readPressure());

  readings["in_altitude"] = String(bme.readAltitude(1013.25));

  String jsonString = JSON.stringify(readings);

  return jsonString;

}

String getOWMReadings() {

  if (WiFi.status() == WL_CONNECTED) {

    endpoint = "http://api.openweathermap.org/data/2.5/weather?q=" + city + "&units=" + temperature_unit  + "&appid=" + openWeatherMapApiKey;

    jsonBuffer = httpGETRequest(endpoint.c_str());

    JSONVar myObject = JSON.parse(jsonBuffer);

    // JSON.typeof(jsonVar) can be used to get the type of the var

    if (JSON.typeof(myObject) == "undefined") {

      Serial.println("Parsing input failed!");

    }

    Icon_ = myObject["weather"][0]["icon"];

    OutTemperature = double(myObject["main"]["temp"]);

    OutHumidity    = int(myObject["main"]["humidity"]);

    Sun_rise     = int(myObject["sys"]["sunrise"]);

    Sun_set      = int(myObject["sys"]["sunset"]);

  }

  else {

    Serial.println("WiFi Disconnected");

  }

  int sensetemp;

  sensetemp = OutTemperature;

  owmreadings["out_temperature"] = String(sensetemp);

  sensetemp = OutTemperature * 10;

  owmreadings["out_temperatureD"] = String(sensetemp % 10);

  owmreadings["out_humidity"] = String(OutHumidity);

  owmreadings["weather_icon"] = String("icons/" + Icon_ + ".png");

  owmreadings["Sunset"] = String(Sun_set);

  owmreadings["Sunrise"] = String(Sun_rise);

  owmreadings["City"] = String(city);

  owmreadings["Key"] = String(openWeatherMapApiKey);

  String jsonString = JSON.stringify(owmreadings);

  Serial.println(jsonString);

  return jsonString;

}

void setup() {

  Serial.begin(115200);

  initWiFi();

  initSPIFFS();

  initSensor();

 

  // Handle Web Server

  server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {

    request->send(SPIFFS, "/index.html", "text/html");

  });

  server.serveStatic("/", SPIFFS, "/");

  server.on("/reset", HTTP_GET, [](AsyncWebServerRequest * request) {

    in_temperature = 0;

    in_temperatureD = 0;

    in_humidity = 0;

    in_presssure = 0;

    in_altitude = 0;

    out_temperature = 0;

    out_temperatureD = 0;

    out_humidity = 0;

    request->send(200, "text/plain", "OK");

  });

  // Handle Web Server Events

  events.onConnect([](AsyncEventSourceClient * client) {

    if (client->lastId()) {

      Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());

    }

    // send event with message "CircuitDigest", id current millis

    // and set reconnect delay to 1 second

    client->send("CircuitDigest!", NULL, millis(), 10000);

  });

  server.addHandler(&events);

  server.begin();

}

void loop() {

  if ((millis() - lastTime) > SensorDelay) {

    // Send Events to the Web Server with the Sensor Readings

    events.send(getReadings().c_str(), "BME_readings", millis());

    lastTime = millis();

  }

  if ((millis() - lastTimeAcc) > OWMapDelay) {

    // Send Events to the Web Server with the Sensor Readings

    events.send(getOWMReadings().c_str(), "OWM_readings", millis());

    lastTimeAcc = millis();

  }

}

 

Video

30 Comments

Hi

I found two typos mistakes:
1. In the text on the website: "...The Scirpt.js file is responsible for image manipulation ...".
I think there should be script.js instead of scirpt.js
2. In the Arduino sketches on the website and in the file on Google Drive. In the library name, instead of #include <Adafruit_BME80.h>, there should be #include <Adafruit_BME280.h>

BTW, I have a two questions:
1. How to modify the Arduino sketch for static IP address?
2. How to modify the Arduino sketch to obtain the pressure value in hPa?

Regards, Kali

I'm commenting to make you know what a useful encounter my wife's daughter had studying your blog. She mastered several pieces, with the inclusion of what it's like to possess an ideal coaching mood to let many people without problems comprehend a number of multifaceted subject areas. You actually exceeded my expectations. Thank you for providing the helpful, trustworthy, explanatory and as well as unique guidance on that topic to Gloria.

I want to show some thanks to the writer just for rescuing me from this type of problem. Right after scouting through the online world and getting proposals which were not beneficial, I thought my entire life was gone. Being alive minus the answers to the issues you have resolved through your main site is a serious case, and ones that would have badly affected my entire career if I hadn't discovered your blog. Your natural talent and kindness in handling all the things was vital. I'm not sure what I would've done if I had not come upon such a thing like this. I can at this point look forward to my future. Thanks a lot very much for this high quality and result oriented guide. I will not hesitate to refer your web page to anybody who ought to have guidance about this topic.

Hi
This is helping me to understand the programming environment, thank you.
The Google Drive link does not work.
Is there an alternative?
Many thanks
BJ

I want to express some thanks to this writer for bailing me out of this particular dilemma. As a result of searching throughout the the web and seeing things which were not helpful, I assumed my life was over. Being alive devoid of the solutions to the issues you've resolved by means of your site is a crucial case, as well as the ones that would have in a wrong way affected my entire career if I hadn't encountered your blog. Your own capability and kindness in touching all the details was helpful. I'm not sure what I would have done if I hadn't come across such a stuff like this. I'm able to at this point look ahead to my future. Thanks for your time so much for this expert and amazing guide. I will not hesitate to suggest your web site to any individual who wants and needs counselling about this matter.

I would like to express my affection for your generosity giving support to individuals who should have help with the topic. Your personal commitment to passing the solution all over was quite practical and have in most cases encouraged individuals like me to attain their objectives. Your own warm and helpful guide means this much to me and a whole lot more to my office colleagues. With thanks; from all of us.

I must express some thanks to you just for bailing me out of this type of difficulty. Because of surfing around throughout the world-wide-web and coming across basics that were not powerful, I thought my entire life was done. Living devoid of the approaches to the difficulties you have fixed through your entire article content is a serious case, and the ones that might have adversely damaged my career if I had not noticed your web blog. Your main expertise and kindness in dealing with a lot of stuff was helpful. I don't know what I would've done if I had not discovered such a subject like this. I am able to at this time relish my future. Thanks for your time so much for the impressive and sensible help. I will not hesitate to refer your web blog to any individual who requires recommendations on this area.

I have to express my love for your kindness for those people that must have guidance on the idea. Your personal commitment to getting the solution all around had been unbelievably valuable and have truly encouraged others much like me to reach their objectives. Your own useful key points implies so much to me and additionally to my fellow workers. Many thanks; from all of us.

Thank you a lot for giving everyone an extremely superb possiblity to read articles and blog posts from this website. It is always very enjoyable and full of a good time for me and my office acquaintances to visit your site minimum thrice weekly to read the latest tips you have. And of course, we're always contented considering the excellent creative ideas you serve. Selected 3 facts in this post are unequivocally the most impressive I've had.

I and my pals came checking out the best information located on your web site then then I got a terrible feeling I never expressed respect to the blog owner for those techniques. These people happened to be as a result very interested to see all of them and already have sincerely been tapping into them. I appreciate you for getting so kind and then for getting varieties of useful issues most people are really desirous to be aware of. My honest regret for not expressing gratitude to you sooner.

I would like to express thanks to the writer just for bailing me out of this incident. As a result of researching through the world-wide-web and obtaining tips which were not beneficial, I was thinking my life was done. Existing minus the strategies to the problems you've resolved by means of the review is a serious case, and the ones which may have in a negative way affected my career if I hadn't discovered your blog post. Your own ability and kindness in maneuvering every aspect was precious. I am not sure what I would have done if I had not come upon such a subject like this. I can at this moment relish my future. Thanks very much for your high quality and effective help. I won't hesitate to refer your web page to any person who should get tips about this problem.

I happen to be writing to make you know what a wonderful experience my wife's girl found reading through your site. She realized a good number of details, with the inclusion of what it's like to possess an ideal teaching spirit to make many others effortlessly know precisely a variety of complicated topics. You truly surpassed readers' expectations. Thanks for supplying these essential, dependable, edifying and even cool thoughts on this topic to Mary.

I must show some thanks to you for rescuing me from such a difficulty. Right after searching through the internet and getting solutions that were not helpful, I believed my life was over. Existing without the presence of solutions to the problems you've solved by way of your entire guideline is a serious case, and the kind which could have in a negative way affected my career if I had not encountered your site. The skills and kindness in dealing with a lot of stuff was invaluable. I'm not sure what I would've done if I had not encountered such a solution like this. I am able to at this point relish my future. Thank you very much for your high quality and effective guide. I will not be reluctant to refer the blog to any individual who should get guidelines on this subject.

My spouse and i got really glad John managed to finish up his homework out of the ideas he discovered using your blog. It's not at all simplistic to just choose to be offering points that many others may have been selling. So we grasp we have you to appreciate for this. The type of explanations you've made, the simple blog navigation, the relationships you will make it easier to create - it's mostly wonderful, and it's aiding our son and us imagine that the idea is entertaining, and that's seriously pressing. Thank you for all!

I intended to put you that tiny observation to help thank you very much once again for your nice techniques you have contributed on this page. It's strangely generous of people like you to grant easily just what a number of people would have made available as an electronic book to end up making some profit for themselves, precisely considering that you could have tried it if you decided. These pointers likewise worked as a good way to be sure that other people have similar zeal like my personal own to know the truth great deal more in respect of this condition. I know there are many more enjoyable periods in the future for people who discover your website.

I simply wanted to thank you very much once more. I do not know the things I would've tried without the ways revealed by you relating to that question. It seemed to be a very challenging crisis in my view, however , taking a look at a professional fashion you resolved it took me to jump for fulfillment. I am just happy for this information and as well , have high hopes you recognize what an amazing job you are carrying out instructing people through the use of your web site. More than likely you've never encountered all of us.

In addition to the comments above about the two typographical errors,
please be aware the CAD drawing showing module interconnection is
in error with regard to the BME280's SDA connection to the ESP32 Dev Board.
It shows that GREEN wire connected to GPIO23 (one pin down from the
GND pin), when in reality it should be connected to GPIO22 (two pins down
from the GND pin of the dev board.
Your actual photo of the rear of the perfboard shows the proper connection
from SDA to GPIO22.
Also, you provide no details of what code needs to be placed in the /data/icons
folder or the /data/fonts folder.
Otherwise, the project compiles and performs perfectly for me.

I actually wanted to write a quick word so as to say thanks to you for all the awesome tricks you are sharing on this website. My extended internet lookup has at the end of the day been recognized with pleasant insight to share with my company. I would believe that we website visitors are very much endowed to exist in a fine website with very many wonderful professionals with useful things. I feel truly blessed to have encountered the web site and look forward to really more excellent minutes reading here. Thank you once again for all the details.

I would like to voice my appreciation for your generosity supporting folks that need guidance on that area. Your special dedication to passing the message throughout became amazingly important and have regularly encouraged some individuals just like me to get to their targets. Your amazing useful report implies a whole lot to me and substantially more to my peers. Best wishes; from each one of us.

I and my friends were found to be reviewing the excellent tips and tricks found on your website and then at once I got a horrible suspicion I never expressed respect to you for those secrets. Most of the young boys are already as a result glad to see all of them and now have without a doubt been enjoying them. We appreciate you turning out to be indeed thoughtful and also for deciding upon such superb resources most people are really wanting to be aware of. Our own honest regret for not saying thanks to you sooner.

I have to show my respect for your kind-heartedness for individuals who should have help on this particular area of interest. Your very own commitment to passing the solution all around had been especially productive and has surely encouraged some individuals much like me to achieve their pursuits. Your new warm and friendly advice entails much a person like me and extremely more to my office workers. With thanks; from all of us.

Thanks a lot for giving everyone an extraordinarily terrific possiblity to read critical reviews from here. It can be so pleasant and also stuffed with a great time for me and my office co-workers to search your blog on the least 3 times a week to study the newest secrets you have. Not to mention, I'm just actually fascinated for the tremendous creative ideas served by you. Certain 3 areas on this page are undeniably the most suitable we have ever had.

I intended to compose you the tiny word to say thanks over again with the amazing tips you have provided above. It was really wonderfully open-handed of you to present openly all that a number of us could possibly have sold as an e-book to make some money for themselves, primarily considering that you might have tried it in the event you desired. Those points also acted to become fantastic way to understand that most people have the same dream just as my very own to realize a lot more related to this issue. I think there are millions of more fun times ahead for individuals who find out your blog post.

There are some interesting points in time on this article but I don抰 know if I see all of them middle to heart. There may be some validity however I will take maintain opinion until I look into it further. Good article , thanks and we want more! Added to FeedBurner as well

Add new comment

The content of this field is kept private and will not be shown publicly.

Plain text

  • No HTML tags allowed.
  • Lines and paragraphs break automatically.
  • Web page addresses and email addresses turn into links automatically.