185 lines
5.7 KiB
C++
185 lines
5.7 KiB
C++
// src/core/CommandMgr.cpp
|
|
#include "CommandMgr.hpp"
|
|
#include "HttpClient.hpp"
|
|
#include "NetMgr.hpp"
|
|
|
|
// Access CFG set in .ino
|
|
#include "../app/AppConfig.hpp"
|
|
extern AppConfig CFG;
|
|
|
|
namespace CommandMgr {
|
|
|
|
struct Command {
|
|
long id = 0;
|
|
String type;
|
|
String payload;
|
|
};
|
|
|
|
static String BASE;
|
|
static String IMEI;
|
|
static uint32_t lastPoll = 0;
|
|
|
|
// ---------- tiny JSON helpers ----------
|
|
static int pickInt(const String& j, const char* key) {
|
|
String pat = String("\"") + key + "\":";
|
|
int k = j.indexOf(pat);
|
|
if (k < 0) return -1;
|
|
k += pat.length();
|
|
while (k < (int)j.length() && (j[k]==' '||j[k]==':')) k++;
|
|
int i = k;
|
|
while (i < (int)j.length() && isDigit(j[i])) i++;
|
|
if (i <= k) return -1;
|
|
return j.substring(k, i).toInt();
|
|
}
|
|
|
|
static String pickStr(const String& j, const char* key) {
|
|
String pat = String("\"") + key + "\":";
|
|
int k = j.indexOf(pat);
|
|
if (k < 0) return "";
|
|
k += pat.length();
|
|
while (k < (int)j.length() && (j[k]==' '||j[k]==':'||j[k]=='\"')) k++;
|
|
if (k < (int)j.length() && j[k-1]=='\"') {
|
|
int e = j.indexOf('\"', k);
|
|
if (e > k) return j.substring(k, e);
|
|
}
|
|
return "";
|
|
}
|
|
|
|
// naive split: [{".."},{".."}]
|
|
static void parseArray(const String& json, std::vector<Command>& out) {
|
|
String s = json; s.trim();
|
|
if (!s.startsWith("[")) return;
|
|
if (s.length()>=2 && s.endsWith("]")) s = s.substring(1, s.length()-1);
|
|
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 = "{" + obj;
|
|
if (!obj.endsWith("}")) obj += "}";
|
|
start = s.length();
|
|
} else {
|
|
obj = s.substring(start, next+1);
|
|
if (!obj.startsWith("{")) obj = "{" + obj;
|
|
start = next + 2;
|
|
}
|
|
if (obj.length()==0) continue;
|
|
|
|
// extract id, type, payload
|
|
Command c;
|
|
c.id = pickInt(obj, "id");
|
|
c.type = pickStr(obj, "type");
|
|
// payload: assume object; take substring from "payload":
|
|
{
|
|
String pat="\"payload\":";
|
|
int k=obj.indexOf(pat);
|
|
if (k>=0) {
|
|
k += pat.length();
|
|
while (k<(int)obj.length() && obj[k]==' ') k++;
|
|
if (k<(int)obj.length() && obj[k]=='{') {
|
|
int depth=0, i=k;
|
|
for (; i<(int)obj.length(); ++i) {
|
|
if (obj[i]=='{') depth++;
|
|
else if (obj[i]=='}') { depth--; if (depth==0) { i++; break; } }
|
|
}
|
|
c.payload = obj.substring(k, i);
|
|
} else if (k<(int)obj.length() && obj[k]=='\"') {
|
|
int e=obj.indexOf('\"', k+1);
|
|
if (e>k) c.payload = obj.substring(k+1, e);
|
|
}
|
|
}
|
|
}
|
|
if (c.id>0 && c.type.length()) out.push_back(c);
|
|
}
|
|
}
|
|
|
|
// ---------- receipts ----------
|
|
static bool sendReceipt(long id, const String& result, const String& detail) {
|
|
String path = "/api/device/" + IMEI + "/command-receipts";
|
|
String body = String("{\"command_id\":") + id + ",\"result\":\"" + result + "\",\"detail\":\"" + detail + "\"}";
|
|
return HttpClient::postJson(BASE, path, body);
|
|
}
|
|
|
|
// ---------- handlers ----------
|
|
bool handleLights(const String& /*payload*/) { return true; } // placeholder
|
|
|
|
bool handleSleep(const String& payload) {
|
|
int sec = pickInt(payload, "interval_sec");
|
|
if (sec>0) { CFG.sleepSec = (uint32_t)sec; Serial.printf("Sleep sec -> %u\n", CFG.sleepSec); return true; }
|
|
return false;
|
|
}
|
|
|
|
bool handleRingFence(const String& payload) {
|
|
Serial.println("Ring fence payload: " + payload);
|
|
return true;
|
|
}
|
|
|
|
// wifi: set home connect flag
|
|
static bool handleWifi(const String& payload) {
|
|
String tgt = pickStr(payload, "target");
|
|
if (tgt.equalsIgnoreCase("home")) { CFG.atHomeRequested = true; Serial.println("WiFi home requested"); return true; }
|
|
return false;
|
|
}
|
|
|
|
// telemetry/poll periods
|
|
static bool handleTelemetrySec(const String& payload) {
|
|
int sec = pickInt(payload, "telemetry_sec");
|
|
if (sec>0) { CFG.telemetryPeriodSec=(uint32_t)sec; Serial.printf("Telemetry sec -> %u\n", CFG.telemetryPeriodSec); return true; }
|
|
return false;
|
|
}
|
|
static bool handlePollSec(const String& payload) {
|
|
int sec = pickInt(payload, "poll_sec");
|
|
if (sec>0) { CFG.commandPollSec=(uint32_t)sec; Serial.printf("Poll sec -> %u\n", CFG.commandPollSec); return true; }
|
|
return false;
|
|
}
|
|
|
|
// ---------- public API ----------
|
|
void configure(const String& baseUrl, const String& imei) {
|
|
BASE = baseUrl;
|
|
IMEI = imei;
|
|
}
|
|
|
|
void poll(uint32_t minIntervalMs) {
|
|
uint32_t now = millis();
|
|
if (minIntervalMs && (now - lastPoll < minIntervalMs)) return;
|
|
lastPoll = now;
|
|
|
|
if (BASE.isEmpty() || IMEI.isEmpty()) return;
|
|
|
|
String path = "/api/device/" + IMEI + "/commands";
|
|
String body;
|
|
bool ok = HttpClient::getJsonExact(BASE, path, body);
|
|
if (!ok || body.length()==0) return;
|
|
|
|
Serial.println("Commands JSON: " + body);
|
|
|
|
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";
|
|
} else if (c.type == "sleep") {
|
|
execOk = handleSleep(c.payload); detail = "sleep";
|
|
} else if (c.type == "ring_fence") {
|
|
execOk = handleRingFence(c.payload); detail = "ring_fence";
|
|
} else if (c.type == "wifi") {
|
|
execOk = handleWifi(c.payload); detail = "wifi";
|
|
} else if (c.type == "telemetry_sec") {
|
|
execOk = handleTelemetrySec(c.payload); detail = "telemetry_sec";
|
|
} else if (c.type == "poll_sec") {
|
|
execOk = handlePollSec(c.payload); detail = "poll_sec";
|
|
} else if (c.type == "ota") {
|
|
// reserved; readd OTAMgr later
|
|
execOk = false; detail = "ota disabled";
|
|
} else {
|
|
execOk = true; detail = "unknown";
|
|
}
|
|
sendReceipt(c.id, execOk ? "ok" : "error", detail);
|
|
}
|
|
}
|
|
|
|
} // namespace CommandMgr
|