diff --git a/src/core/CommandMgr.cpp b/src/core/CommandMgr.cpp index 8d1da96..760bf72 100644 --- a/src/core/CommandMgr.cpp +++ b/src/core/CommandMgr.cpp @@ -1,7 +1,10 @@ #include "CommandMgr.hpp" #include "HttpClient.hpp" +#include "AppConfig.hpp" +extern AppConfig CFG; -namespace { +namespace +{ String BASE; String IMEI; uint32_t lastPoll = 0; @@ -10,97 +13,134 @@ namespace { // 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) { + long extractId(const String &obj) + { String pat = "\"id\":"; int k = obj.indexOf(pat); - if (k < 0) return 0; + if (k < 0) + return 0; k += pat.length(); int e = k; - while (e < (int)obj.length() && (isDigit(obj[e]) || obj[e]=='-')) e++; + 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 extractString(const String &obj, const String &key) + { String pat = "\"" + key + "\":"; int k = obj.indexOf(pat); - if (k < 0) return ""; + if (k < 0) + return ""; k += pat.length(); - while (k < (int)obj.length() && (obj[k]==' ' || obj[k]=='\"')) k++; + while (k < (int)obj.length() && (obj[k] == ' ' || obj[k] == '\"')) + k++; // read quoted string int end = obj.indexOf('\"', k); - if (end <= k) return ""; + if (end <= k) + return ""; return obj.substring(k, end); } - String extractPayload(const String& obj) { + 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 ""; + if (k < 0) + return ""; k += pat.length(); - while (k < (int)obj.length() && (obj[k]==' ')) k++; - if (k >= (int)obj.length()) return ""; - if (obj[k] == '{') { + 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] == '}') { + for (; i < (int)obj.length(); ++i) + { + if (obj[i] == '{') + depth++; + else if (obj[i] == '}') + { depth--; - if (depth == 0) { i++; break; } + 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); + } + 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& out) { + void parseArray(const String &json, std::vector &out) + { String s = json; s.trim(); - if (!s.startsWith("[")) return; + if (!s.startsWith("[")) + return; // strip [ and ] - if (s.length() >= 2 && s.endsWith("]")) s = s.substring(1, s.length()-1); + 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()) { + while (start < (int)s.length()) + { int next = s.indexOf("},{", start); String obj; - if (next == -1) { + if (next == -1) + { obj = s.substring(start); obj.trim(); - if (obj.startsWith("{") && obj.endsWith("}")) { + if (obj.startsWith("{") && obj.endsWith("}")) + { // ok } - else if (!obj.isEmpty()) { + else if (!obj.isEmpty()) + { // Try pad - if (!obj.startsWith("{")) obj = "{" + obj; - if (!obj.endsWith("}")) obj += "}"; + if (!obj.startsWith("{")) + obj = "{" + obj; + if (!obj.endsWith("}")) + obj += "}"; } start = s.length(); - } else { + } + else + { obj = s.substring(start, next + 1); // include the trailing } - if (!obj.startsWith("{")) obj = "{" + obj; + if (!obj.startsWith("{")) + obj = "{" + obj; start = next + 2; // position after }, } obj.trim(); - if (obj.length() == 0) continue; + 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); + if (c.id != 0 && c.type.length()) + out.push_back(c); } } - bool sendReceipt(long id, const String& result, const String& detail) { + 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 + "\"}"; @@ -109,76 +149,134 @@ namespace { } // anon -namespace CommandMgr { +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 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); + void configure(const String &baseUrl, const String &imei) + { + BASE = baseUrl; + IMEI = imei; } -} -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; -} + void poll(uint32_t minIntervalMs) + { + uint32_t now = millis(); + if (now - lastPoll < minIntervalMs) + return; + lastPoll = now; -bool handleSleep(const String& payload) { - // TODO: parse {"interval_sec":120} - Serial.println("handleSleep payload: " + payload); - return true; -} + if (BASE.isEmpty() || IMEI.isEmpty()) + return; -bool handleRingFence(const String& payload) { - // TODO: parse and apply ring fence - Serial.println("handleRingFence payload: " + payload); - return true; -} + String path = "/api/device/" + IMEI + "/commands"; + String body; -// } // namespace CommandMgr \ No newline at end of file + 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 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") + + { + // parse {"interval_sec":N} + int pos = c.payload.indexOf("\"interval_sec\":"); + if (pos != -1) + { + int i = pos + 15; + while (i < (int)c.payload.length() && (c.payload[i] == ' ' || c.payload[i] == ':')) + i++; + int j = i; + while (j < (int)c.payload.length() && isDigit(c.payload[j])) + j++; + if (j > i) + { + CFG.sleepSec = c.payload.substring(i, j).toInt(); + execOk = true; + detail = "sleep interval updated"; + } + else + { + execOk = false; + detail = "bad interval"; + } + } + else + { + execOk = false; + detail = "missing interval"; + } + } + else if (c.type == "ring_fence") + { + execOk = handleRingFence(c.payload); + detail = "ring fence handled"; + } + else if (c.type == "wifi") + { + // payload expected: {"target":"home"} + extern AppConfig CFG; // declare once in CommandMgr.cpp top: extern AppConfig CFG; + if (c.payload.indexOf("\"home\"") != -1 || c.payload.indexOf("\"target\":\"home\"") != -1) + { + CFG.atHomeRequested = true; + execOk = true; + detail = "wifi home requested"; + } + else + { + execOk = false; + detail = "unknown wifi target"; + } + } + 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 \ No newline at end of file