Translate

Monitoring Suhu Kelembaban Sensor DHT11 Webserver Grafik Realtime dan Datalogger Penampil DMD P10 ESP32 WIFI

Monitoring Suhu Kelembaban Sensor DHT11 Webserver Grafik Realtime dan Datalogger Penampil DMD P10 ESP32 WIFI


       Pada kesempatan kali ini saya akan menjelaskan mengenai bagaimana cara membuat sebuah alat yang dapat menampilkan nilai suhu dan kelembaban yang disertai grafik realtime pada webserver. alat ini menggunakan sensor DHT11 dan  penampil DMD P10.untuk mikrokontroller yang digunakan yaitu ESP32. untuk lebih jelasnya berikut adalah koding dan skemanya.


1. Skema




2. Interface Webserver




3. Program ESP32

/*

  DHT11:
  DATA = GPIO 13

  P10:
  1 PANEL 32x16

  Tampilan:
  T28.5
  H72%

  WIFI AP:
  SSID     = ESP32-MONITOR
  PASSWORD = 12345678

  WEB:
  http://192.168.4.1

  FITUR:
  - Suhu realtime
  - Kelembaban realtime
  - Grafik suhu
  - Grafik kelembaban
  - Tabel 60 data terbaru
  - ESP32 Access Point
  - DMD scan terus menerus
  - Tanpa delay()
  - Tanpa hardware timer
  - Tanpa FreeRTOS task
*/


#include <WiFi.h>
#include <WebServer.h>
#include <DHT.h>

#include <DMD32.h>
#include "fonts/SystemFont5x7.h"


// =========================================================
// DHT11
// =========================================================

#define DHTPIN 13
#define DHTTYPE DHT11

DHT dht(DHTPIN, DHTTYPE);


// =========================================================
// WIFI
// =========================================================

const char* ssid = "ESP32-MONITOR";
const char* password = "12345678";

WebServer server(80);


// =========================================================
// DMD P10
// =========================================================

#define DISPLAYS_ACROSS 1
#define DISPLAYS_DOWN   1

DMD dmd(DISPLAYS_ACROSS, DISPLAYS_DOWN);


// =========================================================
// SENSOR
// =========================================================

float suhu = 0.0;
float kelembaban = 0.0;

bool sensorOK = false;


// =========================================================
// TIMER SENSOR
// =========================================================

unsigned long sensorMillis = 0;

const unsigned long SENSOR_INTERVAL = 2000;


// =========================================================
// DATA P10
// =========================================================

int displaySuhu10 = -9999;
int displayHum10  = -9999;

int newSuhu10 = -9999;
int newHum10  = -9999;

bool displayUpdate = false;


// =========================================================
// HTML
// =========================================================

const char MAIN_PAGE[] PROGMEM = R"rawliteral(

<!DOCTYPE html>

<html lang="id">

<head>

<meta charset="UTF-8">

<meta name="viewport"
content="width=device-width, initial-scale=1">

<title>ESP32 Monitoring</title>

<style>

body {
    margin:0;
    padding:15px;
    font-family:Arial,Helvetica,sans-serif;
    background:#eeeeee;
    color:#222;
}

.container {
    max-width:950px;
    margin:auto;
}

h1 {
    text-align:center;
}

.status {
    background:white;
    padding:15px;
    border-radius:10px;
    text-align:center;
    margin-bottom:15px;
    box-shadow:0 2px 8px rgba(0,0,0,0.10);
}

.cards {
    display:flex;
    justify-content:center;
    gap:15px;
    flex-wrap:wrap;
}

.card {
    background:white;
    width:220px;
    padding:20px;
    text-align:center;
    border-radius:10px;
    box-shadow:0 2px 8px rgba(0,0,0,0.15);
}

.value {
    font-size:42px;
    font-weight:bold;
}

.unit {
    font-size:20px;
}

.box {
    background:white;
    margin-top:20px;
    padding:15px;
    border-radius:10px;
    box-shadow:0 2px 8px rgba(0,0,0,0.10);
}

canvas {
    width:100%;
    height:250px;
    border:1px solid #dddddd;
    display:block;
}

.table-container {
    overflow-x:auto;
    max-height:500px;
    overflow-y:auto;
}

table {
    width:100%;
    border-collapse:collapse;
    min-width:500px;
}

th {
    background:#1976d2;
    color:white;
    position:sticky;
    top:0;
    z-index:1;
}

th,td {
    padding:9px;
    border-bottom:1px solid #dddddd;
    text-align:center;
}

tr:nth-child(even) {
    background:#f5f5f5;
}

tr:hover {
    background:#e3f2fd;
}

@media(max-width:600px) {

    .card {
        width:100%;
    }

}

</style>

</head>


<body>

<div class="container">

<h1>
ESP32 MONITORING
</h1>


<div class="status">

<b>ESP32 ACCESS POINT</b>

<br><br>

SSID:
ESP32-MONITOR

<br>

IP:
192.168.4.1

<br>

Update sensor:
2 detik

<br>

Tabel:
60 data terbaru

</div>


<div class="cards">


<div class="card">

<h2>Suhu</h2>

<div class="value">

<span id="suhu">
--
</span>

</div>

<div class="unit">
°C
</div>

</div>


<div class="card">

<h2>Kelembaban</h2>

<div class="value">

<span id="hum">
--
</span>

</div>

<div class="unit">
%
</div>

</div>


</div>


<div class="box">

<h2>
Grafik Suhu
</h2>

<canvas
id="tempCanvas"
width="800"
height="250">
</canvas>

</div>


<div class="box">

<h2>
Grafik Kelembaban
</h2>

<canvas
id="humCanvas"
width="800"
height="250">
</canvas>

</div>


<div class="box">

<h2>
60 Data Terbaru
</h2>

<div class="table-container">

<table>

<thead>

<tr>

<th>No</th>
<th>Waktu</th>
<th>Suhu</th>
<th>Kelembaban</th>

</tr>

</thead>

<tbody id="tableData">

</tbody>

</table>

</div>

</div>

</div>


<script>

let tempData = [];
let humData = [];
let tableData = [];

const MAX_TABLE = 60;
const MAX_GRAPH = 60;


function getTime()
{

    let now = new Date();

    let h =
        String(now.getHours())
        .padStart(2,"0");

    let m =
        String(now.getMinutes())
        .padStart(2,"0");

    let s =
        String(now.getSeconds())
        .padStart(2,"0");

    return h + ":" + m + ":" + s;

}


const tempCanvas =
    document.getElementById("tempCanvas");

const humCanvas =
    document.getElementById("humCanvas");

const tempCtx =
    tempCanvas.getContext("2d");

const humCtx =
    humCanvas.getContext("2d");


function drawChart(ctx, values, title, unit)
{

    let width = ctx.canvas.width;
    let height = ctx.canvas.height;

    ctx.clearRect(
        0,
        0,
        width,
        height
    );

    ctx.fillStyle = "#ffffff";

    ctx.fillRect(
        0,
        0,
        width,
        height
    );

    if(values.length == 0)
        return;


    let min =
        Math.min(...values);

    let max =
        Math.max(...values);


    if(max == min)
    {
        max += 1;
        min -= 1;
    }


    const left = 50;
    const right = 15;
    const top = 30;
    const bottom = 30;


    let graphWidth =
        width - left - right;

    let graphHeight =
        height - top - bottom;


    ctx.strokeStyle = "#dddddd";

    ctx.lineWidth = 1;


    for(let i=0;i<=4;i++)
    {

        let y =
            top +
            i * graphHeight / 4;


        ctx.beginPath();

        ctx.moveTo(
            left,
            y
        );

        ctx.lineTo(
            width-right,
            y
        );

        ctx.stroke();


        let value =
            max -
            i * (max-min) / 4;


        ctx.fillStyle = "#555555";

        ctx.font = "12px Arial";

        ctx.fillText(
            value.toFixed(1),
            5,
            y+4
        );

    }


    ctx.strokeStyle = "#1976d2";

    ctx.lineWidth = 2;

    ctx.beginPath();


    for(let i=0;i<values.length;i++)
    {

        let x;

        if(values.length == 1)
        {
            x =
                left +
                graphWidth / 2;
        }
        else
        {
            x =
                left +
                i *
                graphWidth /
                (values.length-1);
        }


        let y =
            top +
            graphHeight -
            (
                (values[i]-min) /
                (max-min)
            ) *
            graphHeight;


        if(i == 0)
        {
            ctx.moveTo(x,y);
        }
        else
        {
            ctx.lineTo(x,y);
        }

    }


    ctx.stroke();


    ctx.fillStyle = "#1976d2";


    for(let i=0;i<values.length;i++)
    {

        let x;

        if(values.length == 1)
        {
            x =
                left +
                graphWidth / 2;
        }
        else
        {
            x =
                left +
                i *
                graphWidth /
                (values.length-1);
        }


        let y =
            top +
            graphHeight -
            (
                (values[i]-min) /
                (max-min)
            ) *
            graphHeight;


        ctx.beginPath();

        ctx.arc(
            x,
            y,
            3,
            0,
            Math.PI*2
        );

        ctx.fill();

    }


    ctx.fillStyle = "#222";

    ctx.font =
        "bold 15px Arial";

    ctx.fillText(
        title + " (" + unit + ")",
        left,
        18
    );

}


function updateTable()
{

    let tbody =
        document.getElementById(
            "tableData"
        );


    tbody.innerHTML = "";


    for(
        let i=0;
        i<tableData.length;
        i++
    )
    {

        let data =
            tableData[i];


        let row =
            document.createElement(
                "tr"
            );


        row.innerHTML =

            "<td>" +
            (i+1) +
            "</td>" +

            "<td>" +
            data.time +
            "</td>" +

            "<td>" +
            data.suhu.toFixed(1) +
            " °C</td>" +

            "<td>" +
            data.hum.toFixed(1) +
            " %</td>";


        tbody.appendChild(row);

    }

}


function updateSensor()
{

    fetch("/data")

    .then(
        response =>
        response.json()
    )

    .then(
        data =>
        {

            document.getElementById(
                "suhu"
            ).innerHTML =
                data.suhu.toFixed(1);


            document.getElementById(
                "hum"
            ).innerHTML =
                data.hum.toFixed(1);


            tempData.push(
                data.suhu
            );

            humData.push(
                data.hum
            );


            if(
                tempData.length >
                MAX_GRAPH
            )
            {
                tempData.shift();
            }


            if(
                humData.length >
                MAX_GRAPH
            )
            {
                humData.shift();
            }


            drawChart(
                tempCtx,
                tempData,
                "SUHU",
                "°C"
            );


            drawChart(
                humCtx,
                humData,
                "KELEMBABAN",
                "%"
            );


            tableData.unshift(
            {
                time:getTime(),
                suhu:data.suhu,
                hum:data.hum
            });


            if(
                tableData.length >
                MAX_TABLE
            )
            {
                tableData.pop();
            }


            updateTable();

        }
    )

    .catch(
        error =>
        console.log(error)
    );

}


setInterval(
    updateSensor,
    2000
);


updateSensor();

</script>

</body>

</html>

)rawliteral";


// =========================================================
// UPDATE BUFFER P10
// =========================================================

void updateP10Buffer()
{

    if(!displayUpdate)
        return;


    displayUpdate = false;


    int suhu10 =
        newSuhu10;

    int hum =
        newHum10;


    if(
        suhu10 == displaySuhu10 &&
        hum == displayHum10
    )
    {
        return;
    }


    displaySuhu10 = suhu10;
    displayHum10 = hum;


    char textSuhu[12];
    char textHum[12];


    float suhuDisplay =
        ((float)suhu10) / 10.0;


    sprintf(
        textSuhu,
        "T%.1f",
        suhuDisplay
    );


    sprintf(
        textHum,
        "H%d%%",
        hum
    );


    /*
      Jangan gunakan clearScreen()
      setiap kali update.

      Bersihkan buffer satu kali,
      kemudian gambar kedua baris.
    */

    dmd.clearScreen(true);


    dmd.selectFont(
        SystemFont5x7
    );


    dmd.drawString(
        0,
        0,
        textSuhu,
        strlen(textSuhu),
        GRAPHICS_NORMAL
    );


    dmd.drawString(
        0,
        8,
        textHum,
        strlen(textHum),
        GRAPHICS_NORMAL
    );

}


// =========================================================
// BACA SENSOR
// =========================================================

void bacaSensor()
{

    float h =
        dht.readHumidity();

    float t =
        dht.readTemperature();


    if(
        isnan(h) ||
        isnan(t)
    )
    {

        Serial.println(
            "DHT11 ERROR"
        );

        sensorOK = false;

        return;
    }


    suhu = t;

    kelembaban = h;

    sensorOK = true;


    Serial.print(
        "Suhu : "
    );

    Serial.print(
        suhu,
        1
    );

    Serial.println(
        " C"
    );


    Serial.print(
        "Hum  : "
    );

    Serial.print(
        kelembaban,
        1
    );

    Serial.println(
        " %"
    );


    /*
      Jangan langsung menggambar P10
      di sini.

      Hanya siapkan data baru.
    */

    newSuhu10 =
        (int)round(
            suhu * 10.0
        );


    newHum10 =
        (int)round(
            kelembaban
        );


    displayUpdate = true;

}


// =========================================================
// ROOT
// =========================================================

void handleRoot()
{

    server.send_P(
        200,
        "text/html",
        MAIN_PAGE
    );

}


// =========================================================
// DATA
// =========================================================

void handleData()
{

    String json;

    json.reserve(50);


    json = "{";


    json +=
        "\"suhu\":";


    json +=
        String(
            suhu,
            1
        );


    json +=
        ",";


    json +=
        "\"hum\":";


    json +=
        String(
            kelembaban,
            1
        );


    json +=
        "}";


    server.send(
        200,
        "application/json",
        json
    );

}


// =========================================================
// SETUP
// =========================================================

void setup()
{

    Serial.begin(
        115200
    );


    dht.begin();


    // =====================================================
    // DMD
    // =====================================================

    dmd.clearScreen(
        true
    );


    dmd.selectFont(
        SystemFont5x7
    );


    // =====================================================
    // WIFI
    // =====================================================

    WiFi.mode(
        WIFI_AP
    );


    WiFi.setSleep(
        false
    );


    WiFi.softAP(
        ssid,
        password,
        1,
        false,
        4
    );


    Serial.println();

    Serial.println(
        "=============================="
    );

    Serial.println(
        "ESP32 MONITORING ANTI FLICKER"
    );

    Serial.println(
        "=============================="
    );


    Serial.print(
        "SSID : "
    );

    Serial.println(
        ssid
    );


    Serial.print(
        "IP   : "
    );

    Serial.println(
        WiFi.softAPIP()
    );


    // =====================================================
    // WEB
    // =====================================================

    server.on(
        "/",
        handleRoot
    );


    server.on(
        "/data",
        handleData
    );


    server.begin();


    Serial.println(
        "Webserver aktif"
    );


    // =====================================================
    // SENSOR PERTAMA
    // =====================================================

    bacaSensor();


    /*
      Buat tampilan awal.
    */

    updateP10Buffer();

}


// =========================================================
// LOOP
// =========================================================

void loop()
{

    /*
      =====================================================
      PRIORITAS PERTAMA:
      SCAN P10
      =====================================================
    */

    dmd.scanDisplayBySPI();


    /*
      Update buffer hanya ketika
      ada data sensor baru.
    */

    if(displayUpdate)
    {
        updateP10Buffer();
    }


    /*
      Webserver diproses sesudah
      scanning P10.
    */

    server.handleClient();


    /*
      Sensor setiap 2 detik.
    */

    unsigned long sekarang =
        millis();


    if(
        sekarang -
        sensorMillis >=
        SENSOR_INTERVAL
    )
    {

        sensorMillis =
            sekarang;


        bacaSensor();

    }

}



4. VIDEO HASILNYA



Monitoring Suhu Ds18b20 Realtime Grafik dan Datalogger Webserver ESP32 Penampil DMD P10

Monitoring Suhu Ds18b20 Realtime Grafik dan Datalogger Webserver ESP32 Penampil DMD P10
 

        Pada kesempatan kali ini saya akan menjelaskan mengenai bagaimana cara membuat sebuah alat yang dapat memonitor suhu secara realtime grafik dengan datalogger. alat ini juga bisa dipantu via web browser bisa menggunakan chrome atau mozilla. untuk lebih jelasnya berikut adalah koding dan skemanya.
 
 
1. Skema
 
 

2. Interface
 

 
 
3. Program ESP32
 
#include <WiFi.h>
#include <WebServer.h>

#include <OneWire.h>
#include <DallasTemperature.h>

#include <SPI.h>
#include <DMD32.h>
#include "fonts/SystemFont5x7.h"


// =====================================================
// WIFI ACCESS POINT
// =====================================================

const char* ssid = "ESP32-SUHU";
const char* password = "12345678";

WebServer server(80);


// =====================================================
// DS18B20
// =====================================================

#define DS18B20_PIN 13

OneWire oneWire(DS18B20_PIN);
DallasTemperature sensors(&oneWire);

float suhu = 0.0;


// =====================================================
// DMD P10
// =====================================================

#define DISPLAYS_ACROSS 1
#define DISPLAYS_DOWN   1

DMD dmd(
  DISPLAYS_ACROSS,
  DISPLAYS_DOWN
);


// =====================================================
// TIMER DMD
// =====================================================

hw_timer_t *timer = NULL;

void IRAM_ATTR triggerScan()
{
  dmd.scanDisplayBySPI();
}


// =====================================================
// WEB PAGE
// =====================================================

const char webpage[] PROGMEM = R"rawliteral(

<!DOCTYPE html>

<html>

<head>

<meta name="viewport"
content="width=device-width, initial-scale=1">

<title>Monitoring Suhu ESP32</title>


<style>

body {

  background:#111;

  color:white;

  font-family:Arial;

  text-align:center;

  margin:0;

}


.container {

  max-width:900px;

  margin:auto;

  padding:15px;

}


.card {

  background:#222;

  padding:20px;

  margin-bottom:20px;

  border-radius:15px;

}


h1 {

  margin-top:5px;

}


.suhu {

  font-size:70px;

  font-weight:bold;

  margin:20px;

}


.status {

  font-size:18px;

  margin-bottom:10px;

}


.waktu {

  font-size:18px;

  color:#aaa;

}


canvas {

  width:100%;

  max-width:850px;

  height:400px;

  background:#181818;

  border-radius:10px;

}


table {

  width:100%;

  border-collapse:collapse;

  margin-top:15px;

}


th {

  background:#333;

  padding:10px;

}


td {

  padding:8px;

  border-bottom:1px solid #444;

}


.data {

  max-height:300px;

  overflow-y:auto;

}


</style>

</head>


<body>


<div class="container">


<!-- ================================================= -->
<!-- NILAI SUHU -->
<!-- ================================================= -->

<div class="card">

<h1>MONITORING SUHU</h1>


<div class="suhu">

<span id="suhu">

--.--

</span>

°C

</div>


<div class="status"
id="status">

Menghubungkan...

</div>


<div class="waktu">

Waktu sekarang:

<span id="waktu">

--

</span>

</div>

</div>



<!-- ================================================= -->
<!-- GRAFIK -->
<!-- ================================================= -->

<div class="card">

<h2>Grafik Suhu Realtime</h2>


<canvas
id="graph"
width="850"
height="400">
</canvas>


</div>



<!-- ================================================= -->
<!-- TABEL DATA -->
<!-- ================================================= -->

<div class="card">

<h2>Data Suhu</h2>


<div class="data">

<table>

<thead>

<tr>

<th>No</th>

<th>Waktu</th>

<th>Suhu</th>

</tr>

</thead>


<tbody id="tabel">

</tbody>


</table>

</div>

</div>


</div>



<script>


// =====================================================
// ARRAY DATA
// =====================================================

let dataSuhu = [];

let dataWaktu = [];


// maksimal 60 data

const maxData = 60;



// =====================================================
// CANVAS
// =====================================================

const canvas =
document.getElementById("graph");

const ctx =
canvas.getContext("2d");



// =====================================================
// AMBIL DATA DARI ESP32
// =====================================================

function updateSuhu()
{


fetch("/suhu")


.then(
response => response.json()
)


.then(
data =>
{


let nilai =
parseFloat(data.suhu);


// -----------------------------------------------------
// TAMPILKAN NILAI
// -----------------------------------------------------

document.getElementById(
"suhu"
).innerHTML =
nilai.toFixed(2);


document.getElementById(
"status"
).innerHTML =
"ESP32 terhubung";



// -----------------------------------------------------
// WAKTU
// -----------------------------------------------------

let sekarang =
new Date();


let jam =
String(
sekarang.getHours()
).padStart(2,'0');


let menit =
String(
sekarang.getMinutes()
).padStart(2,'0');


let detik =
String(
sekarang.getSeconds()
).padStart(2,'0');


let waktu =
jam +
":" +
menit +
":" +
detik;



document.getElementById(
"waktu"
).innerHTML =
waktu;



// -----------------------------------------------------
// SIMPAN DATA
// -----------------------------------------------------

dataSuhu.push(nilai);

dataWaktu.push(waktu);



if (
dataSuhu.length > maxData
)
{

dataSuhu.shift();

dataWaktu.shift();

}



// -----------------------------------------------------
// UPDATE GRAFIK
// -----------------------------------------------------

gambarGrafik();



// -----------------------------------------------------
// UPDATE TABEL
// -----------------------------------------------------

updateTabel();


})


.catch(
error =>
{

document.getElementById(
"status"
).innerHTML =
"ESP32 tidak terhubung";

});

}



// =====================================================
// GRAFIK
// =====================================================

function gambarGrafik()
{


ctx.clearRect(
0,
0,
canvas.width,
canvas.height
);


if (
dataSuhu.length < 1
)
return;



// -----------------------------------------------------
// CARI NILAI MIN/MAX
// -----------------------------------------------------

let min =
Math.min(...dataSuhu);


let max =
Math.max(...dataSuhu);


if (
max - min < 2
)
{

let tengah =
(min + max) / 2;


min =
tengah - 1;


max =
tengah + 1;

}


min -= 0.5;

max += 0.5;



// -----------------------------------------------------
// AREA GRAFIK
// -----------------------------------------------------

let kiri = 60;

let kanan = 20;

let atas = 30;

let bawah = 55;


let lebar =
canvas.width -
kiri -
kanan;


let tinggi =
canvas.height -
atas -
bawah;



// -----------------------------------------------------
// GRID
// -----------------------------------------------------

ctx.strokeStyle =
"#333";

ctx.lineWidth = 1;


for (
let i = 0;
i <= 5;
i++
)
{

let y =
atas +
i *
(tinggi / 5);


ctx.beginPath();


ctx.moveTo(
kiri,
y
);


ctx.lineTo(
canvas.width - kanan,
y
);


ctx.stroke();

}



// -----------------------------------------------------
// LABEL SUHU
// -----------------------------------------------------

ctx.fillStyle =
"#aaa";


ctx.font =
"14px Arial";


for (
let i = 0;
i <= 5;
i++
)
{

let nilai =
max -
i *
((max - min) / 5);


let y =
atas +
i *
(tinggi / 5);


ctx.fillText(

nilai.toFixed(1)
+
" °C",

5,

y + 5

);

}



// -----------------------------------------------------
// GARIS GRAFIK
// -----------------------------------------------------

ctx.strokeStyle =
"#00ff88";

ctx.lineWidth = 3;


ctx.beginPath();


dataSuhu.forEach(
(suhu,index) =>
{


let x;


if (
dataSuhu.length == 1
)
{

x = kiri;

}
else
{

x =
kiri +
index *
(
lebar /
(dataSuhu.length - 1)
);

}



let y =
atas +
(max - suhu) /
(max - min) *
tinggi;



if (
index == 0
)
{

ctx.moveTo(
x,
y
);

}
else
{

ctx.lineTo(
x,
y
);

}

});


ctx.stroke();



// -----------------------------------------------------
// TITIK
// -----------------------------------------------------

ctx.fillStyle =
"#ffffff";


dataSuhu.forEach(
(suhu,index) =>
{


let x;


if (
dataSuhu.length == 1
)
{

x = kiri;

}
else
{

x =
kiri +
index *
(
lebar /
(dataSuhu.length - 1)
);

}


let y =
atas +
(max - suhu) /
(max - min) *
tinggi;



ctx.beginPath();


ctx.arc(
x,
y,
4,
0,
Math.PI * 2
);


ctx.fill();



// ---------------------------------------------------
// NILAI DI TITIK
// ---------------------------------------------------

ctx.fillStyle =
"#fff";


ctx.font =
"12px Arial";


ctx.fillText(

suhu.toFixed(1)
+
"°C",

x - 15,

y - 10

);

});



// -----------------------------------------------------
// SUMBU WAKTU
// -----------------------------------------------------

ctx.fillStyle =
"#aaa";


ctx.font =
"12px Arial";


// tampilkan beberapa label waktu

let jumlahLabel = 6;


for (
let i = 0;
i < jumlahLabel;
i++
)
{


if (
dataWaktu.length == 0
)
break;


let index =
Math.floor(
i *
(dataWaktu.length - 1) /
(jumlahLabel - 1)
);


let x;


if (
dataWaktu.length == 1
)
{

x = kiri;

}
else
{

x =
kiri +
index *
(
lebar /
(dataWaktu.length - 1)
);

}


ctx.fillText(

dataWaktu[index],

x - 20,

canvas.height - 20

);

}

}



// =====================================================
// TABEL
// =====================================================

function updateTabel()
{


let tabel =
document.getElementById(
"tabel"
);


tabel.innerHTML = "";



// tampilkan data terbaru di atas

for (
let i =
dataSuhu.length - 1;

i >= 0;

i--
)
{


let row =
tabel.insertRow();


let no =
row.insertCell(0);

let waktu =
row.insertCell(1);

let suhu =
row.insertCell(2);


no.innerHTML =
dataSuhu.length - i;


waktu.innerHTML =
dataWaktu[i];


suhu.innerHTML =
dataSuhu[i].toFixed(2)
+
" °C";

}

}



// =====================================================
// MULAI
// =====================================================

updateSuhu();


setInterval(
updateSuhu,
1000
);


</script>


</body>

</html>

)rawliteral";



// =====================================================
// WEB ROOT
// =====================================================

void handleRoot()
{

server.send(
200,
"text/html",
webpage
);

}



// =====================================================
// DATA SUHU
// =====================================================

void handleSuhu()
{

String json =
"{\"suhu\":" +
String(suhu,2) +
"}";


server.send(
200,
"application/json",
json
);

}



// =====================================================
// BACA DS18B20
// =====================================================

void bacaSuhu()
{

sensors.requestTemperatures();


float t =
sensors.getTempCByIndex(0);


if (
t != DEVICE_DISCONNECTED_C
)
{

suhu = t;

}

}



// =====================================================
// TAMPILKAN P10
// =====================================================

void tampilP10()
{

char teks[20];


sprintf(
teks,
"%.1f  ",
suhu
);


//dmd.clearScreen();


dmd.selectFont(
SystemFont5x7
);


dmd.drawString(
0,
0,
"SUHU",
4,
GRAPHICS_NORMAL
);


dmd.drawString(
0,
9,
teks,
strlen(teks),
GRAPHICS_NORMAL
);

}



// =====================================================
// SETUP
// =====================================================

void setup()
{

Serial.begin(
115200
);


delay(1000);


Serial.println();
Serial.println(
"START ESP32"
);



// =====================================================
// WIFI
// =====================================================

WiFi.mode(
WIFI_AP
);


delay(500);


bool ap =
WiFi.softAP(
ssid,
password
);


if(ap)
{

Serial.println(
"ACCESS POINT BERHASIL"
);


Serial.print(
"SSID : "
);

Serial.println(
ssid
);


Serial.print(
"IP   : "
);

Serial.println(
WiFi.softAPIP()
);

}
else
{

Serial.println(
"ACCESS POINT GAGAL"
);

}



// =====================================================
// WEB SERVER
// =====================================================

server.on(
"/",
handleRoot
);


server.on(
"/suhu",
handleSuhu
);


server.begin();


Serial.println(
"WEB SERVER AKTIF"
);



// =====================================================
// DS18B20
// =====================================================

sensors.begin();


Serial.println(
"DS18B20 OK"
);



// =====================================================
// DMD
// =====================================================

//dmd.clearScreen();


dmd.selectFont(
SystemFont5x7
);


// =====================================================
// TIMER DMD
// =====================================================

timer =
timerBegin(
0,
80,
true
);


timerAttachInterrupt(
timer,
&triggerScan,
true
);


timerAlarmWrite(
timer,
300,
true
);


timerAlarmEnable(
timer
);


Serial.println(
"DMD P10 OK"
);


Serial.println();
Serial.println(
"=========================="
);

Serial.println(
"SSID : ESP32-SUHU"
);

Serial.println(
"PASS : 12345678"
);

Serial.println(
"IP   : 192.168.4.1"
);

Serial.println(
"=========================="
);

}



// =====================================================
// LOOP
// =====================================================

unsigned long waktuSensor = 0;


void loop()
{

server.handleClient();



if (
millis() - waktuSensor >= 1000
)
{

waktuSensor =
millis();


bacaSuhu();


tampilP10();


Serial.print(
"Suhu: "
);


Serial.print(
suhu,
2
);


Serial.println(
" C"
);

}

}

 
4. VIDEO HASILNYA
 


Papan Score DMD P10 ESP32 Olahraga Input via Browser

Papan Score DMD P10 ESP32 Olahraga Input via Browser
 

          Pada kesempatan kali ini saya akan menjelaskan mengenai bagaimana cara membuat sebuah papan scoreboard sederhana dengan menggunakan ESP32 dan input via Aplikasi Browser seperti chrome atau mozilla. alat ini menggunakan input nama team dan score melalui browser serta bisa geser posisi untuk titik kemunculan text di dmd p10. berikut koding dan skemanya.
 
 
1. Skema
 

 
2. Interface
 



 3. Program ESP32
 
 /*
  ============================================================
  SCOREBOARD SEPAK BOLA
  ESP32 + P10 + DMD32
  ESP32 sebagai ACCESS POINT / HOTSPOT
  ============================================================

  WIFI
  SSID     : SCOREBOARD
  PASSWORD : 12345678
  IP       : 192.168.4.1

  FITUR
  ------------------------------------------------------------
  - ESP32 sebagai Access Point
  - Web browser melalui HP
  - Nama Team A maksimal 3 huruf
  - Nama Team B maksimal 3 huruf
  - Score Team A
  - Score Team B
  - +1 / -1
  - Reset score
  - Posisi X
  - Posisi Y
  - Geser tampilan dari browser
  - HOME untuk posisi awal
  - P10 32x16
  - Library DMD32
  ============================================================
*/

#include <WiFi.h>
#include <WebServer.h>
#include <SPI.h>
#include <DMD32.h>
#include "fonts/SystemFont5x7.h"


// ============================================================
// KONFIGURASI P10
// ============================================================

#define DISPLAYS_ACROSS 1
#define DISPLAYS_DOWN   1

DMD dmd(DISPLAYS_ACROSS, DISPLAYS_DOWN);


// ============================================================
// TIMER DMD32
// ============================================================

hw_timer_t *timer = NULL;

void IRAM_ATTR triggerScan()
{
  dmd.scanDisplayBySPI();
}


// ============================================================
// WIFI ACCESS POINT
// ============================================================

const char* ssid = "SCOREBOARD";
const char* password = "12345678";

WebServer server(80);


// ============================================================
// DATA SCOREBOARD
// ============================================================

String teamA = "INA";
String teamB = "BRA";

int scoreA = 0;
int scoreB = 0;


// ============================================================
// POSISI TAMPILAN
// ============================================================

int posX = 0;
int posY = 0;


// ============================================================
// BATAS SCORE
// ============================================================

const int MAX_SCORE = 99;


// ============================================================
// TAMPIL SCORE KE P10
// ============================================================

void tampilScore()
{
  dmd.clearScreen(true);

  dmd.selectFont(SystemFont5x7);


  // ========================================================
  // BARIS ATAS
  // TEAM A + SCORE A
  // ========================================================

  String barisA = teamA + " " + String(scoreA);

  dmd.drawString(
    posX,
    posY,
    barisA.c_str(),
    barisA.length(),
    GRAPHICS_NORMAL
  );


  // ========================================================
  // BARIS BAWAH
  // TEAM B + SCORE B
  // ========================================================

  String barisB = teamB + " " + String(scoreB);

  dmd.drawString(
    posX,
    posY + 8,
    barisB.c_str(),
    barisB.length(),
    GRAPHICS_NORMAL
  );
}


// ============================================================
// HALAMAN WEB
// ============================================================

void handleRoot()
{
  String html = R"rawliteral(
<!DOCTYPE html>
<html>

<head>

<meta charset="UTF-8">

<meta name="viewport"
content="width=device-width, initial-scale=1.0">

<title>Scoreboard ESP32</title>

<style>

* {
  box-sizing:border-box;
}

body {
  margin:0;
  padding:15px;
  background:#111;
  color:white;
  font-family:Arial,sans-serif;
  text-align:center;
}

.container {
  max-width:500px;
  margin:auto;
}

h1 {
  margin:5px;
  font-size:30px;
}

.status {
  background:#003b20;
  color:#00ff66;
  padding:10px;
  border-radius:10px;
  margin:15px 0;
}

.teams {
  display:flex;
  gap:10px;
}

.team {
  background:#222;
  width:50%;
  padding:15px;
  border-radius:15px;
}

.team h2 {
  margin-top:0;
}

.nama {
  width:100px;
  padding:8px;
  font-size:28px;
  font-weight:bold;
  text-align:center;
  text-transform:uppercase;
  border:0;
  border-radius:8px;
}

.score {
  font-size:65px;
  font-weight:bold;
  margin:10px;
}

button {
  border:0;
  border-radius:10px;
  padding:12px 20px;
  margin:4px;
  font-size:22px;
  font-weight:bold;
}

.plus {
  background:#00aa44;
  color:white;
}

.minus {
  background:#dd2222;
  color:white;
}

.save {
  width:100%;
  background:#0066dd;
  color:white;
  margin-top:15px;
}

.reset {
  width:100%;
  background:#ff7700;
  color:white;
  margin-top:10px;
}

.position {
  margin-top:20px;
  padding:15px;
  background:#222;
  border-radius:15px;
}

.position button {
  min-width:70px;
  min-height:55px;
  background:#444;
  color:white;
}

.home {
  background:#0066aa !important;
}

.posvalue {
  font-size:20px;
  margin:10px;
  color:#00ff66;
}

@media(max-width:420px) {

  .teams {
    flex-direction:column;
  }

  .team {
    width:100%;
  }

}

</style>


<script>


// ========================================================
// SCORE TEAM A
// ========================================================

function scoreA(v)
{
  fetch("/scoreA?v=" + v)
  .then(() => {
    updateScore();
  });
}


// ========================================================
// SCORE TEAM B
// ========================================================

function scoreB(v)
{
  fetch("/scoreB?v=" + v)
  .then(() => {
    updateScore();
  });
}


// ========================================================
// UPDATE SCORE TANPA RELOAD
// ========================================================

function updateScore()
{
  fetch("/data")
  .then(response => response.json())
  .then(data => {

    document.getElementById("scoreA").innerHTML =
      data.a;

    document.getElementById("scoreB").innerHTML =
      data.b;

    document.getElementById("posX").innerHTML =
      data.x;

    document.getElementById("posY").innerHTML =
      data.y;

  });
}


// ========================================================
// RESET SCORE
// ========================================================

function resetScore()
{
  if(confirm("Reset score?"))
  {
    fetch("/reset")
    .then(() => {
      updateScore();
    });
  }
}


// ========================================================
// SIMPAN NAMA TEAM
// ========================================================

function saveTeam()
{
  let a =
    document.getElementById("teamA").value;

  let b =
    document.getElementById("teamB").value;


  a = a.toUpperCase().substring(0,3);

  b = b.toUpperCase().substring(0,3);


  fetch(
    "/team?a=" +
    encodeURIComponent(a) +
    "&b=" +
    encodeURIComponent(b)
  )
  .then(() => {

    document.getElementById("teamA").value = a;

    document.getElementById("teamB").value = b;

    alert("Nama team disimpan");

  });
}


// ========================================================
// POSISI
// ========================================================

function posisi(arah)
{
  fetch("/position?direction=" + arah)
  .then(() => {
    updateScore();
  });
}


</script>

</head>


<body>

<div class="container">


<h1>⚽ SCOREBOARD</h1>


<div class="status">

ESP32 ACCESS POINT<br>

SCOREBOARD<br>

192.168.4.1

</div>


<!-- ================================================== -->
<!-- TEAM -->
<!-- ================================================== -->

<div class="teams">


<!-- TEAM A -->

<div class="team">

<h2>TEAM A</h2>

<input
class="nama"
id="teamA"
maxlength="3"
value=")rawliteral";

  html += teamA;

  html += R"rawliteral("
oninput="
this.value=this.value
.toUpperCase()
.substring(0,3)
">

<div
class="score"
id="scoreA">

)rawliteral";

  html += String(scoreA);

  html += R"rawliteral(

</div>


<button
class="minus"
onclick="scoreA(-1)">

-1

</button>


<button
class="plus"
onclick="scoreA(1)">

+1

</button>


</div>


<!-- TEAM B -->

<div class="team">

<h2>TEAM B</h2>


<input
class="nama"
id="teamB"
maxlength="3"
value=")rawliteral";

  html += teamB;

  html += R"rawliteral("
oninput="
this.value=this.value
.toUpperCase()
.substring(0,3)
">


<div
class="score"
id="scoreB">

)rawliteral";

  html += String(scoreB);

  html += R"rawliteral(

</div>


<button
class="minus"
onclick="scoreB(-1)">

-1

</button>


<button
class="plus"
onclick="scoreB(1)">

+1

</button>


</div>


</div>


<!-- ================================================== -->
<!-- SIMPAN -->
<!-- ================================================== -->

<button
class="save"
onclick="saveTeam()">

SIMPAN NAMA TEAM

</button>


<!-- ================================================== -->
<!-- RESET -->
<!-- ================================================== -->

<button
class="reset"
onclick="resetScore()">

RESET SCORE

</button>


<!-- ================================================== -->
<!-- POSISI SCORE -->
<!-- ================================================== -->

<div class="position">

<h2>POSISI SCORE</h2>


<div>

<button
onclick="posisi('up')">



</button>

</div>


<div>

<button
onclick="posisi('left')">



</button>


<button
class="home"
onclick="posisi('home')">

HOME

</button>


<button
onclick="posisi('right')">



</button>

</div>


<div>

<button
onclick="posisi('down')">



</button>

</div>


<div class="posvalue">

X =
<span id="posX">)rawliteral";

  html += String(posX);

  html += R"rawliteral(</span>

&nbsp;&nbsp;

Y =
<span id="posY">)rawliteral";

  html += String(posY);

  html += R"rawliteral(</span>

</div>


</div>


</div>

</body>

</html>

)rawliteral";


  server.send(
    200,
    "text/html",
    html
  );
}


// ============================================================
// DATA
// ============================================================

void handleData()
{
  String json = "{";

  json += "\"a\":";
  json += String(scoreA);

  json += ",";

  json += "\"b\":";
  json += String(scoreB);

  json += ",";

  json += "\"x\":";
  json += String(posX);

  json += ",";

  json += "\"y\":";
  json += String(posY);

  json += "}";


  server.send(
    200,
    "application/json",
    json
  );
}


// ============================================================
// SCORE A
// ============================================================

void handleScoreA()
{
  if (server.hasArg("v"))
  {
    scoreA +=
      server.arg("v").toInt();
  }


  if (scoreA < 0)
    scoreA = 0;


  if (scoreA > MAX_SCORE)
    scoreA = MAX_SCORE;


  tampilScore();


  server.send(
    200,
    "text/plain",
    "OK"
  );
}


// ============================================================
// SCORE B
// ============================================================

void handleScoreB()
{
  if (server.hasArg("v"))
  {
    scoreB +=
      server.arg("v").toInt();
  }


  if (scoreB < 0)
    scoreB = 0;


  if (scoreB > MAX_SCORE)
    scoreB = MAX_SCORE;


  tampilScore();


  server.send(
    200,
    "text/plain",
    "OK"
  );
}


// ============================================================
// RESET SCORE
// ============================================================

void handleReset()
{
  scoreA = 0;
  scoreB = 0;


  tampilScore();


  server.send(
    200,
    "text/plain",
    "OK"
  );
}


// ============================================================
// NAMA TEAM
// ============================================================

void handleTeam()
{

  if (server.hasArg("a"))
  {

    teamA =
      server.arg("a");

    teamA.toUpperCase();


    if (teamA.length() > 3)
    {
      teamA =
        teamA.substring(0,3);
    }

  }


  if (server.hasArg("b"))
  {

    teamB =
      server.arg("b");

    teamB.toUpperCase();


    if (teamB.length() > 3)
    {
      teamB =
        teamB.substring(0,3);
    }

  }


  tampilScore();


  server.send(
    200,
    "text/plain",
    "OK"
  );
}


// ============================================================
// POSISI SCORE
// ============================================================

void handlePosition()
{

  if (!server.hasArg("direction"))
  {
    server.send(
      400,
      "text/plain",
      "NO DIRECTION"
    );

    return;
  }


  String arah =
    server.arg("direction");


  // -----------------------------------------
  // KIRI
  // -----------------------------------------

  if (arah == "left")
  {
    posX--;

    // batas kiri
    if (posX < -31)
      posX = -31;
  }


  // -----------------------------------------
  // KANAN
  // -----------------------------------------

  if (arah == "right")
  {
    posX++;

    // batas kanan
    if (posX > 31)
      posX = 31;
  }


  // -----------------------------------------
  // ATAS
  // -----------------------------------------

  if (arah == "up")
  {
    posY--;

    if (posY < -15)
      posY = -15;
  }


  // -----------------------------------------
  // BAWAH
  // -----------------------------------------

  if (arah == "down")
  {
    posY++;

    if (posY > 15)
      posY = 15;
  }


  // -----------------------------------------
  // HOME
  // -----------------------------------------

  if (arah == "home")
  {
    posX = 0;
    posY = 0;
  }


  tampilScore();


  server.send(
    200,
    "text/plain",
    "OK"
  );
}


// ============================================================
// SETUP
// ============================================================

void setup()
{

  Serial.begin(115200);

  delay(1000);


  Serial.println();
  Serial.println("==============================");
  Serial.println(" SCOREBOARD ESP32");
  Serial.println("==============================");


  // ========================================================
  // ACCESS POINT
  // ========================================================

  Serial.println("Membuat Access Point...");


  WiFi.mode(WIFI_AP);


  delay(500);


  bool apOK =
    WiFi.softAP(
      ssid,
      password
    );


  if (apOK)
  {

    Serial.println("ACCESS POINT OK");

    Serial.print("SSID     : ");
    Serial.println(ssid);

    Serial.print("PASSWORD : ");
    Serial.println(password);

    Serial.print("IP       : ");
    Serial.println(
      WiFi.softAPIP()
    );

  }
  else
  {

    Serial.println(
      "ACCESS POINT GAGAL!"
    );

  }


  // ========================================================
  // TIMER DMD32
  // ========================================================

  Serial.println(
    "Memulai DMD32..."
  );


  timer =
    timerBegin(
      0,
      80,
      true
    );


  timerAttachInterrupt(
    timer,
    &triggerScan,
    true
  );


  timerAlarmWrite(
    timer,
    300,
    true
  );


  timerAlarmEnable(timer);


  // ========================================================
  // DMD
  // ========================================================

  dmd.clearScreen(true);

  dmd.selectFont(
    SystemFont5x7
  );


  tampilScore();


  // ========================================================
  // WEB SERVER
  // ========================================================

  server.on(
    "/",
    handleRoot
  );


  server.on(
    "/data",
    handleData
  );


  server.on(
    "/scoreA",
    handleScoreA
  );


  server.on(
    "/scoreB",
    handleScoreB
  );


  server.on(
    "/reset",
    handleReset
  );


  server.on(
    "/team",
    handleTeam
  );


  server.on(
    "/position",
    handlePosition
  );


  server.begin();


  Serial.println();
  Serial.println("==============================");
  Serial.println(" WEB SERVER AKTIF");
  Serial.println("==============================");

  Serial.print(
    "Buka browser: http://"
  );

  Serial.println(
    WiFi.softAPIP()
  );

  Serial.println("==============================");

}


// ============================================================
// LOOP
// ============================================================

void loop()
{

  server.handleClient();

}
 
 
 
4. VIDEO HASILNYA