CommandMgr: add wifi home flag + sleep interval command; control loop uses CFG.atHomeRequested, CFG.sleepSec
This commit is contained in:
@ -1,7 +1,10 @@
|
|||||||
#include "CommandMgr.hpp"
|
#include "CommandMgr.hpp"
|
||||||
#include "HttpClient.hpp"
|
#include "HttpClient.hpp"
|
||||||
|
#include "AppConfig.hpp"
|
||||||
|
extern AppConfig CFG;
|
||||||
|
|
||||||
namespace {
|
namespace
|
||||||
|
{
|
||||||
String BASE;
|
String BASE;
|
||||||
String IMEI;
|
String IMEI;
|
||||||
uint32_t lastPoll = 0;
|
uint32_t lastPoll = 0;
|
||||||
@ -10,97 +13,134 @@ namespace {
|
|||||||
// Extract array of objects from a JSON string like: [{"id":..,"type":"..","payload":{...}}, ...]
|
// 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.
|
// 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\":";
|
String pat = "\"id\":";
|
||||||
int k = obj.indexOf(pat);
|
int k = obj.indexOf(pat);
|
||||||
if (k < 0) return 0;
|
if (k < 0)
|
||||||
|
return 0;
|
||||||
k += pat.length();
|
k += pat.length();
|
||||||
int e = k;
|
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();
|
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 + "\":";
|
String pat = "\"" + key + "\":";
|
||||||
int k = obj.indexOf(pat);
|
int k = obj.indexOf(pat);
|
||||||
if (k < 0) return "";
|
if (k < 0)
|
||||||
|
return "";
|
||||||
k += pat.length();
|
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
|
// read quoted string
|
||||||
int end = obj.indexOf('\"', k);
|
int end = obj.indexOf('\"', k);
|
||||||
if (end <= k) return "";
|
if (end <= k)
|
||||||
|
return "";
|
||||||
return obj.substring(k, end);
|
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.
|
// payload is often an object. Find "payload": and read balanced braces.
|
||||||
String pat = "\"payload\":";
|
String pat = "\"payload\":";
|
||||||
int k = obj.indexOf(pat);
|
int k = obj.indexOf(pat);
|
||||||
if (k < 0) return "";
|
if (k < 0)
|
||||||
|
return "";
|
||||||
k += pat.length();
|
k += pat.length();
|
||||||
while (k < (int)obj.length() && (obj[k]==' ')) k++;
|
while (k < (int)obj.length() && (obj[k] == ' '))
|
||||||
if (k >= (int)obj.length()) return "";
|
k++;
|
||||||
if (obj[k] == '{') {
|
if (k >= (int)obj.length())
|
||||||
|
return "";
|
||||||
|
if (obj[k] == '{')
|
||||||
|
{
|
||||||
int depth = 0;
|
int depth = 0;
|
||||||
int i = k;
|
int i = k;
|
||||||
for (; i < (int)obj.length(); ++i) {
|
for (; i < (int)obj.length(); ++i)
|
||||||
if (obj[i] == '{') depth++;
|
{
|
||||||
else if (obj[i] == '}') {
|
if (obj[i] == '{')
|
||||||
|
depth++;
|
||||||
|
else if (obj[i] == '}')
|
||||||
|
{
|
||||||
depth--;
|
depth--;
|
||||||
if (depth == 0) { i++; break; }
|
if (depth == 0)
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return obj.substring(k, i);
|
return obj.substring(k, i);
|
||||||
} else if (obj[k] == '\"') {
|
}
|
||||||
int end = obj.indexOf('\"', k+1);
|
else if (obj[k] == '\"')
|
||||||
if (end > k) return obj.substring(k+1, end);
|
{
|
||||||
|
int end = obj.indexOf('\"', k + 1);
|
||||||
|
if (end > k)
|
||||||
|
return obj.substring(k + 1, end);
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Very naive split into objects. Works if server returns a small array.
|
// Very naive split into objects. Works if server returns a small array.
|
||||||
void parseArray(const String& json, std::vector<CommandMgr::Command>& out) {
|
void parseArray(const String &json, std::vector<CommandMgr::Command> &out)
|
||||||
|
{
|
||||||
String s = json;
|
String s = json;
|
||||||
s.trim();
|
s.trim();
|
||||||
if (!s.startsWith("[")) return;
|
if (!s.startsWith("["))
|
||||||
|
return;
|
||||||
// strip [ and ]
|
// 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
|
// crude split on "},{" boundaries
|
||||||
int start = 0;
|
int start = 0;
|
||||||
while (start < (int)s.length()) {
|
while (start < (int)s.length())
|
||||||
|
{
|
||||||
int next = s.indexOf("},{", start);
|
int next = s.indexOf("},{", start);
|
||||||
String obj;
|
String obj;
|
||||||
if (next == -1) {
|
if (next == -1)
|
||||||
|
{
|
||||||
obj = s.substring(start);
|
obj = s.substring(start);
|
||||||
obj.trim();
|
obj.trim();
|
||||||
if (obj.startsWith("{") && obj.endsWith("}")) {
|
if (obj.startsWith("{") && obj.endsWith("}"))
|
||||||
|
{
|
||||||
// ok
|
// ok
|
||||||
}
|
}
|
||||||
else if (!obj.isEmpty()) {
|
else if (!obj.isEmpty())
|
||||||
|
{
|
||||||
// Try pad
|
// Try pad
|
||||||
if (!obj.startsWith("{")) obj = "{" + obj;
|
if (!obj.startsWith("{"))
|
||||||
if (!obj.endsWith("}")) obj += "}";
|
obj = "{" + obj;
|
||||||
|
if (!obj.endsWith("}"))
|
||||||
|
obj += "}";
|
||||||
}
|
}
|
||||||
start = s.length();
|
start = s.length();
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
obj = s.substring(start, next + 1); // include the trailing }
|
obj = s.substring(start, next + 1); // include the trailing }
|
||||||
if (!obj.startsWith("{")) obj = "{" + obj;
|
if (!obj.startsWith("{"))
|
||||||
|
obj = "{" + obj;
|
||||||
start = next + 2; // position after },
|
start = next + 2; // position after },
|
||||||
}
|
}
|
||||||
|
|
||||||
obj.trim();
|
obj.trim();
|
||||||
if (obj.length() == 0) continue;
|
if (obj.length() == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
CommandMgr::Command c;
|
CommandMgr::Command c;
|
||||||
c.id = extractId(obj);
|
c.id = extractId(obj);
|
||||||
c.type = extractString(obj, "type");
|
c.type = extractString(obj, "type");
|
||||||
c.payload = extractPayload(obj);
|
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
|
// POST /api/device/{imei}/command-receipts
|
||||||
String path = "/api/device/" + IMEI + "/command-receipts";
|
String path = "/api/device/" + IMEI + "/command-receipts";
|
||||||
String body = String("{\"command_id\":") + id + ",\"result\":\"" + result + "\",\"detail\":\"" + detail + "\"}";
|
String body = String("{\"command_id\":") + id + ",\"result\":\"" + result + "\",\"detail\":\"" + detail + "\"}";
|
||||||
@ -109,76 +149,134 @@ namespace {
|
|||||||
|
|
||||||
} // anon
|
} // anon
|
||||||
|
|
||||||
namespace CommandMgr {
|
namespace CommandMgr
|
||||||
|
{
|
||||||
|
|
||||||
void configure(const String& baseUrl, const String& imei) {
|
void configure(const String &baseUrl, const String &imei)
|
||||||
|
{
|
||||||
BASE = baseUrl;
|
BASE = baseUrl;
|
||||||
IMEI = imei;
|
IMEI = imei;
|
||||||
}
|
}
|
||||||
|
|
||||||
void poll(uint32_t minIntervalMs) {
|
void poll(uint32_t minIntervalMs)
|
||||||
|
{
|
||||||
uint32_t now = millis();
|
uint32_t now = millis();
|
||||||
if (now - lastPoll < minIntervalMs) return;
|
if (now - lastPoll < minIntervalMs)
|
||||||
|
return;
|
||||||
lastPoll = now;
|
lastPoll = now;
|
||||||
|
|
||||||
if (BASE.isEmpty() || IMEI.isEmpty()) return;
|
if (BASE.isEmpty() || IMEI.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
String path = "/api/device/" + IMEI + "/commands";
|
String path = "/api/device/" + IMEI + "/commands";
|
||||||
String body;
|
String body;
|
||||||
|
|
||||||
|
String fullUrl = BASE + path; // e.g., "http://laravel-server.lab.audasmedia.com.au" + "/api/device/IMEI/commands"
|
||||||
|
|
||||||
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::getJsonToFile(fullUrl, body);
|
|
||||||
|
|
||||||
bool ok = HttpClient::getJsonExact(BASE, path, body);
|
bool ok = HttpClient::getJsonExact(BASE, path, body);
|
||||||
Serial.println("Commands JSON: " + body);
|
Serial.println("Commands JSON: " + body);
|
||||||
// TODO: replace with proper JSON parsing later
|
// TODO: replace with proper JSON parsing later
|
||||||
// Crude parse: split by "},{" for now and extract id/type/payload as before.
|
// Crude parse: split by "},{" for now and extract id/type/payload as before.
|
||||||
if (!ok || body.length()==0) return;
|
if (!ok || body.length() == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
std::vector<Command> cmds;
|
std::vector<Command> cmds;
|
||||||
parseArray(body, cmds);
|
parseArray(body, cmds);
|
||||||
for (auto& c : cmds) {
|
for (auto &c : cmds)
|
||||||
|
{
|
||||||
bool execOk = false;
|
bool execOk = false;
|
||||||
String detail = "";
|
String detail = "";
|
||||||
|
|
||||||
if (c.type == "lights") {
|
if (c.type == "lights")
|
||||||
|
{
|
||||||
execOk = handleLights(c.payload);
|
execOk = handleLights(c.payload);
|
||||||
detail = "lights handled";
|
detail = "lights handled";
|
||||||
} else if (c.type == "sleep") {
|
}
|
||||||
execOk = handleSleep(c.payload);
|
else if (c.type == "sleep")
|
||||||
detail = "sleep handled";
|
|
||||||
} else if (c.type == "ring_fence") {
|
{
|
||||||
|
// 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);
|
execOk = handleRingFence(c.payload);
|
||||||
detail = "ring fence handled";
|
detail = "ring fence handled";
|
||||||
} else {
|
}
|
||||||
|
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
|
execOk = true; // unknown; mark ok but do nothing
|
||||||
detail = "unknown type";
|
detail = "unknown type";
|
||||||
}
|
}
|
||||||
|
|
||||||
sendReceipt(c.id, execOk ? "ok" : "error", detail);
|
sendReceipt(c.id, execOk ? "ok" : "error", detail);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool handleLights(const String& payload) {
|
bool handleLights(const String &payload)
|
||||||
|
{
|
||||||
// TODO: parse {"on":true} and toggle GPIO via your GpioCtrl later
|
// TODO: parse {"on":true} and toggle GPIO via your GpioCtrl later
|
||||||
Serial.println("handleLights payload: " + payload);
|
Serial.println("handleLights payload: " + payload);
|
||||||
bool on = (payload.indexOf("\"on\":true") != -1);
|
bool on = (payload.indexOf("\"on\":true") != -1);
|
||||||
GpioCtrl::setLight(on);
|
GpioCtrl::setLight(on);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool handleSleep(const String& payload) {
|
bool handleSleep(const String &payload)
|
||||||
|
{
|
||||||
// TODO: parse {"interval_sec":120}
|
// TODO: parse {"interval_sec":120}
|
||||||
Serial.println("handleSleep payload: " + payload);
|
Serial.println("handleSleep payload: " + payload);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool handleRingFence(const String& payload) {
|
bool handleRingFence(const String &payload)
|
||||||
|
{
|
||||||
// TODO: parse and apply ring fence
|
// TODO: parse and apply ring fence
|
||||||
Serial.println("handleRingFence payload: " + payload);
|
Serial.println("handleRingFence payload: " + payload);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// } // namespace CommandMgr
|
// } // namespace CommandMgr
|
||||||
Reference in New Issue
Block a user