Search This Blog

Friday, 27 June 2025

tidak terima sms notif

 #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

Tandon Air Otomatis (ESP32 Master & Display) + Kode Lengkap

  Panduan Lengkap Tandon Air Otomatis (ESP32 Master & Display) + Kode Lengkap Diperbarui: 09 August 2025 Artikel ini memandu Anda memban...