Translate

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
 

Timer Countdown ESP32 Input via Browser Penampil DMD P10 (Timer ujian atau Timer Futsal)

Timer Countdown ESP32 Input via Browser Penampil DMD P10 (Timer ujian atau Timer Futsal)
 

         Pada kesempatan kali ini saya akan menjelaskan mengenai bagaimana cara membuat sebuah alat yang dapat digunakan untuk Timer ujian sekolah atau Timer Sepak Bola seperti Futsal atau sejenisnya. alat ini pengoperasiannya sangatlah mudah yaitu hanya perlu menyambungkan wifi esp32 lalu masukkan passwordnya setelah itu buka browser lalu ketikkan IP 192.168.4.1 sehingga akan tampil interface seperti dibawah. untuk lebih jelasnya berikut adalah koding dan skemanya.
 
  
1. Skema
 
 
 
2. Interface
 

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

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

// =====================================================
// PENGATURAN P10
// =====================================================

#define DISPLAYS_ACROSS 1
#define DISPLAYS_DOWN   1

DMD dmd(DISPLAYS_ACROSS, DISPLAYS_DOWN);

// =====================================================
// TIMER REFRESH P10
// =====================================================

hw_timer_t *timer = NULL;

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

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

const char* ssid = "P10-COUNTDOWN";
const char* password = "12345678";

WebServer server(80);

// =====================================================
// COUNTDOWN
// =====================================================

long setSeconds = 60;
long remainingSeconds = 60;

bool timerRunning = false;

unsigned long lastSecond = 0;

// =====================================================
// FORMAT WAKTU
// =====================================================

String getTimeString()
{
  int menit = remainingSeconds / 60;
  int detik = remainingSeconds % 60;

  char buffer[10];

  sprintf(buffer, "%02d:%02d", menit, detik);

  return String(buffer);
}

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

void displayTime()
{
  String waktu = getTimeString();

  dmd.clearScreen(true);

  dmd.selectFont(SystemFont5x7);

  /*
     P10 = 32 x 16 pixel

     Arial Black 16:
     "00:00" biasanya terlalu lebar untuk
     1 panel jika menggunakan font besar.

     Posisi awal dapat disesuaikan.
  */

  int panjang = waktu.length();

  // Untuk Arial Black 16
  // posisi kira-kira di tengah
  int x = 1;
  int y = 3;

  dmd.drawString(
    x,
    y,
    waktu.c_str(),
    panjang,
    GRAPHICS_NORMAL
  );
}

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

void handleRoot()
{
  String html = R"rawliteral(

<!DOCTYPE html>

<html>

<head>

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

<title>ESP32 P10 Countdown</title>

<style>

body {
  margin: 0;
  padding: 20px;

  background: #101010;
  color: white;

  font-family: Arial;
  text-align: center;
}

.container {
  max-width: 420px;

  margin: auto;

  background: #202020;

  padding: 25px;

  border-radius: 20px;
}

h1 {
  color: #00ff66;
}

.timer {
  font-size: 55px;

  font-weight: bold;

  color: #00ff66;

  margin: 25px 0;
}

input {
  width: 90%;

  padding: 15px;

  margin: 8px 0;

  font-size: 25px;

  text-align: center;

  border: none;

  border-radius: 10px;
}

button {
  width: 90%;

  padding: 15px;

  margin: 7px;

  border: none;

  border-radius: 10px;

  font-size: 20px;

  color: white;

  cursor: pointer;
}

.set {
  background: #3498db;
}

.start {
  background: #27ae60;
}

.stop {
  background: #e67e22;
}

.reset {
  background: #e74c3c;
}

</style>

</head>

<body>

<div class="container">

<h1>ESP32 P10</h1>

<h2>COUNTDOWN TIMER</h2>

<div class="timer" id="timer">
00:00
</div>

<form action="/set" method="GET">

<input
  type="number"
  name="seconds"
  min="1"
  placeholder="Masukkan detik"
  required
>

<br>

<button class="set" type="submit">
SET WAKTU
</button>

</form>

<button class="start"
onclick="location.href='/start'">
START
</button>

<button class="stop"
onclick="location.href='/stop'">
STOP / PAUSE
</button>

<button class="reset"
onclick="location.href='/reset'">
RESET
</button>

</div>

<script>

function updateTimer()
{
  fetch('/time')
  .then(response => response.text())
  .then(data => {

    document.getElementById("timer").innerHTML = data;

  });
}

setInterval(updateTimer, 500);

updateTimer();

</script>

</body>

</html>

)rawliteral";

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

// =====================================================
// SET WAKTU
// =====================================================

void handleSet()
{
  if (server.hasArg("seconds"))
  {
    long value = server.arg("seconds").toInt();

    if (value > 0)
    {
      setSeconds = value;

      remainingSeconds = value;

      timerRunning = false;

      displayTime();
    }
  }

  server.sendHeader("Location", "/");

  server.send(303);
}

// =====================================================
// START
// =====================================================

void handleStart()
{
  if (remainingSeconds > 0)
  {
    timerRunning = true;

    lastSecond = millis();
  }

  server.sendHeader("Location", "/");

  server.send(303);
}

// =====================================================
// STOP / PAUSE
// =====================================================

void handleStop()
{
  timerRunning = false;

  server.sendHeader("Location", "/");

  server.send(303);
}

// =====================================================
// RESET
// =====================================================

void handleReset()
{
  timerRunning = false;

  remainingSeconds = setSeconds;

  displayTime();

  server.sendHeader("Location", "/");

  server.send(303);
}

// =====================================================
// DATA TIMER
// =====================================================

void handleTime()
{
  server.send(
    200,
    "text/plain",
    getTimeString()
  );
}

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

void setup()
{
  Serial.begin(115200);
   WiFi.mode(WIFI_AP);

  WiFi.softAP(ssid, password);

  Serial.println("WiFi AP aktif");
  Serial.print("SSID: ");
  Serial.println(ssid);

  Serial.print("IP ESP32: ");
  Serial.println(WiFi.softAPIP());
  // ---------------------------------------------------
  // P10
  // ---------------------------------------------------

  dmd.clearScreen(true);

  dmd.selectFont(Arial_Black_16);

  displayTime();

  // ---------------------------------------------------
  // TIMER REFRESH DMD32
  // ---------------------------------------------------

  /*
     DMD32 versi lama menggunakan hardware timer ESP32.

     80 = prescaler

     300 = interval refresh
  */

  timer = timerBegin(0, 80, true);

  timerAttachInterrupt(
    timer,
    &triggerScan,
    true
  );

  timerAlarmWrite(
    timer,
    300,
    true
  );

  timerAlarmEnable(timer);

  // ---------------------------------------------------
  // WIFI ACCESS POINT
  // ---------------------------------------------------

  WiFi.softAP(
    ssid,
    password
  );

  Serial.println();
  Serial.println("==============================");
  Serial.println("ESP32 P10 COUNTDOWN");
  Serial.println("==============================");

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

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

  // ---------------------------------------------------
  // WEB SERVER
  // ---------------------------------------------------

  server.on("/", handleRoot);

  server.on("/set", handleSet);

  server.on("/start", handleStart);

  server.on("/stop", handleStop);

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

  server.on("/time", handleTime);

  server.begin();

  Serial.println("Webserver aktif");
}

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

void loop()
{
  server.handleClient();

  // ---------------------------------------------------
  // COUNTDOWN
  // ---------------------------------------------------

  if (timerRunning)
  {
    if (millis() - lastSecond >= 1000)
    {
      lastSecond += 1000;

      if (remainingSeconds > 0)
      {
        remainingSeconds--;

        displayTime();
      }

      // ------------------------------------------------
      // SELESAI
      // ------------------------------------------------

      if (remainingSeconds <= 0)
      {
        remainingSeconds = 0;

        timerRunning = false;

        displayTime();
      }
    }
  }
 
  
4. VIDEO HASILNYA
 


ESP32 Monitoring Suhu Interface DMD P10 sensor DS18B20

ESP32 Monitoring Suhu Interface DMD P10 sensor DS18B20
 

         Pada kesempatan kali ini saya akan menjelaskan mengenai bagaimana cara membuat sebuah alat dengan menggunakan Esp32 dengan penampil dmd p10. alat ini menggunakan sensor Ds18B20 sehingga bisa mengukur suhu ruangan. untuk lebih jelasnya berikut adalah koding dan skemanya.
 
 
1. Skema
 

 
2. Program ESP32
 
 /*
GND – GND
OE – D23
A – D19
B – D21
CLK – D18
LAT – D2
DR – D23
VCC – VP
GND – EN
*/

#include <DMD32.h>  //--> DMD32 by Qudor-Engineer (KHUDHUR ALFARHAN) : https://github.com/Qudor-Engineer/DMD32
#include "fonts/SystemFont5x7.h"
#include "fonts/Arial_black_16.h"
#include <OneWire.h>
#include <DallasTemperature.h>

#define ONE_WIRE_BUS 13
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

#define DISPLAYS_ACROSS 1
#define DISPLAYS_DOWN 1
DMD dmd(DISPLAYS_ACROSS, DISPLAYS_DOWN);

int suhu;
int suhux;
unsigned long prevTimeDMD = 0;

// Timer setup.
// create a hardware timer  of ESP32.
hw_timer_t * timer = NULL;

char lineBuff[20];
char lineBuffx[20];

//________________________________________________________________________________IRAM_ATTR triggerScan()
//  Interrupt handler for Timer1 (TimerOne) driven DMD refresh scanning,
//  this gets called at the period set in Timer1.initialize();
void IRAM_ATTR triggerScan() {
  dmd.scanDisplayBySPI();
}
//________________________________________________________________________________

//________________________________________________________________________________VOID SETUP()
void setup() {
  // put your setup code here, to run once:
  
  Serial.begin(115200);
  sensors.begin();
  Serial.println();
  delay(500);
 
  Serial.println();
  Serial.println("return the clock speed of the CPU.");
  // return the clock speed of the CPU.
  uint8_t cpuClock = ESP.getCpuFreqMHz();
  delay(500);

  Serial.println();
  Serial.println("Timer Begin");
  // Use 1st timer of 4.
  // devide cpu clock speed on its speed value by MHz to get 1us for each signal  of the timer.
  timer = timerBegin(0, cpuClock, true);
  delay(500);

  Serial.println();
  Serial.println("Attach triggerScan function to our timer.");
  // Attach triggerScan function to our timer.
  timerAttachInterrupt(timer, &triggerScan, true);
  delay(500);

  Serial.println();
  Serial.println("Set alarm to call triggerScan function.");
  // Set alarm to call triggerScan function.
  // Repeat the alarm (third parameter).
  timerAlarmWrite(timer, 300, true);
  delay(500);

  Serial.println();
  Serial.println("Start an alarm.");
  // Start an alarm.
  timerAlarmEnable(timer);
  delay(500);
  
  dmd.clearScreen(true);
  
  delay(1000);
}

void loop() {

  sensors.requestTemperatures();
  suhu = sensors.getTempCByIndex(0);
             
  sprintf(lineBuff, "SUHU   ");
  sprintf(lineBuffx, "T:%d  ", suhu);

  dmd.selectFont(SystemFont5x7);
  dmd.drawString( 0,  0, lineBuff, strlen(lineBuff), GRAPHICS_NORMAL);  
  dmd.drawString( 0,  8, lineBuffx, strlen(lineBuffx), GRAPHICS_NORMAL);   

  delay(100);
}
 
 
3. VIDEO HASILNYA
 

 

ESP32 Monitoring ADC Penampil DMD P10

ESP32 Monitoring ADC Penampil DMD P10
 

           Pada kesempatan kali ini saya akan menjelaskan mengenai bagaimana cara membuat sebuah alat yang menggunakan ESP32 untuk menampilkan ADC ke interface DMD P10. alat ini tanpa wifi atau internet sehingga bisa dengan mudah untuk mengendalikannya. untuk lebih jelasnya berikut kodingnya.
 
 
1. Skema
 

 
2. Program Arduino IDE
 
 /*
GND – GND
OE – D23
A – D19
B – D21
CLK – D18
LAT – D2
DR – D23
VCC – VP
GND – EN
*/

#include <DMD32.h>  //--> DMD32 by Qudor-Engineer (KHUDHUR ALFARHAN) : https://github.com/Qudor-Engineer/DMD32
#include "fonts/SystemFont5x7.h"
#include "fonts/Arial_black_16.h"

#define DISPLAYS_ACROSS 1
#define DISPLAYS_DOWN 1
DMD dmd(DISPLAYS_ACROSS, DISPLAYS_DOWN);

// Timer setup.
// create a hardware timer  of ESP32.
hw_timer_t * timer = NULL;

unsigned int suhu;
unsigned int suhux;
char lineBuff[20];
char lineBuffx[20];

//________________________________________________________________________________IRAM_ATTR triggerScan()
//  Interrupt handler for Timer1 (TimerOne) driven DMD refresh scanning,
//  this gets called at the period set in Timer1.initialize();
void IRAM_ATTR triggerScan() {
  dmd.scanDisplayBySPI();
}
//________________________________________________________________________________

//________________________________________________________________________________VOID SETUP()
void setup() {
  // put your setup code here, to run once:
  
  Serial.begin(115200);
  Serial.println();
  delay(500);
 
  Serial.println();
  Serial.println("return the clock speed of the CPU.");
  // return the clock speed of the CPU.
  uint8_t cpuClock = ESP.getCpuFreqMHz();
  delay(500);

  Serial.println();
  Serial.println("Timer Begin");
  // Use 1st timer of 4.
  // devide cpu clock speed on its speed value by MHz to get 1us for each signal  of the timer.
  timer = timerBegin(0, cpuClock, true);
  delay(500);

  Serial.println();
  Serial.println("Attach triggerScan function to our timer.");
  // Attach triggerScan function to our timer.
  timerAttachInterrupt(timer, &triggerScan, true);
  delay(500);

  Serial.println();
  Serial.println("Set alarm to call triggerScan function.");
  // Set alarm to call triggerScan function.
  // Repeat the alarm (third parameter).
  timerAlarmWrite(timer, 300, true);
  delay(500);

  Serial.println();
  Serial.println("Start an alarm.");
  // Start an alarm.
  timerAlarmEnable(timer);
  delay(500);
  
  dmd.clearScreen(true);
  delay(1000);
}

void loop() {

  suhu = analogRead(34);
  suhux = analogRead(35);
             
  sprintf(lineBuff, "%d  ", suhu);
  sprintf(lineBuffx, "%d  ", suhux);

  dmd.selectFont(SystemFont5x7);
  dmd.drawString( 0,  0, lineBuff, strlen(lineBuff), GRAPHICS_NORMAL);  
  dmd.drawString( 0,  8, lineBuffx, strlen(lineBuffx), GRAPHICS_NORMAL);   

  delay(100);
}
 
 
 
3. VIDEO HASILNYA
 

 

Menampilkan Suhu Kelembaban Interface DMD P10 Sensor DHT11

Menampilkan Suhu Kelembaban Interface DMD P10 Sensor DHT11


       Pada kesempatan kali ini saya akan mnejelaskan mengenai bagaimana cara membuat sebuah alat dengan tampilan dmd p10 untuk menampilkan nilai suhu kelembaban sensor dht11. jadi alat ini cuma butuh 3 item utama yaitu ARduino uno, DMD P10 dan sensor dht11 untuk deteksi suhu kelembaban. untuk lebih jelasnya berikut kodingnya.


1. Program Arduino IDE

#include <Wire.h>
#include <SPI.h>        //SPI.h must be included as DMD is written by SPI (the IDE complains otherwise)
#include <DMD.h>        //Library DMD yang menyediakan fungsi penampilan teks, gambar dsb
#include <TimerOne.h>   //Library peripheral Timer1 untuk menjalankan prosedur pindai panel DMD
#include <Time.h>     //Library waktu yang menyediakan tipe data, struktur, dan obyek waktu
#include "DHT.h"
#include "Arial_black_16.h"
#include "Arial_Black_16_ISO_8859_1.h"
#include "Arial14.h"
#include "DejaVuSans9.h"
#include "DejaVuSansBold9.h"
#include "DejaVuSansItalic9.h"
#include "Droid_Sans_12.h"
#include "Droid_Sans_16.h"
#include "Mono5x7.h"
#include "SystemFont5x7.h"

#define DHTPIN 2
#define DHTTYPE DHT11

#define WAKTU_TAMPIL_JAM      10    //detik
#define WAKTU_TAMPIL_KALENDAR 5     //detik

#define DISPLAY_COLUMN_COUNT  2
#define DISPLAY_ROW_COUNT     1

#define PIXELS_PER_COLUMN  32
#define PIXELS_PER_ROW    16

DMD dmd(DISPLAY_COLUMN_COUNT, DISPLAY_ROW_COUNT);
unsigned char show = 0;

unsigned int suhu;
unsigned int suhux;
unsigned int dataadc;

char lineBuff[20];
char lineBuffx[20];
DHT dht(DHTPIN, DHTTYPE);
  
void ScanDMD()
{
  dmd.scanDisplayBySPI();
}

void setup(void)
{
  dht.begin();
  dmd.clearScreen( true );   //true is normal (all pixels off), false is negative (all pixels on)
  Serial.begin(9600);
   //initialize TimerOne's interrupt/CPU usage used to scan and refresh the display
  Timer1.initialize( 1000 );           //period in microseconds to call ScanDMD. Anything longer than 5000 (5ms) and you can see flicker.
  Timer1.attachInterrupt( ScanDMD );   //attach the Timer1 interrupt to ScanDMD which goes to dmd.scanDisplayBySPI()

  //clear/init the DMD pixels held in RAM
  dmd.clearScreen( true ); 
 
}

void loop(void)
{
       int h = dht.readHumidity();  
       int t = dht.readTemperature();
       dataadc = analogRead(A0);
     
      sprintf(lineBuff, "%d|%d", t, h);
      sprintf(lineBuffx, "%d ", dataadc);

      dmd.selectFont(DejaVuSans9);
      dmd.selectFont(Mono5x7);
      dmd.drawString( 33,  0, lineBuff, strlen(lineBuff), GRAPHICS_NORMAL);  
      dmd.drawString( 33,  8, lineBuffx, strlen(lineBuffx), GRAPHICS_NORMAL);  


delay(1000);
}



2, Skema PIN 

a. Sensor dht11 : VCC ke 5v, GND ke GND , OUT ke pin 2 Arduino
b. P10 ke arduino seperti berikut.
 
 GND - GND
 PIN 6 - A
 PIN 7 - B
 PIN 8 - SCLK
 PIN 9 - OE
 PIN 11 - R
 PIN 13 - CLK




Running Text ESP32 Input Via WIFI HOTSPOT

Running Text ESP32 Input Via WIFI HOTSPOT
 

         Pada kesempatan kali ini saya akan menjelaskan mengenai bagaimana cara membuat sebuah alat yang bisa digunakan untuk input text via wifi handphone. alat ini menggunakan wifi untuk input textnya sehingga meudahkan user dalam penggunaannya. untuk lebih jelasnya berikut adalah koding dan skemanya. jika ingin pesan modul esp32 yang sudah di program dan tinggal pakai bisa cek di link shopee berikut.  

 LINK PEMBELIAN SHOPEE : https://id.shp.ee/eVqaTRRi


 
1. Skema 
 

 
2. Program Arduino IDE
 
 /*
GND – GND
OE – D23
A – D19
B – D21
CLK – D18
LAT – D2
DR – D23
VCC – VP
GND – EN
*/

#include <WiFi.h>
#include <WebServer.h>
#include <DMD32.h>
#include "fonts/SystemFont5x7.h"
#include "fonts/Arial_black_16.h"
#include "PageIndex.h" //--> Include the contents of the User Interface Web page, stored in the same folder as the .ino file
#include <Preferences.h>
//----------------------------------------

//----------------------------------------Defining the key.
// "Key" functions like a password. In order to change the text on the P10, the user must know the "key".
// You can change it to another word.
#define key_Txt "123456789"
//----------------------------------------

// Fire up the DMD library as dmd.
#define DISPLAYS_ACROSS 1
#define DISPLAYS_DOWN 1
DMD dmd(DISPLAYS_ACROSS, DISPLAYS_DOWN);

// Timer setup.
// create a hardware timer  of ESP32
hw_timer_t * timer = NULL;

const char* ssid = "ESP32_WS";  //--> access point name
const char* password = "123456789"; //--> access point password

IPAddress local_ip(192,168,1,1);
IPAddress gateway(192,168,1,1);
IPAddress subnet(255,255,255,0);
//----------------------------------------

String display_Modes = "";
String single_Row_Txt = "";
String double_Row_First_Txt = "";
int double_Row_First_Txt_Pos = 0;
String double_Row_Second_Txt = "";

// Server on port 80.
WebServer server(80);  

// Initialize Preferences.
Preferences preferences;

//________________________________________________________________________________IRAM_ATTR triggerScan()
// Interrupt handler for Timer1 (TimerOne) driven DMD refresh scanning, 
// this gets called at the period set in Timer1.initialize();
void IRAM_ATTR triggerScan() {
  dmd.scanDisplayBySPI();
}
//________________________________________________________________________________

//________________________________________________________________________________handleRoot()
// This routine is executed when you open ESP32 IP Address in browser.
void handleRoot() {
  server.send(200, "text/html", MAIN_page); //Send web page
}
//________________________________________________________________________________

//________________________________________________________________________________handleSettings().
// Subroutine to handle settings. The displayed text and others are set here.
void handleSettings() {
  timerAlarmDisable(timer);
  delay(1000);
  
  String incoming_Settings = server.arg("Settings");
  Serial.println();
  Serial.print("Incoming settings : ");
  Serial.println(incoming_Settings);
  
  // Example of incoming data from a client in double row display mode : 
  // "p10esp32wb,DR,ESP32,1,ESP32 P10 LED Display"
  // - p10esp32wb             = key.
  // - DR                     = Double Row (Two row display mode).
  // - ESP32                  = Text for the first row.
  // - 1                      = Position of text for first row.
  // - ESP32 P10 LED Display  = Text for the second row.
  //
  // - When using the "getValue" string function, the sequence is:
  //   "p10esp32wb,DR,ESP32,1,ESP32 P10 LED Display"
  //        |       |   |   |           |
  //        0       1   2   3           4
  //
  //   > p10esp32wb = getValue(incoming_Settings, ',', 0);
  //   > DR         = getValue(incoming_Settings, ',', 1);
  //   > and so on.

  if (getValue(incoming_Settings, ',', 0) == key_Txt) {
    display_Modes = getValue(incoming_Settings, ',', 1);

    if (display_Modes == "SR") {
      single_Row_Txt = getValue(incoming_Settings, ',', 2);
      
      // Save texts and modes to flash memory.
      preferences.begin("P10_SD", false);
      preferences.putString("DM", display_Modes);
      preferences.putString("SRT", single_Row_Txt);
      preferences.end();
      delay(500);
    }
  
    if (display_Modes == "DR") {
      double_Row_First_Txt = getValue(incoming_Settings, ',', 2);
      double_Row_First_Txt_Pos = getValue(incoming_Settings, ',', 3).toInt();
      double_Row_Second_Txt = getValue(incoming_Settings, ',', 4);
      
      // Save texts and modes to flash memory.
      preferences.begin("P10_SD", false);
      preferences.putString("DM", display_Modes);
      preferences.putString("DRFT", double_Row_First_Txt);
      preferences.putInt("DRFTP", double_Row_First_Txt_Pos);
      preferences.putString("DRST", double_Row_Second_Txt);
      preferences.end();
      delay(500);
    }

    server.send(200, "text/plane", "+OK");  //--> Sending replies to the client.
    delay(500);
  } else {
    server.send(200, "text/plane", "+ERR"); //--> Sending replies to the client.
    delay(500);
  }
  
  timerAlarmEnable(timer);
  delay(500);
}
//________________________________________________________________________________

//________________________________________________________________________________getValue()
// String function to split strings based on certain characters.
String getValue(String data, char separator, int index) {
  int found = 0;
  int strIndex[] = { 0, -1 };
  int maxIndex = data.length() - 1;
  
  for (int i = 0; i <= maxIndex && found <= index; i++) {
    if (data.charAt(i) == separator || i == maxIndex) {
      found++;
      strIndex[0] = strIndex[1] + 1;
      strIndex[1] = (i == maxIndex) ? i+1 : i;
    }
  }
  return found > index ? data.substring(strIndex[0], strIndex[1]) : "";
}
//________________________________________________________________________________ 

//________________________________________________________________________________Single_Row_Display_Mode()
// Subroutine for displaying "running text" on P10 in Single Row mode.
void Single_Row_Display_Mode() {
  char CA_single_Row_Txt[single_Row_Txt.length() + 1];
  single_Row_Txt.toCharArray(CA_single_Row_Txt, single_Row_Txt.length() + 1);

  dmd.clearScreen(true);
  dmd.selectFont(Arial_Black_16);
  dmd.drawMarquee(CA_single_Row_Txt, single_Row_Txt.length(), (32*DISPLAYS_ACROSS)-1, 0);
  long start=millis();
  long timer=start;
  boolean ret=false;
  while(!ret){
   if ((timer+30) < millis()) {
     ret=dmd.stepMarquee(-1,0);
     timer=millis();
   }
  }
  delay(1000);
}
//________________________________________________________________________________ 

//________________________________________________________________________________Double_Row_Display_Mode()
// Subroutine to display text in the first row and display "running text" in the second row in Double Row mode.
void Double_Row_Display_Mode() {
  char CA_double_Row_First_Txt[double_Row_First_Txt.length() + 1];
  double_Row_First_Txt.toCharArray(CA_double_Row_First_Txt, double_Row_First_Txt.length() + 1);

  char CA_double_Row_Second_Txt[double_Row_Second_Txt.length() + 1];
  double_Row_Second_Txt.toCharArray(CA_double_Row_Second_Txt, double_Row_Second_Txt.length() + 1);

  dmd.clearScreen(true);
  dmd.selectFont(SystemFont5x7);
  dmd.drawString(double_Row_First_Txt_Pos, 0, CA_double_Row_First_Txt, double_Row_First_Txt.length(), GRAPHICS_NORMAL);
  
  int scrl_long = (double_Row_Second_Txt.length()*6) + (32*DISPLAYS_ACROSS);
  int i = 32*DISPLAYS_ACROSS;
  long start=millis();
  long timer=start;
  while(true){
    if ((timer+30) < millis()) {
      dmd.drawString(i, 9, CA_double_Row_Second_Txt, double_Row_Second_Txt.length(), GRAPHICS_NORMAL);    
      if (i > ~scrl_long) {
        i--;
      } else {
        break;
      }
      timer=millis();
    }
  }
}
//________________________________________________________________________________ 

//________________________________________________________________________________VOID SETUP()
void setup(void){
  // put your setup code here, to run once:
  
  Serial.begin(115200);
  delay(1000);
  
  Serial.println();

  display_Modes.reserve(5);
  single_Row_Txt.reserve(50);
  delay(500);

  //----------------------------------------Load data stored in flash memory.
  Serial.println("Load data stored in flash memory.");
  preferences.begin("P10_SD", false);
  
  display_Modes = preferences.getString("DM", "");
  single_Row_Txt = preferences.getString("SRT", "");
  double_Row_First_Txt = preferences.getString("DRFT", "");
  double_Row_First_Txt_Pos = preferences.getInt("DRFTP", 0);
  double_Row_Second_Txt = preferences.getString("DRST", "");

  Serial.print("display_Modes : ");
  Serial.println(display_Modes);
  Serial.print("single_Row_Txt : ");
  Serial.println(single_Row_Txt);
  Serial.print("double_Row_First_Txt : ");
  Serial.println(double_Row_First_Txt);
  Serial.print("double_Row_First_Txt_Pos : ");
  Serial.println(double_Row_First_Txt_Pos);
  Serial.print("double_Row_Second_Txt : ");
  Serial.println(double_Row_Second_Txt);

  preferences.end();
  delay(500);
  //----------------------------------------
  
  Serial.println();
  Serial.println("return the clock speed of the CPU.");
  // return the clock speed of the CPU.
  uint8_t cpuClock = ESP.getCpuFreqMHz();
  delay(500);

  Serial.println();
  Serial.println("Timer Begin");
  // Use 1st timer of 4.
  // devide cpu clock speed on its speed value by MHz to get 1us for each signal  of the timer.
  timer = timerBegin(0, cpuClock, true);
  delay(500);

  Serial.println();
  Serial.println("Attach triggerScan function to our timer.");
  // Attach triggerScan function to our timer.
  timerAttachInterrupt(timer, &triggerScan, true);
  delay(500);

  Serial.println();
  Serial.println("Set alarm to call triggerScan function.");
  // Set alarm to call triggerScan function.
  // Repeat the alarm (third parameter).
  timerAlarmWrite(timer, 300, true);
  delay(500);

  Serial.println();
  Serial.println("Start an alarm.");
  // Start an alarm.
  timerAlarmEnable(timer);
  delay(500);

  Serial.println();
  Serial.println("Chose the \"Arial_Black_16\" font.");
  dmd.selectFont(Arial_Black_16);

  Serial.println();
  Serial.println("Clear Screen.");
  // clear/init the DMD pixels held in RAM.
  // true is normal (all pixels off), false is negative (all pixels on).
  dmd.clearScreen(true); 
  delay(500);

  // While the process of connecting to a WiFi network is in progress or when the process of creating an access point is in progress, 
  // the "Alarm Timer" must be disabled.
  timerAlarmDisable(timer);
  delay(1000);

  //----------------------------------------Create ESP32 as Access Point.
Serial.println();
Serial.println("WIFI mode : AP");
WiFi.mode(WIFI_AP);
Serial.println("Setting up ESP32 to be an Access Point.");
WiFi.softAP(ssid, password); //--> Creating Access Points
delay(1000);
Serial.println("Setting up ESP32 softAPConfig.");
WiFi.softAPConfig(local_ip, gateway, subnet);
//----------------------------------------
 
  server.on("/", handleRoot); 
  server.on("/setText", handleSettings);

  // Start server.
  server.begin(); 
  Serial.println();
  Serial.println("HTTP server started");

  delay(500);

  // When successfully connected to a WiFi network or when the access point creation process is complete, 
  // the "Alarm Timer" is re-enabled.
  timerAlarmEnable(timer);
  delay(500);

  Serial.println();
  Serial.print("SSID name : ");
  Serial.println(ssid);
  Serial.print("IP address : ");
  Serial.println(WiFi.softAPIP());
  Serial.println();
  Serial.println("Connect your computer or mobile Wifi to the SSID above.");
  Serial.println("Visit the IP Address above in your browser to open the main page.");
  Serial.println();
  delay(500);

}
//________________________________________________________________________________

//________________________________________________________________________________VOID LOOP()
void loop(void){
  // put your main code here, to run repeatedly:

  // Handle client requests.
  server.handleClient();  

  if (display_Modes == "SR") {
    Single_Row_Display_Mode();
  }

  if (display_Modes == "DR") {
    Double_Row_Display_Mode();
  }
}
//________________________________________________________________________________
//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
 
 
 
3. PageIndex.h
 
const char MAIN_page[] PROGMEM = R"=====(
<!DOCTYPE html>
<html>
  <style>
    h1 { font-size: 2.0rem; color:#2980b9;}

    input[type=text], select {
      width: 100%;
      padding: 8px 8px;
      margin: 8px 0;
      display: inline-block;
      border: 1px solid #ccc;
      border-radius: 4px;
      box-sizing: border-box;
    }

    .div_Form {
      margin: auto;
      width: 70%;
      border-radius: 5px;
      background-color: #f2f2f2;
      padding: 10px 10px;
    }

    .buttonON {
      display: inline-block;
      padding: 5px 25px;
      font-size: 13px;
      cursor: pointer;
      text-align: center;
      text-decoration: none;
      outline: none;
      color: #fff;
      background-color: #4CAF50;
      border: none;
      border-radius: 8px;
      box-shadow: 0 3px #999;
    }
    .buttonON:hover {background-color: #3e8e41}
    .buttonON:active {
      background-color: #3e8e41;
      box-shadow: 0 1px #666;
      transform: translateY(2px);
    }
    .buttonON:disabled {
      background-color: #666;
      box-shadow: 0 1px #666;
      transform: translateY(2px);
    }
  </style>
  
  <body>
    <div style="text-align: center;">
      <h1>ESP32 P10 Module Web Server</h1>
    </div>

    <div class="div_Form">
      <form>
        <label for="Keys_TXT">Key :</label>
        <input type="password" id="Keys_TXT" name="Keys_TXT" placeholder="Enter key here...">
        
        <br>
        <hr style="border: 1px solid #e6e6e6;">
        
        <input type="checkbox" id="cb_SingleRow" name="cb_SingleRow" value="" onclick="CB_SR_Click()">
        <label for="cb_SingleRow"> Single Row</label><br><br>
        
        <label for="SingleRow_TXT">Text :</label>
        <input type="text" id="SingleRow_TXT" name="SingleRow_TXT" placeholder="Enter text here...">
        
        <br>
        <hr style="border: 1px solid #e6e6e6;">
        
        <input type="checkbox" id="cb_DoubleRow" name="cb_DoubleRow" value="" onclick="CB_DR_Click()">
        <label for="cb_DoubleRow"> Double Row</label><br><br>
      
        <label for="DoubleRow_FirtsRow_TXT">Text for First Row :</label>
        <input type="text" id="DoubleRow_FirtsRow_TXT" name="DoubleRow_FirtsRow_TXT" placeholder="Enter text for the first row here...">
        
        <label for="DoubleRow_PositionFirstRow_TXT">Text Position for First Row :</label>
        <input type="text" id="DoubleRow_PositionFirstRow_TXT" name="DoubleRow_PositionFirstRow_TXT" oninput="this.value = this.value.replace(/[^0-9.]/g, '').replace(/(\..*?)\..*/g, '$1');" placeholder="Enter a value for the text position in the first row here...">

        <label for="DoubleRow_SecondRow_TXT">Text for Second Row :</label>
        <input type="text" id="DoubleRow_SecondRow_TXT" name="DoubleRow_SecondRow_TXT" placeholder="Enter text for the second row here...">
        
        <br>
        <hr style="border: 1px solid #e6e6e6;">
        
        <button type="button" class="buttonON" id="BTN_Submit" onclick="BTN_Submit_Click()">Submit</button>
        <label style="padding: 15px; color: black;" id="LBL_Info"></label>
      </form>
    </div>

    <script>
      document.getElementById("cb_SingleRow").checked = false;
      document.getElementById("SingleRow_TXT").disabled = true;
      
      document.getElementById("cb_DoubleRow").checked = false;
      document.getElementById("DoubleRow_FirtsRow_TXT").disabled = true;
      document.getElementById("DoubleRow_PositionFirstRow_TXT").disabled = true;
      document.getElementById("DoubleRow_SecondRow_TXT").disabled = true;
      
      function CB_SR_Click() {
        var checkBox = document.getElementById("cb_SingleRow");
        if (checkBox.checked == true){
          document.getElementById("cb_DoubleRow").disabled = true;
          document.getElementById("SingleRow_TXT").disabled = false;
        } else {
          document.getElementById("cb_DoubleRow").disabled = false;
          document.getElementById("SingleRow_TXT").disabled = true;
        }
      }
      
      function CB_DR_Click() {
        var checkBox = document.getElementById("cb_DoubleRow");
        if (checkBox.checked == true){
          document.getElementById("cb_SingleRow").disabled = true;
          document.getElementById("DoubleRow_FirtsRow_TXT").disabled = false;
          document.getElementById("DoubleRow_PositionFirstRow_TXT").disabled = false;
          document.getElementById("DoubleRow_SecondRow_TXT").disabled = false;
        } else {
          document.getElementById("cb_SingleRow").disabled = false;
          document.getElementById("DoubleRow_FirtsRow_TXT").disabled = true;
          document.getElementById("DoubleRow_PositionFirstRow_TXT").disabled = true;
          document.getElementById("DoubleRow_SecondRow_TXT").disabled = true;
        }
      }
      
      function BTN_Submit_Click() {
        var checkBox_SR = document.getElementById("cb_SingleRow");
        var checkBox_DR = document.getElementById("cb_DoubleRow");
        var key_TXT = document.getElementById("Keys_TXT").value;
        
        if (checkBox_SR.checked == false && checkBox_DR.checked == false) {
          alert("Error ! \rThe Single Row or Double Row checkbox must be checked.");
          return;
        }

        if (key_TXT == "") {
          alert("Error ! \rThe key cannot be empty.");
          return;
        }

        document.getElementById("BTN_Submit").disabled = true;
        document.getElementById("LBL_Info").style.color = "black";
        document.getElementById("LBL_Info").innerHTML = "Please wait...";
        
        if (checkBox_SR.checked == true) {
          var SR_TXT = document.getElementById("SingleRow_TXT").value;
          var msg = key_TXT + ",SR," + SR_TXT;
          Send(msg);
        }
        
        if (checkBox_DR.checked == true) {
          var DR_FR_TXT = document.getElementById("DoubleRow_FirtsRow_TXT").value;
          var DR_PFR_TXT = document.getElementById("DoubleRow_PositionFirstRow_TXT").value;
          var DR_SR_TXT = document.getElementById("DoubleRow_SecondRow_TXT").value;
          var msg = key_TXT + ",DR," + DR_FR_TXT + "," + DR_PFR_TXT + "," + DR_SR_TXT;
          Send(msg);
        }
      }
      
      function Send(x) {
        //alert(x);
        if (window.XMLHttpRequest) {
          // code for IE7+, Firefox, Chrome, Opera, Safari
          xmlhttp = new XMLHttpRequest();
        } else {
          // code for IE6, IE5
          xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
        }
        xmlhttp.onreadystatechange = function() {
          if (this.readyState == 4 && this.status == 200) {
            if (this.responseText == "+OK") {
              document.getElementById("BTN_Submit").disabled = false;
              document.getElementById("LBL_Info").innerHTML = "";
            }
            if (this.responseText == "+ERR") {
              document.getElementById("BTN_Submit").disabled = false;
              document.getElementById("LBL_Info").innerHTML = "Keys are wrong !";
              document.getElementById("LBL_Info").style.color = "red";
            }
          }
        }
        xmlhttp.open("GET","setText?Settings="+x,true);
        xmlhttp.send();
      }
    </script>
  </body>
</html>
)=====";
 
 
4. INTERFACE
 

 
5. VIDEO HASILNYA