Translate

Monitor Suhu & Kelembaban via Bluetooth ESP32 MIT INVENTOR dan Kendali LED

Monitor Suhu & Kelembaban via Bluetooth ESP32 MIT INVENTOR dan Kendali LED
 

       Pada kesempatan kali ini saya akan menjelaskan mengenai bagaimana cara membuat sebuah alat untuk monitor suhu kelembaban secara realtime dengan mnggunakan ESP32 via Bluetooth, aplikasi yang digunakan yaitu MIT Inventor dan koding juga sudah saya berikan pada page ini. untuk lebih jelasnya berikut detailnya.
 
 
1. Skema
 
 
 
2. Interface MIT INVENTOR
 


 
 
3. Program Arduino IDE
 
#include "DHT.h"
#include "BluetoothSerial.h"

#define DHTPIN 13
#define DHTTYPE DHT11

DHT dht(DHTPIN, DHTTYPE);

int LED_PIN = 12;
BluetoothSerial SerialBT;

void setup() {
  Serial.begin(115200);
  pinMode(LED_PIN,OUTPUT);
  dht.begin();

  SerialBT.begin("ESP32_SENSOR");
  
  Serial.println("Bluetooth aktif");
}

void loop() {

  int suhu = dht.readTemperature();
  int kelembaban = dht.readHumidity();

  if (isnan(suhu) || isnan(kelembaban)) {
    return;
  }

  String data = String(suhu) + "|" + String(kelembaban);

  SerialBT.println(data);
  Serial.println(data);

  if (SerialBT.available()) {

    char data = SerialBT.read();

    //Serial.print("Data diterima: ");
    //Serial.println(data);

    if (data == '1') {
      digitalWrite(LED_PIN, HIGH);
      //SerialBT.println("LED ON");
      Serial.println("LED ON");
    }

    else if (data == '0') {
      digitalWrite(LED_PIN, LOW);
      //SerialBT.println("LED OFF");
      Serial.println("LED OFF");
    }
  }


  delay(100);
}
 
 
4. VIDEO HASILNYA
 
 

Monitoring 2 Suhu via Website Hosting + Realtime Grafik DataLogger

Monitoring 2 Suhu via Website Hosting + Realtime Grafik DataLogger 
 

        Pada kesempatan kali ini saya akan menjelaskan mengenai bagaimana cara membuat sebuah alat yang daapaat memonitor 2 buah suhu secaraa terpisah tapi bisa dimasukkan ke dalam 1 website dan 1 database. untuk database saya menggunakan https://supabase.com dan untuk datamanager dan website saya menggunakan hosting dari infinityfree.com. untuk detailnya yaitu seperti berikut.
 
 
1. Skema
 

 
2. Interface
 




 
 
3. VIDEO HASILNYA
 

 
4. Program ESP32 - Batang / Semarang
 
 #include <WiFi.h>
#include <HTTPClient.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <time.h>

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

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

// ================= SUPABASE =================

String supabaseURL =
"https://yizpzxixllbgixxxxxxx.supabase.co/rest/v1/suhu_data";

String supabaseKey =
"sb_publishable_mJNp2PCt9civsxe26URVYg_dxxxxxxx";

// ================= DEVICE =================

// ESP32 pertama

String device = "ESP32_BATANG";
String kota = "Batang";

// ESP32 kedua ubah menjadi:
// String device = "ESP32_SEMARANG";
// String kota = "Semarang";

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

#define ONE_WIRE_BUS 13

OneWire oneWire(ONE_WIRE_BUS);

DallasTemperature sensors(&oneWire);

void setup(){

Serial.begin(115200);

sensors.begin();

// WIFI

WiFi.begin(ssid,password);

Serial.print("Connecting");

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

delay(500);

Serial.print(".");

}

Serial.println();

Serial.println("WiFi Connected");

// waktu WIB

configTime(
7*3600,
0,
"pool.ntp.org",
"time.nist.gov"
);

}

String getTime(){

struct tm timeinfo;

if(!getLocalTime(&timeinfo)){

return "NULL";

}

char waktu[30];

strftime(

waktu,

sizeof(waktu),

"%Y-%m-%d %H:%M:%S",

&timeinfo

);

return String(waktu);

}

void kirimSupabase(float suhu){

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

HTTPClient http;

http.begin(supabaseURL);

http.addHeader(
"Content-Type",
"application/json"
);

http.addHeader(
"apikey",
supabaseKey
);

http.addHeader(
"Authorization",
"Bearer "+supabaseKey
);

String data = "{";

data += "\"device\":\""+device+"\",";

data += "\"kota\":\""+kota+"\",";

data += "\"suhu\":"+String(suhu)+",";

data += "\"waktu\":\""+getTime()+"\"";

data += "}";

Serial.println(data);

int code=http.POST(data);

Serial.print("Response Supabase : ");

Serial.println(code);

http.end();

}

}

void loop(){

sensors.requestTemperatures();

float suhu = sensors.getTempCByIndex(0);

if(suhu == -127){

Serial.println("Sensor DS18B20 tidak terbaca");

return;

}

Serial.print("Suhu ");

Serial.print(kota);

Serial.print(" : ");

Serial.println(suhu);

kirimSupabase(suhu);

delay(5000);

}
 
 
 
5. Api.php
 
<?php

$supabase_url = "https://yizpzxixllbgixxxxxxx.supabase.co/rest/v1/suhu_data";

$supabase_key = "sb_publishable_mJNp2PCt9civsxe26URVYg_dxxxxxxx";

$curl = curl_init();

curl_setopt_array($curl,[

CURLOPT_URL => $supabase_url."?select=*&order=id.desc&limit=100",

CURLOPT_RETURNTRANSFER => true,

CURLOPT_HTTPHEADER => [

"apikey: ".$supabase_key,

"Authorization: Bearer ".$supabase_key,

"Content-Type: application/json"

]

]);

$response = curl_exec($curl);

curl_close($curl);

header("Content-Type: application/json");

echo $response;

?> 
 
 
6. index.php
 
<!DOCTYPE html>
<html>

<head>

<title>Monitoring Suhu ESP32</title>

<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>

<style>

*{
box-sizing:border-box;
}

body{

margin:0;
font-family:'Segoe UI',Arial,sans-serif;
background:#120021;
color:#ffd700;

}

.header{

background:linear-gradient(135deg,#5a189a,#240046);
padding:25px;
text-align:center;
box-shadow:0 5px 20px #000;

}

.header h1{

margin:0;
font-size:34px;

}

.header p{

color:#fff3a0;

}

.container{

width:92%;
margin:auto;

}

.map-box{

margin-top:25px;
background:#240046;
padding:25px;
border-radius:25px;
box-shadow:0 0 20px #000;
text-align:center;

}

.map-box h2{

color:#ffd700;

}

.peta{

width:90%;
max-width:1000px;
height:260px;
object-fit:cover;

border-radius:20px;

border:3px solid #7b2cbf;

}

.cards{

display:grid;
grid-template-columns:repeat(2,1fr);
gap:25px;

margin-top:25px;

}

.card{

background:#240046;
padding:25px;
border-radius:25px;
text-align:center;

box-shadow:0 0 20px #000;

}

.card h2{

color:#ffd700;

}

.nilai{

font-size:70px;
font-weight:bold;

color:#ffd700;

}

.status{

color:#00ff99;
font-weight:bold;

}

.panel{

background:#240046;
margin-top:25px;

padding:25px;

border-radius:25px;

box-shadow:0 0 20px #000;

}

.chart{

height:400px;

}

canvas{

width:100%!important;
height:100%!important;

}

table{

width:100%;
border-collapse:collapse;

}

th{

background:#5a189a;
color:#ffd700;

}

td,th{

padding:12px;
text-align:center;

border-bottom:1px solid #7b2cbf;

}

tr:hover{

background:#3c096c;

}

@media(max-width:700px){

.cards{

grid-template-columns:1fr;

}

}

</style>

</head>

<body>

<div class="header">

<h1>🌡 MONITORING SUHU ESP32</h1>

<p>DS18B20 Temperature Sensor</p>

</div>

<div class="container">

<!-- PETA -->

<div class="map-box">

<h2>🗺 PETA JAWA</h2>

<img src="jawa-map.png" class="peta">

</div>

<!-- NILAI SUHU -->

<div class="cards">

<div class="card">

<h2>📍 ESP32 BATANG</h2>

<div id="batang" class="nilai">

-- °C

</div>

<div class="status">

● ONLINE

</div>

</div>

<div class="card">

<h2>📍 ESP32 SEMARANG</h2>

<div id="semarang" class="nilai">

-- °C

</div>

<div class="status">

● ONLINE

</div>

</div>

</div>

<!-- GRAFIK -->

<div class="panel">

<h2>📈 Grafik Suhu Batang</h2>

<div class="chart">

<canvas id="grafikBatang"></canvas>

</div>

</div>

<div class="panel">

<h2>📈 Grafik Suhu Semarang</h2>

<div class="chart">

<canvas id="grafikSemarang"></canvas>

</div>

</div>

<!-- LOGGER -->

<div class="panel">

<h2>📋 Logger Data 100 Terakhir</h2>

<table>

<tr>

<th>No</th>
<th>Device</th>
<th>Kota</th>
<th>Suhu</th>
<th>Waktu</th>

</tr>

<tbody id="logger"></tbody>

</table>

</div>

</div>

<script>

let chartBatang;

let chartSemarang;

function loadData(){

fetch("api.php")

.then(res=>res.json())

.then(data=>{

let batang=[];

let semarang=[];

let waktuBatang=[];

let waktuSemarang=[];

let suhuBatang=0;

let suhuSemarang=0;

let idBatang=0;

let idSemarang=0;

data.slice().reverse().forEach(item=>{

if(item.device=="ESP32_BATANG"){

batang.push(Number(item.suhu));

waktuBatang.push(item.waktu);

if(Number(item.id)>idBatang){

idBatang=item.id;

suhuBatang=item.suhu;

}

}

if(item.device=="ESP32_SEMARANG"){

semarang.push(Number(item.suhu));

waktuSemarang.push(item.waktu);

if(Number(item.id)>idSemarang){

idSemarang=item.id;

suhuSemarang=item.suhu;

}

}

});

document.getElementById("batang").innerHTML =

Number(suhuBatang).toFixed(2)+" °C";

document.getElementById("semarang").innerHTML =

Number(suhuSemarang).toFixed(2)+" °C";

if(chartBatang){

chartBatang.destroy();

}

chartBatang=new Chart(

document.getElementById("grafikBatang"),

{

type:"line",

data:{

labels:waktuBatang,

datasets:[{

label:"ESP32 BATANG",

data:batang,

borderColor:"#ffd700",

backgroundColor:"rgba(255,215,0,0.2)",

fill:true,

tension:0.4,

borderWidth:3

}]

},

options:{

responsive:true,

maintainAspectRatio:false

}

}

);

if(chartSemarang){

chartSemarang.destroy();

}

chartSemarang=new Chart(

document.getElementById("grafikSemarang"),

{

type:"line",

data:{

labels:waktuSemarang,

datasets:[{

label:"ESP32 SEMARANG",

data:semarang,

borderColor:"#00ffff",

backgroundColor:"rgba(0,255,255,0.2)",

fill:true,

tension:0.4,

borderWidth:3

}]

},

options:{

responsive:true,

maintainAspectRatio:false

}

}

);

let html="";

data.forEach((item,index)=>{

html+=`

<tr>

<td>${index+1}</td>

<td>${item.device}</td>

<td>${item.kota}</td>

<td>${item.suhu} °C</td>

<td>${item.waktu}</td>

</tr>

`;

});

document.getElementById("logger").innerHTML=html;

});

}

loadData();

setInterval(loadData,5000);

</script>

</body>

</html> 
 
 

Monitoring Suhu Kelembaban via Website Fitur Grafik dan Datalogger Realtime

Monitoring Suhu Kelembaban via Website Fitur Grafik dan Datalogger Realtime 
 

         Pada kesempatan kali ini saya akan menjelaskan mengenai bagaimana cara membuat sebuah alat untuk memonitoring suhu dan kelembaban dengan sensor DHT11 kemudian data tersebuut masuk ke database https://supabase.com setelah masuk ke dalam web itu untuk tampilannya saya gunakan https://www.infinityfree.com/ jadi saya gunakan 2 web itu karena jika saya gunakan infinityfree saja ada masalah saat pengiriman datanya. untuk lebih jelasnya berikut koding dan interfacenya.
 
 
1. Skema
 
 
 
2. Interface
 


 


 
 
3. Koding Arduino IDE
 
#include <WiFi.h>
#include <HTTPClient.h>
#include <DHT.h>

#define DHTPIN 13
#define DHTTYPE DHT11

DHT dht(DHTPIN, DHTTYPE);

// WIFI
const char* ssid = "wifihpku";
const char* password = "123456789";

// SUPABASE
String supabaseURL = "https://yizpzxixllbgixxxxxxx.supabase.co/rest/v1/dht11_data";

String supabaseKey = "sb_publishable_mJNp2PCt9civsxe26URVYg_xxxxxxxx";

void setup() {

  Serial.begin(115200);

  dht.begin();

  WiFi.begin(ssid,password);

  Serial.print("Menghubungkan WiFi");

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

    delay(500);
    Serial.print(".");

  }

  Serial.println();
  Serial.println("WiFi Terhubung");

}

void loop() {

  int suhu = dht.readTemperature();
  int kelembaban = dht.readHumidity();

  if(isnan(suhu) || isnan(kelembaban)){

    Serial.println("Sensor gagal membaca");
    delay(1000);
    return;

  }

  Serial.print("Suhu : ");
  Serial.println(suhu);

  Serial.print("Kelembaban : ");
  Serial.println(kelembaban);

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

    HTTPClient http;

    http.begin(supabaseURL);

    http.addHeader(
      "Content-Type",
      "application/json"
    );

    http.addHeader(
      "apikey",
      supabaseKey
    );

    http.addHeader(
      "Authorization",
      "Bearer " + supabaseKey
    );

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

    Serial.println("Kirim:");
    Serial.println(json);
    int response = http.POST(json);

    Serial.print("Response: ");
    Serial.println(response);

    Serial.println(http.getString());

    http.end();

  }

  delay(1000);

}
 
 
4. Program index.html
 
<!DOCTYPE html>
<html>
<head>

<title>ESP32 DHT11 Monitoring</title>

<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>

<style>

*{
box-sizing:border-box;
}

body{
margin:0;
font-family:Arial,sans-serif;
background:#052e22;
color:white;
}

.header{
text-align:center;
padding:25px;
font-size:30px;
font-weight:bold;
}

.container{
width:92%;
margin:auto;
}


/* CARD */

.cards{

display:flex;
justify-content:center;
gap:25px;
flex-wrap:wrap;

}

.card{

background:#0b5139;
width:280px;
padding:25px;
border-radius:20px;
text-align:center;
box-shadow:0 5px 20px #0005;

}

.value{

font-size:45px;
font-weight:bold;
color:#00ff99;

}


/* BUTTON */

.btn-download{

background:#00a86b;
color:white;

border:none;

padding:12px 25px;

border-radius:25px;

font-size:16px;

cursor:pointer;

font-weight:bold;

margin-bottom:20px;

}

.btn-download:hover{

background:#00ff99;
color:#003b25;

}

/* CHART */

.chart-box{

background:#083b2b;

margin-top:25px;

padding:20px;

border-radius:20px;

cursor:pointer;

}

.chart-container{

height:230px;

}

.chart-container.fullscreen{

position:fixed;

top:20px;

left:20px;

right:20px;

bottom:20px;

height:auto;

background:#052e22;

z-index:999;

padding:30px;

border-radius:20px;

}

.chart-container canvas{

width:100%!important;

height:100%!important;

}

/* TABLE */

.table-box{

background:#083b2b;

margin-top:25px;

padding:20px;

border-radius:20px;

}

table{

width:100%;

border-collapse:collapse;

}

th{

background:#00a86b;

padding:14px;

}

td{

padding:12px;

text-align:center;

}

tr:nth-child(even){

background:#0b5139;

}

tr:nth-child(odd){

background:#06452f;

}

tr:hover{

background:#007f5f;

}

.badge-suhu{

background:#ff7b54;

padding:7px 15px;

border-radius:20px;

font-weight:bold;

}

.badge-hum{

background:#3498db;

padding:7px 15px;

border-radius:20px;

font-weight:bold;

}

</style>
</head>
<body>
<div class="header">

🌿 ESP32 DHT11 MONITORING

</div>

<div class="container">

<div class="cards">

<div class="card">

<h2>🌡 Suhu</h2>

<div class="value">

<span id="suhu">0</span> °C

</div>

</div>

<div class="card">

<h2>💧 Kelembaban</h2>

<div class="value">

<span id="kelembaban">0</span> %

</div>

</div>


</div>

<div class="chart-box">

<h2>📈 Grafik Suhu</h2>

<div class="chart-container"
onclick="besarGrafik(this)">

<canvas id="grafikSuhu"></canvas>

</div>

</div>

<div class="chart-box">

<h2>📈 Grafik Kelembaban</h2>

<div class="chart-container"
onclick="besarGrafik(this)">

<canvas id="grafikHum"></canvas>

</div>

</div>

<div class="table-box">

<h2>📋 Logger 100 Data Terakhir</h2>

<button class="btn-download"
onclick="downloadExcel()">

📥 Download Excel

</button>

<table>

<thead>

<tr>

<th>No</th>

<th>Waktu</th>

<th>Suhu</th>

<th>Kelembaban</th>

</tr>

</thead>

<tbody id="tabel">

</tbody>

</table>

</div>

</div>

<script type="module">

import { createClient }

from "https://cdn.jsdelivr.net/npm/@supabase/supabase-js/+esm";

// SUPABASE

const SUPABASE_URL =
"https://yizpzxixllbgixxxxxxx.supabase.co";

const SUPABASE_KEY =
"sb_publishable_mJNp2PCt9civsxe26URVYg_xxxxxxxx";

const supabase =
createClient(
SUPABASE_URL,
SUPABASE_KEY
);

let chartSuhu;

let chartHum;

let dataExcel=[];

async function loadData(){

let {data,error}=await supabase

.from("dht11_data")

.select("*")

.order("id",{ascending:false})

.limit(100);

if(error){

console.log(error);

return;

}

dataExcel=data;

data=data.reverse();

let waktu=[];

let suhu=[];

let hum=[];

data.forEach(item=>{

waktu.push(

new Date(item.waktu)
.toLocaleTimeString()

);

suhu.push(item.suhu);

hum.push(item.kelembaban);

});

document.getElementById("suhu").innerHTML=

suhu[suhu.length-1] ?? 0;

document.getElementById("kelembaban").innerHTML=

hum[hum.length-1] ?? 0;

let isi="";

let no=1;

data.slice().reverse().forEach(item=>{

isi+=`

<tr>

<td>${no}</td>


<td>

${new Date(item.waktu).toLocaleString()}

</td>

<td>

<span class="badge-suhu">

${item.suhu} °C

</span>

</td>

<td>

<span class="badge-hum">

${item.kelembaban} %

</span>

</td>

</tr>

`;

no++;

});

document.getElementById("tabel").innerHTML=isi;

if(chartSuhu)

chartSuhu.destroy();

chartSuhu=new Chart(

document.getElementById("grafikSuhu"),

{

type:"line",

data:{

labels:waktu,

datasets:[{

label:"Suhu °C",

data:suhu,

borderColor:"#00ff99",

backgroundColor:
"rgba(0,255,153,.2)",

fill:true,

tension:.4

}]

},

options:{

responsive:true,

maintainAspectRatio:false

}

}

);

if(chartHum)

chartHum.destroy();

chartHum=new Chart(

document.getElementById("grafikHum"),

{

type:"line",

data:{

labels:waktu,

datasets:[{

label:"Kelembaban %",

data:hum,

borderColor:"#3498db",

backgroundColor:
"rgba(52,152,219,.2)",

fill:true,

tension:.4

}]

},

options:{

responsive:true,

maintainAspectRatio:false

}

}

);

}

window.besarGrafik=function(el){

el.classList.toggle("fullscreen");

}

window.downloadExcel=function(){

let csv="No,Waktu,Suhu,Kelembaban\n";

dataExcel.forEach((item,index)=>{

csv+=

`${index+1},${item.waktu},${item.suhu},${item.kelembaban}\n`;

});

let blob=new Blob(

[csv],

{
type:"text/csv"
}

);

let url=URL.createObjectURL(blob);

let a=document.createElement("a");

a.href=url;

a.download="logger_DHT11.csv";

a.click();

URL.revokeObjectURL(url);

}

setInterval(loadData,2000);

loadData();

</script>

</body>
</html> 
 
 
 
4. VIDEO HASILNYA
 

 
 
 
 
 
 

ESP32 Cam Kirim Foto ke Telegram

ESP32 Cam Kirim Foto ke Telegram
 

        Pada kesempatan kali ini saya akan menjelaskan bagaimana cara membuat sebuah alat dengan ESP32 cam untuk mengirim gambar ke telegram, fungsi dari alat ini yaitu untuk fitur pemantauan, jadi misal lagi di tempat kerja lalu ingin melihat kondisi real yang ada disana bisa dipantau via telegram dan gambarnya bisa di share ke group atau status media sosial. untuk lebih jelasnya berkut adalah koding dan haslnya.
 
 
1. Hasilnya
 

 
2. Program ESP32 Cam
 
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include "esp_camera.h"

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


const char* WIFI_SSID = "hotspotkuwifi";
const char* WIFI_PASSWORD = "123456789";

// ================================
// TELEGRAM
// ================================

// Gunakan token baru
String BOT_TOKEN = "8599566365:AAHWGXsuTQp5IROs0d9HIV9gFd9DyG8xx1x";


// Chat ID tujuan
const char* CHAT_ID = "7796726911";

// =====================================================
// PIN ESP32-CAM AI THINKER
// =====================================================

#define PWDN_GPIO_NUM     32
#define RESET_GPIO_NUM    -1
#define XCLK_GPIO_NUM      0
#define SIOD_GPIO_NUM     26
#define SIOC_GPIO_NUM     27

#define Y9_GPIO_NUM       35
#define Y8_GPIO_NUM       34
#define Y7_GPIO_NUM       39
#define Y6_GPIO_NUM       36
#define Y5_GPIO_NUM       21
#define Y4_GPIO_NUM       19
#define Y3_GPIO_NUM       18
#define Y2_GPIO_NUM        5

#define VSYNC_GPIO_NUM    25
#define HREF_GPIO_NUM     23
#define PCLK_GPIO_NUM     22

// =====================================================
// TELEGRAM UPDATE
// =====================================================

long lastUpdateID = 0;

unsigned long lastCheck = 0;

const unsigned long CHECK_INTERVAL = 1000;


// =====================================================
// INISIALISASI CAMERA
// =====================================================

bool setupCamera() {

  camera_config_t config = {};

  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer   = LEDC_TIMER_0;

  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;

  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;

  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;

  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href  = HREF_GPIO_NUM;

  // Kompatibilitas beberapa versi ESP32 Core

#if defined(ESP_ARDUINO_VERSION_MAJOR) && ESP_ARDUINO_VERSION_MAJOR >= 2

  config.pin_sccb_sda = SIOD_GPIO_NUM;
  config.pin_sccb_scl = SIOC_GPIO_NUM;

#else

  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;

#endif

  config.pin_pwdn  = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;

  config.xclk_freq_hz = 20000000;

  // Langsung menghasilkan JPG
  config.pixel_format = PIXFORMAT_JPEG;

  // Mulai VGA supaya stabil
  config.frame_size = FRAMESIZE_VGA;

  config.jpeg_quality = 12;

  config.fb_count = 1;


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


  esp_err_t err =
    esp_camera_init(&config);


  if (err != ESP_OK) {

    Serial.printf(
      "Camera gagal. Error: 0x%x\n",
      err
    );

    return false;
  }


  sensor_t* sensor =
    esp_camera_sensor_get();


  sensor->set_framesize(
    sensor,
    FRAMESIZE_VGA
  );


  sensor->set_quality(
    sensor,
    12
  );


  Serial.println(
    "Camera berhasil."
  );


  return true;
}


// =====================================================
// CONNECT WIFI
// =====================================================

void connectWiFi() {

  WiFi.mode(WIFI_STA);

  WiFi.begin(
    WIFI_SSID,
    WIFI_PASSWORD
  );


  Serial.print(
    "Menghubungkan WiFi"
  );


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

    delay(500);

    Serial.print(".");
  }


  Serial.println();

  Serial.println(
    "WiFi terhubung"
  );


  Serial.print(
    "IP: "
  );

  Serial.println(
    WiFi.localIP()
  );


  Serial.print(
    "Signal: "
  );

  Serial.print(
    WiFi.RSSI()
  );

  Serial.println(
    " dBm"
  );
}


// =====================================================
// HAPUS WEBHOOK
// Supaya getUpdates bisa digunakan
// =====================================================

void deleteWebhook() {

  WiFiClientSecure client;

  client.setInsecure();


  HTTPClient https;


  String url =
    "https://api.telegram.org/bot"
    + BOT_TOKEN
    + "/deleteWebhook?drop_pending_updates=true";


  Serial.println(
    "Menyiapkan Telegram..."
  );


  if (
    https.begin(
      client,
      url
    )
  ) {

    int httpCode =
      https.GET();


    Serial.print(
      "deleteWebhook HTTP: "
    );

    Serial.println(
      httpCode
    );


    https.end();
  }
}


// =====================================================
// KIRIM FOTO JPG KE TELEGRAM
// =====================================================

bool sendPhotoTelegram(
  String chatID
) {

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

  Serial.println(
    "MENGAMBIL FOTO"
  );

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


  // ===================================================
  // BUANG FRAME PERTAMA
  // ===================================================

  camera_fb_t* fb =
    esp_camera_fb_get();


  if (fb) {

    esp_camera_fb_return(fb);

    fb = NULL;
  }


  delay(200);


  // ===================================================
  // AMBIL FOTO BARU
  // ===================================================

  fb =
    esp_camera_fb_get();


  if (!fb) {

    Serial.println(
      "Gagal mengambil gambar!"
    );

    return false;
  }


  Serial.print(
    "Resolusi: "
  );

  Serial.print(
    fb->width
  );

  Serial.print(
    " x "
  );

  Serial.println(
    fb->height
  );


  Serial.print(
    "Ukuran JPG: "
  );

  Serial.print(
    fb->len
  );

  Serial.println(
    " bytes"
  );


  // ===================================================
  // CONNECT TELEGRAM
  // ===================================================

  WiFiClientSecure client;

  client.setInsecure();

  client.setTimeout(15000);


  Serial.println(
    "Menghubungkan Telegram..."
  );


  if (
    !client.connect(
      "api.telegram.org",
      443
    )
  ) {

    Serial.println(
      "Koneksi Telegram gagal!"
    );


    esp_camera_fb_return(fb);

    return false;
  }


  Serial.println(
    "Telegram connected"
  );


  // ===================================================
  // MULTIPART FORM DATA
  // ===================================================

  String boundary =
    "ESP32CAMBoundary";


  String head =

    "--" + boundary + "\r\n"

    "Content-Disposition: form-data; "
    "name=\"chat_id\"\r\n\r\n"

    + chatID +

    "\r\n"

    "--" + boundary + "\r\n"

    "Content-Disposition: form-data; "
    "name=\"photo\"; "
    "filename=\"esp32cam.jpg\"\r\n"

    "Content-Type: image/jpeg\r\n\r\n";


  String tail =

    "\r\n--"
    + boundary
    + "--\r\n";


  size_t contentLength =
    head.length()
    +
    fb->len
    +
    tail.length();


  // ===================================================
  // HTTP POST
  // ===================================================

  client.print(
    "POST /bot"
  );

  client.print(
    BOT_TOKEN
  );

  client.println(
    "/sendPhoto HTTP/1.1"
  );


  client.println(
    "Host: api.telegram.org"
  );


  client.println(
    "Connection: close"
  );


  client.print(
    "Content-Type: multipart/form-data; boundary="
  );

  client.println(
    boundary
  );


  client.print(
    "Content-Length: "
  );

  client.println(
    contentLength
  );


  client.println();


  // ===================================================
  // KIRIM HEADER
  // ===================================================

  client.print(
    head
  );


  // ===================================================
  // KIRIM DATA JPG
  // ===================================================

  size_t sent = 0;

  unsigned long sendTimeout =
    millis();


  while (
    sent < fb->len
  ) {

    size_t remaining =
      fb->len - sent;


    size_t chunkSize =
      remaining > 1024
      ? 1024
      : remaining;


    size_t written =
      client.write(
        fb->buf + sent,
        chunkSize
      );


    if (written > 0) {

      sent += written;

      sendTimeout =
        millis();

    } else {

      if (
        millis() - sendTimeout >
        10000
      ) {

        Serial.println(
          "Timeout mengirim JPG"
        );

        break;
      }

      delay(10);
    }


    delay(1);
  }


  client.print(
    tail
  );


  Serial.print(
    "JPG terkirim: "
  );

  Serial.print(
    sent
  );

  Serial.print(
    "/"
  );

  Serial.println(
    fb->len
  );


  // Frame sudah selesai
  esp_camera_fb_return(fb);

  fb = NULL;


  // ===================================================
  // BACA RESPONSE TELEGRAM
  // ===================================================

  bool telegramOK =
    false;


  unsigned long timeout =
    millis();


  Serial.println(
    "Menunggu response Telegram..."
  );


  while (
    millis() - timeout <
    15000
  ) {

    while (
      client.available()
    ) {

      String line =
        client.readStringUntil('\n');


      Serial.println(
        line
      );


      if (
        line.indexOf(
          "\"ok\":true"
        ) >= 0
      ) {

        telegramOK =
          true;
      }


      timeout =
        millis();
    }


    if (
      !client.connected()
    ) {

      break;
    }


    delay(10);
  }


  client.stop();


  if (telegramOK) {

    Serial.println();
    Serial.println(
      "FOTO BERHASIL DIKIRIM!"
    );

  } else {

    Serial.println();
    Serial.println(
      "FOTO GAGAL DIKIRIM!"
    );
  }


  return telegramOK;
}


// =====================================================
// CEK PESAN TELEGRAM
// =====================================================

void checkTelegram() {

  WiFiClientSecure client;

  client.setInsecure();


  HTTPClient https;


  String url =
    "https://api.telegram.org/bot"
    + BOT_TOKEN
    + "/getUpdates?offset="
    + String(lastUpdateID + 1)
    + "&limit=5&timeout=0";


  if (
    !https.begin(
      client,
      url
    )
  ) {

    Serial.println(
      "HTTP Telegram gagal"
    );

    return;
  }


  int httpCode =
    https.GET();


  if (
    httpCode != 200
  ) {

    Serial.print(
      "getUpdates HTTP Error: "
    );

    Serial.println(
      httpCode
    );


    https.end();

    return;
  }


  String payload =
    https.getString();


  https.end();


  // ===================================================
  // PARSE JSON
  // ===================================================

  DynamicJsonDocument doc(
    16384
  );


  DeserializationError error =
    deserializeJson(
      doc,
      payload
    );


  if (error) {

    Serial.print(
      "JSON error: "
    );

    Serial.println(
      error.c_str()
    );

    return;
  }


  JsonArray results =
    doc["result"].as<JsonArray>();


  for (
    JsonObject update : results
  ) {

    long updateID =
      update["update_id"] | 0;


    if (
      updateID >
      lastUpdateID
    ) {

      lastUpdateID =
        updateID;
    }


    // Pastikan update berisi message

    if (
      update["message"].isNull()
    ) {

      continue;
    }


    const char* textRaw =
      update["message"]["text"] | "";


    String text =
      String(textRaw);


    // Telegram chat ID sekarang bisa besar.
    // Ambil sebagai 64-bit.

    int64_t chatIDNumber =
      update["message"]["chat"]["id"]
      .as<int64_t>();


    char chatBuffer[32];


    snprintf(
      chatBuffer,
      sizeof(chatBuffer),
      "%lld",
      (long long)chatIDNumber
    );


    String chatID =
      String(chatBuffer);


    Serial.println();
    Serial.println(
      "------------------------------"
    );

    Serial.print(
      "Chat ID : "
    );

    Serial.println(
      chatID
    );


    Serial.print(
      "Pesan   : "
    );

    Serial.println(
      text
    );


    // =================================================
    // PERINTAH /foto
    // =================================================

    if (
      text == "/foto"
      ||
      text.startsWith(
        "/foto@"
      )
    ) {

      Serial.println(
        "Perintah /foto diterima"
      );


      sendPhotoTelegram(
        chatID
      );
    }
  }
}


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

void setup() {

  Serial.begin(
    115200
  );


  delay(1000);


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

  Serial.println(
    "ESP32-CAM TELEGRAM /FOTO"
  );

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


  // CAMERA

  if (
    !setupCamera()
  ) {

    Serial.println(
      "Camera gagal."
    );

    return;
  }


  // WIFI

  connectWiFi();


  // getUpdates tidak bisa digunakan
  // jika bot masih memakai webhook.

  deleteWebhook();


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

  Serial.println(
    "SISTEM SIAP"
  );

  Serial.println(
    "Kirim /foto ke Telegram"
  );

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


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

void loop() {

  // Reconnect WiFi

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

    connectWiFi();
  }


  // Cek Telegram setiap 1 detik

  if (
    millis() - lastCheck >=
    CHECK_INTERVAL
  ) {

    lastCheck =
      millis();


    checkTelegram();
  }
}
 
 
 
3. VIDEO HASILNYA
 


ESP32 Cam Deteksi dan Counter / Hitung Benda / Orang Lewat

ESP32 Cam Deteksi dan Counter / Hitung Benda / Orang Lewat 
 

         Pada kesempatan kali ini saya akan menjelaskan mengenai bagaimana cara membuat sebuah esp32 cam bisa berfungsi sebagai penghitung orang yang masuk atau keluar, atau juga bisa menghitung benda yang lewat didepan kamera. untuk detailnya berikut adalah koding dan interfacenya.
 
 
1. Interface
 
 
 
2. Program ESP32 Cam
 
#include <WiFi.h>
#include <WebServer.h>
#include "esp_camera.h"

// ======================================================
// ESP32-CAM AI THINKER PIN
// ======================================================

#define PWDN_GPIO_NUM     32
#define RESET_GPIO_NUM    -1
#define XCLK_GPIO_NUM      0
#define SIOD_GPIO_NUM     26
#define SIOC_GPIO_NUM     27

#define Y9_GPIO_NUM       35
#define Y8_GPIO_NUM       34
#define Y7_GPIO_NUM       39
#define Y6_GPIO_NUM       36
#define Y5_GPIO_NUM       21
#define Y4_GPIO_NUM       19
#define Y3_GPIO_NUM       18
#define Y2_GPIO_NUM        5

#define VSYNC_GPIO_NUM    25
#define HREF_GPIO_NUM     23
#define PCLK_GPIO_NUM     22


// ======================================================
// HOTSPOT
// ======================================================

const char* AP_SSID = "ESP32CAM_COUNTER";
const char* AP_PASSWORD = "12345678";

WebServer server(80);


// ======================================================
// UKURAN FRAME
// QQVGA = 160 x 120
// ======================================================

#define FRAME_WIDTH   160
#define FRAME_HEIGHT  120
#define FRAME_SIZE    (FRAME_WIDTH * FRAME_HEIGHT)

uint8_t* previousFrame = NULL;


// ======================================================
// ZONA DETEKSI
// ======================================================

// Zona A
#define ZONE_A_X1  30
#define ZONE_A_X2  60

// Zona B
#define ZONE_B_X1  100
#define ZONE_B_X2  130

// Area vertikal jalan
#define ZONE_Y1    20
#define ZONE_Y2    105


// ======================================================
// SENSITIVITAS
// ======================================================

// Perbedaan brightness pixel
#define PIXEL_DIFF_THRESHOLD 25

// Berapa persen pixel harus berubah
#define MOTION_THRESHOLD 12

// Timeout kendaraan mencapai zona berikutnya
#define CROSS_TIMEOUT 3000

// Frame tenang sebelum siap menghitung lagi
#define CLEAR_FRAME_REQUIRED 5


// ======================================================
// COUNTER
// ======================================================

unsigned long vehicleCount = 0;

unsigned long countAToB = 0;
unsigned long countBToA = 0;

String lastDirection = "-";

int motionA = 0;
int motionB = 0;


// ======================================================
// STATE MACHINE
// ======================================================

enum DetectionState {

  STATE_IDLE,

  STATE_WAIT_B,

  STATE_WAIT_A,

  STATE_LOCKED

};

DetectionState state = STATE_IDLE;

unsigned long stateStartTime = 0;

int clearFrameCounter = 0;


// ======================================================
// HTML WEBSERVER
// ======================================================

const char PAGE[] PROGMEM = R"rawliteral(

<!DOCTYPE html>

<html>

<head>

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

<title>ESP32 Vehicle Counter</title>

<style>

body {

  background:#111;
  color:white;

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

  margin:0;
  padding:20px;

}

.container {

  max-width:500px;
  margin:auto;

}

.card {

  background:#222;

  padding:20px;
  margin:15px 0;

  border-radius:12px;

}

h1 {

  font-size:25px;

}

.counter {

  font-size:70px;

  color:#00ff88;

  font-weight:bold;

}

.label {

  color:#aaa;

  font-size:13px;

}

.value {

  font-size:24px;

  margin:7px;

}

.zone {

  display:flex;

  justify-content:space-around;

}

.zonebox {

  width:45%;

  padding:15px;

  background:#333;

  border-radius:10px;

}

button {

  width:100%;

  padding:15px;

  background:#d32f2f;

  color:white;

  border:none;

  border-radius:8px;

  font-size:16px;

  cursor:pointer;

}

.status {

  color:#ffb74d;

}

</style>

</head>


<body>

<div class="container">

<h1>
ESP32-CAM Vehicle Counter
</h1>


<div class="card">

<div class="label">
JUMLAH KENDARAAN
</div>

<div
class="counter"
id="count">
0
</div>

</div>


<div class="card">

<div class="zone">

<div class="zonebox">

<div class="label">
A → B
</div>

<div
class="value"
id="ab">
0
</div>

</div>


<div class="zonebox">

<div class="label">
B → A
</div>

<div
class="value"
id="ba">
0
</div>

</div>

</div>

</div>


<div class="card">

<div class="label">
ARAH TERAKHIR
</div>

<div
class="value"
id="direction">
-
</div>


<div class="label">
STATUS SISTEM
</div>

<div
class="value status"
id="state">
MENUNGGU
</div>

</div>


<div class="card">

<div class="zone">

<div class="zonebox">

<div class="label">
MOTION ZONA A
</div>

<div
class="value"
id="motionA">
0%
</div>

</div>


<div class="zonebox">

<div class="label">
MOTION ZONA B
</div>

<div
class="value"
id="motionB">
0%
</div>

</div>

</div>

</div>


<div class="card">

<button onclick="resetCounter()">

RESET COUNTER

</button>

</div>


<div class="label">

WiFi: ESP32CAM_COUNTER

<br>

IP: 192.168.4.1

</div>

</div>


<script>

function updateData() {

  fetch("/status")

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

  .then(data => {

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


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


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


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


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


    document.getElementById(
      "motionA"
    ).innerHTML =
      data.motionA + "%";


    document.getElementById(
      "motionB"
    ).innerHTML =
      data.motionB + "%";

  });

}


function resetCounter() {

  fetch("/reset");

}


setInterval(
  updateData,
  500
);

updateData();

</script>

</body>

</html>

)rawliteral";


// ======================================================
// HITUNG PERUBAHAN PIXEL DALAM ZONA
// ======================================================

int calculateMotion(

  uint8_t* current,

  uint8_t* previous,

  int x1,
  int x2,

  int y1,
  int y2

) {

  unsigned long changed = 0;

  unsigned long total = 0;


  // Skip beberapa pixel supaya lebih ringan
  const int STEP = 2;


  for (
    int y = y1;
    y < y2;
    y += STEP
  ) {

    for (
      int x = x1;
      x < x2;
      x += STEP
    ) {

      int index =
        y * FRAME_WIDTH + x;


      int difference =

        abs(
          (int)current[index]
          -
          (int)previous[index]
        );


      if (
        difference >
        PIXEL_DIFF_THRESHOLD
      ) {

        changed++;
      }


      total++;
    }
  }


  if (
    total == 0
  ) {

    return 0;
  }


  return
    (changed * 100)
    /
    total;
}


// ======================================================
// PROSES COUNTER
// ======================================================

void processVehicle(

  bool zoneA,
  bool zoneB

) {

  unsigned long now =
    millis();


  switch(state) {


    // ==================================================
    // MENUNGGU KENDARAAN
    // ==================================================

    case STATE_IDLE:


      if (
        zoneA &&
        !zoneB
      ) {

        state =
          STATE_WAIT_B;

        stateStartTime =
          now;

        Serial.println(
          "Zona A aktif -> menunggu Zona B"
        );
      }


      else if (
        zoneB &&
        !zoneA
      ) {

        state =
          STATE_WAIT_A;

        stateStartTime =
          now;

        Serial.println(
          "Zona B aktif -> menunggu Zona A"
        );
      }


      break;



    // ==================================================
    // A -> B
    // ==================================================

    case STATE_WAIT_B:


      if (zoneB) {

        vehicleCount++;

        countAToB++;

        lastDirection =
          "A -> B";


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

        Serial.println(
          "KENDARAAN TERHITUNG"
        );

        Serial.print(
          "Arah  : "
        );

        Serial.println(
          "A -> B"
        );

        Serial.print(
          "Total : "
        );

        Serial.println(
          vehicleCount
        );

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


        state =
          STATE_LOCKED;

        clearFrameCounter =
          0;
      }


      else if (
        now - stateStartTime >
        CROSS_TIMEOUT
      ) {

        state =
          STATE_IDLE;
      }


      break;



    // ==================================================
    // B -> A
    // ==================================================

    case STATE_WAIT_A:


      if (zoneA) {

        vehicleCount++;

        countBToA++;

        lastDirection =
          "B -> A";


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

        Serial.println(
          "KENDARAAN TERHITUNG"
        );

        Serial.print(
          "Arah  : "
        );

        Serial.println(
          "B -> A"
        );

        Serial.print(
          "Total : "
        );

        Serial.println(
          vehicleCount
        );

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


        state =
          STATE_LOCKED;

        clearFrameCounter =
          0;
      }


      else if (
        now - stateStartTime >
        CROSS_TIMEOUT
      ) {

        state =
          STATE_IDLE;
      }


      break;



    // ==================================================
    // TUNGGU KENDARAAN KELUAR
    // ==================================================

    case STATE_LOCKED:


      if (
        !zoneA &&
        !zoneB
      ) {

        clearFrameCounter++;


        if (
          clearFrameCounter >=
          CLEAR_FRAME_REQUIRED
        ) {

          state =
            STATE_IDLE;

          clearFrameCounter =
            0;

          Serial.println(
            "Area kosong. Siap kendaraan berikutnya."
          );
        }

      }

      else {

        clearFrameCounter = 0;
      }


      break;
  }
}


// ======================================================
// NAMA STATUS
// ======================================================

String getStateName() {

  switch(state) {

    case STATE_IDLE:

      return "MENUNGGU";


    case STATE_WAIT_B:

      return "A TERDETEKSI";


    case STATE_WAIT_A:

      return "B TERDETEKSI";


    case STATE_LOCKED:

      return "KENDARAAN LEWAT";

  }


  return "-";
}


// ======================================================
// WEBSERVER
// ======================================================

void setupWebServer() {


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

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


  server.on(
    "/status",
    HTTP_GET,
    []() {

      String json =
        "{";


      json +=
        "\"count\":" +
        String(vehicleCount) +
        ",";


      json +=
        "\"ab\":" +
        String(countAToB) +
        ",";


      json +=
        "\"ba\":" +
        String(countBToA) +
        ",";


      json +=
        "\"direction\":\"" +
        lastDirection +
        "\",";


      json +=
        "\"state\":\"" +
        getStateName() +
        "\",";


      json +=
        "\"motionA\":" +
        String(motionA) +
        ",";


      json +=
        "\"motionB\":" +
        String(motionB);


      json += "}";


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

    }
  );


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

      vehicleCount = 0;

      countAToB = 0;

      countBToA = 0;

      lastDirection = "-";

      state =
        STATE_IDLE;


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


      Serial.println(
        "Counter di-reset"
      );
    }
  );


  server.begin();


  Serial.println(
    "Webserver aktif"
  );
}


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

void setup() {

  Serial.begin(
    115200
  );


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

  Serial.println(
    "ESP32-CAM VEHICLE COUNTER"
  );

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


  // ==================================================
  // CAMERA CONFIG
  // ==================================================

  camera_config_t config;


  config.ledc_channel =
    LEDC_CHANNEL_0;

  config.ledc_timer =
    LEDC_TIMER_0;


  config.pin_d0 =
    Y2_GPIO_NUM;

  config.pin_d1 =
    Y3_GPIO_NUM;

  config.pin_d2 =
    Y4_GPIO_NUM;

  config.pin_d3 =
    Y5_GPIO_NUM;

  config.pin_d4 =
    Y6_GPIO_NUM;

  config.pin_d5 =
    Y7_GPIO_NUM;

  config.pin_d6 =
    Y8_GPIO_NUM;

  config.pin_d7 =
    Y9_GPIO_NUM;


  config.pin_xclk =
    XCLK_GPIO_NUM;

  config.pin_pclk =
    PCLK_GPIO_NUM;

  config.pin_vsync =
    VSYNC_GPIO_NUM;

  config.pin_href =
    HREF_GPIO_NUM;


  config.pin_sscb_sda =
    SIOD_GPIO_NUM;

  config.pin_sscb_scl =
    SIOC_GPIO_NUM;


  config.pin_pwdn =
    PWDN_GPIO_NUM;

  config.pin_reset =
    RESET_GPIO_NUM;


  config.xclk_freq_hz =
    20000000;


  // Grayscale jauh lebih ringan
  // daripada RGB / JPEG

  config.pixel_format =
    PIXFORMAT_GRAYSCALE;


  config.frame_size =
    FRAMESIZE_QQVGA;


  config.jpeg_quality =
    12;


  config.fb_count =
    1;


  // ==================================================
  // START CAMERA
  // ==================================================

  esp_err_t err =
    esp_camera_init(
      &config
    );


  if (
    err != ESP_OK
  ) {

    Serial.printf(
      "Camera error: 0x%x\n",
      err
    );

    return;
  }


  Serial.println(
    "Camera berhasil"
  );


  // ==================================================
  // ALOKASI FRAME SEBELUMNYA
  // ==================================================

  if (
    psramFound()
  ) {

    previousFrame =
      (uint8_t*)
      ps_malloc(
        FRAME_SIZE
      );

  }

  else {

    previousFrame =
      (uint8_t*)
      malloc(
        FRAME_SIZE
      );
  }


  if (
    previousFrame == NULL
  ) {

    Serial.println(
      "Gagal alokasi previousFrame"
    );

    return;
  }


  memset(
    previousFrame,
    0,
    FRAME_SIZE
  );


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

  WiFi.mode(
    WIFI_AP
  );


  IPAddress localIP(
    192,
    168,
    4,
    1
  );


  IPAddress gateway(
    192,
    168,
    4,
    1
  );


  IPAddress subnet(
    255,
    255,
    255,
    0
  );


  WiFi.softAPConfig(
    localIP,
    gateway,
    subnet
  );


  WiFi.softAP(
    AP_SSID,
    AP_PASSWORD
  );


  Serial.println();
  Serial.println(
    "HOTSPOT AKTIF"
  );


  Serial.print(
    "SSID     : "
  );

  Serial.println(
    AP_SSID
  );


  Serial.print(
    "Password : "
  );

  Serial.println(
    AP_PASSWORD
  );


  Serial.print(
    "IP       : "
  );

  Serial.println(
    WiFi.softAPIP()
  );


  setupWebServer();


  Serial.println();
  Serial.println(
    "Buka:"
  );

  Serial.println(
    "http://192.168.4.1"
  );


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


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

void loop() {

  server.handleClient();


  camera_fb_t* fb =
    esp_camera_fb_get();


  if (
    fb == NULL
  ) {

    delay(20);

    return;
  }


  // Pastikan grayscale
  // 160 x 120

  if (
    fb->format !=
    PIXFORMAT_GRAYSCALE
  ) {

    esp_camera_fb_return(
      fb
    );

    return;
  }


  // ==================================================
  // FRAME PERTAMA
  // ==================================================

  static bool firstFrame =
    true;


  if(firstFrame) {

    memcpy(
      previousFrame,
      fb->buf,
      FRAME_SIZE
    );


    firstFrame =
      false;


    esp_camera_fb_return(
      fb
    );


    delay(100);

    return;
  }


  // ==================================================
  // HITUNG MOTION
  // ==================================================

  motionA =
    calculateMotion(

      fb->buf,
      previousFrame,

      ZONE_A_X1,
      ZONE_A_X2,

      ZONE_Y1,
      ZONE_Y2

    );


  motionB =
    calculateMotion(

      fb->buf,
      previousFrame,

      ZONE_B_X1,
      ZONE_B_X2,

      ZONE_Y1,
      ZONE_Y2

    );


  bool zoneAActive =
    motionA >=
    MOTION_THRESHOLD;


  bool zoneBActive =
    motionB >=
    MOTION_THRESHOLD;


  processVehicle(
    zoneAActive,
    zoneBActive
  );


  // ==================================================
  // SIMPAN FRAME SEKARANG
  // ==================================================

  memcpy(
    previousFrame,
    fb->buf,
    FRAME_SIZE
  );


  esp_camera_fb_return(
    fb
  );


  delay(70);
}

 
 
3. VIDEO HASILNYA