#include <WiFi.h>
#include <WebServer.h>
#include <Preferences.h>
// For Hardware Serial, use Serial1 or Serial2 on ESP32.
// Serial1: RX (GPIO9), TX (GPIO10)
// Serial2: RX (GPIO16), TX (GPIO17)
// Ensure these pins are not used for anything else.
HardwareSerial sim800l(2); // Using Hardware Serial '2' (Serial2)
// --- Basic Settings ---
#define MAX_CODES 10 // Maximum number of notification codes that can be stored
// Global Variables
Preferences preferences; // Object to store and load settings from NVS (Non-Volatile Storage)
WebServer server; // WebServer initialization without specific port here
int web_port = 80; // Default web server port
// WiFi Client Settings (Default)
String client_ssid = "Dara@home";
String client_pass = "rejeki88";
// New global variables for static IP configuration
String client_ip = ""; // Static IP address
String client_gateway = ""; // Static Gateway
String client_subnet = ""; // Static Subnet Mask
String client_dns1 = ""; // Optional: Primary DNS Server
String client_dns2 = ""; // Optional: Secondary DNS Server
// Access Point Settings (Fallback) - Used if client WiFi connection fails
const char* ap_ssid = "PUTU@TECKNO-SIM800L";
const char* ap_pass = "rejeki88";
// Array to store allowed notification codes
String allowedCodes[MAX_CODES];
// New global variables for recipient phone numbers
String targetPhoneNumber1 = "";
String targetPhoneNumber2 = "";
String targetPhoneNumber3 = "";
// Variables to store current status
String lastNotification = "Belum ada"; // Displays the last received notification
String ipAddress = "N/A"; // Displays the current ESP32 IP address
String testActionStatus = ""; // Variable to store test action status (SMS/Call)
// Static variable to track the next phone number to call from notification
static int nextCallIndex = 0;
// Variabel baru untuk melacak status panggilan masuk
bool incomingCallDetected = false;
String callerID = "";
// --- SIM800L Configuration ---
// Pins for Hardware Serial 2 (Serial2): RX2 (GPIO16), TX2 (GPIO17)
// Make sure you connect SIM800L TX to RX2 (GPIO16) and SIM800L RX to TX2 (GPIO17)
// You can change these pins if necessary when initializing HardwareSerial,
// but GPIO16 and GPIO17 are default pins for Serial2.
/**
* @brief Mengirim SMS ke semua nomor telepon yang dikonfigurasi atau ke nomor tertentu.
*
* @param message Pesan SMS yang akan dikirim.
* @param recipientOverride (Opsional) Nomor telepon spesifik untuk dikirim SMS.
* Jika kosong, akan dikirim ke nomor target yang dikonfigurasi.
*/
void sendSMS(String message, String recipientOverride = "") {
Serial.println("Mengirim SMS: " + message);
// Clear any previous responses in SIM800L buffer
while(sim800l.available()) sim800l.read();
String phoneNumbersToSend[3]; // Array untuk menyimpan nomor yang akan dikirimi SMS
int numRecipients = 0;
if (recipientOverride != "") {
phoneNumbersToSend[0] = recipientOverride;
numRecipients = 1;
Serial.println("Mengirim ke penerima spesifik: " + recipientOverride);
} else {
// Perilaku asli: kirim ke nomor target yang dikonfigurasi
phoneNumbersToSend[0] = targetPhoneNumber1;
phoneNumbersToSend[1] = targetPhoneNumber2;
phoneNumbersToSend[2] = targetPhoneNumber3;
numRecipients = 3;
Serial.println("Mengirim ke nomor-nomor target yang dikonfigurasi.");
}
bool smsSent = false; // Flag untuk melacak apakah setidaknya satu SMS berhasil dikirim
for (int i = 0; i < numRecipients; i++) {
String currentPhoneNumber = phoneNumbersToSend[i];
if (currentPhoneNumber != "") { // Hanya kirim jika nomor tidak kosong
Serial.println("Mengirim ke: " + currentPhoneNumber);
sim800l.println("AT+CMGF=1"); // Atur mode teks
delay(1000); // Beri waktu modul untuk merespons
// Clear buffer again before next command
while(sim800l.available()) sim800l.read();
sim800l.println("AT+CMGS=\"" + currentPhoneNumber + "\""); // Gunakan nomor telepon saat ini
delay(1000); // Beri waktu modul untuk merespons dengan '>'
// Clear buffer again before next command
while(sim800l.available()) sim800l.read();
sim800l.print(message); // Isi pesan
delay(100);
sim800l.write(26); // Karakter CTRL+Z (ASCII 26) untuk mengirim SMS
delay(5000); // Beri waktu modul untuk mengirim SMS dan merespons
String simResponse = "";
// Baca respons dari modul SIM800L dengan timeout
unsigned long responseStartTime = millis();
while (millis() - responseStartTime < 5000 && sim800l.available()) { // Tunggu hingga 5 detik untuk respons
simResponse += (char)sim800l.read();
yield();
}
Serial.print("Respons SIM800L: " + simResponse);
if (simResponse.indexOf("OK") != -1 && simResponse.indexOf("+CMGS:") != -1) {
Serial.println("\nSMS terkirim ke " + currentPhoneNumber);
smsSent = true;
// Jika berhasil dikirim ke penerima override spesifik, kita bisa berhenti
if (recipientOverride != "") break;
} else {
Serial.println("\nGagal mengirim SMS ke " + currentPhoneNumber);
}
}
}
// Perbarui status aksi tes berdasarkan keberhasilan pengiriman SMS
if (smsSent) {
testActionStatus = "SMS tes berhasil dikirim!";
} else {
testActionStatus = "SMS tes gagal dikirim atau tidak ada nomor terdaftar.";
}
}
/**
* @brief Melakukan panggilan ke nomor telepon yang dikonfigurasi.
*
* @param phoneNumber Nomor telepon tujuan panggilan.
*/
void makeCall(String phoneNumber) {
if (phoneNumber == "") {
Serial.println("Tidak ada nomor telepon untuk melakukan panggilan tes.");
testActionStatus = "Gagal: Tidak ada nomor telepon tes terdaftar.";
return;
}
// Clear any previous responses in SIM800L buffer
while(sim800l.available()) sim800l.read();
Serial.println("Melakukan panggilan ke: " + phoneNumber);
sim800l.println("ATD" + phoneNumber + ";"); // Perintah AT untuk melakukan panggilan
delay(1000); // Small delay after sending command
String simResponse = "";
// Baca respons dari modul SIM800L dengan timeout
unsigned long responseStartTime = millis();
while (millis() - responseStartTime < 10000 && sim800l.available()) { // Tunggu hingga 10 detik untuk respons
simResponse += (char)sim800l.read();
yield();
}
Serial.print("Respons SIM800L: " + simResponse);
if (simResponse.indexOf("OK") != -1) {
Serial.println("\nPanggilan tes berhasil dimulai ke " + phoneNumber);
testActionStatus = "Panggilan tes berhasil dimulai ke " + phoneNumber + ".";
} else {
Serial.println("\nGagal memulai panggilan tes ke " + phoneNumber);
testActionStatus = "Panggilan tes gagal dimulai ke " + phoneNumber + ".";
}
// Anda mungkin ingin menambahkan perintah ATH untuk mengakhiri panggilan setelah beberapa waktu
delay(15000); // Biarkan panggilan berdering sebentar
sim800l.println("ATH"); // Akhiri panggilan
delay(1000);
// Clear buffer after ATH
while(sim800l.available()) sim800l.read();
}
/**
* @brief Normalisasi format nomor telepon untuk perbandingan.
* Menghapus spasi dan mengubah format '08...' menjadi '628...' jika memungkinkan.
* @param phone Nomor telepon mentah.
* @return Nomor telepon yang dinormalisasi.
*/
String normalizePhoneNumber(String phone) {
phone.trim();
phone.replace(" ", ""); // Hapus spasi
if (phone.startsWith("+")) {
phone = phone.substring(1); // Hapus '+' di awal
}
// Jika dimulai dengan '0' dan panjangnya lebih dari 1, ubah menjadi '62'
if (phone.startsWith("0") && phone.length() > 1) {
phone = "62" + phone.substring(1);
}
return phone;
}
/**
* @brief Memeriksa SMS masuk dan peristiwa panggilan dari modul SIM800L.
* Fungsi ini sekarang membaca seluruh buffer yang tersedia untuk menangani respons multi-baris.
*/
void checkIncomingSMS() { // Function name is kept for consistency, now handles more than just SMS
// Read all available data from SIM800L serial buffer
// Use readString() with a timeout (default is 1000ms or 1 second for HardwareSerial)
// This will capture a burst of data, like RING and CLIP, as one String.
String fullSimResponse = sim800l.readString();
// Only proceed if there's any data
if (fullSimResponse.length() > 0) {
Serial.print("SIM800L Raw Full: " + fullSimResponse + "\n"); // Debug full raw response
// --- Logic for Incoming SMS ---
// Check for +CMT: followed by actual message content
int cmtIndex = fullSimResponse.indexOf("+CMT:");
if (cmtIndex != -1) {
Serial.println("Notifikasi SMS masuk terdeteksi.");
// Find the end of the +CMT: line
int cmtLineEnd = fullSimResponse.indexOf('\n', cmtIndex);
if (cmtLineEnd == -1) cmtLineEnd = fullSimResponse.length();
String cmtLine = fullSimResponse.substring(cmtIndex, cmtLineEnd);
cmtLine.trim();
Serial.println("CMT Line: " + cmtLine);
// Parse sender phone number from the +CMT: line
int firstQuote = cmtLine.indexOf("\"");
int secondQuote = cmtLine.indexOf("\"", firstQuote + 1);
String senderPhoneNumberRaw = "";
if (firstQuote != -1 && secondQuote != -1) {
senderPhoneNumberRaw = cmtLine.substring(firstQuote + 1, secondQuote);
}
String normalizedSender = normalizePhoneNumber(senderPhoneNumberRaw);
Serial.println("Pengirim (Dinormalisasi): " + normalizedSender);
// Extract message content, which is usually the line immediately following +CMT:
String messageContent = "";
int messageStart = cmtLineEnd + 1; // Start after the newline of the CMT line
// Find the next newline to get the message content line
int messageEnd = fullSimResponse.indexOf('\n', messageStart);
if (messageEnd == -1) messageEnd = fullSimResponse.length(); // Take till end if no newline
if (messageStart < fullSimResponse.length()) {
messageContent = fullSimResponse.substring(messageStart, messageEnd);
messageContent.trim(); // Trim spaces and newlines
Serial.println("Pesan: " + messageContent);
} else {
Serial.println("Tidak dapat mengekstrak konten pesan.");
}
bool isAllowedSender = false;
String normalizedTarget1 = normalizePhoneNumber(targetPhoneNumber1);
String normalizedTarget2 = normalizePhoneNumber(targetPhoneNumber2);
String normalizedTarget3 = normalizePhoneNumber(targetPhoneNumber3);
// Check if the sender is one of the allowed target numbers
if (normalizedSender == normalizedTarget1 ||
normalizedSender == normalizedTarget2 ||
normalizedSender == normalizedTarget3) {
isAllowedSender = true;
}
// Check if the message content is "ada apa" (case-insensitive)
if (isAllowedSender && messageContent.equalsIgnoreCase("ada apa")) {
Serial.println("Menerima 'ada apa' dari pengirim yang diizinkan. Membalas dengan notifikasi terakhir.");
String replyMessage = "Belum ada notifikasi terakhir.";
if (lastNotification != "Belum ada") {
replyMessage = "Notifikasi terakhir: " + lastNotification;
}
// Send SMS back to sender
sendSMS(replyMessage, senderPhoneNumberRaw); // <<< PENTING: Kirim kembali ke pengirim SMS
} else {
Serial.println("SMS tidak ditangani (pengirim tidak diizinkan atau pesan bukan 'ada apa').");
}
}
// --- Logic for Incoming Call / Missed Call ---
else if (fullSimResponse.indexOf("RING") != -1) {
Serial.println("Panggilan masuk terdeteksi!");
incomingCallDetected = true; // Set flag that a call is ringing
// Look for CLIP information (caller ID) within the full response
int clipIndex = fullSimResponse.indexOf("+CLIP:");
if (clipIndex != -1) {
int firstQuote = fullSimResponse.indexOf("\"", clipIndex);
int secondQuote = fullSimResponse.indexOf("\"", firstQuote + 1);
if (firstQuote != -1 && secondQuote != -1) {
callerID = fullSimResponse.substring(firstQuote + 1, secondQuote);
Serial.println("Caller ID: " + callerID);
// Normalize caller ID for comparison
callerID = normalizePhoneNumber(callerID);
Serial.println("Caller ID (Normalized): " + callerID);
}
}
}
// Check for NO CARRIER or BUSY after an incoming call was detected
else if (incomingCallDetected && (fullSimResponse.indexOf("NO CARRIER") != -1 || fullSimResponse.indexOf("BUSY") != -1)) {
Serial.println("Panggilan tak terjawab terdeteksi.");
incomingCallDetected = false; // Reset flag
// Check if the missed call was from an allowed number
String normalizedTarget1 = normalizePhoneNumber(targetPhoneNumber1);
String normalizedTarget2 = normalizePhoneNumber(targetPhoneNumber2);
String normalizedTarget3 = normalizePhoneNumber(targetPhoneNumber3);
if ((callerID != "" && (callerID == normalizedTarget1 || callerID == normalizedTarget2 || callerID == normalizedTarget3))) {
Serial.println("Missed call dari nomor yang diizinkan. Mengirim notifikasi terakhir.");
String replyMessage = "Belum ada notifikasi terakhir.";
if (lastNotification != "Belum ada") {
replyMessage = "Notifikasi terakhir: " + lastNotification;
}
sendSMS(replyMessage, callerID); // <<< PENTING: Kirim SMS kembali ke nomor penelepon
callerID = ""; // Clear caller ID after use
} else {
Serial.println("Missed call dari nomor tidak dikenal atau tidak diizinkan.");
}
}
else {
// Print other AT command responses or unsolicited messages
Serial.println("Respons SIM800L Lainnya: " + fullSimResponse);
}
}
}
// --- Web Server Functions ---
/**
* @brief Handles requests to the root path ("/").
* Builds and sends an HTML page for device configuration.
*/
void handleRoot() {
// Build HTML page for configuration
String html = "<!DOCTYPE html><html><head><meta charset='UTF-8' name='viewport' content='width=device-width, initial-scale=1.0'>"
"<title>ESP32 Receiver Config</title>"
"<style>"
"body{font-family:Arial,sans-serif;background:#f2f2f2;margin:0;padding:15px;}"
".container{max-width:800px;margin:auto;background:white;padding:20px;border-radius:10px;box-shadow:0 2px 10px rgba(0,0,0,0.1);}"
"h1,h2{color:#333;}"
"form label{display:block;margin-top:10px;font-weight:bold;}"
"form input[type='text'],form input[type='password']{width:calc(100% - 20px);padding:8px;margin-top:5px;border:1px solid #ccc;border-radius:5px;}"
"form input[type='submit'], .test-button{background-color:#007bff;color:white;border:none;padding:12px 20px;border-radius:5px;cursor:pointer;font-size:16px;margin-top:20px;display:inline-block;margin-right:10px;}"
".test-button.call{background-color:#28a745;}"
".test-button.sms{background-color:#ffc107; color:black;}"
".status{background:#e9f5ff;border-left:5px solid #007bff;padding:10px;margin-top:20px;}"
".grid{display:grid;grid-template-columns:repeat(auto-fit, minmax(250px, 1fr));grid-gap:10px;}"
".message {background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; padding: 10px; border-radius: 5px; margin-bottom: 15px;}"
".error-message {background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; padding: 10px; border-radius: 5px; margin-bottom: 15px;}"
"</style></head><body>"
"<div class='container'>"
"<h1>Konfigurasi Penerima Notifikasi</h1>";
// Display test action status (SMS/Call) if any
if (testActionStatus != "") {
if (testActionStatus.startsWith("Gagal")) {
html += "<div class='error-message'>" + testActionStatus + "</div>";
} else {
html += "<div class='message'>" + testActionStatus + "</div>";
}
testActionStatus = ""; // Clear status after display to prevent repeated display
}
html += "<div class='status'>"
"<h2>Status Saat Ini</h2>"
"<strong>Status WiFi:</strong> " + (WiFi.status() == WL_CONNECTED ? "Terhubung ke " + client_ssid : "Mode Access Point Aktif") + "<br>"
"<strong>Alamat IP:</strong> " + ipAddress + "<br>"
"<strong>Port Web:</strong> " + String(web_port) + "<br>" // Display current web port
"<strong>Notifikasi Terakhir:</strong> " + lastNotification +
"</div>"
"<form action='/save' method='post'>"
"<h2>Pengaturan WiFi Client</h2>"
"<label for='ssid'>Nama WiFi (SSID):</label>"
"<input type='text' name='ssid' value='" + client_ssid + "'>"
"<label for='password'>Password WiFi:</label>"
"<input type='password' name='password' value='" + client_pass + "'>"
"<h2>Pengaturan Port Web</h2>" // New section for web port
"<label for='port'>Port Web (misalnya 80, 8080):</label>"
"<input type='text' name='port' value='" + String(web_port) + "'>"
"<h2>Pengaturan IP Statis (Kosongkan untuk DHCP)</h2>" // Section for static IP
"<label for='ip'>Alamat IP:</label>"
"<input type='text' name='ip' value='" + client_ip + "'>"
"<label for='gateway'>Gateway:</label>"
"<input type='text' name='gateway' value='" + client_gateway + "'>"
"<label for='subnet'>Subnet Mask:</label>"
"<input type='text' name='subnet' value='" + client_subnet + "'>"
"<label for='dns1'>DNS Primer (Opsional):</label>"
"<input type='text' name='dns1' value='" + client_dns1 + "'>"
"<label for='dns2'>DNS Sekunder (Opsional):</label>"
"<input type='text' name='dns2' value='" + client_dns2 + "'>"
"<h2>Nomor Telepon Penerima Notifikasi</h2>" // Section for phone numbers
"<label for='phone1'>Nomor HP 1:</label>"
"<input type='text' name='phone1' value='" + targetPhoneNumber1 + "'>"
"<label for='phone2'>Nomor HP 2:</label>"
"<input type='text' name='phone2' value='" + targetPhoneNumber2 + "'>"
"<label for='phone3'>Nomor HP 3:</label>"
"<input type='text' name='phone3' value='" + targetPhoneNumber3 + "'>"
"<h2>Kode Notifikasi yang Diizinkan (10 Kode)</h2>"
"<div class='grid'>";
// Add input fields for each allowed code
for (int i = 0; i < MAX_CODES; i++) {
html += "<div><label for='code" + String(i) + "'>Kode #" + String(i + 1) + "</label>"
"<input type='text' name='code" + String(i) + "' value='" + allowedCodes[i] + "'></div>";
}
html += "</div>"
"<input type='submit' value='Simpan Pengaturan & Restart'>"
"</form>"
"<h2>Tes SIM800L</h2>"
"<a href='/testCall' class='test-button call'>Tes Panggilan ke HP1</a>"
"<a href='/testSms' class='test-button sms'>Tes SMS ke HP1</a>"
"</div></body></html>";
// Send HTML response to client
server.send(200, "text/html", html);
}
/**
* @brief Handles requests to save configuration sent via POST form.
* Saves settings to NVS and restarts ESP32.
*/
void handleSave() {
// Start preferences in read-write mode
preferences.begin("config", false);
// Save WiFi credentials
client_ssid = server.arg("ssid");
client_pass = server.arg("password");
preferences.putString("ssid", client_ssid);
preferences.putString("password", client_pass);
// Save web port settings
web_port = server.arg("port").toInt();
if (web_port == 0) web_port = 80; // Ensure valid port, default to 80 if 0 or invalid
preferences.putInt("web_port", web_port);
// Save static IP settings
client_ip = server.arg("ip");
client_gateway = server.arg("gateway");
client_subnet = server.arg("subnet");
client_dns1 = server.arg("dns1");
client_dns2 = server.arg("dns2");
preferences.putString("ip", client_ip);
preferences.putString("gateway", client_gateway);
preferences.putString("subnet", client_subnet);
preferences.putString("dns1", client_dns1);
preferences.putString("dns2", client_dns2);
// Save recipient phone numbers
targetPhoneNumber1 = server.arg("phone1");
targetPhoneNumber2 = server.arg("phone2");
targetPhoneNumber3 = server.arg("phone3");
preferences.putString("phone1", targetPhoneNumber1);
preferences.putString("phone2", targetPhoneNumber2);
preferences.putString("phone3", targetPhoneNumber3);
// Save 10 notification codes
for (int i = 0; i < MAX_CODES; i++) {
allowedCodes[i] = server.arg("code" + String(i));
// Use .c_str() to convert String to const char* for preferences.putString
preferences.putString(("code" + String(i)).c_str(), allowedCodes[i]);
}
// End preferences, committing changes to NVS
preferences.end();
// HTML response after saving
String html = "<!DOCTYPE html><html><head><title>Saved</title><style>"
"body{font-family:Arial,sans-serif;text-align:center;padding-top:50px;}"
"h1{color:#4CAF50;}"
"</style></head><body>"
"<h1>Pengaturan Disimpan!</h1>"
"<p>Perangkat akan restart dalam 2 detik...</p>"
"</body></html>";
server.send(200, "text/html", html);
delay(2000); // Wait 2 seconds before restarting
ESP.restart(); // Restart ESP32 to apply new settings
}
/**
* @brief Handles incoming notification requests to the "/notif" path.
* Checks the received code and triggers actions (alternating calls) if valid.
*/
void handleNotif() {
// Check if the 'src' argument exists in the GET request
if (server.hasArg("src")) {
String receivedCode = server.arg("src"); // Get code from 'src' parameter
Serial.println("Notifikasi diterima: " + receivedCode);
lastNotification = receivedCode; // Update last notification status in UI
bool isValid = false;
// Check if the received code is in the allowed list
for (int i = 0; i < MAX_CODES; i++) {
// Ensure allowed code is not empty before comparing
if (allowedCodes[i] != "" && receivedCode == allowedCodes[i]) {
isValid = true;
break; // Code found, no need to check further
}
}
if (isValid) {
Serial.println("Kode valid. Memicu aksi panggilan bergantian...");
server.send(200, "text/plain", "OK. Notifikasi diterima.");
// --- PLACE YOUR ACTIONS HERE ---
// Replace sendSMS with makeCall to all numbers alternately
String phoneNumbers[3] = {targetPhoneNumber1, targetPhoneNumber2, targetPhoneNumber3};
// Find the next valid phone number to call alternately
bool numberFound = false;
int initialIndex = nextCallIndex; // Save initial index for loop detection
// Loop to find the next valid number, skipping empty ones
for (int i = 0; i < 3; ++i) { // Try a maximum of 3 times to ensure we pass through all slots
if (phoneNumbers[nextCallIndex] != "") {
makeCall(phoneNumbers[nextCallIndex]);
numberFound = true;
nextCallIndex = (nextCallIndex + 1) % 3; // Advance to the next number
break; // Number found and called, exit loop
}
nextCallIndex = (nextCallIndex + 1) % 3; // If current number is empty, advance
if (nextCallIndex == initialIndex) { // If returned to the start and haven't found a number
break; // Prevent infinite loop if all numbers are empty
}
}
if (!numberFound) {
testActionStatus = "Panggilan notifikasi gagal: Tidak ada nomor telepon terdaftar yang valid.";
Serial.println("Panggilan notifikasi gagal: Tidak ada nomor telepon terdaftar yang valid.");
} else {
// makeCall already updates testActionStatus, so no need here
Serial.println("Panggilan notifikasi berhasil dipicu.");
}
// ------------------------------------
} else {
Serial.println("Kode tidak valid.");
server.send(401, "text/plain", "Tidak Sah. Kode tidak ada dalam daftar yang diizinkan.");
}
} else {
// If 'src' parameter is missing
server.send(400, "text/plain", "Permintaan Buruk. Parameter 'src' hilang.");
}
}
/**
* @brief Handles test call requests to the first HP number.
* Triggers the makeCall function and redirects back to the main page.
*/
void handleTestCall() {
Serial.println("Permintaan tes panggilan diterima.");
makeCall(targetPhoneNumber1); // Make call to the first HP number
// After action, redirect back to the root page to update status
server.sendHeader("Location", "/");
server.send(302, "text/plain", "Redirecting to home...");
}
/**
* @brief Handles SMS test requests.
* Triggers the sendSMS function with a test message and redirects back to the main page.
*/
void handleTestSms() {
Serial.println("Permintaan tes SMS diterima.");
sendSMS("Tes SMS dari ESP32!"); // Send test SMS
// After action, redirect back to the root page to update status
server.sendHeader("Location", "/");
server.send(302, "text/plain", "Redirecting to home...");
}
// --- Setup and Loop Functions ---
/**
* @brief Loads saved settings from NVS (Non-Volatile Storage).
*/
void loadSettings() {
preferences.begin("config", true); // Open NVS in read-only mode
client_ssid = preferences.getString("ssid", "Dara@home"); // Load SSID, with default value
client_pass = preferences.getString("password", "rejeki88"); // Load password, with default value
web_port = preferences.getInt("web_port", 80); // Load web port, default to 80
// Load static IP settings
client_ip = preferences.getString("ip", "");
client_gateway = preferences.getString("gateway", "");
client_subnet = preferences.getString("subnet", "");
client_dns1 = preferences.getString("dns1", "");
client_dns2 = preferences.getString("dns2", "");
// Load recipient phone numbers
targetPhoneNumber1 = preferences.getString("phone1", "");
targetPhoneNumber2 = preferences.getString("phone2", "");
targetPhoneNumber3 = preferences.getString("phone3", "");
// Load 10 notification codes, with default messages for the first few
for (int i = 0; i < MAX_CODES; i++) {
String default_msg = "";
if (i == 0) default_msg = "UPS1 MATI";
if (i == 1) default_msg = "UPS2 MATI";
if (i == 2) default_msg = "PLN MATI";
if (i == 3) default_msg = "GENSET MATI";
allowedCodes[i] = preferences.getString(("code" + String(i)).c_str(), default_msg);
}
preferences.end(); // Close preferences
}
/**
* @brief Sets up WiFi connection, trying client mode first,
* then switching to Access Point if client connection fails.
*/
void setupWiFi() {
WiFi.mode(WIFI_STA); // Set WiFi to Station mode (client)
// Check if static IP settings are provided
if (client_ip != "" && client_gateway != "" && client_subnet != "") {
IPAddress ip, gateway, subnet, dns1, dns2;
// Try to convert IP strings to IPAddress objects
if (ip.fromString(client_ip) && gateway.fromString(client_gateway) && subnet.fromString(client_subnet)) {
if (client_dns1 != "" && dns1.fromString(client_dns1)) {
if (client_dns2 != "" && dns2.fromString(client_dns2)) {
WiFi.config(ip, gateway, subnet, dns1, dns2); // Use static IP with primary and secondary DNS
} else {
WiFi.config(ip, gateway, subnet, dns1); // Use static IP with only primary DNS
}
} else {
WiFi.config(ip, gateway, subnet); // Use only IP, Gateway, Subnet
}
Serial.println("Menggunakan IP Statis.");
} else {
Serial.println("IP Statis tidak valid, menggunakan DHCP.");
// Clear invalid static IP settings to fall back to DHCP
client_ip = "";
client_gateway = "";
client_subnet = "";
client_dns1 = "";
client_dns2 = "";
}
} else {
Serial.println("IP Statis tidak dikonfigurasi, menggunakan DHCP.");
}
WiFi.begin(client_ssid.c_str(), client_pass.c_str()); // Try connecting to saved WiFi
Serial.print("Menyambungkan ke " + client_ssid);
int attempts = 0;
// Wait for connection or timeout (10 seconds = 20 * 500ms)
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
delay(500); // Wait 0.5 seconds
Serial.print(".");
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\nTersambung!");
ipAddress = WiFi.localIP().toString(); // Get assigned IP address
Serial.println("Alamat IP: " + ipAddress);
} else {
Serial.println("\nKoneksi Gagal. Mengaktifkan Mode Access Point.");
WiFi.mode(WIFI_AP); // Switch to Access Point mode
WiFi.softAP(ap_ssid, ap_pass); // Start Access Point
ipAddress = WiFi.softAPIP().toString(); // Get AP IP address
Serial.println("SSID AP: " + String(ap_ssid));
Serial.println("Alamat IP AP: " + ipAddress);
}
}
/**
* @brief Fungsi untuk mendeteksi baud rate yang benar untuk SIM800L.
* Mencoba baud rate umum hingga 'OK' diterima dari modul SIM800L.
* @return Baud rate yang terdeteksi, atau 0 jika tidak ditemukan.
*/
long detectSim800lBaudRate() {
long baudRates[] = {9600, 19200, 38400, 57600, 115200}; // Baud rate umum
String simResponse;
Serial.println("Mendeteksi baud rate SIM800L...");
for (long baud : baudRates) {
sim800l.begin(baud, SERIAL_8N1, 16, 17);
delay(200); // Penundaan yang ditingkatkan untuk memungkinkan SIM800L menstabilkan serial
// Bersihkan data sisa di buffer sebelum mengirim AT
while(sim800l.available()) {
sim800l.read();
}
sim800l.println("AT"); // Kirim perintah AT
// Baca respons dengan timeout yang lebih lama
unsigned long responseStartTime = millis();
simResponse = "";
while (millis() - responseStartTime < 2000) { // Tunggu hingga 2 detik untuk respons
if (sim800l.available()) {
simResponse += (char)sim800l.read();
}
yield();
}
simResponse.trim();
Serial.print("Mencoba baud rate " + String(baud) + ": ");
Serial.println(simResponse);
if (simResponse.indexOf("OK") != -1) {
Serial.println("Baud rate terdeteksi: " + String(baud));
return baud; // Baud rate yang benar ditemukan
}
}
Serial.println("Tidak dapat mendeteksi baud rate SIM800L. Tetap menggunakan 9600.");
return 9600; // Default ke 9600 jika tidak ada yang terdeteksi
}
/**
* @brief Fungsi setup Arduino (berjalan sekali saat power-on atau reset).
* Menginisialisasi serial, SIM800L, memuat pengaturan, mengatur WiFi,
* dan memulai server web.
*/
void setup() {
Serial.begin(115200); // Inisialisasi komunikasi serial untuk debugging
Serial.println("\nMemulai ESP32 Receiver...");
// Detect SIM800L baud rate dynamically
long detectedBaudRate = detectSim800lBaudRate();
// Initialize SIM800L with the detected baud rate
sim800l.begin(detectedBaudRate, SERIAL_8N1, 16, 17);
Serial.println("SIM800L (Hardware Serial) diinisialisasi pada baud rate: " + String(detectedBaudRate));
// Clear buffer before sending initial commands
while(sim800l.available()) sim800l.read();
// Ensure SIM800L module is set to text mode for SMS and activate Caller ID
sim800l.println("AT+CMGF=1");
delay(1000); // Give the module time to respond
while(sim800l.available()) sim800l.read(); // Clear response
sim800l.println("AT+CLIP=1"); // Enable Caller ID presentation
delay(1000);
while(sim800l.available()) sim800l.read(); // Clear response
loadSettings(); // Load saved settings from NVS
setupWiFi(); // WiFi connection setup (client or AP)
// Register web server handlers for different URLs
server.on("/", HTTP_GET, handleRoot); // Root path for configuration page
server.on("/save", HTTP_POST, handleSave); // Save configuration from form
server.on("/notif", HTTP_GET, handleNotif); // Notification endpoint to receive codes
server.on("/testCall", HTTP_GET, handleTestCall); // Handler for SIM800L call test
server.on("/testSms", HTTP_GET, handleTestSms); // Handler for SIM800L SMS test
server.begin(web_port); // Start web server on the configured port
Serial.println("Server Web dimulai pada port: " + String(web_port));
}
/**
* @brief Fungsi loop Arduino (berjalan berulang kali).
* Terus-menerus menangani permintaan web yang masuk dan memeriksa peristiwa SMS dan panggilan.
*/
void loop() {
server.handleClient(); // Must be called continuously to handle incoming web requests
checkIncomingSMS(); // This function now also handles incoming call detection
}
No comments:
Post a Comment