#define BLYNK_TEMPLATE_ID "TMPL6LceqZT8g"
#define BLYNK_TEMPLATE_NAME "Esp32 Test"
#define BLYNK_AUTH_TOKEN "B0HDGole9rry6Q3RZPk1WlrBFNtfedV4"
#include <WiFi.h>
#include <WebServer.h>
#include <WiFiClient.h>
#include <BlynkSimpleEsp32.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <RTClib.h>
#include <Preferences.h>
// ================= HARDWARE PIN =================
#define RELAY_STARTER 25
#define RELAY_KONTAK 26
#define RELAY_SOLENOID 27
#define PIN_VOLT 34
const int ledPin = 2;
// ================= ADC CONFIG =================
#define ADC_MAX 4095
#define VREF 3.3
#define R1 100000.0 // resistor atas (ohm)
#define R2 10000.0 // resistor bawah (ohm)
// ================= OBJECT =================
RTC_DS3231 rtc;
LiquidCrystal_I2C lcd(0x27, 16, 2);
Preferences prefs;
BlynkTimer timer;
WebServer server(80);
// ================= VARIABEL =================
char ssid_ap[] = "MesinAP";
char pass_ap[] = "mesin123"; // ubah sesuai kebutuhan
String wifiStationSSID = "Wokwi-GUEST";
String wifiStationPASS = "";
bool modeAuto = true;
bool mesinOn = false;
unsigned long startTime = 0;
// ================== JADWAL ==================
const int MAX_JADWAL = 8; // fleksibel
int jumlahJadwal = 5; // jumlah jadwal aktif (default)
int jadwalJam[MAX_JADWAL] = {7, 11, 14, 18, 21, 0,0,0};
int jadwalMenit[MAX_JADWAL] = {30, 0, 30, 0, 30, 0,0,0};
int durasiMenit = 30; // lama mesin ON (menit)
// ================= SENSOR TEGANGAN =================
float bacaTegangan() {
int adcVal = analogRead(PIN_VOLT);
float v = (adcVal * VREF / ADC_MAX); // tegangan ADC
float vin = v * (R1 + R2) / R2; // tegangan real aki
return vin;
}
// ================= ANTI STARTER =================
bool mesinHidup() {
float total = 0;
for (int i=0; i<10; i++) { // ambil rata-rata 10x
total += bacaTegangan();
delay(5);
}
float v = total / 10.0;
return (v > 13.0); // anggap mesin hidup jika >13V
}
// ================= FUNGSI MESIN =================
void startMesin() {
if (mesinHidup()) {
Serial.println("Mesin sudah hidup, cegah starter!");
return;
}
Serial.println("Menyalakan mesin...");
digitalWrite(ledPin, HIGH);
// Hidupkan kontak & solenoid
digitalWrite(RELAY_KONTAK, HIGH);
delay(500);
digitalWrite(RELAY_SOLENOID, HIGH);
delay(500);
int retry = 0;
while (!mesinHidup() && retry < 3) {
Serial.printf("Percobaan starter ke-%d...\n", retry+1);
digitalWrite(RELAY_STARTER, HIGH);
delay(2000); // putar starter 2 detik
digitalWrite(RELAY_STARTER, LOW);
delay(1000); // tunggu sebentar cek RPM lagi
retry++;
}
if (mesinHidup()) {
Serial.println("Mesin berhasil hidup!");
mesinOn = true;
startTime = millis();
Blynk.virtualWrite(V4, "ON");
} else {
Serial.println("Gagal menyalakan mesin setelah 3 percobaan!");
stopMesin(); // pastikan mati
}
}
void stopMesin() {
Serial.println("Mematikan mesin...");
digitalWrite(RELAY_STARTER, LOW);
digitalWrite(RELAY_SOLENOID, LOW);
digitalWrite(RELAY_KONTAK, LOW);
digitalWrite(ledPin, LOW);
mesinOn = false;
Blynk.virtualWrite(V4, "OFF");
Blynk.virtualWrite(V0, 0);
}
// ================= CEK JADWAL =================
void cekJadwal() {
DateTime now = rtc.now();
// cek apakah cocok dengan salah satu jadwal (toleransi 5 detik)
for (int i = 0; i < jumlahJadwal; i++) {
if (jadwalJam[i] < 0) continue;
if (now.hour() == jadwalJam[i] && now.minute() == jadwalMenit[i] && now.second() < 5) {
if (!mesinOn) {
startMesin();
}
}
}
// cek durasi
if (mesinOn && (millis() - startTime > (unsigned long)durasiMenit * 60000UL)) {
stopMesin();
}
}
// ================= BLYNK HANDLER =================
BLYNK_WRITE(V0) { // Start manual
if (param.asInt() == 1) startMesin();
else stopMesin();
}
BLYNK_WRITE(V2) { // Kontak ON/OFF manual
digitalWrite(RELAY_KONTAK, param.asInt() ? HIGH : LOW);
}
BLYNK_WRITE(V3) { // Mode Auto/Manual
modeAuto = param.asInt();
prefs.putBool("modeAuto", modeAuto);
Blynk.virtualWrite(V5, modeAuto ? "AUTO" : "MANUAL");
Blynk.virtualWrite(V3, modeAuto ? 1 : 0);
}
BLYNK_CONNECTED() {
Blynk.syncAll();
Blynk.virtualWrite(V4, mesinOn ? "ON" : "OFF");
Blynk.virtualWrite(V5, modeAuto ? "AUTO" : "MANUAL");
}
// ================= WEB UI =================
String html_escape(const String &input) {
String output = input;
output.replace("&", "&");
output.replace("<", "<");
output.replace(">", ">");
output.replace("\"", """);
output.replace("'", "'");
return output;
}
// ================== Web UI ==================
String pageRoot(){
String s = "<!doctype html><html><head><meta charset='utf-8'>";
s += "<meta name='viewport' content='width=device-width,initial-scale=1'>";
s += "<title>ESP32 Mesin</title>";
s += "<style>";
s += "body{font-family:Arial,Helvetica,sans-serif;margin:0;padding:0;background:#f7f7f7;color:#333;}";
s += "header{background:#1976d2;color:white;padding:12px;text-align:center;font-size:1.2em;}";
s += ".container{max-width:700px;margin:20px auto;padding:10px;}";
s += ".card{background:white;border-radius:10px;box-shadow:0 2px 6px rgba(0,0,0,0.1);padding:15px;margin-bottom:15px;}";
s += "h2,h3,h4{margin-top:0;color:#1976d2;}";
s += "label{display:block;margin-top:8px;font-size:0.9em;}";
s += "input,select,button{padding:8px;margin-top:5px;width:100%;box-sizing:border-box;border-radius:6px;border:1px solid #ccc;}";
s += "button{background:#1976d2;color:white;border:none;cursor:pointer;transition:0.2s;}";
s += "button:hover{background:#125a9c;}";
s += ".btn-danger{background:#d32f2f;}";
s += ".btn-danger:hover{background:#a31616;}";
s += ".status{font-weight:bold;}";
s += "</style></head><body>";
s += "<header>ESP32 Mesin Control</header>";
s += "<div class='container'>";
// status (pakai id agar bisa diupdate JS)
s += "<div class='card'><h3>Status</h3>";
s += "<p>Mesin: <span id='mesinStatus' class='status'>" + String(mesinOn ? "ON" : "OFF") + "</span></p>";
s += "<p>Mode: <span id='modeStatus' class='status'>" + String(modeAuto ? "AUTO" : "MANUAL") + "</span></p></div>";
// kontrol manual
s += "<div class='card'><h3>Kontrol Manual</h3>";
s += "<form action='/action' method='get'>";
s += "<button name='cmd' value='start'>▶ Start Sekarang</button>";
s += "<button class='btn-danger' name='cmd' value='stop'>■ Stop Sekarang</button>";
s += "</form></div>";
// mode
s += "<div class='card'><h3>Mode Operasi</h3>";
s += "<form action='/action' method='get'>Mode: ";
s += "<select name='mode'><option value='1' " + String(modeAuto?"selected":"") + ">AUTO</option>";
s += "<option value='0' " + String(!modeAuto?"selected":"") + ">MANUAL</option></select>";
s += "<button type='submit'>Simpan</button></form></div>";
// durasi
s += "<div class='card'><h3>Durasi Mesin</h3>";
s += "<form action='/durasi' method='get'>Durasi (menit):";
s += "<input name='d' type='number' min='1' max='1440' value='" + String(durasiMenit) + "'>";
s += "<button type='submit'>Simpan</button></form></div>";
// jadwal
s += "<div class='card'><h3>Jadwal (max " + String(MAX_JADWAL) + ")</h3>";
s += "<form action='/jadwal' method='get'>";
for (int i=0;i<MAX_JADWAL;i++){
String jamStr = (jadwalJam[i] >= 0 && jadwalMenit[i] >= 0)
? String(jadwalJam[i]) + ":" + (jadwalMenit[i]<10?"0":"") + String(jadwalMenit[i])
: "";
s += "<label>Jadwal " + String(i+1) + ": <input name='jt" + String(i) + "' type='time' value='" + jamStr + "'></label>";
}
s += "<button type='submit'>Simpan Jadwal</button></form></div>";
// wifi
s += "<div class='card'><h3>WiFi Rumah</h3>";
s += "<form action='/wifi' method='get'>SSID:<input name='s' value='" + html_escape(wifiStationSSID) + "'>";
s += "Password:<input name='p' type='password' value='" + html_escape(wifiStationPASS) + "'>";
s += "<button type='submit'>Simpan & Connect</button></form>";
s += "<p style='font-size:0.85em;color:#666'>Hotspot aktif: <b>" + String(ssid_ap) + "</b> (pwd " + String(pass_ap) + ")</p></div>";
float v = bacaTegangan();
s += "<div class='card'><h3>Status Tegangan</h3>";
s += "<p>Tegangan terukur: <b><span id='teganganStatus'>" + String(v,2) + "</span> V</b></p></div>";
s += "</div></body></html>";
return s;
}
void handleRoot(){ server.send(200, "text/html", pageRoot()); }
void handleStatus(){
float v = bacaTegangan();
String json = "{";
json += "\"mesin\":" + String(mesinOn ? "true" : "false");
json += ",\"mode\":" + String(modeAuto ? "true" : "false");
json += ",\"tegangan\":" + String(v,2);
json += "}";
server.send(200, "application/json", json);
}
void handleSave(){
if (server.method() != HTTP_POST) { server.send(405); return; }
String mode = server.arg("mode");
String dur = server.arg("durasi");
if (mode.length()){
modeAuto = (mode=="1");
prefs.putBool("modeAuto", modeAuto);
}
if (dur.length()){
durasiMenit = dur.toInt();
prefs.putInt("durasiMenit", durasiMenit);
}
// baca jadwal
for (int i=0;i<MAX_JADWAL;i++){
String sj = server.arg("jam"+String(i));
String sm = server.arg("menit"+String(i));
int jj = sj.length()?sj.toInt():-1;
int mm = sm.length()?sm.toInt():-1;
if (jj>=0 && mm>=0){
jadwalJam[i] = jj;
jadwalMenit[i] = mm;
prefs.putInt((String("jadwalJam")+i).c_str(), jadwalJam[i]);
prefs.putInt((String("jadwalMenit")+i).c_str(), jadwalMenit[i]);
} else {
jadwalJam[i] = -1; jadwalMenit[i] = -1;
prefs.putInt((String("jadwalJam")+i).c_str(), -1);
prefs.putInt((String("jadwalMenit")+i).c_str(), -1);
}
}
prefs.putInt("jumlahJadwal", jumlahJadwal);
server.sendHeader("Location", "/");
server.send(303);
}
void handleStart(){
if (server.method() == HTTP_POST) startMesin();
server.sendHeader("Location", "/");
server.send(303);
}
void handleStop(){
if (server.method() == HTTP_POST) stopMesin();
server.sendHeader("Location", "/");
server.send(303);
}
void handleWifi(){
if (server.method() != HTTP_POST) { server.send(405); return; }
String s = server.arg("ssid");
String p = server.arg("pass");
if (s.length()>0){
wifiStationSSID = s;
wifiStationPASS = p;
prefs.putString("wifi_ssid", wifiStationSSID);
prefs.putString("wifi_pass", wifiStationPASS);
// coba connect langsung
WiFi.disconnect();
WiFi.begin(wifiStationSSID.c_str(), wifiStationPASS.c_str());
unsigned long start = millis();
while (millis() - start < 10000){
if (WiFi.status() == WL_CONNECTED) break;
delay(200);
}
}
server.sendHeader("Location", "/");
server.send(303);
}
// ================= SETUP =================
void setup() {
Serial.begin(115200);
Wire.begin();
rtc.begin();
lcd.init(); lcd.backlight();
prefs.begin("mesin", false);
modeAuto = prefs.getBool("modeAuto", true);
durasiMenit = prefs.getInt("durasiMenit", 30);
jumlahJadwal = prefs.getInt("jumlahJadwal", 5);
// load jadwal dari NVS jika ada
for (int i=0;i<MAX_JADWAL;i++){
jadwalJam[i] = prefs.getInt((String("jadwalJam")+i).c_str(), jadwalJam[i]);
jadwalMenit[i] = prefs.getInt((String("jadwalMenit")+i).c_str(), jadwalMenit[i]);
}
wifiStationSSID = prefs.getString("wifi_ssid", "");
wifiStationPASS = prefs.getString("wifi_pass", "");
pinMode(RELAY_STARTER, OUTPUT);
pinMode(RELAY_KONTAK, OUTPUT);
pinMode(RELAY_SOLENOID, OUTPUT);
pinMode(ledPin, OUTPUT);
stopMesin();
// Coba konek ke WiFi rumah dulu, jika gagal buka AP
if (wifiStationSSID.length()>0){
Serial.println("Mencoba konek ke WiFi saved...");
WiFi.begin(wifiStationSSID.c_str(), wifiStationPASS.c_str());
unsigned long start = millis();
while (millis() - start < 8000){
if (WiFi.status() == WL_CONNECTED) break;
delay(200);
}
}
if (WiFi.status() == WL_CONNECTED){
Serial.println("Terhubung ke WiFi rumah: "+WiFi.SSID());
Serial.print("IP: "); Serial.println(WiFi.localIP());
} else {
Serial.println("Tidak terhubung ke WiFi. Membuat Access Point...");
WiFi.softAP(ssid_ap, pass_ap);
IPAddress ip = WiFi.softAPIP();
Serial.print("AP IP: "); Serial.println(ip);
}
// Init Blynk (tetap pakai Blynk, jika perangkat online)
if (WiFi.status() == WL_CONNECTED) Blynk.begin(BLYNK_AUTH_TOKEN, wifiStationSSID.c_str(), wifiStationPASS.c_str());
// setup web server routes
server.on("/status", handleStatus);
server.on("/", HTTP_GET, handleRoot);
server.on("/save", HTTP_POST, handleSave);
server.on("/start", HTTP_POST, handleStart);
server.on("/stop", HTTP_POST, handleStop);
server.on("/wifi", HTTP_POST, handleWifi);
server.begin();
// update status LCD & Blynk tiap 1 detik
timer.setInterval(1000L, []() {
DateTime now = rtc.now();
char buf[17];
snprintf(buf, sizeof(buf), "Jam:%02d:%02d:%02d", now.hour(), now.minute(), now.second());
lcd.setCursor(0,0); lcd.print(String(buf)+" ");
lcd.setCursor(0,1); lcd.print(String("M:" ) + (mesinOn?"ON ":"OFF") + " " + (modeAuto?"AUTO":"MANU") + " ");
// Kirim status ke Blynk jika terkoneksi
if (WiFi.status() == WL_CONNECTED) {
Blynk.virtualWrite(V4, mesinOn ? "ON" : "OFF");
Blynk.virtualWrite(V5, modeAuto ? "AUTO" : "MANUAL");
}
});
}
// ================= LOOP =================
void loop() {
if (WiFi.status() == WL_CONNECTED) {
Blynk.run();
}
timer.run();
server.handleClient();
if (modeAuto) {
cekJadwal(); // cek apakah waktunya nyala / mati
}
}