From aac68aa77c1253ec5b4c1b91df82e95d3d79e392 Mon Sep 17 00:00:00 2001 From: sam rolfe Date: Tue, 2 Sep 2025 09:20:23 +1000 Subject: [PATCH] Modular PR1: NetMgr, HttpClient, SmsMgr with working attach/HTTP/SMS --- CAR_GPS_TRACKER.ino | 23 +++++++++-- src/app/App.cpp | 1 + src/core/HttpClient.cpp | 34 ++++++++++++++++ src/core/HttpClient.hpp | 6 +++ src/core/NetMgr.cpp | 61 ++++++++++++++++++++++++++++ src/core/NetMgr.hpp | 9 +++++ src/core/SmsMgr.cpp | 89 +++++++++++++++++++++++++++++++++++++++++ src/core/SmsMgr.hpp | 9 +++++ 8 files changed, 229 insertions(+), 3 deletions(-) diff --git a/CAR_GPS_TRACKER.ino b/CAR_GPS_TRACKER.ino index d67ed98..520edd4 100644 --- a/CAR_GPS_TRACKER.ino +++ b/CAR_GPS_TRACKER.ino @@ -1,9 +1,26 @@ -#include "app/App.hpp" +#include "src/core/NetMgr.hpp" +#include "src/core/HttpClient.hpp" +#include "src/core/SmsMgr.hpp" + +static const char* APN = "hologram"; +static const char* BASE_URL = "http://laravel-server.lab.audasmedia.com.au"; +static const char* PATH = "/api/gps"; void setup() { - App::instance().setup(); + Serial.begin(115200); + Serial2.begin(9600, SERIAL_8N1, 16, 17); // RX=16, TX=17 + + NetMgr::powerOnSIM7080(4); // PWRKEY pin 4 + NetMgr::attachAndPdp(APN); // LTE reg + CNACT + DNS + SmsMgr::setup(); // enable text mode + URCs + + String json = "{\"device_id\":\"sim7080g-01\",\"lat\":-33.865143," + "\"lng\":151.2099,\"speed\":12.5,\"altitude\":30.2}"; + HttpClient::postJson(BASE_URL, PATH, json); } void loop() { - App::instance().loop(); + SmsMgr::pollUrc(); // non‑blocking URC scan; handles +CMTI + SmsMgr::pollUnread(); // optional safety poll + delay(250); } \ No newline at end of file diff --git a/src/app/App.cpp b/src/app/App.cpp index 3ba04f6..5901bcd 100644 --- a/src/app/App.cpp +++ b/src/app/App.cpp @@ -1,5 +1,6 @@ #include "App.hpp" +#include "../core/HttpClient.hpp" App& App::instance() { static App inst; diff --git a/src/core/HttpClient.cpp b/src/core/HttpClient.cpp index e69de29..43b0ee5 100644 --- a/src/core/HttpClient.cpp +++ b/src/core/HttpClient.cpp @@ -0,0 +1,34 @@ +#include "HttpClient.hpp" +#include "NetMgr.hpp" + +namespace { + +bool ensureHttpReady(const String& baseUrl) { + NetMgr::sendAT("AT+SHCONF=\"URL\",\"" + baseUrl + "\"", 5000); + NetMgr::sendAT("AT+SHCONF=\"BODYLEN\",1024", 2000); + NetMgr::sendAT("AT+SHCONF=\"HEADERLEN\",350", 2000); + String r = NetMgr::sendAT("AT+SHCONN", 15000); + if (r.indexOf("OK") == -1) return false; + NetMgr::sendAT("AT+SHCHEAD", 2000); + NetMgr::sendAT("AT+SHAHEAD=\"Content-Type\",\"application/json\"", 2000); + return true; +} + +} + +namespace HttpClient { + +bool postJson(const String& baseUrl, const String& path, const String& jsonBody) { + if (!ensureHttpReady(baseUrl)) { Serial.println("SHCONN failed"); return false; } + NetMgr::sendAT("AT+SHBOD=" + String(jsonBody.length()) + ",10000", 2000); + Serial2.print(jsonBody); + delay(200); + String r = NetMgr::sendAT("AT+SHREQ=\"" + path + "\",3", 20000); // 3=POST + int p = r.indexOf("+SHREQ:"); + if (p != -1) Serial.println("Status/URC: " + r.substring(p)); + NetMgr::sendAT("AT+SHREAD=0,2048", 4000); // non-fatal if CME 3 + NetMgr::sendAT("AT+SHDISC", 3000); + return true; +} + +} // namespace HttpClient \ No newline at end of file diff --git a/src/core/HttpClient.hpp b/src/core/HttpClient.hpp index e69de29..2228a96 100644 --- a/src/core/HttpClient.hpp +++ b/src/core/HttpClient.hpp @@ -0,0 +1,6 @@ +#pragma once +#include + +namespace HttpClient { + bool postJson(const String& baseUrl, const String& path, const String& jsonBody); +} \ No newline at end of file diff --git a/src/core/NetMgr.cpp b/src/core/NetMgr.cpp index e69de29..83ebd1b 100644 --- a/src/core/NetMgr.cpp +++ b/src/core/NetMgr.cpp @@ -0,0 +1,61 @@ +#include "NetMgr.hpp" + +namespace { + String at(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(); + } + } + resp.trim(); + Serial.println("\n[Resp]\n" + resp); + return resp; + } +} + +namespace NetMgr { + +String sendAT(const String& cmd, uint32_t timeoutMs) { return at(cmd, timeoutMs); } + +void powerOnSIM7080(uint8_t pwrkeyPin) { + pinMode(pwrkeyPin, OUTPUT); + digitalWrite(pwrkeyPin, LOW); delay(2000); + digitalWrite(pwrkeyPin, HIGH); delay(1000); + digitalWrite(pwrkeyPin, LOW); delay(30000); +} + +bool waitLTE(uint32_t ms) { + uint32_t t0 = millis(); + while (millis() - t0 < ms) { + String r = at("AT+CEREG?", 4000); + if (r.indexOf("+CEREG: 0,1") != -1 || r.indexOf("+CEREG: 0,5") != -1) return true; + delay(3000); + } + return false; +} + +void attachAndPdp(const char* apn) { + at("AT", 2000); + at("AT+CMEE=1", 2000); + at("AT+CPIN?", 2000); + at("AT+CSQ", 2000); + at("AT+CNMP=38", 5000); + at("AT+CMNB=1", 5000); + at("AT+CGDCONT=1,\"IP\",\"" + String(apn) + "\"", 5000); + at("AT+CGATT=1", 10000); + + if (!waitLTE()) { Serial.println("No LTE reg"); while (true) delay(1000); } + + at("AT+CNCFG=0,1,\"" + String(apn) + "\"", 5000); + at("AT+CNACT=0,1", 45000); // may 767 if already active + at("AT+CNACT?", 3000); + at("AT+CDNSCFG=\"8.8.8.8\",\"1.1.1.1\"", 3000); +} + +} // namespace NetMgr \ No newline at end of file diff --git a/src/core/NetMgr.hpp b/src/core/NetMgr.hpp index e69de29..259f618 100644 --- a/src/core/NetMgr.hpp +++ b/src/core/NetMgr.hpp @@ -0,0 +1,9 @@ +#pragma once +#include + +namespace NetMgr { + void powerOnSIM7080(uint8_t pwrkeyPin); + bool waitLTE(uint32_t ms = 120000); + void attachAndPdp(const char* apn); + String sendAT(const String& cmd, uint32_t timeoutMs); +} \ No newline at end of file diff --git a/src/core/SmsMgr.cpp b/src/core/SmsMgr.cpp index e69de29..6db4762 100644 --- a/src/core/SmsMgr.cpp +++ b/src/core/SmsMgr.cpp @@ -0,0 +1,89 @@ +#include "SmsMgr.hpp" +#include "NetMgr.hpp" + +namespace { + +String readAndDelete(int idx) { + String r = NetMgr::sendAT("AT+CMGR=" + String(idx), 4000); + 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(); + NetMgr::sendAT("AT+CMGD=" + String(idx), 2000); + return s; +} + +void parseSimpleJson(const String& msg) { + auto extract = [&](const String& key)->String { + String pat = "\"" + key + "\":"; + int k = msg.indexOf(pat); + if (k < 0) return ""; + k += pat.length(); + while (k < (int)msg.length() && (msg[k]==' '||msg[k]=='\"')) k++; + if (k>0 && msg[k-1]=='\"') { + int e = msg.indexOf('\"', k); + return (e>k) ? msg.substring(k, e) : ""; + } else { + int e=k; while (e<(int)msg.length() && (isDigit(msg[e])||msg[e]=='.'||msg[e]=='-')) e++; + return msg.substring(k, e); + } + }; + String deviceId = extract("device_id"); + String speed = extract("speed"); + Serial.println("SMS JSON parsed: device_id=" + deviceId + " speed=" + speed); +} + +} // anon + +namespace SmsMgr { + +void setup() { + NetMgr::sendAT("AT+CMGF=1", 2000); + NetMgr::sendAT("AT+CPMS=\"SM\",\"SM\",\"SM\"", 2000); + NetMgr::sendAT("AT+CNMI=2,1,0,0,0", 2000); +} + +void handleBody(const String& body) { + Serial.println("SMS body: " + body); + if (body.startsWith("{")) parseSimpleJson(body); + // else map plain-text commands here +} + +void pollUrc() { + static String urc; + while (Serial2.available()) { + char c = (char)Serial2.read(); + urc += c; + if (c=='\n') { + urc.trim(); + if (urc.startsWith("+CMTI:")) { + int comma = urc.lastIndexOf(','); + if (comma != -1) { + int idx = urc.substring(comma+1).toInt(); + String body = readAndDelete(idx); + handleBody(body); + } + } + urc = ""; + } + } +} + +void pollUnread() { + String list = NetMgr::sendAT("AT+CMGL=\"REC UNREAD\"", 3000); + int pos = 0; + while (true) { + int p = list.indexOf("+CMGL:", pos); + if (p == -1) break; + int comma = list.indexOf(',', p); + int idxStart = list.indexOf(' ', p); + if (comma==-1 || idxStart==-1 || idxStart>comma) break; + int idx = list.substring(idxStart+1, comma).toInt(); + String body = readAndDelete(idx); + handleBody(body); + pos = comma + 1; + } +} + +} // namespace SmsMgr \ No newline at end of file diff --git a/src/core/SmsMgr.hpp b/src/core/SmsMgr.hpp index e69de29..d415423 100644 --- a/src/core/SmsMgr.hpp +++ b/src/core/SmsMgr.hpp @@ -0,0 +1,9 @@ +#pragma once +#include + +namespace SmsMgr { + void setup(); + void pollUrc(); // handle +CMTI + void pollUnread(); // optional periodic safety poll + void handleBody(const String& body); // your command handler entry +} \ No newline at end of file