mono/packages/kbot/tests/test-data/glob/PHAppWeb.cpp

464 lines
21 KiB
C++

#include "PHApp.h"
#include <components/RestServer.h>
#include <ESPAsyncWebServer.h>
short PHApp::registerRoutes(RESTServer *instance)
{
#ifdef ENABLE_PLUNGER
instance->server.on("/api/v1/plunger/settings", HTTP_GET, [instance](AsyncWebServerRequest *request)
{
Component* comp = instance->owner->byId(COMPONENT_KEY_PLUNGER);
if (!comp) {
request->send(404, "application/json", "{\"success\":false,\"error\":\"Plunger component not found\"}");
return;
}
Plunger* plunger = static_cast<Plunger*>(comp);
AsyncResponseStream *response = request->beginResponseStream("application/json");
JsonDocument doc;
plunger->getSettingsJson(doc);
serializeJson(doc, *response);
request->send(response); });
AsyncCallbackJsonWebHandler *setPlungerSettingsHandler = new AsyncCallbackJsonWebHandler("/api/v1/plunger/settings",
[instance](AsyncWebServerRequest *request, JsonVariant &json)
{
Component *comp = instance->owner->byId(COMPONENT_KEY_PLUNGER);
if (!comp)
{
request->send(404, "application/json", "{\"success\":false,\"error\":\"Plunger component not found\"}");
return;
}
Plunger *plunger = static_cast<Plunger *>(comp);
if (!json.is<JsonObject>())
{
request->send(400, "application/json", "{\"success\":false,\"error\":\"Invalid JSON payload: Expected an object.\"}");
return;
}
JsonObject jsonObj = json.as<JsonObject>();
if (plunger->updateSettingsFromJson(jsonObj))
{
request->send(200, "application/json", "{\"success\":true,\"message\":\"Plunger settings updated and saved.\"}");
}
else
{
request->send(500, "application/json", "{\"success\":false,\"error\":\"Failed to update or save Plunger settings.\"}");
}
});
setPlungerSettingsHandler->setMethod(HTTP_POST);
instance->server.addHandler(setPlungerSettingsHandler);
instance->server.on("/api/v1/plunger/settings/load-defaults", HTTP_POST, [instance](AsyncWebServerRequest *request)
{
Component* comp = instance->owner->byId(COMPONENT_KEY_PLUNGER);
if (!comp) {
request->send(404, "application/json", "{\"success\":false,\"error\":\"Plunger component not found\"}");
return;
}
Plunger* plunger = static_cast<Plunger*>(comp);
if (plunger->loadDefaultSettings()) {
request->send(200, "application/json", "{\"success\":true,\"message\":\"Plunger default settings loaded and applied to operational settings.\"}");
} else {
request->send(500, "application/json", "{\"success\":false,\"error\":\"Failed to load default settings or save them to operational path.\"}");
} });
#endif
#ifdef ENABLE_PROFILE_TEMPERATURE
// --- Temperature Profile Routes ---
instance->server.on("/api/v1/profiles", HTTP_GET, [this](AsyncWebServerRequest *request)
{ this->getProfilesHandler(request); });
// Handler for POST /api/v1/profiles
// The slot is now taken from the JSON payload.
AsyncCallbackJsonWebHandler* postProfileHandler = new AsyncCallbackJsonWebHandler("/api/v1/profiles",
[this](AsyncWebServerRequest *request, JsonVariant &json) {
if (!json.is<JsonObject>() || !json["slot"].is<int>()) {
request->send(400, "application/json", "{\"success\":false,\"error\":\"Invalid payload: Must be JSON object containing an integer 'slot' field.\"}");
return;
}
int slot = json["slot"].as<int>();
// Basic validation for slot number (e.g., non-negative)
// You might want to add an upper bound check against PROFILE_TEMPERATURE_COUNT if it's accessible here
// or rely on setProfilesHandler to do more thorough validation.
if (slot < 0) {
request->send(400, "application/json", "{\"success\":false,\"error\":\"Invalid profile slot number in payload.\"}");
return;
}
// Call the actual handler, passing the parsed JSON and extracted slot number
this->setProfilesHandler(request, json, slot);
});
postProfileHandler->setMethod(HTTP_POST); // Ensure it's set for POST
instance->server.addHandler(postProfileHandler);
#endif
#ifdef ENABLE_PROFILE_SIGNAL_PLOT
// --- Signal Plot Profile Routes ---
instance->server.on("/api/v1/signalplots", HTTP_GET, [this](AsyncWebServerRequest *request)
{ this->getSignalPlotsHandler(request); });
AsyncCallbackJsonWebHandler* postSignalPlotHandler = new AsyncCallbackJsonWebHandler("/api/v1/signalplots",
[this](AsyncWebServerRequest *request, JsonVariant &json) {
if (!json.is<JsonObject>() || !json["slot"].is<int>()) {
request->send(400, "application/json", "{\"success\":false,\"error\":\"Invalid payload: Must be JSON object containing an integer 'slot' field.\"}");
return;
}
int slot = json["slot"].as<int>();
if (slot < 0) {
request->send(400, "application/json", "{\"success\":false,\"error\":\"Invalid profile slot number in payload.\"}");
return;
}
this->setSignalPlotsHandler(request, json, slot);
});
postSignalPlotHandler->setMethod(HTTP_POST);
instance->server.addHandler(postSignalPlotHandler);
#endif
#ifdef ENABLE_WEBSERVER_WIFI_SETTINGS
instance->server.on("/api/network/settings", HTTP_GET, std::bind(&PHApp::handleGetNetworkSettings, this, std::placeholders::_1));
AsyncCallbackJsonWebHandler *setNetworkSettingsHandler = new AsyncCallbackJsonWebHandler("/api/network/settings",
std::bind(&PHApp::handleSetNetworkSettings, this, std::placeholders::_1, std::placeholders::_2));
setNetworkSettingsHandler->setMethod(HTTP_POST);
instance->server.addHandler(setNetworkSettingsHandler);
#endif
instance->server.on("/api/v1/system/logs", HTTP_GET, [this](AsyncWebServerRequest *request)
{ this->getSystemLogsHandler(request); });
instance->server.on("/api/v1/methods", HTTP_GET, [this](AsyncWebServerRequest *request)
{ this->getBridgeMethodsHandler(request); });
AsyncCallbackJsonWebHandler* postMethodHandler = new AsyncCallbackJsonWebHandler("/api/v1/methods",
[this](AsyncWebServerRequest *request, JsonVariant &json) {
if (!json.is<JsonObject>() || !json["command"].is<String>()) {
request->send(400, "application/json", "{\"success\":false,\"error\":\"Invalid payload: Must be JSON object containing a 'command' string field.\"}");
return;
}
String cmdStr = json["command"].as<String>();
Log.infoln("REST: Received method call command: %s", cmdStr.c_str());
Bridge* bridge = static_cast<Bridge*>(byId(COMPONENT_KEY_MB_BRIDGE));
if (!bridge) {
Log.errorln("REST: Bridge component not found!");
request->send(500, "application/json", "{\"success\":false,\"error\":\"Bridge component not found\"}");
return;
}
CommandMessage msg;
if (!msg.parse(cmdStr)) {
Log.errorln("REST: Failed to parse command string: %s", cmdStr.c_str());
request->send(400, "application/json", "{\"success\":false,\"error\":\"Invalid command string format\"}");
return;
}
short result = bridge->onMessage(msg.id, msg.verb, msg.flags, msg.payload, this);
if (result == E_OK) {
request->send(200, "application/json", "{\"success\":true,\"message\":\"Method executed successfully\"}");
} else {
Log.errorln("REST: Method execution failed with error %d", result);
request->send(500, "application/json", "{\"success\":false,\"error\":\"Method execution failed\"}");
}
});
postMethodHandler->setMethod(HTTP_POST);
instance->server.addHandler(postMethodHandler);
return E_OK;
}
#ifdef ENABLE_WEBSERVER_WIFI_SETTINGS
void PHApp::handleGetNetworkSettings(AsyncWebServerRequest *request)
{
JsonDocument doc = wifiSettings.toJSON();
String responseStr;
serializeJson(doc, responseStr);
request->send(200, "application/json", responseStr);
}
void PHApp::handleSetNetworkSettings(AsyncWebServerRequest *request, JsonVariant &json)
{
if (!json.is<JsonObject>())
{
request->send(400, "application/json", "{\"success\":false,\"error\":\"Invalid JSON payload: Expected an object.\"}");
return;
}
JsonObject jsonObj = json.as<JsonObject>();
// Attempt to save the settings
short saveResult = saveNetworkSettings(jsonObj);
if (saveResult != E_OK)
{
Log.errorln("REST: Failed to save network settings, error: %d", saveResult);
request->send(500, "application/json", "{\"success\":false,\"error\":\"Failed to save network settings to persistent storage.\"}");
return;
}
// Attempt to load and apply the new settings immediately
short loadResult = loadNetworkSettings();
if (loadResult != E_OK && loadResult != E_NOT_FOUND)
{ // E_NOT_FOUND is ok if we just saved it, means it was applied from the save buffer
Log.warningln("REST: Issue loading network settings after save, error: %d. Settings might not be immediately active.", loadResult);
// Decide if this is a critical failure for the response
}
request->send(200, "application/json", "{\"success\":true,\"message\":\"Network settings saved. Device will attempt to apply them. A restart might be required for all changes to take effect.\"}");
}
void PHApp::getSystemLogsHandler(AsyncWebServerRequest *request)
{
String levelStr = "verbose"; // Default to verbose
if (request->hasParam("level"))
{
levelStr = request->getParam("level")->value();
}
// Map string log levels to their integer values
int requestedLevel = LOG_LEVEL_VERBOSE; // Default to verbose
if (levelStr == "none")
requestedLevel = LOG_LEVEL_SILENT;
else if (levelStr == "error")
requestedLevel = LOG_LEVEL_ERROR;
else if (levelStr == "warning")
requestedLevel = LOG_LEVEL_WARNING;
else if (levelStr == "notice")
requestedLevel = LOG_LEVEL_NOTICE;
else if (levelStr == "trace")
requestedLevel = LOG_LEVEL_TRACE;
else if (levelStr == "verbose")
requestedLevel = LOG_LEVEL_VERBOSE;
else
{
request->send(400, "application/json", "{\"error\":\"Invalid log level\"}");
return;
}
String response;
// Get logs using existing logBuffer implementation in PHApp
std::vector<String> logSnapshot = getLogSnapshot();
// Begin JSON array response
response = "[";
bool first = true;
// Function to escape special characters in JSON
auto escapeJSON = [](const String &str) -> String
{
String result;
for (size_t i = 0; i < str.length(); i++)
{
char c = str.charAt(i);
switch (c)
{
case '"':
result += "\\\"";
break;
case '\\':
result += "\\\\";
break;
case '\b':
result += "\\b";
break;
case '\f':
result += "\\f";
break;
case '\n':
result += "\\n";
break;
case '\r':
result += "\\r";
break;
case '\t':
result += "\\t";
break;
default:
if (c < ' ')
{
char hex[7];
snprintf(hex, sizeof(hex), "\\u%04x", c);
result += hex;
}
else
{
result += c;
}
}
}
return result;
};
// Function to determine log level from a log line
auto getLogLevel = [](const String &line) -> int
{
if (line.startsWith("E:"))
return LOG_LEVEL_ERROR;
if (line.startsWith("W:"))
return LOG_LEVEL_WARNING;
if (line.startsWith("N:"))
return LOG_LEVEL_NOTICE;
if (line.startsWith("T:"))
return LOG_LEVEL_TRACE;
if (line.startsWith("V:"))
return LOG_LEVEL_VERBOSE;
if (line.startsWith("I:"))
return LOG_LEVEL_INFO;
return LOG_LEVEL_VERBOSE; // Default to verbose if no prefix found
};
// Add each log entry to the response if it meets the requested level
for (const auto &logLine : logSnapshot)
{
int lineLevel = getLogLevel(logLine);
if (lineLevel <= requestedLevel)
{
if (!first)
response += ",";
response += "\"" + escapeJSON(logLine) + "\"";
first = false;
}
}
response += "]";
request->send(200, "application/json", response);
}
#endif
#ifdef ENABLE_PROFILE_SIGNAL_PLOT
/**
* @brief Handles GET requests to /api/v1/signalplots
* Returns a list of available signal plot profiles.
*/
void PHApp::getSignalPlotsHandler(AsyncWebServerRequest *request)
{
AsyncResponseStream *response = request->beginResponseStream("application/json");
JsonDocument doc;
JsonArray profilesArray = doc["signalplots"].to<JsonArray>();
for (int i = 0; i < PROFILE_SIGNAL_PLOT_COUNT; ++i)
{
SignalPlot *profile = this->signalPlots[i];
if (profile)
{
Log.verboseln(" Processing SignalPlot Slot %d: %s", i, profile->name.c_str());
JsonObject profileObj = profilesArray.add<JsonObject>();
profileObj["slot"] = i;
profileObj["name"] = profile->name;
profileObj["duration"] = profile->getDuration();
profileObj["status"] = (int)profile->getCurrentStatus();
profileObj["enabled"] = profile->enabled();
profileObj["elapsed"] = profile->getElapsedMs();
profileObj["remaining"] = profile->getRemainingTime();
JsonArray pointsArray = profileObj["controlPoints"].to<JsonArray>();
const S_SignalControlPoint *points = profile->getControlPoints();
uint8_t numPoints = profile->getNumControlPoints();
for (uint8_t j = 0; j < numPoints; ++j)
{
const S_SignalControlPoint& cp = points[j];
JsonObject pointObj = pointsArray.add<JsonObject>();
pointObj["id"] = cp.id;
pointObj["time"] = cp.time;
pointObj["name"] = cp.name;
pointObj["description"] = cp.description;
pointObj["state"] = (int16_t)cp.state;
pointObj["type"] = (int16_t)cp.type;
pointObj["arg_0"] = cp.arg_0;
pointObj["arg_1"] = cp.arg_1;
pointObj["arg_2"] = cp.arg_2;
}
}
else
{
Log.warningln(" SignalPlot slot %d is null", i);
}
}
serializeJson(doc, *response);
request->send(response);
}
/**
* @brief Handles POST requests to /api/v1/signalplots (slot from payload)
* Updates the specified signal plot profile using the provided JSON data.
*
* @param request The incoming web request.
* @param json The parsed JSON body from the request.
* @param slot The profile slot number extracted from the payload.
*/
void PHApp::setSignalPlotsHandler(AsyncWebServerRequest *request, JsonVariant &json, int slot)
{
if (slot < 0 || slot >= PROFILE_SIGNAL_PLOT_COUNT)
{
Log.warningln("REST: setSignalPlotsHandler - Invalid slot number %d provided.", slot);
request->send(400, "application/json", "{\"success\":false,\"error\":\"Invalid profile slot number\"}");
return;
}
SignalPlot *targetProfile = this->signalPlots[slot];
if (!targetProfile)
{
Log.warningln("REST: setSignalPlotsHandler - No profile found for slot %d.", slot);
request->send(404, "application/json", "{\"success\":false,\"error\":\"Profile slot not found or not initialized\"}");
return;
}
if (!json.is<JsonObject>())
{
Log.warningln("REST: setSignalPlotsHandler - Invalid JSON payload (not an object) for slot %d.", slot);
request->send(400, "application/json", "{\"success\":false,\"error\":\"Invalid JSON payload: must be an object.\"}");
return;
}
JsonObject jsonObj = json.as<JsonObject>();
bool success = targetProfile->load(jsonObj);
if (success)
{
Log.infoln("REST: SignalPlot slot %d updated successfully.", slot);
if (saveSignalPlotsToJson()) {
Log.infoln("REST: All SignalPlot profiles saved to JSON successfully after update.");
request->send(200, "application/json", "{\"success\":true, \"message\":\"SignalPlot profile updated and saved.\"}");
} else {
Log.errorln("REST: SignalPlot slot %d updated, but failed to save all profiles to JSON.", slot);
request->send(500, "application/json", "{\"success\":true, \"message\":\"SignalPlot profile updated but failed to save configuration.\"}");
}
}
else
{
Log.errorln("REST: Failed to update SignalPlot slot %d from JSON.", slot);
request->send(400, "application/json", "{\"success\":false,\"error\":\"Failed to load SignalPlot profile data. Check format and values.\"}");
}
}
#endif // ENABLE_PROFILE_SIGNAL_PLOT
void PHApp::getBridgeMethodsHandler(AsyncWebServerRequest *request)
{
AsyncResponseStream *response = request->beginResponseStream("application/json");
JsonDocument doc;
JsonArray methodsArray = doc.to<JsonArray>();
Bridge *bridge = static_cast<Bridge *>(byId(COMPONENT_KEY_MB_BRIDGE));
if (!bridge)
{
Log.errorln(F("REST: Bridge component not found!"));
request->send(500, "application/json", "{\"success\":false,\"error\":\"Bridge component not found\"}");
return;
}
const Vector<SComponentInfo *> &componentList = bridge->getComponentList();
for (size_t i = 0; i < componentList.size(); ++i)
{
SComponentInfo *compInfo = componentList.at(i);
if (compInfo && compInfo->instance)
{
Component *component = static_cast<Component *>(compInfo->instance);
JsonObject methodObj = methodsArray.add<JsonObject>();
methodObj["id"] = compInfo->key;
methodObj["component"] = component->name;
methodObj["method"] = compInfo->methodName;
}
}
serializeJson(doc, *response);
request->send(response);
}