Embedded System (ESP32 Project 10): Data Visualization & Data Logging: ESP32

Imam Rusydi Ibrahim
10 min readMay 7, 2023

--

Haloooo, Setelah sukses dengan project ke-9 yaitu Database, kali ini aku akan membuat project ke-10 yaitu Data Visualization & Data Logging. Di project ini, kita akan menampilkan data dalam bentuk grafik. Project ini hampir sama dengan project kesembilan, bedanya adalah dalam pembuatan tampilan website.

TOOLS

  • ESP32 DOIT DEVKIT V1
  • Breadboard
  • Kabel jumper
  • Kabel Data Micro USB
  • Hosting Server
  • Sensor BMP280
  • Laptop/PC yang sudah terinstall Arduino IDE dan punya browser

Jika belum melakukan instalasi Arduino IDE, kamu dapat menginstalnya pada https://www.arduino.cc/en/software

Jangan lupa untuk melakukan setup Arduino IDE, jika belum silahkan untuk membaca pada postingan sebelumnya.

Berikut linknya! Embedded System (ESP32 Project 1): LED Blink

EXPERIMENT

Untuk melanjutkan experiment, silahkan untuk mengerjakan project 9 terlebih dahulu pada link sebagai berikut.

CHART -1

  • Masuk ke web hosting.
  • Masuk ke file manager kalian, kemudian ke folder public_html. Setelah itu edit pada file index.php. Berikut adalah kode yang saya gunakan.
<!--
Rui Santos
Complete project details at https://RandomNerdTutorials.com

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";

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

// Customize with your database query
$sql = "SELECT id, temperature, pressure, altitude, reading_time FROM DataSensor order by reading_time desc limit 40";

$result = $conn->query($sql);

while ($data = $result->fetch_assoc()){
$sensor_data[] = $data;
}

$readings_time = array_column($sensor_data, 'reading_time');

// ******* Uncomment to convert readings time array to your timezone ********
/*$i = 0;
foreach ($readings_time as $reading){
// Uncomment to set timezone to - 1 hour (you can change 1 to any number)
$readings_time[$i] = date("Y-m-d H:i:s", strtotime("$reading - 1 hours"));
// Uncomment to set timezone to + 4 hours (you can change 4 to any number)
//$readings_time[$i] = date("Y-m-d H:i:s", strtotime("$reading + 4 hours"));
$i += 1;
}*/

$temperature = json_encode(array_reverse(array_column($sensor_data, 'temperature')), JSON_NUMERIC_CHECK);
$pressure = json_encode(array_reverse(array_column($sensor_data, 'pressure')), JSON_NUMERIC_CHECK);
$altitude = json_encode(array_reverse(array_column($sensor_data, 'altitude')), JSON_NUMERIC_CHECK);
$reading_time = json_encode(array_reverse($readings_time), JSON_NUMERIC_CHECK);

/*echo $temperature;
echo $pressure;
echo $altitude;
echo $reading_time;*/

$result->free();
$conn->close();
?>

<!DOCTYPE html>
<html>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://code.highcharts.com/highcharts.js"></script>
<style>
body {
min-width: 310px;
max-width: 1280px;
height: 500px;
margin: 0 auto;
}
h2 {
font-family: Arial;
font-size: 2.5rem;
text-align: center;
}
</style>
<body>
<h2>ESP Weather Station</h2>
<div id="chart-temperature" class="container"></div>
<div id="chart-pressure" class="container"></div>
<div id="chart-altitude" class="container"></div>
<script>

var temperature = <?php echo $temperature; ?>;
var pressure = <?php echo $pressure; ?>;
var altitude = <?php echo $altitude; ?>;
var reading_time = <?php echo $reading_time; ?>;

var chartT = new Highcharts.Chart({
chart:{ renderTo : 'chart-temperature' },
title: { text: 'BMP280 Temperature' },
series: [{
showInLegend: false,
data: temperature
}],
plotOptions: {
line: { animation: false,
dataLabels: { enabled: true }
},
series: { color: '#059e8a' }
},
xAxis: {
type: 'datetime',
categories: reading_time
},
yAxis: {
title: { text: 'Temperature (Celsius)' }
//title: { text: 'Temperature (Fahrenheit)' }
},
credits: { enabled: false }
});

var chartH = new Highcharts.Chart({
chart:{ renderTo:'chart-pressure' },
title: { text: 'BMP280 Pressure' },
series: [{
showInLegend: false,
data: pressure
}],
plotOptions: {
line: { animation: false,
dataLabels: { enabled: true }
}
},
xAxis: {
type: 'datetime',
//dateTimeLabelFormats: { second: '%H:%M:%S' },
categories: reading_time
},
yAxis: {
title: { text: 'Pressure (hPa)' }
},
credits: { enabled: false }
});


var chartP = new Highcharts.Chart({
chart:{ renderTo:'chart-altitude' },
title: { text: 'BMP280 Approx. Altitude' },
series: [{
showInLegend: false,
data: altitude
}],
plotOptions: {
line: { animation: false,
dataLabels: { enabled: true }
},
series: { color: '#18009c' }
},
xAxis: {
type: 'datetime',
categories: reading_time
},
yAxis: {
title: { text: 'Approx. Altitude (m)' }
},
credits: { enabled: false }
});

</script>
</body>
</html>

Berikut adalah hasilnya

CHART-2

  • Masuk ke file manager, kemudian folder public_html
  • Buat terlebih dahulu file esp-database.php. Berikut adalah kode yang digunakan.
<!--
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 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, temperature, pressure, altitude, reading_time FROM DataSensor 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, temperature, pressure, altitude, reading_time FROM DataSensor 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 DataSensor 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 DataSensor 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 DataSensor order by reading_time desc limit " . $limit . ") AS avg";
if ($result = $conn->query($sql)) {
return $result->fetch_assoc();
}
else {
return false;
}
$conn->close();
}
?>
  • Kemudian edit file index.php kita, dengan menggunakan kode berikut.
<!--
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["temperature"];
$last_reading_pressure = $last_reading["pressure"];
$last_reading_altitude = $last_reading["altitude"];
$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, 'temperature');
$max_temp = maxReading($readings_count, 'temperature');
$avg_temp = avgReading($readings_count, 'temperature');

$min_pressure = minReading($readings_count, 'pressure');
$max_pressure = maxReading($readings_count, 'pressure');
$avg_pressure = avgReading($readings_count, 'pressure');

$min_altitude = minReading($readings_count, 'altitude');
$max_altitude = maxReading($readings_count, 'altitude');
$avg_altitude = avgReading($readings_count, 'altitude');
?>

<!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>PRESSURE</h3>
<div class="mask">
<div class="semi-circle"></div>
<div class="semi-circle--mask"></div>
</div>
<p style="font-size: 30px;" id="pressure">--</p>
<table cellspacing="5" cellpadding="5">
<tr>
<th colspan="3">Pressure <?php echo $readings_count; ?> readings</th>
</tr>
<tr>
<td>Min</td>
<td>Max</td>
<td>Average</td>
</tr>
<tr>
<td><?php echo $min_pressure['min_amount']; ?> hPa</td>
<td><?php echo $max_pressure['max_amount']; ?> hPa</td>
<td><?php echo round($avg_pressure['avg_amount'], 2); ?> hPa</td>
</tr>
</table>
</div>
<div class="box gauge--3">
<h3>APPROX. ALTITUDE</h3>
<div class="mask">
<div class="semi-circle"></div>
<div class="semi-circle--mask"></div>
</div>
<p style="font-size: 30px;" id="altitude">--</p>
<table cellspacing="5" cellpadding="5">
<tr>
<th colspan="3">Approx. Altitude <?php echo $readings_count; ?> readings</th>
</tr>
<tr>
<td>Min</td>
<td>Max</td>
<td>Average</td>
</tr>
<tr>
<td><?php echo $min_altitude['min_amount']; ?> m</td>
<td><?php echo $max_altitude['max_amount']; ?> m</td>
<td><?php echo round($avg_altitude['avg_amount'], 2); ?> m</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>Temperature</th>
<th>Pressure</th>
<th>Approx. Altitude</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_temperature = $row["temperature"];
$row_pressure = $row["pressure"];
$row_altitude = $row["altitude"];
$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_temperature . '</td>
<td>' . $row_pressure . '</td>
<td>' . $row_altitude . '</td>
<td>' . $row_reading_time . '</td>
</tr>';
}
echo '</table>';
$result->free();
}
?>

<script>
var temperature = <?php echo $last_reading_temp; ?>;
var pressure = <?php echo $last_reading_pressure; ?>;
var altitude = <?php echo $last_reading_altitude; ?>;
setTemperature(temperature);
setPressure(pressure);
setAltitude(altitude);

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 setPressure(curVal){
//set range for Pressure
var minPressure = 0;
var maxPressure = 1000;

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

function setAltitude(curVal){
//set range for Altitude
var minAltitude = 0;
var maxAltitude = 1500;

var newVal = scaleValue(curVal, [minAltitude, maxAltitude], [0, 180]);
$('.gauge--3 .semi-circle--mask').attr({
style: '-webkit-transform: rotate(' + newVal + 'deg);' +
'-moz-transform: rotate(' + newVal + 'deg);' +
'transform: rotate(' + newVal + 'deg);'
});
$("#altitude").text(curVal + ' m');
}

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>

Kemudian buat file esp-style.css. Berikut adalah kode css yang digunakan.

/**
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: 750px) {
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 {
justify-content: center;
position: relative;
overflow: hidden;
display: inline-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,
#0294f5 0%,
#0270ba 33%,
#013152 70%,
#011726 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%;
}

#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;
}

Berikut adalah hasilnya.

Akhirnya kita telah menyelesaikan project kesepuluh ini, sampai jumpa di project berikutnyaa!!!

--

--