Compare commits
3 Commits
v0.2-http-
...
v0.4-comma
| Author | SHA1 | Date | |
|---|---|---|---|
| c1cd3e653f | |||
| 2c09289eaa | |||
| aac68aa77c |
@ -1,9 +1,52 @@
|
|||||||
#include "app/App.hpp"
|
#include "src/core/NetMgr.hpp"
|
||||||
|
#include "src/core/HttpClient.hpp"
|
||||||
|
#include "src/core/SmsMgr.hpp"
|
||||||
|
#include "src/core/CommandMgr.hpp"
|
||||||
|
|
||||||
|
static String DEVICE_IMEI = "";
|
||||||
|
|
||||||
|
static const char* APN = "hologram";
|
||||||
|
static const char* BASE_URL = "http://laravel-server.lab.audasmedia.com.au";
|
||||||
|
static const char* PATH = "/api/gps";
|
||||||
|
|
||||||
|
static String queryImei() {
|
||||||
|
String r = NetMgr::sendAT("AT+CGSN", 3000);
|
||||||
|
// Response often includes the IMEI line + OK; take the first 15-digit line
|
||||||
|
int s = 0;
|
||||||
|
for (int i=0;i<(int)r.length();++i) {
|
||||||
|
if (isDigit(r[i])) { s = i; break; }
|
||||||
|
}
|
||||||
|
String imei = r.substring(s, s+15);
|
||||||
|
imei.trim();
|
||||||
|
return imei;
|
||||||
|
}
|
||||||
|
|
||||||
void setup() {
|
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
|
||||||
|
|
||||||
|
DEVICE_IMEI = queryImei();
|
||||||
|
if (DEVICE_IMEI.length()==0) DEVICE_IMEI = "860016049744324"; // fallback if needed
|
||||||
|
|
||||||
|
CommandMgr::configure(BASE_URL, DEVICE_IMEI);
|
||||||
|
|
||||||
|
double lat,lng,alt,spd,hdg;
|
||||||
|
Telemetry::readGNSS(lat,lng,alt,spd,hdg);
|
||||||
|
String json = Telemetry::buildJson(DEVICE_IMEI, lat,lng,alt,spd,hdg);
|
||||||
|
|
||||||
|
//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() {
|
void loop() {
|
||||||
App::instance().loop();
|
SmsMgr::pollUrc(); // non‑blocking URC scan; handles +CMTI
|
||||||
|
CommandMgr::poll(30000); // every 30s
|
||||||
|
SmsMgr::pollUnread(); // optional safety poll
|
||||||
|
delay(250);
|
||||||
}
|
}
|
||||||
47
backup/CAR_GPS_TRACKER (Copy).ino
Normal file
47
backup/CAR_GPS_TRACKER (Copy).ino
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
#include "src/core/NetMgr.hpp"
|
||||||
|
#include "src/core/HttpClient.hpp"
|
||||||
|
#include "src/core/SmsMgr.hpp"
|
||||||
|
#include "src/core/CommandMgr.hpp"
|
||||||
|
|
||||||
|
static String DEVICE_IMEI = "";
|
||||||
|
|
||||||
|
static const char* APN = "hologram";
|
||||||
|
static const char* BASE_URL = "http://laravel-server.lab.audasmedia.com.au";
|
||||||
|
static const char* PATH = "/api/gps";
|
||||||
|
|
||||||
|
static String queryImei() {
|
||||||
|
String r = NetMgr::sendAT("AT+CGSN", 3000);
|
||||||
|
// Response often includes the IMEI line + OK; take the first 15-digit line
|
||||||
|
int s = 0;
|
||||||
|
for (int i=0;i<(int)r.length();++i) {
|
||||||
|
if (isDigit(r[i])) { s = i; break; }
|
||||||
|
}
|
||||||
|
String imei = r.substring(s, s+15);
|
||||||
|
imei.trim();
|
||||||
|
return imei;
|
||||||
|
}
|
||||||
|
|
||||||
|
void 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
|
||||||
|
|
||||||
|
DEVICE_IMEI = queryImei();
|
||||||
|
if (DEVICE_IMEI.length()==0) DEVICE_IMEI = "860016049744324"; // fallback if needed
|
||||||
|
|
||||||
|
CommandMgr::configure(BASE_URL, DEVICE_IMEI);
|
||||||
|
|
||||||
|
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() {
|
||||||
|
SmsMgr::pollUrc(); // non‑blocking URC scan; handles +CMTI
|
||||||
|
CommandMgr::poll(30000); // every 30s
|
||||||
|
SmsMgr::pollUnread(); // optional safety poll
|
||||||
|
delay(250);
|
||||||
|
}
|
||||||
209
backup/CAR_GPS_TRACKER_working_GET.ino
Normal file
209
backup/CAR_GPS_TRACKER_working_GET.ino
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
#include <HardwareSerial.h>
|
||||||
|
|
||||||
|
#define RXD2 16
|
||||||
|
#define TXD2 17
|
||||||
|
#define PWRKEY 4
|
||||||
|
|
||||||
|
|
||||||
|
int parseLenFromSHREQ(const String& buf) {
|
||||||
|
int start = 0;
|
||||||
|
while (start < (int)buf.length()) {
|
||||||
|
int end = buf.indexOf('\n', start);
|
||||||
|
if (end == -1) end = buf.length();
|
||||||
|
String line = buf.substring(start, end);
|
||||||
|
line.trim();
|
||||||
|
if (line.startsWith("+SHREQ:")) {
|
||||||
|
int lastComma = line.lastIndexOf(',');
|
||||||
|
if (lastComma < 0) return -1;
|
||||||
|
int i = lastComma + 1;
|
||||||
|
while (i < (int)line.length() && line[i] == ' ') i++;
|
||||||
|
int j = i;
|
||||||
|
while (j < (int)line.length() && isDigit(line[j])) j++;
|
||||||
|
if (j <= i) return -1;
|
||||||
|
return line.substring(i, j).toInt();
|
||||||
|
}
|
||||||
|
start = end + 1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
String at(const String& cmd, uint32_t timeoutMs=5000, uint32_t urcWindowMs=500) {
|
||||||
|
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 on activity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Short URC window to catch lines like +SHREQ: "GET",200,<len>
|
||||||
|
uint32_t u0 = millis();
|
||||||
|
while (millis()-u0 < urcWindowMs) {
|
||||||
|
while (Serial2.available()) {
|
||||||
|
char c = (char)Serial2.read();
|
||||||
|
resp += c; Serial.print(c);
|
||||||
|
u0 = millis(); // extend on activity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.trim();
|
||||||
|
Serial.println("\n[Resp]\n" + resp);
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
void powerOnSIM7080() {
|
||||||
|
pinMode(PWRKEY, OUTPUT);
|
||||||
|
digitalWrite(PWRKEY, LOW); delay(2000);
|
||||||
|
digitalWrite(PWRKEY, HIGH); delay(1000);
|
||||||
|
digitalWrite(PWRKEY, LOW); delay(30000);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool waitLTE(uint32_t ms=120000) {
|
||||||
|
uint32_t t0=millis();
|
||||||
|
while (millis()-t0 < ms) {
|
||||||
|
String r = at("AT+CEREG?", 3000);
|
||||||
|
if (r.indexOf("+CEREG: 0,1")!=-1 || r.indexOf("+CEREG: 0,5")!=-1) return true;
|
||||||
|
delay(2000);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void attachPDP() {
|
||||||
|
at("AT",2000);
|
||||||
|
at("AT+CMEE=1",2000);
|
||||||
|
at("AT+CPIN?",2000);
|
||||||
|
at("AT+CSQ",2000);
|
||||||
|
at("AT+CNMP=38",3000);
|
||||||
|
at("AT+CMNB=1",3000);
|
||||||
|
at("AT+CGDCONT=1,\"IP\",\"hologram\"",3000);
|
||||||
|
at("AT+CGATT=1",10000);
|
||||||
|
if (!waitLTE()) { Serial.println("No LTE"); while(true) delay(1000); }
|
||||||
|
at("AT+CNCFG=0,1,\"hologram\"",3000);
|
||||||
|
at("AT+CNACT=0,1",20000);
|
||||||
|
at("AT+CNACT?",2000);
|
||||||
|
at("AT+CDNSCFG=\"8.8.8.8\",\"1.1.1.1\"",2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pure SH GET for one host+path, prints raw SHREAD output(s)
|
||||||
|
void shGet(const String& base, const String& path) {
|
||||||
|
Serial.println("\n=== SH GET ===");
|
||||||
|
Serial.println("Base: " + base);
|
||||||
|
Serial.println("Path: " + path);
|
||||||
|
|
||||||
|
at("AT+SHDISC", 2000); // clear any stale session (ignore CME 3)
|
||||||
|
at("AT+SHCHEAD", 2000); // clear headers
|
||||||
|
|
||||||
|
|
||||||
|
at("AT+SHCONF=\"URL\",\"" + base + "\"", 4000);
|
||||||
|
//at("AT+SHCONF=\"BODYLEN\",1024", 2000);
|
||||||
|
//at("AT+SHCONF=\"HEADERLEN\",350", 2000);
|
||||||
|
|
||||||
|
String r = at("AT+SHCONN", 12000);
|
||||||
|
|
||||||
|
Serial.println("---- CONNECTION !!----");
|
||||||
|
Serial.println(r);
|
||||||
|
|
||||||
|
if (r.indexOf("OK")==-1) { Serial.println("SHCONN failed"); return; }
|
||||||
|
|
||||||
|
at("AT+SHCHEAD", 2000);
|
||||||
|
// Optionally set UA if your server cares:
|
||||||
|
//at("AT+SHAHEAD=\"User-Agent\",\"SIM7080G\"",2000);
|
||||||
|
|
||||||
|
at("AT+SHAHEAD=\"User-Agent\",\"IOE Client\"",2000);
|
||||||
|
// at("AT+SHAHEAD=\"User-Agent\",\"curl/7.47.0\"",2000);
|
||||||
|
at("AT+SHAHEAD=\"Cache-control\",\"no-cache\"",2000);
|
||||||
|
at("AT+SHAHEAD=\"Connection\",\"keep-alive\"",2000);
|
||||||
|
|
||||||
|
// at("AT+SHAHEAD=\"Content-Type\",\"application/json\"",2000);
|
||||||
|
|
||||||
|
// at("AT+SHAHEAD=\"Content-Type\",\"text/html\"",2000);
|
||||||
|
at("AT+SHAHEAD=\"Accept\",\"text/html,*/*\"",2000);
|
||||||
|
//at("AT+SHAHEAD=\"Accept-encoding\",\"gzip, deflate\"",2000);
|
||||||
|
|
||||||
|
String u = at("AT+SHREQ=\"" +path + "\",1", 20000); // 1 = GET
|
||||||
|
|
||||||
|
|
||||||
|
Serial.println("---- REQUESTED !!----");
|
||||||
|
Serial.println(u);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//String u = at("AT+SHREQ=\"" + path + "\",1", 20000);
|
||||||
|
int bodyLen = parseLenFromSHREQ(u);
|
||||||
|
Serial.print("Parsed body length: "); Serial.println(bodyLen);
|
||||||
|
|
||||||
|
|
||||||
|
if (bodyLen > 0) {
|
||||||
|
String body = at("AT+SHREAD=0," + String(bodyLen), 5000);
|
||||||
|
Serial.println("---- SHREAD(N) RAW ----");
|
||||||
|
Serial.println(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// First read
|
||||||
|
|
||||||
|
//String body0 = at("AT+SHREAD=0,8", 3000);
|
||||||
|
//Serial.println("---- SHREAD(8) RAW ----");
|
||||||
|
//Serial.println(body0);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//String body1 = at("AT+SHREAD=0,2048", 4000);
|
||||||
|
//Serial.println("01 ---- SHREAD(2048) RAW ----");
|
||||||
|
//Serial.println(body1);
|
||||||
|
// Second smaller read
|
||||||
|
//String body2 = at("AT+SHREAD=0,512", 3000);
|
||||||
|
//Serial.println("---- SHREAD(512) RAW ----");
|
||||||
|
//Serial.println(body2);
|
||||||
|
|
||||||
|
|
||||||
|
//String body3 = at("AT+SHREAD=0,20", 3000);
|
||||||
|
//Serial.println("---- SHREAD(20) RAW ----");
|
||||||
|
//Serial.println(body3);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
at("AT+SHDISC", 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
Serial2.begin(9600, SERIAL_8N1, RXD2, TXD2);
|
||||||
|
|
||||||
|
powerOnSIM7080();
|
||||||
|
attachPDP();
|
||||||
|
|
||||||
|
// 1) Your Laravel endpoint
|
||||||
|
shGet("http://laravel-server.lab.audasmedia.com.au",
|
||||||
|
"/api/device/860016049744324/commands");
|
||||||
|
|
||||||
|
|
||||||
|
//shGet("http://httpforever.com/","/");
|
||||||
|
|
||||||
|
// 2) Example.com root
|
||||||
|
// shGet("http://example.com", "/");
|
||||||
|
|
||||||
|
// 3) Adafruit WiFi test page
|
||||||
|
// shGet("http://wifitest.adafruit.com", "/testwifi/index.html");
|
||||||
|
|
||||||
|
// 4) httpbin GET
|
||||||
|
// shGet("http://httpbin.org", "/get");
|
||||||
|
|
||||||
|
Serial.println("\n--- All GETs attempted ---");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
delay(1000);
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
|
|
||||||
#include "App.hpp"
|
#include "App.hpp"
|
||||||
|
#include "../core/HttpClient.hpp"
|
||||||
|
|
||||||
App& App::instance() {
|
App& App::instance() {
|
||||||
static App inst;
|
static App inst;
|
||||||
|
|||||||
@ -0,0 +1,184 @@
|
|||||||
|
#include "CommandMgr.hpp"
|
||||||
|
#include "HttpClient.hpp"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
String BASE;
|
||||||
|
String IMEI;
|
||||||
|
uint32_t lastPoll = 0;
|
||||||
|
|
||||||
|
// Minimal JSON parsing helpers (string-based)
|
||||||
|
// Extract array of objects from a JSON string like: [{"id":..,"type":"..","payload":{...}}, ...]
|
||||||
|
// We’ll do a naive split on "},{" for small payloads; replace with proper JSON parsing later.
|
||||||
|
|
||||||
|
long extractId(const String& obj) {
|
||||||
|
String pat = "\"id\":";
|
||||||
|
int k = obj.indexOf(pat);
|
||||||
|
if (k < 0) return 0;
|
||||||
|
k += pat.length();
|
||||||
|
int e = k;
|
||||||
|
while (e < (int)obj.length() && (isDigit(obj[e]) || obj[e]=='-')) e++;
|
||||||
|
return obj.substring(k, e).toInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
String extractString(const String& obj, const String& key) {
|
||||||
|
String pat = "\"" + key + "\":";
|
||||||
|
int k = obj.indexOf(pat);
|
||||||
|
if (k < 0) return "";
|
||||||
|
k += pat.length();
|
||||||
|
while (k < (int)obj.length() && (obj[k]==' ' || obj[k]=='\"')) k++;
|
||||||
|
// read quoted string
|
||||||
|
int end = obj.indexOf('\"', k);
|
||||||
|
if (end <= k) return "";
|
||||||
|
return obj.substring(k, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
String extractPayload(const String& obj) {
|
||||||
|
// payload is often an object. Find "payload": and read balanced braces.
|
||||||
|
String pat = "\"payload\":";
|
||||||
|
int k = obj.indexOf(pat);
|
||||||
|
if (k < 0) return "";
|
||||||
|
k += pat.length();
|
||||||
|
while (k < (int)obj.length() && (obj[k]==' ')) k++;
|
||||||
|
if (k >= (int)obj.length()) return "";
|
||||||
|
if (obj[k] == '{') {
|
||||||
|
int depth = 0;
|
||||||
|
int i = k;
|
||||||
|
for (; i < (int)obj.length(); ++i) {
|
||||||
|
if (obj[i] == '{') depth++;
|
||||||
|
else if (obj[i] == '}') {
|
||||||
|
depth--;
|
||||||
|
if (depth == 0) { i++; break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return obj.substring(k, i);
|
||||||
|
} else if (obj[k] == '\"') {
|
||||||
|
int end = obj.indexOf('\"', k+1);
|
||||||
|
if (end > k) return obj.substring(k+1, end);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Very naive split into objects. Works if server returns a small array.
|
||||||
|
void parseArray(const String& json, std::vector<CommandMgr::Command>& out) {
|
||||||
|
String s = json;
|
||||||
|
s.trim();
|
||||||
|
if (!s.startsWith("[")) return;
|
||||||
|
// strip [ and ]
|
||||||
|
if (s.length() >= 2 && s.endsWith("]")) s = s.substring(1, s.length()-1);
|
||||||
|
|
||||||
|
// crude split on "},{" boundaries
|
||||||
|
int start = 0;
|
||||||
|
while (start < (int)s.length()) {
|
||||||
|
int next = s.indexOf("},{", start);
|
||||||
|
String obj;
|
||||||
|
if (next == -1) {
|
||||||
|
obj = s.substring(start);
|
||||||
|
obj.trim();
|
||||||
|
if (obj.startsWith("{") && obj.endsWith("}")) {
|
||||||
|
// ok
|
||||||
|
}
|
||||||
|
else if (!obj.isEmpty()) {
|
||||||
|
// Try pad
|
||||||
|
if (!obj.startsWith("{")) obj = "{" + obj;
|
||||||
|
if (!obj.endsWith("}")) obj += "}";
|
||||||
|
}
|
||||||
|
start = s.length();
|
||||||
|
} else {
|
||||||
|
obj = s.substring(start, next + 1); // include the trailing }
|
||||||
|
if (!obj.startsWith("{")) obj = "{" + obj;
|
||||||
|
start = next + 2; // position after },
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.trim();
|
||||||
|
if (obj.length() == 0) continue;
|
||||||
|
|
||||||
|
CommandMgr::Command c;
|
||||||
|
c.id = extractId(obj);
|
||||||
|
c.type = extractString(obj, "type");
|
||||||
|
c.payload = extractPayload(obj);
|
||||||
|
if (c.id != 0 && c.type.length()) out.push_back(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sendReceipt(long id, const String& result, const String& detail) {
|
||||||
|
// POST /api/device/{imei}/command-receipts
|
||||||
|
String path = "/api/device/" + IMEI + "/command-receipts";
|
||||||
|
String body = String("{\"command_id\":") + id + ",\"result\":\"" + result + "\",\"detail\":\"" + detail + "\"}";
|
||||||
|
return HttpClient::postJson(BASE, path, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // anon
|
||||||
|
|
||||||
|
namespace CommandMgr {
|
||||||
|
|
||||||
|
void configure(const String& baseUrl, const String& imei) {
|
||||||
|
BASE = baseUrl;
|
||||||
|
IMEI = imei;
|
||||||
|
}
|
||||||
|
|
||||||
|
void poll(uint32_t minIntervalMs) {
|
||||||
|
uint32_t now = millis();
|
||||||
|
if (now - lastPoll < minIntervalMs) return;
|
||||||
|
lastPoll = now;
|
||||||
|
|
||||||
|
if (BASE.isEmpty() || IMEI.isEmpty()) return;
|
||||||
|
|
||||||
|
String path = "/api/device/" + IMEI + "/commands";
|
||||||
|
String body;
|
||||||
|
|
||||||
|
|
||||||
|
String fullUrl = BASE + path; // e.g., "http://laravel-server.lab.audasmedia.com.au" + "/api/device/IMEI/commands"
|
||||||
|
|
||||||
|
//bool ok =HttpClient::getJsonToFile(fullUrl, body);
|
||||||
|
|
||||||
|
bool ok = HttpClient::getJsonExact(BASE, path, body);
|
||||||
|
Serial.println("Commands JSON: " + body);
|
||||||
|
// TODO: replace with proper JSON parsing later
|
||||||
|
// Crude parse: split by "},{" for now and extract id/type/payload as before.
|
||||||
|
if (!ok || body.length()==0) return;
|
||||||
|
|
||||||
|
std::vector<Command> cmds;
|
||||||
|
parseArray(body, cmds);
|
||||||
|
for (auto& c : cmds) {
|
||||||
|
bool execOk = false;
|
||||||
|
String detail = "";
|
||||||
|
|
||||||
|
if (c.type == "lights") {
|
||||||
|
execOk = handleLights(c.payload);
|
||||||
|
detail = "lights handled";
|
||||||
|
} else if (c.type == "sleep") {
|
||||||
|
execOk = handleSleep(c.payload);
|
||||||
|
detail = "sleep handled";
|
||||||
|
} else if (c.type == "ring_fence") {
|
||||||
|
execOk = handleRingFence(c.payload);
|
||||||
|
detail = "ring fence handled";
|
||||||
|
} else {
|
||||||
|
execOk = true; // unknown; mark ok but do nothing
|
||||||
|
detail = "unknown type";
|
||||||
|
}
|
||||||
|
|
||||||
|
sendReceipt(c.id, execOk ? "ok" : "error", detail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool handleLights(const String& payload) {
|
||||||
|
// TODO: parse {"on":true} and toggle GPIO via your GpioCtrl later
|
||||||
|
Serial.println("handleLights payload: " + payload);
|
||||||
|
bool on = (payload.indexOf("\"on\":true") != -1);
|
||||||
|
GpioCtrl::setLight(on);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool handleSleep(const String& payload) {
|
||||||
|
// TODO: parse {"interval_sec":120}
|
||||||
|
Serial.println("handleSleep payload: " + payload);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool handleRingFence(const String& payload) {
|
||||||
|
// TODO: parse and apply ring fence
|
||||||
|
Serial.println("handleRingFence payload: " + payload);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// } // namespace CommandMgr
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
namespace CommandMgr {
|
||||||
|
|
||||||
|
struct Command {
|
||||||
|
long id = 0;
|
||||||
|
String type; // "lights","camera","sleep","ring_fence","custom"
|
||||||
|
String payload; // raw JSON payload
|
||||||
|
};
|
||||||
|
|
||||||
|
void configure(const String& baseUrl, const String& imei);
|
||||||
|
|
||||||
|
// Poll the server for queued commands and execute them.
|
||||||
|
void poll(uint32_t minIntervalMs = 30000);
|
||||||
|
|
||||||
|
// (Optional) simple handlers you can expand later.
|
||||||
|
bool handleLights(const String& payload);
|
||||||
|
bool handleSleep(const String& payload);
|
||||||
|
bool handleRingFence(const String& payload);
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,93 @@
|
|||||||
|
#include "HttpClient.hpp"
|
||||||
|
#include "NetMgr.hpp"
|
||||||
|
|
||||||
|
namespace HttpClient {
|
||||||
|
|
||||||
|
bool postJson(const String& baseUrl, const String& path, const String& jsonBody) {
|
||||||
|
NetMgr::sendAT("AT+SHDISC", 2000);
|
||||||
|
NetMgr::sendAT("AT+SHCHEAD", 2000);
|
||||||
|
|
||||||
|
if (NetMgr::sendAT("AT+SHCONF=\"URL\",\"" + baseUrl + "\"", 4000).indexOf("OK") == -1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
String r = NetMgr::sendAT("AT+SHCONN", 12000);
|
||||||
|
if (r.indexOf("OK") == -1) return false;
|
||||||
|
|
||||||
|
NetMgr::sendAT("AT+SHCHEAD", 2000);
|
||||||
|
NetMgr::sendAT("AT+SHAHEAD=\"Content-Type\",\"application/json\"", 2000);
|
||||||
|
|
||||||
|
NetMgr::sendAT("AT+SHBOD=" + String(jsonBody.length()) + ",10000", 2000);
|
||||||
|
Serial2.print(jsonBody);
|
||||||
|
delay(200);
|
||||||
|
|
||||||
|
r = NetMgr::sendAT("AT+SHREQ=\"" + path + "\",3", 20000); // 3 = POST
|
||||||
|
int p = r.indexOf("+SHREQ:");
|
||||||
|
if (p != -1) Serial.println("POST status URC: " + r.substring(p));
|
||||||
|
|
||||||
|
// optional read (non-fatal)
|
||||||
|
NetMgr::sendAT("AT+SHREAD=0,2048", 4000);
|
||||||
|
|
||||||
|
NetMgr::sendAT("AT+SHDISC", 2000);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool getJsonExact(const String& baseUrl, const String& path, String& outBody) {
|
||||||
|
outBody = "";
|
||||||
|
|
||||||
|
NetMgr::sendAT("AT+SHDISC", 2000);
|
||||||
|
NetMgr::sendAT("AT+SHCHEAD", 2000);
|
||||||
|
|
||||||
|
if (NetMgr::sendAT("AT+SHCONF=\"URL\",\"" + baseUrl + "\"", 4000).indexOf("OK") == -1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
String r = NetMgr::sendAT("AT+SHCONN", 12000);
|
||||||
|
if (r.indexOf("OK") == -1) return false;
|
||||||
|
|
||||||
|
NetMgr::sendAT("AT+SHCHEAD", 2000);
|
||||||
|
NetMgr::sendAT("AT+SHAHEAD=\"Connection\",\"keep-alive\"", 2000);
|
||||||
|
NetMgr::sendAT("AT+SHAHEAD=\"Accept\",\"application/json\"", 2000);
|
||||||
|
|
||||||
|
// GET + parse length
|
||||||
|
String urc = NetMgr::sendAT("AT+SHREQ=\"" + path + "\",1", 20000);
|
||||||
|
|
||||||
|
// parse len from URC
|
||||||
|
int len = -1;
|
||||||
|
{
|
||||||
|
int start = 0;
|
||||||
|
while (start < (int)urc.length()) {
|
||||||
|
int end = urc.indexOf('\n', start);
|
||||||
|
if (end == -1) end = urc.length();
|
||||||
|
String line = urc.substring(start, end);
|
||||||
|
line.trim();
|
||||||
|
if (line.startsWith("+SHREQ:")) {
|
||||||
|
int lastComma = line.lastIndexOf(',');
|
||||||
|
if (lastComma >= 0) {
|
||||||
|
int i = lastComma + 1;
|
||||||
|
while (i < (int)line.length() && line[i]==' ') i++;
|
||||||
|
int j = i;
|
||||||
|
while (j < (int)line.length() && isDigit(line[j])) j++;
|
||||||
|
if (j > i) len = line.substring(i, j).toInt();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
start = end + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len > 0) {
|
||||||
|
String raw = NetMgr::sendAT("AT+SHREAD=0," + String(len), 6000);
|
||||||
|
int nl = raw.indexOf('\n');
|
||||||
|
if (nl != -1) {
|
||||||
|
String b = raw.substring(nl + 1);
|
||||||
|
int okPos = b.lastIndexOf("\nOK");
|
||||||
|
if (okPos >= 0) b = b.substring(0, okPos);
|
||||||
|
b.trim();
|
||||||
|
outBody = b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NetMgr::sendAT("AT+SHDISC", 2000);
|
||||||
|
return outBody.length() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace HttpClient
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
namespace HttpClient {
|
||||||
|
bool postJson(const String& baseUrl, const String& path, const String& jsonBody);
|
||||||
|
bool getJsonExact(const String& baseUrl, const String& path, String& outBody);
|
||||||
|
}
|
||||||
@ -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
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
@ -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
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
namespace SmsMgr {
|
||||||
|
void setup();
|
||||||
|
void pollUrc(); // handle +CMTI
|
||||||
|
void pollUnread(); // optional periodic safety poll
|
||||||
|
void handleBody(const String& body); // your command handler entry
|
||||||
|
}
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
#include "Telemetry.hpp"
|
||||||
|
#include "NetMgr.hpp"
|
||||||
|
|
||||||
|
namespace Telemetry {
|
||||||
|
|
||||||
|
bool readGNSS(double& lat, double& lng, double& alt, double& speed, double& heading) {
|
||||||
|
lat=lng=alt=speed=heading=0;
|
||||||
|
String r = NetMgr::sendAT("AT+CGNSINF", 3000);
|
||||||
|
// +CGNSINF: 1,1,yyyyMMddhhmmss.sss,lat,lng,alt, ... ,speed_over_ground, ... ,course
|
||||||
|
int p = r.indexOf("+CGNSINF:");
|
||||||
|
if (p < 0) return false;
|
||||||
|
// crude split
|
||||||
|
int first = r.indexOf('\n', p); // skip header if present
|
||||||
|
String line = (first!=-1) ? r.substring(p, first) : r.substring(p);
|
||||||
|
// safer: take last line
|
||||||
|
int lastNl = r.lastIndexOf('\n');
|
||||||
|
line = (lastNl!=-1) ? r.substring(lastNl+1) : line;
|
||||||
|
line.trim();
|
||||||
|
|
||||||
|
// tokenization
|
||||||
|
int commas[10]; int count=0;
|
||||||
|
for (int i=0;i<(int)line.length() && count<10;i++) if (line[i]==',') commas[count++]=i;
|
||||||
|
if (count < 6) return false;
|
||||||
|
|
||||||
|
lat = line.substring(commas[1]+1, commas[2]).toDouble();
|
||||||
|
lng = line.substring(commas[2]+1, commas[3]).toDouble();
|
||||||
|
alt = line.substring(commas[3]+1, commas[4]).toDouble();
|
||||||
|
// speed (km/h) field may be later; do a find:
|
||||||
|
int next = line.indexOf(',', commas[4]+1);
|
||||||
|
next = line.indexOf(',', next+1);
|
||||||
|
next = line.indexOf(',', next+1);
|
||||||
|
int speedStart = next+1;
|
||||||
|
int speedEnd = line.indexOf(',', speedStart);
|
||||||
|
if (speedStart>0 && speedEnd>speedStart) speed = line.substring(speedStart, speedEnd).toDouble();
|
||||||
|
// heading/course similarly if needed
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
String buildJson(const String& imei, double lat, double lng, double alt, double speed, double heading) {
|
||||||
|
String j = String("{\"device_id\":\"") + imei + "\","
|
||||||
|
+ "\"lat\":" + String(lat,6) + ","
|
||||||
|
+ "\"lng\":" + String(lng,6) + ","
|
||||||
|
+ "\"speed\":" + String(speed,2) + ","
|
||||||
|
+ "\"altitude\":" + String(alt,2) + "}";
|
||||||
|
return j;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Telemetry
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Arduino.h>
|
||||||
|
namespace Telemetry {
|
||||||
|
bool readGNSS(double& lat, double& lng, double& alt, double& speed, double& heading);
|
||||||
|
String buildJson(const String& imei, double lat, double lng, double alt, double speed, double heading);
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
#include "GpioCtrl.hpp"
|
||||||
|
namespace {
|
||||||
|
uint8_t lp = 255;
|
||||||
|
}
|
||||||
|
namespace GpioCtrl {
|
||||||
|
void init(uint8_t lightPin) { lp = lightPin; pinMode(lp, OUTPUT); digitalWrite(lp, LOW); }
|
||||||
|
void setLight(bool on) { if (lp != 255) digitalWrite(lp, on ? HIGH : LOW); }
|
||||||
|
}
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Arduino.h>
|
||||||
|
namespace GpioCtrl {
|
||||||
|
void init(uint8_t lightPin);
|
||||||
|
void setLight(bool on);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user