What's wrong with the program that the SIM card is not detected in the SIM800L?
=== MODEM STATUS UPDATE ===
Checking modem parameters...
Current Modem Status:
- Present SIM: No
- Signal Strength: 0
- Network Mode: Unknown
- APN Status:
- Network Registered: No
========================
=== ERROR CONDITION ===
✗ SIM card not detected!
Skipping AC line status check
=====================
=== SIM STATUS CHECK ===
Previous state: Not Present
Current state: Not Present
========================
#include <ESP8266WebServer.h>
#include <LittleFS.h>
#include <ArduinoJson.h>
#include <SoftwareSerial.h>
ESP8266WebServer server(80);
#define OCTOCOUPLER_PIN 2
#define SIM800L_RX 3
#define SIM800L_TX 4
SoftwareSerial sim800l(SIM800L_RX, SIM800L_TX);
String indexHtml;
bool isBusy = false;
struct Config {
String phoneNumber;
String networkMode; // New field for network mode
String apn;
String apnUsername;
String apnPassword;
String smsConnected;
String smsDisconnected;
bool smsConnectedEnabled;
bool smsDisconnectedEnabled;
} config;
bool currentACState = false;
bool previousACState = false;
bool hasNotifiedLow = false;
bool hasNotifiedHigh = false;
bool isSimPresent = false;
bool previousSimState = false;
int signalStrength = 0;
unsigned long lastSimCheckTime = 0;
unsigned long lastModemUpdate = 0;
const unsigned long SIM_CHECK_INTERVAL = 5000;
const unsigned long MODEM_UPDATE_INTERVAL = 30000;
struct ModemStatus {
bool simPresent;
int signalStrength;
String networkMode;
String apnStatus;
bool isRegistered;
} modemStatus;
void setupWiFi() {
Serial.println("\n=== WIFI SETUP ===");
Serial.println("Setting up WiFi Access Point...");
WiFi.mode(WIFI_AP);
String ssid = "ESP-Config-" + String(ESP.getChipId(), HEX);
Serial.printf("Attempting to create AP with SSID: %s\n", ssid.c_str());
if (WiFi.softAP(ssid.c_str(), "password123")) {
Serial.println("✓ AP Created successfully");
Serial.printf("SSID: %s\n", ssid.c_str());
Serial.println("Password: password123");
Serial.printf("IP Address: %s\n", WiFi.softAPIP().toString().c_str());
Serial.printf("MAC Address: %s\n", WiFi.softAPmacAddress().c_str());
Serial.printf("Channel: %d\n", WiFi.channel());
Serial.printf("Current AP Connections: %d\n", WiFi.softAPgetStationNum());
} else {
Serial.println("✗ AP Creation failed!");
Serial.println("Possible reasons:");
Serial.println("- Invalid SSID length");
Serial.println("- Insufficient memory");
Serial.println("- Hardware initialization failure");
}
Serial.println("=================\n");
}
void setupWebServer() {
Serial.println("\n=== WEB SERVER SETUP ===");
if (!LittleFS.begin()) {
Serial.println("✗ Failed to mount LittleFS");
Serial.println("Possible reasons:");
Serial.println("- File system not formatted");
Serial.println("- Bad flash connection");
Serial.println("- Incorrect partition scheme");
return;
}
Serial.println("✓ LittleFS mounted successfully");
Serial.println("Loading index.html from LittleFS...");
File file = LittleFS.open("/index.html", "r");
if (!file) {
Serial.println("✗ Failed to open index.html");
Serial.println("Ensure index.html is uploaded to LittleFS");
return;
}
indexHtml = file.readString();
file.close();
Serial.printf("✓ Loaded index.html (size: %d bytes)\n", indexHtml.length());
server.on("/", HTTP_GET, handleRoot);
server.on("/config", HTTP_GET, handleGetConfig);
server.on("/config", HTTP_POST, handleSetConfig);
server.on("/modem-status", HTTP_GET, handleModemStatus);
server.begin();
Serial.println("✓ HTTP server started on port 80");
Serial.println("========================\n");
}
bool loadConfiguration() {
if (!LittleFS.begin()) {
Serial.println("Failed to mount LittleFS, formatting...");
LittleFS.format();
if (!LittleFS.begin()) {
Serial.println("LittleFS still failed after formatting");
return false;
}
}
File configFile = LittleFS.open("/config.json", "r");
if (!configFile) {
Serial.println("Config file not found, creating default config...");
File newFile = LittleFS.open("/config.json", "w");
if (newFile) {
StaticJsonDocument<512> doc;
doc["phoneNumber"] = "";
doc["networkMode"] = "auto";
doc["apn"] = "internet";
doc["apnUsername"] = "";
doc["apnPassword"] = "";
doc["smsConnected"] = "AC Line Connected";
doc["smsDisconnected"] = "AC Line Disconnected";
doc["smsConnectedEnabled"] = true;
doc["smsDisconnectedEnabled"] = true;
serializeJson(doc, newFile);
newFile.close();
}
return false;
}
StaticJsonDocument<512> doc;
DeserializationError error = deserializeJson(doc, configFile);
configFile.close();
if (error) {
Serial.println("Failed to parse config file");
return false;
}
config.phoneNumber = doc["phoneNumber"].as<String>();
config.networkMode = doc["networkMode"].as<String>();
config.apn = doc["apn"].as<String>();
config.apnUsername = doc["apnUsername"].as<String>();
config.apnPassword = doc["apnPassword"].as<String>();
config.smsConnected = doc["smsConnected"].as<String>();
config.smsDisconnected = doc["smsDisconnected"].as<String>();
config.smsConnectedEnabled = doc["smsConnectedEnabled"].as<bool>();
config.smsDisconnectedEnabled = doc["smsDisconnectedEnabled"].as<bool>();
return true;
}
bool saveConfiguration() {
StaticJsonDocument<512> doc;
doc["phoneNumber"] = config.phoneNumber;
doc["networkMode"] = config.networkMode; // Add network mode
doc["apn"] = config.apn;
doc["apnUsername"] = config.apnUsername;
doc["apnPassword"] = config.apnPassword;
doc["smsConnected"] = config.smsConnected;
doc["smsDisconnected"] = config.smsDisconnected;
doc["smsConnectedEnabled"] = config.smsConnectedEnabled;
doc["smsDisconnectedEnabled"] = config.smsDisconnectedEnabled;
File configFile = LittleFS.open("/config.json", "w");
if (!configFile) {
Serial.println("Failed to open config file for writing");
return false;
}
serializeJson(doc, configFile);
configFile.close();
return true;
}
void handleRoot() {
Serial.println("\n=== HANDLING ROOT REQUEST ===");
Serial.printf("Client IP: %s\n", server.client().remoteIP().toString().c_str());
Serial.printf("User Agent: %s\n", server.header("User-Agent").c_str());
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
server.sendHeader("Pragma", "no-cache");
server.sendHeader("Expires", "-1");
server.send(200, "text/html", indexHtml);
Serial.println("✓ Root page served successfully");
Serial.println("========================\n");
}
void handleGetConfig() {
Serial.println("\n=== HANDLING CONFIG GET REQUEST ===");
Serial.printf("Client IP: %s\n", server.client().remoteIP().toString().c_str());
StaticJsonDocument<512> doc;
doc["phoneNumber"] = config.phoneNumber;
doc["networkMode"] = config.networkMode; // Menambahkan networkMode
doc["apn"] = config.apn;
doc["apnUsername"] = config.apnUsername;
doc["apnPassword"] = config.apnPassword;
doc["smsConnected"] = config.smsConnected;
doc["smsDisconnected"] = config.smsDisconnected;
doc["smsConnectedEnabled"] = config.smsConnectedEnabled; // Menambahkan smsConnectedEnabled
doc["smsDisconnectedEnabled"] = config.smsDisconnectedEnabled; // Menambahkan smsDisconnectedEnabled
String response;
serializeJson(doc, response);
Serial.println("Current Configuration:");
Serial.printf("Phone Number: %s\n", config.phoneNumber.c_str());
Serial.printf("Network Mode: %s\n", config.networkMode.c_str());
Serial.printf("APN: %s\n", config.apn.c_str());
Serial.printf("APN Username: %s\n", config.apnUsername.c_str());
Serial.printf("SMS Connected Message: %s\n", config.smsConnected.c_str());
Serial.printf("SMS Connected Enabled: %s\n", config.smsConnectedEnabled ? "Yes" : "No");
Serial.printf("SMS Disconnected Message: %s\n", config.smsDisconnected.c_str());
Serial.printf("SMS Disconnected Enabled: %s\n", config.smsDisconnectedEnabled ? "Yes" : "No");
server.send(200, "application/json", response);
Serial.println("✓ Configuration sent successfully");
Serial.println("========================\n");
}
void handleSetConfig() {
Serial.println("\n=== HANDLING CONFIG POST REQUEST ===");
Serial.printf("Client IP: %s\n", server.client().remoteIP().toString().c_str());
if (!server.hasArg("plain")) {
Serial.println("✗ No data received");
server.send(400, "text/plain", "No data received");
return;
}
String data = server.arg("plain");
Serial.printf("Received data: %s\n", data.c_str());
StaticJsonDocument<512> doc;
DeserializationError error = deserializeJson(doc, data);
if (error) {
Serial.printf("✗ JSON parsing failed: %s\n", error.c_str());
server.send(400, "text/plain", "Invalid JSON");
return;
}
Serial.println("Updating configuration...");
// Store old values for logging
String oldNetworkMode = config.networkMode;
bool oldConnectedEnabled = config.smsConnectedEnabled;
bool oldDisconnectedEnabled = config.smsDisconnectedEnabled;
config.phoneNumber = doc["phoneNumber"] | config.phoneNumber;
config.networkMode = doc["networkMode"] | config.networkMode;
config.apn = doc["apn"] | config.apn;
config.apnUsername = doc["apnUsername"] | config.apnUsername;
config.apnPassword = doc["apnPassword"] | config.apnPassword;
config.smsConnected = doc["smsConnected"] | config.smsConnected;
config.smsDisconnected = doc["smsDisconnected"] | config.smsDisconnected;
config.smsConnectedEnabled = doc["smsConnectedEnabled"] | config.smsConnectedEnabled;
config.smsDisconnectedEnabled = doc["smsDisconnectedEnabled"] | config.smsDisconnectedEnabled;
// Log changes in network mode
if (oldNetworkMode != config.networkMode) {
Serial.printf("Network mode changed: %s -> %s\n",
oldNetworkMode.c_str(),
config.networkMode.c_str());
// Reinitialize modem if network mode changed
if (modemStatus.simPresent) {
Serial.println("Reinitializing modem with new network mode...");
initializeSIM800L();
}
}
if (saveConfiguration()) {
Serial.println("✓ Configuration saved successfully");
server.send(200, "text/plain", "Configuration saved");
} else {
Serial.println("✗ Failed to save configuration");
server.send(500, "text/plain", "Failed to save configuration");
}
Serial.println("========================\n");
}
void handleModemStatus() {
StaticJsonDocument<256> doc;
doc["simPresent"] = modemStatus.simPresent;
doc["signalStrength"] = map(modemStatus.signalStrength, 0, 31, 0, 100); // Convert signal strength to percentage
doc["networkMode"] = modemStatus.networkMode;
doc["isRegistered"] = modemStatus.isRegistered;
String response;
serializeJson(doc, response);
server.send(200, "application/json", response);
}
bool sendATCommand(const char* command, const char* expectedResponse, unsigned long timeout) {
sim800l.flush(); // Hapus buffer sebelum kirim command
sim800l.println(command);
unsigned long startTime = millis();
String response = "";
while (millis() - startTime < timeout) {
while (sim800l.available()) {
char c = sim800l.read();
response += c;
}
if (response.indexOf(expectedResponse) >= 0) {
Serial.println("AT Response: " + response);
return true;
}
}
Serial.println("AT Command Failed: " + String(command));
Serial.println("Response: " + response);
return false;
}
bool setNetworkMode(const String& mode) {
String command;
sendATCommand("AT+CFUN=1", "OK", 2000); // Pastikan modem aktif
delay(2000);
if (mode == "2g") {
command = "AT+CNMP=13"; // GSM only
} else if (mode == "3g") {
command = "AT+CNMP=14"; // WCDMA only
} else if (mode == "4g") {
command = "AT+CNMP=38"; // LTE only
} else {
command = "AT+CNMP=2"; // Automatic
}
return sendATCommand(command.c_str(), "OK", 5000);
}
bool configureAPN() {
sendATCommand("AT+SAPBR=0,1", "OK", 5000);
delay(1000);
sendATCommand(("AT+CGDCONT=1,\"IP\",\"" + config.apn + "\"").c_str(), "OK", 5000);
delay(1000);
sendATCommand("AT+SAPBR=3,1,\"Contype\",\"GPRS\"", "OK", 1000);
delay(500);
sendATCommand(("AT+SAPBR=3,1,\"APN\",\"" + config.apn + "\"").c_str(), "OK", 1000);
delay(500);
if (config.apnUsername.length() > 0) {
sendATCommand(("AT+SAPBR=3,1,\"USER\",\"" + config.apnUsername + "\"").c_str(), "OK", 1000);
delay(500);
}
if (config.apnPassword.length() > 0) {
sendATCommand(("AT+SAPBR=3,1,\"PWD\",\"" + config.apnPassword + "\"").c_str(), "OK", 1000);
delay(500);
}
return sendATCommand("AT+SAPBR=1,1", "OK", 10000);
}
bool waitForNetworkRegistration() {
unsigned long startTime = millis();
while (millis() - startTime < 60000) {
if (checkNetworkRegistration()) {
return true;
}
delay(1000);
}
return false;
}
bool initializeSIM800L() {
if (isBusy) return false;
isBusy = true;
delay(1000);
Serial.println("Initializing SIM800L...");
sim800l.println("AT+CFUN=1,1");
delay(10000);
bool success = sendATCommand("AT", "OK", 3000) &&
sendATCommand("AT+CMGF=1", "OK", 1000) &&
checkSimPresent() &&
setNetworkMode(config.networkMode) && // Add network mode configuration
waitForNetworkRegistration() &&
configureAPN();
if (success) {
Serial.println("SIM800L initialized successfully!");
}
isBusy = false;
return success;
}
void updateModemStatus() {
if (isBusy) {
Serial.println("System busy, skipping modem status update");
return;
}
Serial.println("\n=== MODEM STATUS UPDATE ===");
Serial.println("Checking modem parameters...");
isBusy = true;
// Previous states
bool prevSimPresent = modemStatus.simPresent;
int prevSignal = modemStatus.signalStrength;
String prevNetwork = modemStatus.networkMode;
bool prevRegistered = modemStatus.isRegistered;
// Update states
modemStatus.simPresent = checkSimPresent();
modemStatus.signalStrength = getSignalStrength();
modemStatus.networkMode = getNetworkMode();
modemStatus.apnStatus = getAPNStatus();
modemStatus.isRegistered = checkNetworkRegistration();
// Log changes
if (prevSimPresent != modemStatus.simPresent) {
Serial.printf("! SIM Status changed: %s -> %s\n",
prevSimPresent ? "Present" : "Not Present",
modemStatus.simPresent ? "Present" : "Not Present");
}
if (prevSignal != modemStatus.signalStrength) {
Serial.printf("! Signal Strength changed: %d -> %d\n",
prevSignal, modemStatus.signalStrength);
}
if (prevNetwork != modemStatus.networkMode) {
Serial.printf("! Network Mode changed: %s -> %s\n",
prevNetwork.c_str(), modemStatus.networkMode.c_str());
}
if (prevRegistered != modemStatus.isRegistered) {
Serial.printf("! Registration Status changed: %s -> %s\n",
prevRegistered ? "Registered" : "Not Registered",
modemStatus.isRegistered ? "Registered" : "Not Registered");
}
// Current Status Summary
Serial.println("\nCurrent Modem Status:");
Serial.printf("- SIM Present: %s\n", modemStatus.simPresent ? "Yes" : "No");
Serial.printf("- Signal Strength: %d\n", modemStatus.signalStrength);
Serial.printf("- Network Mode: %s\n", modemStatus.networkMode.c_str());
Serial.printf("- APN Status: %s\n", modemStatus.apnStatus.c_str());
Serial.printf("- Network Registered: %s\n", modemStatus.isRegistered ? "Yes" : "No");
isBusy = false;
Serial.println("========================\n");
}
void checkAndHandleSimChange() {
if (isBusy) {
Serial.println("System busy, skipping SIM check");
return;
}
bool currentSimState = checkSimPresent();
Serial.printf("\n=== SIM STATUS CHECK ===\n");
Serial.printf("Previous state: %s\n", previousSimState ? "Present" : "Not Present");
Serial.printf("Current state: %s\n", currentSimState ? "Present" : "Not Present");
if (currentSimState != previousSimState) {
Serial.println("! SIM state changed !");
if (currentSimState) {
Serial.println("✓ SIM card inserted");
if (initializeSIM800L()) {
Serial.println("Sending system restart notification...");
if (sendSMSNotification("System running with new SIM card")) {
Serial.println("✓ Restart notification sent");
} else {
Serial.println("✗ Failed to send restart notification");
}
hasNotifiedLow = false;
hasNotifiedHigh = false;
}
} else {
Serial.println("✗ SIM card removed");
}
previousSimState = currentSimState;
}
Serial.println("========================\n");
}
bool checkSimPresent() {
sim800l.println("AT+CSMINS?");
String response = sim800l.readString();
return response.indexOf("+CSMINS: 0,1") >= 0;
}
int getSignalStrength() {
sim800l.println("AT+CSQ");
String response = sim800l.readString();
if (response.indexOf("+CSQ:") >= 0) {
int start = response.indexOf(" ") + 1;
int end = response.indexOf(",");
return response.substring(start, end).toInt();
}
return 0;
}
String getNetworkMode() {
sim800l.println("AT+COPS?");
String response = sim800l.readString();
if (response.indexOf("+COPS:") >= 0) {
return response.substring(response.indexOf("\"") + 1, response.lastIndexOf("\""));
}
return "Unknown";
}
String getAPNStatus() {
sim800l.println("AT+CGACT?");
return sim800l.readString();
}
bool checkNetworkRegistration() {
sim800l.println("AT+CREG?");
String response = sim800l.readString();
return (response.indexOf("+CREG: 0,1") >= 0) || (response.indexOf("+CREG: 0,5") >= 0);
}
void processACLineStatus() {
if (isBusy) {
Serial.println("System busy, skipping AC line status check");
return;
}
currentACState = digitalRead(OCTOCOUPLER_PIN);
Serial.println("\n=== AC LINE STATUS ===");
Serial.printf("Previous State: %s\n", previousACState ? "HIGH" : "LOW");
Serial.printf("Current State: %s\n", currentACState ? "HIGH" : "LOW");
// Check enabled status before sending notifications
if (currentACState == HIGH && !hasNotifiedHigh && config.smsConnectedEnabled) {
Serial.println("! Detected HIGH state change, sending notification...");
if (sendSMSNotification(config.smsConnected.c_str())) {
hasNotifiedHigh = true;
hasNotifiedLow = false;
Serial.println("✓ HIGH state notification sent");
}
}
else if (currentACState == LOW && !hasNotifiedLow && config.smsDisconnectedEnabled) {
Serial.println("! Detected LOW state change, sending notification...");
if (sendSMSNotification(config.smsDisconnected.c_str())) {
hasNotifiedLow = true;
hasNotifiedHigh = false;
Serial.println("✓ LOW state notification sent");
}
}
previousACState = currentACState;
}
bool sendSMSNotification(const char* message) {
Serial.println("\n=== SENDING SMS NOTIFICATION ===");
if (!modemStatus.simPresent || !modemStatus.isRegistered) {
Serial.println("✗ Cannot send SMS:");
if (!modemStatus.simPresent) Serial.println(" - No SIM card present");
if (!modemStatus.isRegistered) Serial.println(" - Not registered to network");
return false;
}
Serial.printf("Sending SMS to: %s\n", config.phoneNumber.c_str());
Serial.printf("Message: %s\n", message);
sim800l.println("AT+CMGF=1");
delay(1000);
sim800l.print("AT+CMGS=\"");
sim800l.print(config.phoneNumber);
sim800l.println("\"");
delay(1000);
sim800l.println(message);
delay(100);
sim800l.write(26);
delay(1000);
// Check for "OK" response
String response = sim800l.readString();
bool success = response.indexOf("OK") >= 0;
if (success) {
Serial.println("✓ SMS sent successfully");
} else {
Serial.println("✗ Failed to send SMS");
Serial.printf("Response: %s\n", response.c_str());
}
Serial.println("========================\n");
return success;
}
void setup() {
Serial.begin(9600);
Serial.println("\n\n====================================");
Serial.println("Starting ESP8266 Configuration Portal");
Serial.println("====================================");
// System Information
Serial.println("\n=== SYSTEM INITIALIZATION ===");
Serial.printf("Firmware Version: 1.0.0\n");
Serial.printf("Chip ID: %s\n", String(ESP.getChipId(), HEX).c_str());
Serial.printf("Flash Size: %dMB\n", ESP.getFlashChipSize() / 1024 / 1024);
Serial.printf("CPU Frequency: %dMHz\n", ESP.getCpuFreqMHz());
Serial.printf("Free Heap: %d bytes\n", ESP.getFreeHeap());
// Initialize SIM800L Serial
Serial.println("\nInitializing SIM800L serial communication...");
sim800l.begin(9600);
Serial.println("✓ SIM800L serial initialized");
// Initialize GPIO
Serial.println("\nInitializing GPIO pins...");
pinMode(OCTOCOUPLER_PIN, INPUT);
Serial.printf("✓ Pin %d configured as INPUT for optocoupler\n", OCTOCOUPLER_PIN);
Serial.printf("✓ Pin %d configured for SIM800L RX\n", SIM800L_RX);
Serial.printf("✓ Pin %d configured for SIM800L TX\n", SIM800L_TX);
// Initialize File System
Serial.println("\nMounting file system...");
if (LittleFS.begin()) {
FSInfo fs_info;
LittleFS.info(fs_info);
Serial.println("✓ LittleFS mounted successfully");
Serial.printf("Total space: %d bytes\n", fs_info.totalBytes);
Serial.printf("Used space: %d bytes\n", fs_info.usedBytes);
} else {
Serial.println("✗ LittleFS mount failed!");
Serial.println("System will continue but configuration storage will be unavailable");
}
loadConfiguration();
// Setup WiFi
setupWiFi();
// Setup Web Server
setupWebServer();
// Initialize SIM Card
Serial.println("\nChecking initial SIM card state...");
previousSimState = checkSimPresent();
Serial.printf("Initial SIM state: %s\n", previousSimState ? "Present" : "Not Present");
if (previousSimState) {
Serial.println("Performing initial SIM800L setup...");
if (initializeSIM800L()) {
Serial.println("✓ SIM800L initialized successfully");
} else {
Serial.println("✗ SIM800L initialization failed!");
Serial.println("Will retry in main loop");
}
}
// Log initial memory state
Serial.println("\n=== SETUP COMPLETION STATUS ===");
Serial.printf("Free Heap after setup: %d bytes\n", ESP.getFreeHeap());
Serial.printf("Heap Fragmentation: %d%%\n", ESP.getHeapFragmentation());
Serial.println("Setup completed!");
Serial.println("====================================\n");
}
void loop() {
unsigned long currentMillis = millis();
static unsigned long lastHeapLog = 0;
const unsigned long HEAP_LOG_INTERVAL = 300000; // Log heap every 5 minutes
// Handle Web Server Clients
server.handleClient();
// Periodic System Status Logging
if (currentMillis - lastHeapLog >= HEAP_LOG_INTERVAL) {
Serial.println("\n=== PERIODIC SYSTEM STATUS ===");
Serial.printf("Uptime: %lu seconds\n", currentMillis / 1000);
Serial.printf("Free Heap: %d bytes\n", ESP.getFreeHeap());
Serial.printf("Heap Fragmentation: %d%%\n", ESP.getHeapFragmentation());
Serial.printf("WiFi Stations Connected: %d\n", WiFi.softAPgetStationNum());
lastHeapLog = currentMillis;
Serial.println("============================\n");
}
// Check SIM Card Status
if (currentMillis - lastSimCheckTime >= SIM_CHECK_INTERVAL) {
checkAndHandleSimChange();
lastSimCheckTime = currentMillis;
}
// Update Modem Status
if (currentMillis - lastModemUpdate >= MODEM_UPDATE_INTERVAL) {
updateModemStatus();
lastModemUpdate = currentMillis;
}
// Process AC Line Status with Error Checking
if (!modemStatus.simPresent) {
Serial.println("\n=== ERROR CONDITION ===");
Serial.println("✗ SIM card tidak terdeteksi!");
Serial.println("Skipping AC line status check");
Serial.println("=====================\n");
delay(1000);
return;
}
if (modemStatus.signalStrength < 10) {
Serial.println("\n=== WARNING CONDITION ===");
Serial.printf("✗ Sinyal lemah! (Strength: %d)\n", modemStatus.signalStrength);
Serial.println("Continuing with reduced reliability");
Serial.println("=====================\n");
delay(5000);
return;
}
processACLineStatus();
delay(1000);
}