diff --git a/.vscode/settings.json b/.vscode/settings.json index ec9f4ef..d7f478e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,7 @@ { "idf.pythonInstallPath": "/usr/bin/python3", "files.associations": { - "string": "cpp" + "string": "cpp", + "ostream": "cpp" } } \ No newline at end of file diff --git a/CAR_GPS_TRACKER.code-workspace b/CAR_GPS_TRACKER.code-workspace deleted file mode 100644 index 876a149..0000000 --- a/CAR_GPS_TRACKER.code-workspace +++ /dev/null @@ -1,8 +0,0 @@ -{ - "folders": [ - { - "path": "." - } - ], - "settings": {} -} \ No newline at end of file diff --git a/CAR_GPS_TRACKER.ino b/CAR_GPS_TRACKER.ino index 008ed6c..1026a87 100644 --- a/CAR_GPS_TRACKER.ino +++ b/CAR_GPS_TRACKER.ino @@ -1,275 +1,260 @@ -#include #include -// --- SIM7080G Pin Definitions --- -#define LTE_RX_PIN 16 // ESP32 GPIO16 connected to SIM7080G TX -#define LTE_TX_PIN 17 // ESP32 GPIO17 connected to SIM7080G RX -#define PWRKEY_PIN 4 // ESP32 GPIO4 connected to SIM7080G PWRKEY +// Pins +#define LTE_RX_PIN 16 // ESP32 RX <- SIM7080 TX +#define LTE_TX_PIN 17 // ESP32 TX -> SIM7080 RX +#define PWRKEY_PIN 4 // SIM7080 PWRKEY -// --- Network Configuration --- -const char *HOLOGRAM_APN = "hologram"; +// Network and endpoint +const char* APN = "hologram"; +const char* BASE_URL = "http://laravel-server.lab.audasmedia.com.au"; +const char* PATH = "/api/gps"; -// --- Global Variables --- -String currentIMEI = ""; +// -------- Utilities -------- +String sendAT(const String& cmd, uint32_t timeoutMs) { + Serial.print(">> "); Serial.println(cmd); + while (Serial2.available()) (void)Serial2.read(); + Serial2.println(cmd); -// --- Function Prototypes --- -void powerOnModule(); -void powerOffModule(); -String sendATCommand(String command, unsigned long timeoutMs = 3000); -String getIMEI(); -bool activatePDPContextAndBearer(); -void getGpsLocation(); // Function to get and print GPS data + String resp; + uint32_t t0 = millis(); + while (millis() - t0 < timeoutMs) { + while (Serial2.available()) { + char c = (char)Serial2.read(); + resp += c; + Serial.print(c); + t0 = millis(); // extend timeout on input + } + } + resp.trim(); + Serial.println("\n[Resp]\n" + resp); + return resp; +} +void powerOnSIM7080() { + pinMode(PWRKEY_PIN, OUTPUT); + digitalWrite(PWRKEY_PIN, LOW); + delay(2000); + digitalWrite(PWRKEY_PIN, HIGH); + delay(1000); + digitalWrite(PWRKEY_PIN, LOW); + delay(30000); // full boot +} + +bool waitLTE(uint32_t ms = 120000) { + uint32_t t0 = millis(); + while (millis() - t0 < ms) { + String r = sendAT("AT+CEREG?", 4000); + if (r.indexOf("+CEREG: 0,1") != -1 || r.indexOf("+CEREG: 0,5") != -1) return true; + delay(3000); + } + return false; +} + +// Simple reusable HTTP POST via SH client +bool httpPostSH(const String& baseUrl, const String& path, const String& jsonBody) { + // Configure HTTP client + sendAT("AT+SHCONF=\"URL\",\"" + baseUrl + "\"", 5000); + sendAT("AT+SHCONF=\"BODYLEN\",1024", 2000); + sendAT("AT+SHCONF=\"HEADERLEN\",350", 2000); + + // Connect (no micromanagement; rely on OK) + String r = sendAT("AT+SHCONN", 15000); + if (r.indexOf("OK") == -1) { + Serial.println("SHCONN failed"); + return false; + } + + // Clear and set headers + sendAT("AT+SHCHEAD", 2000); + sendAT("AT+SHAHEAD=\"Content-Type\",\"application/json\"", 2000); + + // Send body + String cmd = "AT+SHBOD=" + String(jsonBody.length()) + ",10000"; + sendAT(cmd, 2000); + Serial2.print(jsonBody); + delay(200); + + // POST (3 = POST) + r = sendAT("AT+SHREQ=\"" + path + "\",3", 20000); + // Expect URC like: +SHREQ: "POST",, + // Print status line if present + int p = r.indexOf("+SHREQ:"); + if (p != -1) { + Serial.println("Status/URC: " + r.substring(p)); + } + + // If there is a body, try to read (non-fatal if none) + sendAT("AT+SHREAD=0,2048", 4000); + + // Disconnect + sendAT("AT+SHDISC", 3000); + return true; +} + +// -------- Arduino setup/loop -------- void setup() { - Serial.begin(115200); - while (!Serial) { - delay(10); - } - Serial.println("--- Starting ESP32 to SIM7080G GPS Test (Final) ---"); + Serial.begin(115200); + Serial2.begin(9600, SERIAL_8N1, LTE_RX_PIN, LTE_TX_PIN); + Serial.println("--- SIM7080: Simple Connect + HTTP POST ---"); - Serial2.begin(115200, SERIAL_8N1, LTE_RX_PIN, LTE_TX_PIN); + powerOnSIM7080(); - powerOffModule(); - delay(3000); - powerOnModule(); + // Basic checks + sendAT("AT", 2000); + sendAT("AT+CMEE=1", 2000); + sendAT("AT+CPIN?", 2000); + sendAT("AT+CSQ", 2000); - // Step 1: "Nudge and Listen" for the initial boot-up. - Serial.println( - "Waiting for initial module boot (90s timeout)..."); - unsigned long startTime = millis(); - unsigned long lastNudgeTime = 0; - const unsigned long NUDGE_INTERVAL = 3000; - bool moduleBooted = false; - String bootResponse = ""; + // Force Cat‑M1 + APN attach + sendAT("AT+CNMP=38", 5000); + sendAT("AT+CMNB=1", 5000); + sendAT("AT+CGDCONT=1,\"IP\",\"" + String(APN) + "\"", 5000); + sendAT("AT+CGATT=1", 10000); - while (millis() - startTime < 90000 && !moduleBooted) { - while (Serial2.available()) { - char c = Serial2.read(); - Serial.write(c); - bootResponse += c; - if (bootResponse.indexOf("SMS Ready") != -1) { - moduleBooted = true; - break; - } - } - if (moduleBooted) { - break; - } - if (millis() - lastNudgeTime > NUDGE_INTERVAL) { - Serial2.println("AT"); - lastNudgeTime = millis(); - } - } + // Wait for LTE registration + if (!waitLTE()) { + Serial.println("No LTE registration; stopping."); + while (true) delay(1000); + } - if (!moduleBooted) { - Serial.println("Module did not complete initial boot. Halting."); - while (true) - ; - } - Serial.println("\nInitial boot successful!"); + // Activate SIM7080 APP PDP (required for HTTP) + sendAT("AT+CNCFG=0,1,\"" + String(APN) + "\"", 5000); + sendAT("AT+CNACT=0,1", 45000); + sendAT("AT+CNACT?", 3000); - // Step 2: Apply the proven network configuration from your manual test. - Serial.println("\nApplying network configuration..."); - sendATCommand("AT+CMEE=1", 1000); - sendATCommand("AT+CNMP=38", 5000); - sendATCommand("AT+CMNB=3", 5000); + smsSetup(); - // Step 3: Perform the full CFUN reset cycle to apply settings. - Serial.println("Applying settings with full CFUN reset..."); - sendATCommand("AT+CFUN=0", 5000); - delay(5000); - sendATCommand("AT+CFUN=1", 10000); + // Optional DNS + sendAT("AT+CDNSCFG=\"8.8.8.8\",\"1.1.1.1\"", 3000); - // Step 4: Wait for the modem to apply settings and find the network. - Serial.println( - "Waiting 45 seconds for modem to apply settings and find network..."); - delay(45000); + // Build your JSON (adjust as needed) + String json = "{\"device_id\":\"sim7080g-01\",\"lat\":-33.865143," + "\"lng\":151.2099,\"speed\":12.5,\"altitude\":30.2}"; - Serial.println("\nChecking network status..."); - currentIMEI = getIMEI(); + // Do the POST + bool ok = httpPostSH(BASE_URL, PATH, json); + Serial.println(ok ? "HTTP POST sent (see status above)" : "HTTP POST failed"); - // Step 5: Check for network registration. - unsigned long networkRegStartTime = millis(); - bool networkRegistered = false; - while (millis() - networkRegStartTime < 120000 && !networkRegistered) { - String regResponse = sendATCommand("AT+CGREG?", 3000); - if (regResponse.indexOf("+CGREG: 0,1") != -1 || - regResponse.indexOf("+CGREG: 0,5") != -1) { - networkRegistered = true; - } - if (networkRegistered) { - Serial.println("Network registration successful!"); - break; - } - Serial.println("Not yet registered. Retrying..."); - delay(5000); - } - - if (!networkRegistered) { - Serial.println("Failed to register to network. Halting."); - while (true) - ; - } - - // --- GPS INITIALIZATION --- - Serial.println("\n--- Turning on GPS ---"); - sendATCommand("AT+CGPS=1", 1000); // Turn on the GPS receiver - Serial.println( - "GPS is on. It may take several minutes to get the first fix."); - - Serial.println("\n--- Setup complete. Starting main loop. ---"); + Serial.println("--- Done ---"); } void loop() { - Serial.println("\n--- Requesting GPS Location ---"); - getGpsLocation(); - delay(30000); // Wait 30 seconds before the next request + // Non-blocking read to capture unsolicited lines like +CMTI + static String urc; + while (Serial2.available()) { + char c = (char)Serial2.read(); + urc += c; + if (c == '\n') { + urc.trim(); + if (urc.startsWith("+CMTI:")) { + // +CMTI: "SM", + int comma = urc.lastIndexOf(','); + if (comma != -1) { + int idx = urc.substring(comma + 1).toInt(); + String body = smsReadAndDelete(idx); + Serial.println("SMS (URC): " + body); + handleSmsCommand(body); + } + } + urc = ""; + } + } + + // Optional periodic poll in case URC was missed + static uint32_t lastPoll = 0; + if (millis() - lastPoll > 60000) { // every 60 s + smsPollUnread(); + lastPoll = millis(); + } + + // Your normal work here... } -void getGpsLocation() { - String response = sendATCommand("AT+CGPSINFO", 5000); - - if (response.indexOf("+CGPSINFO:") != -1) { - response.replace("+CGPSINFO: ", ""); - response.trim(); - - if (response.startsWith(",")) { - Serial.println("GPS has no fix yet."); - return; - } - - int firstComma = response.indexOf(','); - String latDDMM = response.substring(0, firstComma); - int secondComma = response.indexOf(',', firstComma + 1); - String nsIndicator = response.substring(firstComma + 1, secondComma); - int thirdComma = response.indexOf(',', secondComma + 1); - String lonDDMM = response.substring(secondComma + 1, thirdComma); - int fourthComma = response.indexOf(',', thirdComma + 1); - String ewIndicator = response.substring(thirdComma + 1, fourthComma); - - double latitude = 0.0; - if (latDDMM.length() > 2) { - double degrees = latDDMM.substring(0, 2).toDouble(); - double minutes = latDDMM.substring(2).toDouble(); - latitude = degrees + (minutes / 60.0); - if (nsIndicator == "S") { - latitude = -latitude; - } - } - - double longitude = 0.0; - if (lonDDMM.length() > 3) { - double degrees = lonDDMM.substring(0, 3).toDouble(); - double minutes = lonDDMM.substring(3).toDouble(); - longitude = degrees + (minutes / 60.0); - if (ewIndicator == "W") { - longitude = -longitude; - } - } - - Serial.print("Latitude: "); - Serial.println(latitude, 6); - Serial.print("Longitude: "); - Serial.println(longitude, 6); - - } else { - Serial.println("Failed to get GPS info from module."); - } +// Call once after PDP is active (or right after registration) +void smsSetup() { + sendAT("AT+CMGF=1", 2000); // text mode + sendAT("AT+CPMS=\"SM\",\"SM\",\"SM\"", 2000); // SIM storage + sendAT("AT+CNMI=2,1,0,0,0", 2000); // new SMS URC: +CMTI: "SM", + // Optional: show extended text params + // sendAT("AT+CSDH=1", 2000); } -// --- All other helper functions remain the same --- +// Read & delete one SMS by index; returns the body +String smsReadAndDelete(int idx) { + String r = sendAT("AT+CMGR=" + String(idx), 4000); + // Extract last non-empty line before OK + int okPos = r.lastIndexOf("\nOK"); + String s = (okPos>0) ? r.substring(0, okPos) : r; + int lastLf = s.lastIndexOf('\n'); + if (lastLf != -1) s = s.substring(lastLf + 1); + s.trim(); -void powerOnModule() { - Serial.println("Executing SIM7080G power-on sequence..."); - pinMode(PWRKEY_PIN, OUTPUT); - digitalWrite(PWRKEY_PIN, HIGH); - delay(100); - digitalWrite(PWRKEY_PIN, LOW); - delay(2000); - digitalWrite(PWRKEY_PIN, HIGH); + sendAT("AT+CMGD=" + String(idx), 2000); + return s; } -void powerOffModule() { - Serial.println("Executing SIM7080G power-off sequence..."); - pinMode(PWRKEY_PIN, OUTPUT); - digitalWrite(PWRKEY_PIN, HIGH); - delay(100); - digitalWrite(PWRKEY_PIN, LOW); - delay(2000); - digitalWrite(PWRKEY_PIN, HIGH); +// Optional: poll unread SMS (if you want to scan periodically) +void smsPollUnread() { + String list = sendAT("AT+CMGL=\"REC UNREAD\"", 5000); + // list may contain many entries like: + // +CMGL: ,"REC UNREAD",... + int pos = 0; + while (true) { + int p = list.indexOf("+CMGL:", pos); + if (p == -1) break; + int comma = list.indexOf(',', p); + if (comma == -1) break; + // Extract index after "+CMGL: " + int idxStart = list.indexOf(' ', p); + if (idxStart == -1 || idxStart > comma) break; + int idx = list.substring(idxStart + 1, comma).toInt(); + String body = smsReadAndDelete(idx); + Serial.println("SMS (polled): " + body); + handleSmsCommand(body); + pos = comma + 1; + } } -String sendATCommand(String command, unsigned long timeoutMs) { - Serial.print("Sending: "); - Serial.println(command); - while (Serial2.available()) { - Serial2.read(); - } - Serial2.println(command); - String response = ""; - unsigned long startTime = millis(); - while (millis() - startTime < timeoutMs) { - while (Serial2.available()) { - char c = (char)Serial2.read(); - response += c; - startTime = millis(); - if (response.endsWith("OK\r\n") || response.endsWith("ERROR\r\n") || - response.endsWith("FAIL\r\n")) { - response.trim(); - return response; - } - } - } - response.trim(); - return response; -} +void handleSmsCommand(String msg) { + Serial.println("Command received via SMS: " + msg); -String getIMEI() { - String response = sendATCommand("AT+CGSN", 5000); - int currentPos = 0; - while (currentPos < response.length()) { - int nextNewline = response.indexOf('\n', currentPos); - String line = (nextNewline == -1) - ? response.substring(currentPos) - : response.substring(currentPos, nextNewline); - currentPos = (nextNewline == -1) ? response.length() : nextNewline + 1; - line.trim(); - if (line.length() == 15) { - bool allDigits = true; - for (int i = 0; i < line.length(); i++) { - if (!isDigit(line.charAt(i))) { - allDigits = false; - break; - } - } - if (allDigits) { - return line; - } - } - } - return ""; -} + msg.trim(); + if (msg.isEmpty()) { + Serial.println("Empty body"); + return; + } -// This function is no longer called in setup, but is kept for future use -bool activatePDPContextAndBearer() { - Serial.println("Defining PDP Context..."); - String response = sendATCommand( - "AT+CGDCONT=1,\"IP\",\"" + String(HOLOGRAM_APN) + "\"", 5000); - if (response.indexOf("OK") == -1) - return false; + // Very simple JSON extraction: look for device_id and speed keys + auto extract = [&](const String& key)->String { + String pat = String("\"") + key + "\":"; + int k = msg.indexOf(pat); + if (k < 0) return ""; + k += pat.length(); - Serial.println("Activating PDP Context..."); - response = sendATCommand("AT+CGACT=1,1", 10000); - if (response.indexOf("OK") == -1) - return false; + // Skip quotes/spaces + while (k < (int)msg.length() && (msg[k]==' ' || msg[k]=='\"')) k++; - Serial.println("Waiting 5 seconds for IP assignment..."); - delay(5000); + // If quoted value + if (k < (int)msg.length() && msg[k-1] == '\"') { + int end = msg.indexOf('\"', k); + if (end > k) return msg.substring(k, end); + } - Serial.println("Checking IP address..."); - response = sendATCommand("AT+CGPADDR=1", 5000); - if (response.indexOf("+CGPADDR: 1,") == -1) - return false; + // Numeric value + int end = k; + while (end < (int)msg.length() && (isDigit(msg[end]) || msg[end]=='.' || msg[end]=='-' )) end++; + return msg.substring(k, end); + }; - return true; + String deviceId = extract("device_id"); + String speed = extract("speed"); + + Serial.println("Parsed device_id=" + deviceId + " speed=" + speed); + + // Example action: if speed present + if (speed.length()) { + Serial.println("Action: set speed to " + speed); + // TODO: apply speed to your app logic + } } \ No newline at end of file diff --git a/CAR_GPS_TRACKER_2025-08-30_backup.ino b/CAR_GPS_TRACKER_2025-08-30_backup.ino new file mode 100644 index 0000000..1026a87 --- /dev/null +++ b/CAR_GPS_TRACKER_2025-08-30_backup.ino @@ -0,0 +1,260 @@ +#include + +// Pins +#define LTE_RX_PIN 16 // ESP32 RX <- SIM7080 TX +#define LTE_TX_PIN 17 // ESP32 TX -> SIM7080 RX +#define PWRKEY_PIN 4 // SIM7080 PWRKEY + +// Network and endpoint +const char* APN = "hologram"; +const char* BASE_URL = "http://laravel-server.lab.audasmedia.com.au"; +const char* PATH = "/api/gps"; + +// -------- Utilities -------- +String sendAT(const String& cmd, uint32_t timeoutMs) { + Serial.print(">> "); Serial.println(cmd); + while (Serial2.available()) (void)Serial2.read(); + Serial2.println(cmd); + + String resp; + uint32_t t0 = millis(); + while (millis() - t0 < timeoutMs) { + while (Serial2.available()) { + char c = (char)Serial2.read(); + resp += c; + Serial.print(c); + t0 = millis(); // extend timeout on input + } + } + resp.trim(); + Serial.println("\n[Resp]\n" + resp); + return resp; +} + +void powerOnSIM7080() { + pinMode(PWRKEY_PIN, OUTPUT); + digitalWrite(PWRKEY_PIN, LOW); + delay(2000); + digitalWrite(PWRKEY_PIN, HIGH); + delay(1000); + digitalWrite(PWRKEY_PIN, LOW); + delay(30000); // full boot +} + +bool waitLTE(uint32_t ms = 120000) { + uint32_t t0 = millis(); + while (millis() - t0 < ms) { + String r = sendAT("AT+CEREG?", 4000); + if (r.indexOf("+CEREG: 0,1") != -1 || r.indexOf("+CEREG: 0,5") != -1) return true; + delay(3000); + } + return false; +} + +// Simple reusable HTTP POST via SH client +bool httpPostSH(const String& baseUrl, const String& path, const String& jsonBody) { + // Configure HTTP client + sendAT("AT+SHCONF=\"URL\",\"" + baseUrl + "\"", 5000); + sendAT("AT+SHCONF=\"BODYLEN\",1024", 2000); + sendAT("AT+SHCONF=\"HEADERLEN\",350", 2000); + + // Connect (no micromanagement; rely on OK) + String r = sendAT("AT+SHCONN", 15000); + if (r.indexOf("OK") == -1) { + Serial.println("SHCONN failed"); + return false; + } + + // Clear and set headers + sendAT("AT+SHCHEAD", 2000); + sendAT("AT+SHAHEAD=\"Content-Type\",\"application/json\"", 2000); + + // Send body + String cmd = "AT+SHBOD=" + String(jsonBody.length()) + ",10000"; + sendAT(cmd, 2000); + Serial2.print(jsonBody); + delay(200); + + // POST (3 = POST) + r = sendAT("AT+SHREQ=\"" + path + "\",3", 20000); + // Expect URC like: +SHREQ: "POST",, + // Print status line if present + int p = r.indexOf("+SHREQ:"); + if (p != -1) { + Serial.println("Status/URC: " + r.substring(p)); + } + + // If there is a body, try to read (non-fatal if none) + sendAT("AT+SHREAD=0,2048", 4000); + + // Disconnect + sendAT("AT+SHDISC", 3000); + return true; +} + +// -------- Arduino setup/loop -------- +void setup() { + Serial.begin(115200); + Serial2.begin(9600, SERIAL_8N1, LTE_RX_PIN, LTE_TX_PIN); + Serial.println("--- SIM7080: Simple Connect + HTTP POST ---"); + + powerOnSIM7080(); + + // Basic checks + sendAT("AT", 2000); + sendAT("AT+CMEE=1", 2000); + sendAT("AT+CPIN?", 2000); + sendAT("AT+CSQ", 2000); + + // Force Cat‑M1 + APN attach + sendAT("AT+CNMP=38", 5000); + sendAT("AT+CMNB=1", 5000); + sendAT("AT+CGDCONT=1,\"IP\",\"" + String(APN) + "\"", 5000); + sendAT("AT+CGATT=1", 10000); + + // Wait for LTE registration + if (!waitLTE()) { + Serial.println("No LTE registration; stopping."); + while (true) delay(1000); + } + + // Activate SIM7080 APP PDP (required for HTTP) + sendAT("AT+CNCFG=0,1,\"" + String(APN) + "\"", 5000); + sendAT("AT+CNACT=0,1", 45000); + sendAT("AT+CNACT?", 3000); + + smsSetup(); + + // Optional DNS + sendAT("AT+CDNSCFG=\"8.8.8.8\",\"1.1.1.1\"", 3000); + + // Build your JSON (adjust as needed) + String json = "{\"device_id\":\"sim7080g-01\",\"lat\":-33.865143," + "\"lng\":151.2099,\"speed\":12.5,\"altitude\":30.2}"; + + // Do the POST + bool ok = httpPostSH(BASE_URL, PATH, json); + Serial.println(ok ? "HTTP POST sent (see status above)" : "HTTP POST failed"); + + Serial.println("--- Done ---"); +} + +void loop() { + // Non-blocking read to capture unsolicited lines like +CMTI + static String urc; + while (Serial2.available()) { + char c = (char)Serial2.read(); + urc += c; + if (c == '\n') { + urc.trim(); + if (urc.startsWith("+CMTI:")) { + // +CMTI: "SM", + int comma = urc.lastIndexOf(','); + if (comma != -1) { + int idx = urc.substring(comma + 1).toInt(); + String body = smsReadAndDelete(idx); + Serial.println("SMS (URC): " + body); + handleSmsCommand(body); + } + } + urc = ""; + } + } + + // Optional periodic poll in case URC was missed + static uint32_t lastPoll = 0; + if (millis() - lastPoll > 60000) { // every 60 s + smsPollUnread(); + lastPoll = millis(); + } + + // Your normal work here... +} + +// Call once after PDP is active (or right after registration) +void smsSetup() { + sendAT("AT+CMGF=1", 2000); // text mode + sendAT("AT+CPMS=\"SM\",\"SM\",\"SM\"", 2000); // SIM storage + sendAT("AT+CNMI=2,1,0,0,0", 2000); // new SMS URC: +CMTI: "SM", + // Optional: show extended text params + // sendAT("AT+CSDH=1", 2000); +} + +// Read & delete one SMS by index; returns the body +String smsReadAndDelete(int idx) { + String r = sendAT("AT+CMGR=" + String(idx), 4000); + // Extract last non-empty line before OK + int okPos = r.lastIndexOf("\nOK"); + String s = (okPos>0) ? r.substring(0, okPos) : r; + int lastLf = s.lastIndexOf('\n'); + if (lastLf != -1) s = s.substring(lastLf + 1); + s.trim(); + + sendAT("AT+CMGD=" + String(idx), 2000); + return s; +} + +// Optional: poll unread SMS (if you want to scan periodically) +void smsPollUnread() { + String list = sendAT("AT+CMGL=\"REC UNREAD\"", 5000); + // list may contain many entries like: + // +CMGL: ,"REC UNREAD",... + int pos = 0; + while (true) { + int p = list.indexOf("+CMGL:", pos); + if (p == -1) break; + int comma = list.indexOf(',', p); + if (comma == -1) break; + // Extract index after "+CMGL: " + int idxStart = list.indexOf(' ', p); + if (idxStart == -1 || idxStart > comma) break; + int idx = list.substring(idxStart + 1, comma).toInt(); + String body = smsReadAndDelete(idx); + Serial.println("SMS (polled): " + body); + handleSmsCommand(body); + pos = comma + 1; + } +} + +void handleSmsCommand(String msg) { + Serial.println("Command received via SMS: " + msg); + + msg.trim(); + if (msg.isEmpty()) { + Serial.println("Empty body"); + return; + } + + // Very simple JSON extraction: look for device_id and speed keys + auto extract = [&](const String& key)->String { + String pat = String("\"") + key + "\":"; + int k = msg.indexOf(pat); + if (k < 0) return ""; + k += pat.length(); + + // Skip quotes/spaces + while (k < (int)msg.length() && (msg[k]==' ' || msg[k]=='\"')) k++; + + // If quoted value + if (k < (int)msg.length() && msg[k-1] == '\"') { + int end = msg.indexOf('\"', k); + if (end > k) return msg.substring(k, end); + } + + // Numeric value + int end = k; + while (end < (int)msg.length() && (isDigit(msg[end]) || msg[end]=='.' || msg[end]=='-' )) end++; + return msg.substring(k, end); + }; + + String deviceId = extract("device_id"); + String speed = extract("speed"); + + Serial.println("Parsed device_id=" + deviceId + " speed=" + speed); + + // Example action: if speed present + if (speed.length()) { + Serial.println("Action: set speed to " + speed); + // TODO: apply speed to your app logic + } +} \ No newline at end of file