model Device { id String @id @default(uuid()) imei String @unique name String? telemetry Telemetry[] @@map("devices") } model Telemetry { id String @id @default(uuid()) deviceId String device Device @relation(fields: [deviceId], references [id]) recordedAt DateTime @default(now) lat Float lng Float altitude Float? speed Float? heading Float? accuracy Float? battery Float? isCarOn Boolean? raw Json? @@map("telemetry") } model Command { id Int @id @default(autoincrement()) deviceId String device Device @relation(fields: [deviceId], references [id]) type String payload Json createdAt DateTime @default(now()) @@map("commands") }iniclude // 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 } }