mono/packages/kbot/tests/test-data/glob/PHApp-Profiles.cpp

402 lines
18 KiB
C++

#include "PHApp.h"
#include "config.h"
#include <ArduinoLog.h>
#include <enums.h>
#include <profiles/TemperatureProfile.h>
#ifdef ENABLE_PROFILE_TEMPERATURE
#include <LittleFS.h>
#include <ArduinoJson.h>
#endif
short PHApp::load(short val0, short val1)
{
Log.infoln(F("PHApp::load() - Loading application data..."));
Log.infoln(F("PHApp::load() - Attempting to load temperature profiles..."));
if (!LittleFS.begin(true))
{ // Ensure LittleFS is mounted (true formats if necessary)
Log.errorln(F("PHApp::load() - Failed to mount LittleFS. Cannot load profiles."));
return E_INVALID_PARAMETER; // Use invalid parameter as fallback
}
const char *filename = "/profiles/defaults.json"; // Path in LittleFS
File file = LittleFS.open(filename, "r");
if (!file)
{
Log.errorln(F("PHApp::load() - Failed to open profile file: %s"), filename);
LittleFS.end(); // Close LittleFS
return E_NOT_FOUND; // Use standard not found
}
// Increased size slightly for safety, adjust if needed
// DynamicJsonDocument doc(JSON_ARRAY_SIZE(PROFILE_TEMPERATURE_COUNT) + PROFILE_TEMPERATURE_COUNT * JSON_OBJECT_SIZE(5 + MAX_TEMP_CONTROL_POINTS));
// Replace DynamicJsonDocument with JsonDocument, letting it handle allocation.
JsonDocument doc;
// Deserialize the JSON document
DeserializationError error = deserializeJson(doc, file);
file.close(); // Close the file ASAP
LittleFS.end(); // Close LittleFS
if (error)
{
Log.errorln(F("PHApp::load() - Failed to parse profile JSON: %s"), error.c_str());
return E_INVALID_PARAMETER; // Use invalid parameter
}
// Check if the root is a JSON array
if (!doc.is<JsonArray>())
{
Log.errorln(F("PHApp::load() - Profile JSON root is not an array."));
return E_INVALID_PARAMETER; // Use invalid parameter
}
JsonArray profilesArray = doc.as<JsonArray>();
Log.infoln(F("PHApp::load() - Found %d profiles in JSON file."), profilesArray.size());
uint8_t profileIndex = 0;
for (JsonObject profileJson : profilesArray)
{
if (profileIndex >= PROFILE_TEMPERATURE_COUNT)
{
Log.warningln(F("PHApp::load() - Too many profiles in JSON (%d), only loading the first %d."), profilesArray.size(), PROFILE_TEMPERATURE_COUNT);
break;
}
if (!tempProfiles[profileIndex])
{
Log.errorln(F("PHApp::load() - TemperatureProfile slot %d is not initialized. Skipping JSON profile."), profileIndex);
// Don't increment profileIndex here, try to load next JSON into same slot if possible?
// Or increment profileIndex to align JSON index with slot index? Let's align.
profileIndex++;
continue;
}
// Assuming TemperatureProfile (or its base PlotBase) has a public method
// like loadFromJson that takes the JsonObject and calls the protected virtual load.
// We also assume it returns bool or short (E_OK for success).
Log.infoln(F("PHApp::load() - Loading JSON data into TemperatureProfile slot %d..."), profileIndex);
// Now call the protected load() directly, as PHApp is a friend
if (tempProfiles[profileIndex]->load(profileJson))
{ // returns bool
const char *name = profileJson["name"] | "Unnamed"; // Get name for logging
Log.infoln(F("PHApp::load() - Successfully loaded profile '%s' into slot %d."), name, profileIndex);
}
else
{
Log.errorln(F("PHApp::load() - Failed to load profile data into slot %d."), profileIndex);
// Decide if we should return an error or just continue loading others
// return E_INVALID_PARAMETER; // Option: Stop loading on first failure
}
Log.infoln(F("PHApp::load() - Loaded %d profiles from JSON into %d available slots and %d target registers."), profileIndex, PROFILE_TEMPERATURE_COUNT, tempProfiles[profileIndex]->getTargetRegisters().size());
profileIndex++; // Move to the next TemperatureProfile slot
}
// Handle case where JSON has fewer profiles than allocated slots
if (profileIndex < profilesArray.size())
{
Log.warningln(F("PHApp::load() - Processed %d JSON profiles but only %d slots were available/initialized."), profilesArray.size(), profileIndex);
}
else if (profileIndex < PROFILE_TEMPERATURE_COUNT)
{
Log.infoln(F("PHApp::load() - Loaded %d profiles from JSON into %d available slots."), profileIndex, PROFILE_TEMPERATURE_COUNT);
}
// --- Load Signal Plot Profiles ---
#ifdef ENABLE_PROFILE_SIGNAL_PLOT
Log.infoln(F("PHApp::load() - Attempting to load signal plot profiles..."));
if (!LittleFS.begin(true)) { // Ensure LittleFS is mounted, or re-mount if it was closed
Log.errorln(F("PHApp::load() - Failed to mount LittleFS for signal plots. Cannot load signal profiles."));
// Decide on return strategy: return E_INVALID_PARAMETER or continue?
// For now, let's log and continue, as temp profiles might have loaded.
} else {
const char *signalPlotFilename = "/profiles/signal_plots.json";
File signalPlotFile = LittleFS.open(signalPlotFilename, "r");
if (!signalPlotFile) {
Log.errorln(F("PHApp::load() - Failed to open signal plot profile file: %s. This might be normal if it doesn't exist yet."), signalPlotFilename);
} else {
JsonDocument signalPlotDoc; // Use a new document for signal plots
DeserializationError spError = deserializeJson(signalPlotDoc, signalPlotFile);
signalPlotFile.close();
if (spError) {
Log.errorln(F("PHApp::load() - Failed to parse signal plot JSON: %s"), spError.c_str());
} else if (!signalPlotDoc.is<JsonArray>()) {
Log.errorln(F("PHApp::load() - Signal plot JSON root is not an array."));
} else {
JsonArray spArray = signalPlotDoc.as<JsonArray>();
Log.infoln(F("PHApp::load() - Found %d signal plot profiles in JSON file."), spArray.size());
uint8_t spIndex = 0;
for (JsonObject spJson : spArray) {
if (spIndex >= PROFILE_SIGNAL_PLOT_COUNT) {
Log.warningln(F("PHApp::load() - Too many signal plot profiles in JSON (%d), only loading the first %d."), spArray.size(), PROFILE_SIGNAL_PLOT_COUNT);
break;
}
if (signalPlots[spIndex]) {
Log.infoln(F("PHApp::load() - Loading JSON data into SignalPlot slot %d..."), spIndex);
if (signalPlots[spIndex]->load(spJson)) {
const char *spName = spJson["name"] | "Unnamed Signal Plot";
Log.infoln(F("PHApp::load() - Successfully loaded signal plot profile '%s' into slot %d."), spName, spIndex);
} else {
Log.errorln(F("PHApp::load() - Failed to load signal plot profile data into slot %d."), spIndex);
}
} else {
Log.errorln(F("PHApp::load() - SignalPlot slot %d is not initialized. Skipping JSON profile."), spIndex);
}
spIndex++;
}
Log.infoln(F("PHApp::load() - Loaded %d signal plot profiles from JSON into %d available slots."), spIndex, PROFILE_SIGNAL_PLOT_COUNT);
}
}
LittleFS.end(); // Close LittleFS after signal plots are done
}
#endif // ENABLE_PROFILE_SIGNAL_PLOT
return E_OK;
}
#ifdef ENABLE_PROFILE_TEMPERATURE
/**
* @brief Handles GET requests to /api/v1/profiles
* Returns a list of available temperature profile slots.
*/
void PHApp::getProfilesHandler(AsyncWebServerRequest *request)
{
AsyncResponseStream *response = request->beginResponseStream("application/json");
JsonDocument doc;
JsonArray profilesArray = doc["profiles"].to<JsonArray>();
for (int i = 0; i < PROFILE_TEMPERATURE_COUNT; ++i)
{
TemperatureProfile *profile = this->tempProfiles[i];
if (profile)
{
Log.verboseln(" Processing Profile Slot %d: %s", i, profile->name.c_str());
JsonObject profileObj = profilesArray.add<JsonObject>();
profileObj["slot"] = i;
profileObj["duration"] = profile->getDuration();
profileObj["status"] = (int)profile->getCurrentStatus();
profileObj["currentTemp"] = profile->getTemperature(profile->getElapsedMs());
profileObj["name"] = profile->name;
profileObj["max"] = profile->max;
profileObj["enabled"] = profile->enabled();
profileObj["elapsed"] = profile->getElapsedMs();
profileObj["remaining"] = profile->getRemainingTime();
profileObj["signalPlot"] = profile->getSignalPlotSlotId();
JsonArray pointsArray = profileObj["controlPoints"].to<JsonArray>();
const TempControlPoint *points = profile->getTempControlPoints();
uint8_t numPoints = profile->getNumTempControlPoints();
for (uint8_t j = 0; j < numPoints; ++j)
{
JsonObject pointObj = pointsArray.add<JsonObject>();
pointObj["x"] = points[j].x;
pointObj["y"] = points[j].y;
}
JsonArray targetRegistersArray = profileObj["targetRegisters"].to<JsonArray>();
const std::vector<uint16_t> &targets = profile->getTargetRegisters();
for (uint16_t targetReg : targets)
{
targetRegistersArray.add(targetReg);
}
}
else
{
Log.warningln(" Profile slot %d is null", i);
}
}
serializeJson(doc, *response);
request->send(response);
}
/**
* @brief Handles POST requests to /api/v1/profiles/{slot}
* Updates the specified temperature 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 URL.
*/
void PHApp::setProfilesHandler(AsyncWebServerRequest *request, JsonVariant &json, int slot)
{
if (slot < 0 || slot >= PROFILE_TEMPERATURE_COUNT)
{
Log.warningln("REST: setProfileHandler - Invalid slot number %d provided.", slot);
request->send(400, "application/json", "{\"success\":false,\"error\":\"Invalid profile slot number\"}");
return;
}
// Check if the profile object exists for this slot
TemperatureProfile *targetProfile = this->tempProfiles[slot];
if (!targetProfile)
{
Log.warningln("REST: setProfileHandler - No profile found for slot %d.", slot);
request->send(404, "application/json", "{\"success\":false,\"error\":\"Profile slot not found or not initialized\"}");
return;
}
// Check if the JSON is an object
if (!json.is<JsonObject>())
{
Log.warningln("REST: setProfileHandler - 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>();
// Attempt to load the configuration into the profile object
bool success = targetProfile->load(jsonObj);
if (success)
{
Log.infoln("REST: Profile slot %d updated successfully.", slot);
// Attempt to save all profiles back to JSON
if (saveProfilesToJson()) {
Log.infoln("REST: All profiles saved to JSON successfully after update.");
request->send(200, "application/json", "{\"success\":true, \"message\":\"Profile updated and saved.\"}");
} else {
Log.errorln("REST: Profile slot %d updated, but failed to save all profiles to JSON.", slot);
request->send(500, "application/json", "{\"success\":true, \"message\":\"Profile updated but failed to save configuration.\"}"); // Send 200 as profile was updated, but indicate save error
}
}
else
{
Log.errorln("REST: Failed to update profile slot %d from JSON.", slot);
// Provide a more specific error if `load` can indicate the reason
request->send(400, "application/json", "{\"success\":false,\"error\":\"Failed to load profile data. Check format and values.\"}");
}
}
bool PHApp::saveProfilesToJson() {
Log.infoln(F("PHApp::saveProfilesToJson() - Saving all temperature profiles to JSON..."));
if (!LittleFS.begin(true)) {
Log.errorln(F("PHApp::saveProfilesToJson() - Failed to mount LittleFS. Cannot save profiles."));
return false;
}
const char *filename = "/profiles/defaults.json"; // Path in LittleFS
JsonDocument doc; // Use a single JsonDocument for the array
JsonArray profilesArray = doc.to<JsonArray>();
for (int i = 0; i < PROFILE_TEMPERATURE_COUNT; ++i) {
TemperatureProfile *profile = this->tempProfiles[i];
if (profile) {
JsonObject profileJson = profilesArray.add<JsonObject>();
profileJson["type"] = "temperature"; // Assuming this is fixed for now
profileJson["slot"] = i;
profileJson["name"] = profile->name;
profileJson["duration"] = profile->getDuration(); // Duration in ms
profileJson["max"] = profile->max;
profileJson["signalPlot"] = profile->getSignalPlotSlotId();
// controlPoints
JsonArray pointsArray = profileJson.createNestedArray("controlPoints");
const TempControlPoint *points = profile->getTempControlPoints();
uint8_t numPoints = profile->getNumTempControlPoints();
for (uint8_t j = 0; j < numPoints; ++j) {
JsonObject pointObj = pointsArray.add<JsonObject>();
pointObj["x"] = points[j].x;
pointObj["y"] = points[j].y;
}
// targetRegisters
JsonArray targetRegistersArray = profileJson.createNestedArray("targetRegisters");
const std::vector<uint16_t> &targets = profile->getTargetRegisters();
for (uint16_t targetReg : targets) {
targetRegistersArray.add(targetReg);
}
} else {
Log.warningln(F("PHApp::saveProfilesToJson() - Profile slot %d is null, skipping."), i);
}
}
File file = LittleFS.open(filename, "w"); // Open for writing, creates if not exists, truncates if exists
if (!file) {
Log.errorln(F("PHApp::saveProfilesToJson() - Failed to open profile file for writing: %s"), filename);
LittleFS.end();
return false;
}
size_t bytesWritten = serializeJson(doc, file);
file.close();
LittleFS.end();
if (bytesWritten > 0) {
Log.infoln(F("PHApp::saveProfilesToJson() - Successfully wrote %d bytes to %s"), bytesWritten, filename);
return true;
} else {
Log.errorln(F("PHApp::saveProfilesToJson() - Failed to serialize JSON or write to file: %s"), filename);
return false;
}
}
#endif // ENABLE_PROFILE_TEMPERATURE
#ifdef ENABLE_PROFILE_SIGNAL_PLOT
bool PHApp::saveSignalPlotsToJson() {
Log.infoln(F("PHApp::saveSignalPlotsToJson() - Saving all SignalPlot profiles to JSON..."));
if (!LittleFS.begin(true)) {
Log.errorln(F("PHApp::saveSignalPlotsToJson() - Failed to mount LittleFS. Cannot save profiles."));
return false;
}
const char *filename = "/profiles/signal_plots.json"; // Corrected filename
JsonDocument doc;
JsonArray profilesArray = doc.to<JsonArray>();
for (int i = 0; i < PROFILE_SIGNAL_PLOT_COUNT; ++i) {
SignalPlot *profile = this->signalPlots[i];
if (profile) {
JsonObject profileJson = profilesArray.add<JsonObject>();
profileJson["type"] = "signal";
profileJson["slot"] = i;
profileJson["name"] = profile->name;
profileJson["duration"] = profile->getDuration();
JsonArray pointsArray = profileJson.createNestedArray("controlPoints");
const S_SignalControlPoint *points = profile->getControlPoints();
uint8_t numPoints = profile->getNumControlPoints();
for (uint8_t j = 0; j < numPoints; ++j) {
JsonObject pointObj = pointsArray.add<JsonObject>();
pointObj["id"] = points[j].id;
pointObj["time"] = points[j].time;
pointObj["name"] = points[j].name;
pointObj["description"] = points[j].description;
pointObj["state"] = (int16_t)points[j].state;
pointObj["type"] = (int16_t)points[j].type;
pointObj["arg_0"] = points[j].arg_0;
pointObj["arg_1"] = points[j].arg_1;
pointObj["arg_2"] = points[j].arg_2;
}
} else {
Log.warningln(F("PHApp::saveSignalPlotsToJson() - SignalPlot slot %d is null, skipping."), i);
}
}
File file = LittleFS.open(filename, "w");
if (!file) {
Log.errorln(F("PHApp::saveSignalPlotsToJson() - Failed to open profile file for writing: %s"), filename);
LittleFS.end();
return false;
}
size_t bytesWritten = serializeJson(doc, file);
file.close();
LittleFS.end();
if (bytesWritten > 0) {
Log.infoln(F("PHApp::saveSignalPlotsToJson() - Successfully wrote %d bytes to %s"), bytesWritten, filename);
return true;
} else {
Log.errorln(F("PHApp::saveSignalPlotsToJson() - Failed to serialize JSON or write to file: %s"), filename);
return false;
}
}
#endif // ENABLE_PROFILE_SIGNAL_PLOT