DIY Cloud Weather Station with ESP32/ESP8266 (MySQL Database and PHP)

Build a cloud weather station dashboard to visualize your ESP32 or ESP8266 sensor readings from anywhere in the world. You’ll see your sensor data displayed on gauges and on a table. The ESP32 or ESP8266 will make an HTTP POST request to a PHP script to insert your data into a MySQL database.

DIY Cloud Weather Station with ESP32/ESP8266 MySQL Database and PHP

Previously, we’ve stored sensor readings to a database and display them on a table or charts that you can access from anywhere using your own server. Now, I’ve decided to take a few steps further and add some more information to the web page.

I’ve added two gauges to display the latest temperature and humidity readings as well as some statistics about the minimum, maximum and average readings from an amount of readings that you can define. You can also visualize all the latest readings on a table and you can select how many readings you want to show.

To build this project, you’ll use these technologies:

  • ESP32 or ESP8266 programmed with Arduino IDE
  • Hosting server and domain name
  • PHP script to insert data into MySQL and display it on a web page
  • MySQL database to store readings

Watch the Video Demonstration

To see how the project works, you can watch the following video demonstration:

0. Download Source Code

For this project, you’ll need these files:

1. Hosting Your PHP Application and MySQL Database

The goal of this project is to have your own domain name and hosting account that allows you to store sensor readings from the ESP32 or ESP8266. You can visualize the readings from anywhere in the world by accessing your own server domain.

Here’s a high level overview on how the projects works:

Hosting PHP Application and MySQL Database ESP32 ESP8266 Gauge weather station project overview
  1. You have an ESP32 or ESP8266 that sends sensor readings to your own server. For this, you have your board connected to your router;
  2. In your server, there’s a php script that allows you to store your readings in a MySQL database;
  3. Then, another php script will display the web page with the gauges, table and all the other information;
  4. Finally, you can visualize the readings from anywhere in the world by accessing your own domain name.

Hosting Services

I recommend using one of the following hosting services that can handle all the project requirements:

  • Bluehost (user-friendly with cPanel): free domain name when you sign up for the 3-year plan. I recommend choosing the unlimited websites option;
  • Digital Ocean: Linux server that you manage through a command line. I only recommended this option for advanced users.

Those two services are the ones that I use and personally recommend, but you can use any other hosting service. Any hosting service that offers PHP and MySQL will work with this tutorial. If you don’t have a hosting account, I recommend signing up for Bluehost.

Get Hosting and Domain Name with Bluehost »

When buying a hosting account, you’ll also have to purchase a domain name. This is what makes this project interesting: you’ll be able to go your domain name (http://example.com) and see your ESP readings.

If you like our projects, you might consider signing up to one of the recommended hosting services, because you’ll be supporting our work.

Note: you can also run a LAMP (Linux, Apache, MySQL, PHP) server on a Raspberry Pi to access data in your local network. However, the purpose of this tutorial is to publish readings in your own domain name that you can access from anywhere in the world. This allows you to easily access your ESP readings without relying on a third-party IoT platform.

2. Preparing Your MySQL Database

After signing up for a hosting account and setting up a domain name, you can login to your cPanel or similar dashboard. After that, follow the next steps to create your database, username, password and SQL table.

Creating a database and user

Open the “Advanced” tab:

Bluehost Advanced tab

1. Type “database” in the search bar and select “MySQL Database Wizard”.

CPanel select MySQL database wizard to create db

2. Enter your desired Database name. In my case, the database name is esp_data. Then, press the “Next Step” button:

ESP32 ESP8266 CPanel Create MySQL Database

Note: later you’ll have to use the database name with the prefix that your host gives you (my database prefix in the screenshot above is blurred). I’ll refer to it as example_esp_data from now on.

3. Type your Database username and set a password. You must save all those details, because you’ll need them later to establish a database connection with your PHP code.

ESP32 ESP8266 CPanel Create MySQL Database User and Password

That’s it! Your new database and user were created successfully. Now, save all your details because you’ll need them later:

  • Database name: example_esp_data
  • Username: example_esp_board
  • Password: your password

Creating a SQL table

After creating your database and user, go back to cPanel dashboard and search for “phpMyAdmin”.

ESP32 ESP8266 CPanel Open PHPMyAdmin

In the left sidebar, select your database name example_esp_data and open the “SQL” tab.

ESP32 ESP8266 PHPMyAdmin Open Database

Important: make sure you’ve opened the example_esp_data database. Then, click the SQL tab. If you don’t follow these exact steps and run the SQL query, you might create a table in the wrong database.

Copy the SQL query in the following snippet:

CREATE TABLE SensorData (
    id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    sensor VARCHAR(30) NOT NULL,
    location VARCHAR(30) NOT NULL,
    value1 VARCHAR(10),
    value2 VARCHAR(10),
    value3 VARCHAR(10),
    reading_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)

View raw code

Paste it in the SQL query field (highlighted with a red rectangle) and press the “Go” button to create your table:

ESP32 ESP8266 PHPMyAdmin Create SQL Table

After that, you should see your newly created table called SensorData in the example_esp_data database as shown in the figure below:

ESP32 ESP8266 PHPMyAdmin View SQL Database

3. PHP Script HTTP POST – Receive and Insert Data in MySQL Database

In this section, we’re going to create a PHP script that is responsible for receiving incoming requests from the ESP32 or ESP8266 and inserting the data into a MySQL database.

If you’re using a hosting provider with cPanel, you can search for “File Manager”:

ESP32 ESP8266 CPanel Open Edit PHP Files with File Manager

Then, select the public_html option and press the “+ File” button to create a new .php file.

ESP32 ESP8266 CPanel Create New PHP File

Note: if you’re following this tutorial and you’re not familiar with PHP or MySQL, I recommend creating these exact files. Otherwise, you’ll need to modify the ESP sketch provided with different URL paths.

Create a new file in /public_html with this exact name and extension: esp-post-data.php

Create esp post data PHP file

Edit the newly created file (esp-post-data.php) and copy the following snippet:

<!--
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/cloud-weather-station-esp32-esp8266/

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.

  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
-->
<?php
  include_once('esp-database.php');

  // Keep this API Key value to be compatible with the ESP code provided in the project page. If you change this value, the ESP sketch needs to match
  $api_key_value = "tPmAT5Ab3j7F9";

  $api_key= $sensor = $location = $value1 = $value2 = $value3 = "";

  if ($_SERVER["REQUEST_METHOD"] == "POST") {
    $api_key = test_input($_POST["api_key"]);
    if($api_key == $api_key_value) {
      $sensor = test_input($_POST["sensor"]);
      $location = test_input($_POST["location"]);
      $value1 = test_input($_POST["value1"]);
      $value2 = test_input($_POST["value2"]);
      $value3 = test_input($_POST["value3"]);

      $result = insertReading($sensor, $location, $value1, $value2, $value3);
      echo $result;
    }
    else {
      echo "Wrong API Key provided.";
    }
  }
  else {
    echo "No data posted with HTTP POST.";
  }

  function test_input($data) {
    $data = trim($data);
    $data = stripslashes($data);
    $data = htmlspecialchars($data);
    return $data;
  }

View raw code

4. PHP Script for Database Functions

Create a new file in /public_html that is responsible for inserting and accessing data in your database. Name your file: esp-database.php

Create esp database PHP file

Copy that PHP script:

<!--
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/cloud-weather-station-esp32-esp8266/

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.

  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
-->
<?php
  $servername = "localhost";

  // REPLACE with your Database name
  $dbname = "REPLACE_WITH_YOUR_DATABASE_NAME";
  // REPLACE with Database user
  $username = "REPLACE_WITH_YOUR_USERNAME";
  // REPLACE with Database user password
  $password = "REPLACE_WITH_YOUR_PASSWORD";

  function insertReading($sensor, $location, $value1, $value2, $value3) {
    global $servername, $username, $password, $dbname;

    // Create connection
    $conn = new mysqli($servername, $username, $password, $dbname);
    // Check connection
    if ($conn->connect_error) {
      die("Connection failed: " . $conn->connect_error);
    }

    $sql = "INSERT INTO SensorData (sensor, location, value1, value2, value3)
    VALUES ('" . $sensor . "', '" . $location . "', '" . $value1 . "', '" . $value2 . "', '" . $value3 . "')";

    if ($conn->query($sql) === TRUE) {
      return "New record created successfully";
    }
    else {
      return "Error: " . $sql . "<br>" . $conn->error;
    }
    $conn->close();
  }
  
  function getAllReadings($limit) {
    global $servername, $username, $password, $dbname;

    // Create connection
    $conn = new mysqli($servername, $username, $password, $dbname);
    // Check connection
    if ($conn->connect_error) {
      die("Connection failed: " . $conn->connect_error);
    }

    $sql = "SELECT id, sensor, location, value1, value2, value3, reading_time FROM SensorData order by reading_time desc limit " . $limit;
    if ($result = $conn->query($sql)) {
      return $result;
    }
    else {
      return false;
    }
    $conn->close();
  }
  function getLastReadings() {
    global $servername, $username, $password, $dbname;

    // Create connection
    $conn = new mysqli($servername, $username, $password, $dbname);
    // Check connection
    if ($conn->connect_error) {
      die("Connection failed: " . $conn->connect_error);
    }

    $sql = "SELECT id, sensor, location, value1, value2, value3, reading_time FROM SensorData order by reading_time desc limit 1" ;
    if ($result = $conn->query($sql)) {
      return $result->fetch_assoc();
    }
    else {
      return false;
    }
    $conn->close();
  }

  function minReading($limit, $value) {
     global $servername, $username, $password, $dbname;

    // Create connection
    $conn = new mysqli($servername, $username, $password, $dbname);
    // Check connection
    if ($conn->connect_error) {
      die("Connection failed: " . $conn->connect_error);
    }

    $sql = "SELECT MIN(" . $value . ") AS min_amount FROM (SELECT " . $value . " FROM SensorData order by reading_time desc limit " . $limit . ") AS min";
    if ($result = $conn->query($sql)) {
      return $result->fetch_assoc();
    }
    else {
      return false;
    }
    $conn->close();
  }

  function maxReading($limit, $value) {
     global $servername, $username, $password, $dbname;

    // Create connection
    $conn = new mysqli($servername, $username, $password, $dbname);
    // Check connection
    if ($conn->connect_error) {
      die("Connection failed: " . $conn->connect_error);
    }

    $sql = "SELECT MAX(" . $value . ") AS max_amount FROM (SELECT " . $value . " FROM SensorData order by reading_time desc limit " . $limit . ") AS max";
    if ($result = $conn->query($sql)) {
      return $result->fetch_assoc();
    }
    else {
      return false;
    }
    $conn->close();
  }

  function avgReading($limit, $value) {
     global $servername, $username, $password, $dbname;

    // Create connection
    $conn = new mysqli($servername, $username, $password, $dbname);
    // Check connection
    if ($conn->connect_error) {
      die("Connection failed: " . $conn->connect_error);
    }

    $sql = "SELECT AVG(" . $value . ") AS avg_amount FROM (SELECT " . $value . " FROM SensorData order by reading_time desc limit " . $limit . ") AS avg";
    if ($result = $conn->query($sql)) {
      return $result->fetch_assoc();
    }
    else {
      return false;
    }
    $conn->close();
  }
?>

View raw code

Before saving the file, you need to modify the $dbname, $username and $password variables with your unique details:

// Your Database name
$dbname = "example_esp_data";
// Your Database user
$username = "example_esp_board";
// Your Database user password
$password = "YOUR_USER_PASSWORD";

After adding the database name, username and password, save the file and continue with this tutorial. If you try to access your domain name in the next URL path, you’ll see the following:

http://example.com/esp-post-data.php
esp post data via HTTP example PHP file

5. PHP Script – Display Database Readings on Gauges and Table

You’ll also need to add a CSS file to style your dashboard, name it: esp-style.css:

Create esp style CSS file

Copy that CSS to your file and save it:

/**
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/cloud-weather-station-esp32-esp8266/

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.

  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
**/
body {
    width: 60%;
    margin: auto;
    text-align: center;
    font-family: Arial;
    top: 50%;
    left: 50%;
}

@media screen and (max-width: 800px) {
    body {
        width: 100%;
    }
}

table {
    margin-left: auto;
    margin-right: auto;
}

div {
    margin-left: auto;
    margin-right: auto;
}

h2 { font-size: 2.5rem; }

.header {
	 padding: 1rem;
	 margin: 0 0 2rem 0;
	 background: #f2f2f2;
}

h1 {
    font-size: 2rem;
    font-family: Arial, sans-serif;
    text-align: center;
    text-transform: uppercase;
}

.content {
    display: flex;
}

@media screen and (max-width: 500px) /* Mobile */ {
    .content {
        flex-direction: column;
    }
}

.mask {
    position: relative;
    overflow: hidden;
    display: block;
    width: 12.5rem;
    height: 6.25rem;
    margin: 1.25rem;
}

.semi-circle {
    position: relative;
    display: block;
    width: 12.5rem;
    height: 6.25rem;
    background: linear-gradient(to right, #3498db 0%, #05b027 33%, #f1c40f 70%, #c0392b 100%);
    border-radius: 50% 50% 50% 50% / 100% 100% 0% 0%;
}

.semi-circle::before {
    content: "";
    position: absolute;
    bottom: 0;
    left: 50%;
    z-index: 2;
    display: block;
    width: 8.75rem;
    height: 4.375rem;
    margin-left: -4.375rem;
    background: #fff;
    border-radius: 50% 50% 50% 50% / 100% 100% 0% 0%;
}

.semi-circle--mask {
    position: absolute;
    top: 0;
    left: 0;
    width: 12.5rem;
    height: 12.5rem;
    background: transparent;
    transform: rotate(120deg) translate3d(0, 0, 0);
    transform-origin: center center;
    backface-visibility: hidden;
    transition: all 0.3s ease-in-out;
}

.semi-circle--mask::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0%;
    z-index: 2;
    display: block;
    width: 12.625rem;
    height: 6.375rem;
    margin: -1px 0 0 -1px;
    background: #f2f2f2;
    border-radius: 50% 50% 50% 50% / 100% 100% 0% 0%;
}

.gauge--2 .semi-circle { background: #3498db; }

.gauge--2 .semi-circle--mask { transform: rotate(20deg) translate3d(0, 0, 0); }

#tableReadings { border-collapse: collapse; }

#tableReadings td, #tableReadings th {
    border: 1px solid #ddd;
    padding: 10px;
}

#tableReadings tr:nth-child(even){ background-color: #f2f2f2; }

#tableReadings tr:hover { background-color: #ddd; }

#tableReadings th {
    padding: 10px;
    background-color: #2f4468;
    color: white;
}

View raw code

Finally, create another PHP file in the /public_html directory that will display all the database content in a web page. Name your new file: esp-weather-station.php

Create esp weather station PHP file

Edit the newly created file (esp-weather-station.php) and copy the following code:

<!--
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/cloud-weather-station-esp32-esp8266/

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.

  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
-->
<?php
    include_once('esp-database.php');
    if ($_GET["readingsCount"]){
      $data = $_GET["readingsCount"];
      $data = trim($data);
      $data = stripslashes($data);
      $data = htmlspecialchars($data);
      $readings_count = $_GET["readingsCount"];
    }
    // default readings count set to 20
    else {
      $readings_count = 20;
    }

    $last_reading = getLastReadings();
    $last_reading_temp = $last_reading["value1"];
    $last_reading_humi = $last_reading["value2"];
    $last_reading_time = $last_reading["reading_time"];

    // Uncomment to set timezone to - 1 hour (you can change 1 to any number)
    //$last_reading_time = date("Y-m-d H:i:s", strtotime("$last_reading_time - 1 hours"));
    // Uncomment to set timezone to + 7 hours (you can change 7 to any number)
    //$last_reading_time = date("Y-m-d H:i:s", strtotime("$last_reading_time + 7 hours"));

    $min_temp = minReading($readings_count, 'value1');
    $max_temp = maxReading($readings_count, 'value1');
    $avg_temp = avgReading($readings_count, 'value1');

    $min_humi = minReading($readings_count, 'value2');
    $max_humi = maxReading($readings_count, 'value2');
    $avg_humi = avgReading($readings_count, 'value2');
?>

<!DOCTYPE html>
<html>
    <head><meta http-equiv="Content-Type" content="text/html; charset=utf-8">

        <link rel="stylesheet" type="text/css" href="esp-style.css">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    </head>
    <header class="header">
        <h1>📊 ESP Weather Station</h1>
        <form method="get">
            <input type="number" name="readingsCount" min="1" placeholder="Number of readings (<?php echo $readings_count; ?>)">
            <input type="submit" value="UPDATE">
        </form>
    </header>
<body>
    <p>Last reading: <?php echo $last_reading_time; ?></p>
    <section class="content">
	    <div class="box gauge--1">
	    <h3>TEMPERATURE</h3>
              <div class="mask">
			  <div class="semi-circle"></div>
			  <div class="semi-circle--mask"></div>
			</div>
		    <p style="font-size: 30px;" id="temp">--</p>
		    <table cellspacing="5" cellpadding="5">
		        <tr>
		            <th colspan="3">Temperature <?php echo $readings_count; ?> readings</th>
	            </tr>
		        <tr>
		            <td>Min</td>
                    <td>Max</td>
                    <td>Average</td>
                </tr>
                <tr>
                    <td><?php echo $min_temp['min_amount']; ?> &deg;C</td>
                    <td><?php echo $max_temp['max_amount']; ?> &deg;C</td>
                    <td><?php echo round($avg_temp['avg_amount'], 2); ?> &deg;C</td>
                </tr>
            </table>
        </div>
        <div class="box gauge--2">
            <h3>HUMIDITY</h3>
            <div class="mask">
                <div class="semi-circle"></div>
                <div class="semi-circle--mask"></div>
            </div>
            <p style="font-size: 30px;" id="humi">--</p>
            <table cellspacing="5" cellpadding="5">
                <tr>
                    <th colspan="3">Humidity <?php echo $readings_count; ?> readings</th>
                </tr>
                <tr>
                    <td>Min</td>
                    <td>Max</td>
                    <td>Average</td>
                </tr>
                <tr>
                    <td><?php echo $min_humi['min_amount']; ?> %</td>
                    <td><?php echo $max_humi['max_amount']; ?> %</td>
                    <td><?php echo round($avg_humi['avg_amount'], 2); ?> %</td>
                </tr>
            </table>
        </div>
    </section>
<?php
    echo   '<h2> View Latest ' . $readings_count . ' Readings</h2>
            <table cellspacing="5" cellpadding="5" id="tableReadings">
                <tr>
                    <th>ID</th>
                    <th>Sensor</th>
                    <th>Location</th>
                    <th>Value 1</th>
                    <th>Value 2</th>
                    <th>Value 3</th>
                    <th>Timestamp</th>
                </tr>';

    $result = getAllReadings($readings_count);
        if ($result) {
        while ($row = $result->fetch_assoc()) {
            $row_id = $row["id"];
            $row_sensor = $row["sensor"];
            $row_location = $row["location"];
            $row_value1 = $row["value1"];
            $row_value2 = $row["value2"];
            $row_value3 = $row["value3"];
            $row_reading_time = $row["reading_time"];
            // Uncomment to set timezone to - 1 hour (you can change 1 to any number)
            //$row_reading_time = date("Y-m-d H:i:s", strtotime("$row_reading_time - 1 hours"));
            // Uncomment to set timezone to + 7 hours (you can change 7 to any number)
            //$row_reading_time = date("Y-m-d H:i:s", strtotime("$row_reading_time + 7 hours"));

            echo '<tr>
                    <td>' . $row_id . '</td>
                    <td>' . $row_sensor . '</td>
                    <td>' . $row_location . '</td>
                    <td>' . $row_value1 . '</td>
                    <td>' . $row_value2 . '</td>
                    <td>' . $row_value3 . '</td>
                    <td>' . $row_reading_time . '</td>
                  </tr>';
        }
        echo '</table>';
        $result->free();
    }
?>

<script>
    var value1 = <?php echo $last_reading_temp; ?>;
    var value2 = <?php echo $last_reading_humi; ?>;
    setTemperature(value1);
    setHumidity(value2);

    function setTemperature(curVal){
    	//set range for Temperature in Celsius -5 Celsius to 38 Celsius
    	var minTemp = -5.0;
    	var maxTemp = 38.0;
        //set range for Temperature in Fahrenheit 23 Fahrenheit to 100 Fahrenheit
    	//var minTemp = 23;
    	//var maxTemp = 100;

    	var newVal = scaleValue(curVal, [minTemp, maxTemp], [0, 180]);
    	$('.gauge--1 .semi-circle--mask').attr({
    		style: '-webkit-transform: rotate(' + newVal + 'deg);' +
    		'-moz-transform: rotate(' + newVal + 'deg);' +
    		'transform: rotate(' + newVal + 'deg);'
    	});
    	$("#temp").text(curVal + ' ºC');
    }

    function setHumidity(curVal){
    	//set range for Humidity percentage 0 % to 100 %
    	var minHumi = 0;
    	var maxHumi = 100;

    	var newVal = scaleValue(curVal, [minHumi, maxHumi], [0, 180]);
    	$('.gauge--2 .semi-circle--mask').attr({
    		style: '-webkit-transform: rotate(' + newVal + 'deg);' +
    		'-moz-transform: rotate(' + newVal + 'deg);' +
    		'transform: rotate(' + newVal + 'deg);'
    	});
    	$("#humi").text(curVal + ' %');
    }

    function scaleValue(value, from, to) {
        var scale = (to[1] - to[0]) / (from[1] - from[0]);
        var capped = Math.min(from[1], Math.max(from[0], value)) - from[0];
        return ~~(capped * scale + to[0]);
    }
</script>
</body>
</html>

View raw code

If you try to access your domain name in the following URL path, you’ll see the following:

http://example.com/esp-weather-station.php
ESP32 ESP8266 Weather Station Empty Test Dashboard

That’s it! If you see that web page with empty values in your browser, it means that everything is ready. In the next section, you’ll learn how to insert data from your ESP32 or ESP8266 into the database.

6. Preparing Your ESP32 or ESP8266

This project is compatible with both the ESP32 and ESP8266 boards. You just need to assemble a simple circuit and upload the sketch provided to insert temperature, humidity, pressure and more into your database every 10 minutes.

ESP32 vs ESP8266 Development Boards

Parts Required

For this example we’ll get sensor readings from the BME280 sensor. Here’s a list of parts you need to build the circuit for this project:

You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

Schematics

The BME280 sensor module we’re using communicates via I2C communication protocol, so you need to connect it to the ESP32 or ESP8266 I2C pins.

BME280 wiring to ESP32

The ESP32 I2C pins are:

  • GPIO 22: SCL (SCK)
  • GPIO 21: SDA (SDI)

So, assemble your circuit as shown in the next schematic diagram (Guide for ESP32 with BME280 and ESP32 BME280 Web Server).

BME280 wiring to ESP32

Recommended reading: ESP32 Pinout Reference Guide

BME280 wiring to ESP8266

The ESP8266 I2C pins are:

  • GPIO 5 (D1): SCL (SCK)
  • GPIO 4 (D2): SDA (SDI)

Assemble your circuit as in the next schematic diagram if you’re using an ESP8266 board (read Guide for ESP8266 with BME280).

BME280 wiring to ESP8266

Recommended reading: ESP8266 Pinout Reference Guide

ESP32/ESP8266 Code

We’ll program the ESP32/ESP8266 using Arduino IDE, so you must have the ESP add-on installed in your Arduino IDE.

Follow one of the next tutorials depending on the board you’re using:

After installing the necessary board add-ons and libraries, copy the following code to your Arduino IDE, but don’t upload it yet. You need to make some changes to make it work for you.

/*
  Rui Santos
  Complete project details at Complete project details at https://RandomNerdTutorials.com/cloud-weather-station-esp32-esp8266/

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.

  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.

*/

#ifdef ESP32
  #include <WiFi.h>
  #include <HTTPClient.h>
#else
  #include <ESP8266WiFi.h>
  #include <ESP8266HTTPClient.h>
  #include <WiFiClient.h>
#endif

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

const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

//Your Domain name with URL path or IP address with path
const char* serverName = "http://example.com/esp-post-data.php";

// Keep this API Key value to be compatible with the PHP code provided in the project page.
// If you change the apiKeyValue value, the PHP file /esp-post-data.php also needs to have the same key
String apiKeyValue = "tPmAT5Ab3j7F9";
String sensorName = "BME280";
String sensorLocation = "Office";

/*#include <SPI.h>
#define BME_SCK 18
#define BME_MISO 19
#define BME_MOSI 23
#define BME_CS 5*/

#define SEALEVELPRESSURE_HPA (1013.25)

Adafruit_BME280 bme;  // I2C
//Adafruit_BME280 bme(BME_CS);  // hardware SPI
//Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK);  // software SPI

// the following variables are unsigned longs because the time, measured in
// milliseconds, will quickly become a bigger number than can be stored in an int.
unsigned long lastTime = 0;
// Timer set to 10 minutes (600000)
//unsigned long timerDelay = 600000;
// Set timer to 30 seconds (30000)
unsigned long timerDelay = 30000;

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

  WiFi.begin(ssid, password);
  Serial.println("Connecting");
  while(WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to WiFi network with IP Address: ");
  Serial.println(WiFi.localIP());

  // (you can also pass in a Wire library object like &Wire2)
  bool status = bme.begin(0x76);
  if (!status) {
    Serial.println("Could not find a valid BME280 sensor, check wiring or change I2C address!");
    while (1);
  }
  
  Serial.println("Timer set to 30 seconds (timerDelay variable), it will take 30 seconds before publishing the first reading.");
}

void loop() {
  //Send an HTTP POST request every 10 minutes
  if ((millis() - lastTime) > timerDelay) {
    //Check WiFi connection status
    if(WiFi.status()== WL_CONNECTED){
      HTTPClient http;

      // Your Domain name with URL path or IP address with path
      http.begin(serverName);

      // Specify content-type header
      http.addHeader("Content-Type", "application/x-www-form-urlencoded");

      // Prepare your HTTP POST request data
      String httpRequestData = "api_key=" + apiKeyValue + "&sensor=" + sensorName
                            + "&location=" + sensorLocation + "&value1=" + String(bme.readTemperature())
                            + "&value2=" + String(bme.readHumidity()) + "&value3=" + String(bme.readPressure()/100.0F) + "";
      Serial.print("httpRequestData: ");
      Serial.println(httpRequestData);

      // You can comment the httpRequestData variable above
      // then, use the httpRequestData variable below (for testing purposes without the BME280 sensor)
      //String httpRequestData = "api_key=tPmAT5Ab3j7F9&sensor=BME280&location=Office&value1=24.75&value2=49.54&value3=1005.14";

      // Send HTTP POST request
      int httpResponseCode = http.POST(httpRequestData);

      // If you need an HTTP request with a content type: text/plain
      //http.addHeader("Content-Type", "text/plain");
      //int httpResponseCode = http.POST("Hello, World!");

      // If you need an HTTP request with a content type: application/json, use the following:
      //http.addHeader("Content-Type", "application/json");
      //int httpResponseCode = http.POST("{\"value1\":\"19\",\"value2\":\"67\",\"value3\":\"78\"}");

      if (httpResponseCode>0) {
        Serial.print("HTTP Response code: ");
        Serial.println(httpResponseCode);
      }
      else {
        Serial.print("Error code: ");
        Serial.println(httpResponseCode);
      }
      // Free resources
      http.end();
    }
    else {
      Serial.println("WiFi Disconnected");
    }
    lastTime = millis();
  }
}

View raw code

Setting your network credentials

You need to modify the following lines with your network credentials: SSID and password. The code is well commented on where you should make the changes.

// Replace with your network credentials
const char* ssid     = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

Setting your serverName

You also need to type your domain name, so the ESP publishes the readings to your own server.

const char* serverName = "http://example.com/esp-post-data.php";

Now, you can upload the code to your board. It should work straight away both in the ESP32 or ESP8266 board. If you want to learn how the code works, read the next section.

How the code works

This project is already quite long, so we won’t cover in detail how the code works, but here’s a quick summary:

  • Import all the libraries to make it work (it will import either the ESP32 or ESP8266 libraries based on the selected board in your Arduino IDE)
  • Set variables that you might want to change (apiKeyValue, sensorName, sensorLocation)
  • The apiKeyValue is just a random string that you can modify. It’s used for security reasons, so only anyone that knows your API key can publish data to your database
  • Initialize the serial communication for debugging purposes
  • Establish a Wi-Fi connection with your router
  • Initialize the BME280 to get readings

Then, in the loop() is where you actually make the HTTP POST request every 10 minutes with the latest BME280 readings:

// Your Domain name with URL path or IP address with path
http.begin(serverName);

// Specify content-type header
http.addHeader("Content-Type", "application/x-www-form-urlencoded");

// Prepare your HTTP POST request data
String httpRequestData = "api_key=" + apiKeyValue + "&sensor=" + sensorName                      + "&location=" + sensorLocation + "&value1=" + String(bme.readTemperature())                      + "&value2=" + String(bme.readHumidity()) + "&value3=" + String(bme.readPressure()/100.0F) + "";

int httpResponseCode = http.POST(httpRequestData);

You can comment the httpRequestData variable above that concatenates all the BME280 readings and use the httpRequestData variable below for testing purposes:

String httpRequestData = "api_key=tPmAT5Ab3j7F9&sensor=BME280&location=Office&value1=24.75&value2=49.54&value3=1005.14";

Demonstration

After completing all the steps, let your ESP board collect some readings and publish them to your server.

ESP32 BME280 Arduino IDE MySQL

If everything is correct, this is what you should see in your Arduino IDE Serial Monitor:

Arduino IDE Serial Monitor ESP32 ESP8266 Weather Station

If you open your domain name in this URL path:

http://example.com/esp-weather-station.php

You should see the latest 20 readings stored in your database. There’s two gauges that show the latest temperature and humidity readings, and a timestamp.

Refresh the web page to see the latest readings:

ESP32 ESP8266 Weather Station Data Example Temperature Humidity Gauges

There’s a field where you can type the number of readings to visualize, as well as the number of readings for these statistics: minimum, maximum and average. By default it’s set to 20. For example, if you type 30 and press the update button, you’ll see that your web page updates and recalculates all the values.

input field esp32 esp8266 weather station

The web page is also mobile responsive, so you can use any device to access:

ESP32 ESP8266 Weather Station Data mobile responsive web page smartphone

You can also go to phpMyAdmin to manage the data stored in your SensorData table. You can delete it, edit, etc…

ESP32 ESP8266 Weather Station SensorData Table MySQL PHPMyAdmin

Wrapping Up

In this tutorial you’ve learned how to publish sensor data into a database in your own server domain that you can access from anywhere in the world. This requires that you have your own server and domain name (alternatively, you can use a Raspberry Pi LAMP Server for local access).

I encourage you to change the web page appearance, add more features (like email notifications), publish data from different sensors, use multiple ESP boards, and much more.

You might also like reading:

I hope you liked this project. If you have any questions, post a comment below and we’ll try to get back to you.

If you like ESP32, you might consider enrolling in our course “Learn ESP32 with Arduino IDE“. You can also access our free ESP32 resources here.

Thank you for reading.



Learn how to program and build projects with the ESP32 and ESP8266 using MicroPython firmware DOWNLOAD »

Learn how to program and build projects with the ESP32 and ESP8266 using MicroPython firmware DOWNLOAD »


Enjoyed this project? Stay updated by subscribing our weekly newsletter!

60 thoughts on “DIY Cloud Weather Station with ESP32/ESP8266 (MySQL Database and PHP)”

  1. Excellent project. It integrates aspects of sensors, data storage and web display into a comprehensive package with server based ‘management’. I’ll likely use a LAMP server on one of my Raspberry Pis. Thanks Rui.

    Reply
  2. Thanks for this post. I always imagine how cayenne and many more website work. I learn a lot of things with this post. I use 000webhost for SQL PHP and HTML file hosting. How I use an NTC thermistor with this code at a place of adafruit bme280
    Keep uploading these Posts

    Reply
  3. Amazing project…. A great improvement over the earlier one – visually.
    However, can you please check the the semicircle graphs once again both for Temp and humidity. There seems to be an error in how the reading shows up graphically… I tried by changing the temp range to 0-45*C but does not help much.

    Reply
  4. Thank you sara for this Awesome project.
    I am done with all parts with esp32
    but not done with esp8266 it shows the error code -1 as an Http response.

    Reply
  5. Hi,
    Thanks for this project.
    I have it running on an ESP8266 and have modified it slightly to read values from a DHT22 and a capacitive soil moisture sensor.
    So have three gauges showing temp, humidity and Soil moisture level.
    Can you show how to add a slider and a button to the to the web page so that when the soil moisture level drops down to a certain % ( as set by the slider) the ESP will turn on a small water pump. Or if desired the button can be used to turn the pump on manually

    Reply
    • Hello John, it’s definitely possible to implement that feature, however at the moment I don’t have any tutorials on that exact subject. In the future, I’ll try to build a project where you set a threshold input like that.
      Regards,
      Rui

      Reply
    • Hello John. My name is Gabriel, I am new to programming and I am studying to do a project very similar to yours, using the DHT22 and soil moisture sensor. Would you agree to share your project with me so I could have an idea on the structure you used to implement different sensors with this code? I would be very kind of yours and I would very much appreciate it!
      My email is [email protected], if you agree to help me.
      Thanks in advance, and cheers from Brazil!

      Reply
  6. Thank you Rui and Sarah for this project,
    I modified the code to get readings from DHT11 sensor via arduino ethernet shield and added email notifications. Every seem to work just fine. Will be looking forward to more of DIY’s.

    Reply
  7. I have this problem when I load the code and display it on the console

    Connected to WiFi network with IP Address: 192.168.0.19
    Could not find a valid BME280 sensor, check wiring or change I2C address!

    Soft WDT reset

    >>>stack>>>

    ctx: cont
    sp: 3ffffde0 end: 3fffffc0 offset: 01b0
    3fffff90: 40208ba8 1300a8c0 feefeffe feefeffe
    3fffffa0: feefeffe 00000000 3ffee78c 402062dc
    3fffffb0: feefeffe feefeffe 3ffe8500 40100f41
    <<<stack<<<

    ets Jan 8 2013,rst cause:2, boot mode:(3,7)

    load 0x4010f000, len 1392, room 16
    tail 0
    chksum 0xd0
    csum 0xd0
    v3d128e5c
    ~ld

    Reply
  8. Awesome tutorial. I have a question though: What would be the best way to manage readings from more than 10 ESP32? Would it be better to create an individual database for each one or add a DEVICEID variable which gets filtered when creating the graphs?

    Reply
    • It would be better to have 2 Tables:
      #1 Table with all your board and #2 Table with all the sensor readings. Each reading would be related to a board using a foreign key to Table #1.
      That’s the correct method of building that type of application.

      However, if you’re not familiar with SQL, you have to easier options:
      1) To create 1 Table for each board (creating 10 tables).
      2) Or use a single Table like in this project that each reading also has the board information… Then, you would use an SQL query with a filter to find all the readings for each board

      I hope that helps!

      Reply
  9. thank-you
    i have added the air pressure gauge in as well, the fact that it is building a history db meaning i can run all sorts of queries and graphs against them is really cool. all of this is a big step up from what i knew on this subject yesterday
    thank-you

    Reply
  10. hi the data is going to fill the sql db up pretty quick, if i wanted readings posted to the db every 20 minutes would i just make the below change?

    // the following variables are unsigned longs because the time, measured in
    // milliseconds, will quickly become a bigger number than can be stored in an int.
    unsigned long lastTime = 0;
    // Timer set to 20 minutes (1200000)
    unsigned long timerDelay = 1200000;
    // Set timer to 30 seconds (30000)
    //unsigned long timerDelay = 30000;

    Reply
  11. Hi thank you so much for the wonderful tutorial. Im using esp32, so do I need to use esp8266WiFi.h library or only WiFi.h library is enough ?

    Is it possible to send the data to firebase ? Do you have any tutorials for it?

    Reply
    • Hi.
      If you’re using the ESP32, you just need the WiFi.h library.
      We don’t have any tutorials about firebase.
      Regards,
      Sara

      Reply
  12. Hi – I’ve built the solar-powered weather station and am sending data to a database which contains temp, humidity, rainfall, battery level, day, hour, minute for each upload. I have some experience in programming but not in PHP and am now trying to understand the PHP code for the web gauges so that I can query my database and post rainfall for the last 24 hours along with the temp and humidity gauges. My problem: I don’t understand how this ‘$last_reading_temp = $last_reading[“temperature”];’ and the following commands work. Is the ‘$last_reading[]’ a function? And, if so, where is it defined? I’ve searched the PHP online manuals and do not see this function defined. Any help would be appreciated.

    Reply
  13. Excellent project.
    I just noticed with my build that it seems to stop posting data from time to time.
    The project is solar powered and uses deep sleep as per the other tutorial.
    Any advice on how to trouble shoot?

    Reply
      • Hi Sara,

        yes it happens quite frequently. I get gaps of up to 8 hours, the device is supposed to wake up every hour.
        I implemented a restart routine in case it is unable to connect to wifi after 15 seconds as i noticed that it seems to fail from time to time.
        void setup() {
        pinMode(SENSOR, OUTPUT);
        Serial.begin(115200);
        digitalWrite(SENSOR, HIGH);
        Serial.println(“Powered GPIO2”);
        delay(3000);
        WiFi.begin(ssid, password);
        Serial.println(“Connecting”);
        while(WiFi.status() != WL_CONNECTED) {
        delay(1000);
        Serial.print(“.”);
        if (millis() >= 15000) {
        esp_restart();
        }
        }

        Everytime i tried with the usb console connected it solved tha “can’t connect issue”. Now i am at a bit of a loss here on why it won’t work.
        I noticed quite a large power drop on the battery right after the problem occured. If my last measurement said 93% battery, 8 hours no readings, the next will say 83% battery.

        Reply
        • Quick update:
          After making my own pcb for the solar /battery power supply (including battery charge monitoring) in accordance with the tutorial from randomnerds and modifying the weather station pcb from randomnerds (changed the ldr gpio so it wont habe issues with the wifi library and added some more gpio breakouts ) it woors flawlessly. I assume that i had a bad solder joint on one of the cables causing the station to sometimes get a reading and sometimes not

          Reply
    • It means your connection to your database file did not work.
      Check your config in esp-database.php again and make sure that the
      $servername = “localhost”;
      // Your Database name
      $dbname = “example_esp_data”;
      // Your Database user
      $username = “example_esp_board”;
      // Your Database user password
      $password = “YOUR_USER_PASSWORD”;
      part is actually correct and that it is possible to connect with them to the db.
      I had to use an ip address instead of localhost. So doublecheck those values.

      Reply
  14. Thank you for the really detailed tutorial. This was the first time I have done anything with the esp32, your tutorial was really easy to follow.

    Reply
  15. Hi, i would connect OLED SSD1306 I2C display..
    But PIN 21 or 22 is used for BME280 and display.
    How i can change pin 21 and 22 for BME280?

    Reply

Leave a Comment

Download our Free eBooks and Resources

Get instant access to our FREE eBooks, Resources, and Exclusive Electronics Projects by entering your email address below.