#include <WiFi.h>
#include <WebServer.h>
#include <SPI.h>
#include <SD.h>
#include <ESP32Servo.h>
// ---------- Konfigurasi Access Point ----------
const char* AP_SSID = "SmartLock-ESP32";
const char* AP_PASS = "kunci1234";
// ---------- Konfigurasi pin ----------
#define PIN_SERVO 13
#define PIN_LED_HIJAU 26
#define PIN_LED_MERAH 27
#define PIN_BUZZER 25
#define PIN_SD_CS 5
// ---------- Konfigurasi sistem ----------
const int SUDUT_TERKUNCI = 0;
const int SUDUT_TERBUKA = 90;
const int MAKS_GAGAL = 3;
const unsigned long DURASI_BUKA = 5000; // pintu terbuka 5 detik
const unsigned long DURASI_BLOKIR = 15000; // blokir 15 detik
WebServer server(80);
Servo servoPintu;
int jumlahGagal = 0;
bool pintuTerbuka = false;
bool terblokir = false;
unsigned long waktuBuka = 0;
unsigned long waktuBlokir = 0;
bool sdSiap = false;
// Data cadangan bila MicroSD tidak dapat dipakai
const char* DATA_AWAL[] = { "admin,Rahasia01", "tamu1,abc123", "tamu2,xyz789" };
const int JUMLAH_DATA_AWAL = 3;
// ---------- Fungsi output ----------
void bunyi(int frekuensi, int durasi) {
tone(PIN_BUZZER, frekuensi);
delay(durasi);
noTone(PIN_BUZZER);
}
void kunciPintu() {
servoPintu.write(SUDUT_TERKUNCI);
digitalWrite(PIN_LED_HIJAU, LOW);
digitalWrite(PIN_LED_MERAH, HIGH);
pintuTerbuka = false;
}
void bukaPintu() {
servoPintu.write(SUDUT_TERBUKA);
digitalWrite(PIN_LED_HIJAU, HIGH);
digitalWrite(PIN_LED_MERAH, LOW);
pintuTerbuka = true;
waktuBuka = millis();
bunyi(1500, 200);
}
// ---------- Pencarian data pada MicroSD ----------
// Membuat akses.csv bila belum ada (Wokwi gratis tidak bisa unggah file SD)
void siapkanFileAkses() {
if (SD.exists("/akses.csv")) return;
File f = SD.open("/akses.csv", FILE_WRITE);
if (!f) return;
for (int i = 0; i < JUMLAH_DATA_AWAL; i++) f.println(DATA_AWAL[i]);
f.close();
Serial.println("akses.csv dibuat otomatis di MicroSD");
}
bool cocokBaris(String baris, String key, String pass) {
baris.trim();
int pemisah = baris.indexOf(',');
if (pemisah <= 0) return false;
String k = baris.substring(0, pemisah);
String p = baris.substring(pemisah + 1);
k.trim();
p.trim();
return (k == key && p == pass);
}
bool cariAkses(String key, String pass) {
if (sdSiap) {
File f = SD.open("/akses.csv");
if (f) {
bool ketemu = false;
while (f.available()) {
if (cocokBaris(f.readStringUntil('\n'), key, pass)) {
ketemu = true;
break;
}
}
f.close();
return ketemu;
}
}
// cadangan: data di memori
for (int i = 0; i < JUMLAH_DATA_AWAL; i++) {
if (cocokBaris(String(DATA_AWAL[i]), key, pass)) return true;
}
return false;
}
// ---------- Logika login ----------
// 0 = diterima, 1 = ditolak, 2 = sedang diblokir
int prosesLogin(String key, String pass) {
if (terblokir) return 2;
if (cariAkses(key, pass)) {
jumlahGagal = 0;
bukaPintu();
return 0;
}
jumlahGagal++;
bunyi(300, 400);
if (jumlahGagal >= MAKS_GAGAL) {
terblokir = true;
waktuBlokir = millis();
bunyi(300, 800);
}
return 1;
}
int sisaBlokirDetik() {
unsigned long lewat = millis() - waktuBlokir;
if (lewat >= DURASI_BLOKIR) return 0;
return (DURASI_BLOKIR - lewat) / 1000 + 1;
}
// ---------- Halaman web ----------
String halamanHasil(String judul, String pesan, String warna) {
String h = "<html><head><meta name='viewport' content='width=device-width,initial-scale=1'></head>";
h += "<body style='font-family:Arial;text-align:center;margin-top:60px'>";
h += "<h2 style='color:" + warna + "'>" + judul + "</h2>";
h += "<p>" + pesan + "</p>";
h += "<a href='/'>Kembali</a></body></html>";
return h;
}
void handleRoot() {
String html = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Smart Lock ESP32</title>
<style>
body { font-family: Arial; background: #eef1f5; text-align: center; margin-top: 40px; }
.kartu { width: 300px; margin: auto; padding: 24px; background: #fff; border-radius: 12px; }
input { width: 90%; padding: 10px; margin: 6px 0; box-sizing: border-box; }
button { width: 90%; padding: 10px; margin-top: 10px; }
</style>
</head>
<body>
<div class="kartu">
<h2>SMART DOOR LOCK</h2>
<form action="/buka" method="POST">
<input type="text" name="kode" placeholder="Kode akses" required>
<input type="password" name="sandi" placeholder="Sandi" required>
<button type="submit">BUKA PINTU</button>
</form>
</div>
</body>
</html>
)rawliteral";
server.send(200, "text/html", html);
}
void handleBuka() {
String key = server.arg("kode");
String pass = server.arg("sandi");
int hasil = prosesLogin(key, pass);
if (hasil == 0) {
server.send(200, "text/html",
halamanHasil("PINTU TERBUKA", "Pintu akan terkunci otomatis dalam 5 detik.", "green"));
} else if (hasil == 1) {
int sisa = MAKS_GAGAL - jumlahGagal;
String pesan = (sisa > 0) ? "Kode atau sandi salah. Sisa percobaan: " + String(sisa)
: "Terlalu banyak percobaan. Sistem diblokir sementara.";
server.send(200, "text/html", halamanHasil("AKSES DITOLAK", pesan, "red"));
} else {
server.send(200, "text/html",
halamanHasil("DIBLOKIR", "Coba lagi dalam " + String(sisaBlokirDetik()) + " detik.", "orange"));
}
}
// ---------- Pengujian lewat Serial Monitor ----------
// Format input: kode,sandi (contoh: tamu1,abc123)
void cekSerial() {
if (!Serial.available()) return;
String masuk = Serial.readStringUntil('\n');
masuk.trim();
int pemisah = masuk.indexOf(',');
if (pemisah <= 0) {
Serial.println("Format salah. Gunakan: kode,sandi");
return;
}
String key = masuk.substring(0, pemisah);
String pass = masuk.substring(pemisah + 1);
key.trim();
pass.trim();
int hasil = prosesLogin(key, pass);
Serial.print("[LOGIN] kode=");
Serial.print(key);
Serial.print(" -> ");
if (hasil == 0) Serial.println("DITERIMA, pintu terbuka");
else if (hasil == 1) Serial.println("DITOLAK, gagal ke-" + String(jumlahGagal));
else Serial.println("DIBLOKIR, sisa " + String(sisaBlokirDetik()) + " detik");
}
// ---------- Pembaruan status berkala (tanpa delay) ----------
void perbaruiStatus() {
if (pintuTerbuka && millis() - waktuBuka >= DURASI_BUKA) {
kunciPintu();
Serial.println("[INFO] Pintu terkunci otomatis");
}
if (terblokir && millis() - waktuBlokir >= DURASI_BLOKIR) {
terblokir = false;
jumlahGagal = 0;
Serial.println("[INFO] Blokir berakhir");
}
}
void setup() {
Serial.begin(115200);
pinMode(PIN_LED_HIJAU, OUTPUT);
pinMode(PIN_LED_MERAH, OUTPUT);
pinMode(PIN_BUZZER, OUTPUT);
servoPintu.attach(PIN_SERVO);
kunciPintu();
sdSiap = SD.begin(PIN_SD_CS);
if (sdSiap) {
Serial.println("MicroSD siap");
siapkanFileAkses();
File f = SD.open("/akses.csv");
if (f) {
Serial.println("Data akses ditemukan:");
while (f.available()) Serial.write(f.read());
f.close();
Serial.println();
} else {
Serial.println("File akses.csv tidak ditemukan, pakai data cadangan");
}
} else {
Serial.println("MicroSD gagal diinisialisasi, pakai data cadangan");
}
WiFi.softAP(AP_SSID, AP_PASS);
Serial.print("Access Point aktif, IP: ");
Serial.println(WiFi.softAPIP());
server.on("/", HTTP_GET, handleRoot);
server.on("/buka", HTTP_POST, handleBuka);
server.begin();
Serial.println("Web Server aktif. Uji lewat Serial: kode,sandi");
}
void loop() {
server.handleClient();
cekSerial();
perbaruiStatus();
}