#include "PixelStreamService.h" #include "spore/util/Logging.h" #include PixelStreamService::PixelStreamService(NodeContext& ctx, ApiServer& apiServer, PixelStreamController* controller) : ctx(ctx), apiServer(apiServer), controller(controller) {} void PixelStreamService::registerEndpoints(ApiServer& api) { // Config endpoint for setting pixelstream configuration api.registerEndpoint("/api/pixelstream/config", HTTP_PUT, [this](AsyncWebServerRequest* request) { handleConfigRequest(request); }, std::vector{ ParamSpec{String("pin"), false, String("body"), String("number"), {}, String("")}, ParamSpec{String("pixel_count"), false, String("body"), String("number"), {}, String("")}, ParamSpec{String("brightness"), false, String("body"), String("number"), {}, String("")}, ParamSpec{String("matrix_width"), false, String("body"), String("number"), {}, String("")}, ParamSpec{String("matrix_serpentine"), false, String("body"), String("boolean"), {}, String("")}, ParamSpec{String("pixel_type"), false, String("body"), String("number"), {}, String("")} }); // Config endpoint for getting pixelstream configuration api.registerEndpoint("/api/pixelstream/config", HTTP_GET, [this](AsyncWebServerRequest* request) { handleGetConfigRequest(request); }, std::vector{}); } void PixelStreamService::registerTasks(TaskManager& taskManager) { // PixelStreamService doesn't register any tasks itself } PixelStreamConfig PixelStreamService::loadConfig() { // Initialize with proper defaults PixelStreamConfig config; config.pin = 2; config.pixelCount = 16; config.brightness = 80; config.matrixWidth = 16; config.matrixSerpentine = false; config.pixelType = NEO_GRB + NEO_KHZ800; if (!LittleFS.begin()) { LOG_WARN("PixelStream", "Failed to initialize LittleFS, using defaults"); return config; } if (!LittleFS.exists(CONFIG_FILE())) { LOG_INFO("PixelStream", "No pixelstream config file found, using defaults"); // Save defaults saveConfig(config); return config; } File file = LittleFS.open(CONFIG_FILE(), "r"); if (!file) { LOG_ERROR("PixelStream", "Failed to open config file for reading"); return config; } JsonDocument doc; DeserializationError error = deserializeJson(doc, file); file.close(); if (error) { LOG_ERROR("PixelStream", "Failed to parse config file: " + String(error.c_str())); return config; } if (doc["pin"].is()) config.pin = doc["pin"].as(); if (doc["pixel_count"].is()) config.pixelCount = doc["pixel_count"].as(); if (doc["brightness"].is()) config.brightness = doc["brightness"].as(); if (doc["matrix_width"].is()) config.matrixWidth = doc["matrix_width"].as(); if (doc["matrix_serpentine"].is()) config.matrixSerpentine = doc["matrix_serpentine"].as(); if (doc["pixel_type"].is()) config.pixelType = static_cast(doc["pixel_type"].as()); LOG_INFO("PixelStream", "Configuration loaded from " + String(CONFIG_FILE())); return config; } bool PixelStreamService::saveConfig(const PixelStreamConfig& config) { if (!LittleFS.begin()) { LOG_ERROR("PixelStream", "LittleFS not initialized, cannot save config"); return false; } File file = LittleFS.open(CONFIG_FILE(), "w"); if (!file) { LOG_ERROR("PixelStream", "Failed to open config file for writing"); return false; } JsonDocument doc; doc["pin"] = config.pin; doc["pixel_count"] = config.pixelCount; doc["brightness"] = config.brightness; doc["matrix_width"] = config.matrixWidth; doc["matrix_serpentine"] = config.matrixSerpentine; doc["pixel_type"] = static_cast(config.pixelType); size_t bytesWritten = serializeJson(doc, file); file.close(); if (bytesWritten > 0) { LOG_INFO("PixelStream", "Configuration saved to " + String(CONFIG_FILE()) + " (" + String(bytesWritten) + " bytes)"); return true; } else { LOG_ERROR("PixelStream", "Failed to write configuration to file"); return false; } } void PixelStreamService::handleConfigRequest(AsyncWebServerRequest* request) { // Load current config from file PixelStreamConfig config = loadConfig(); bool updated = false; // Handle individual form parameters if (request->hasParam("pin", true)) { String pinStr = request->getParam("pin", true)->value(); if (pinStr.length() > 0) { int pinValue = pinStr.toInt(); if (pinValue >= 0 && pinValue <= 255) { config.pin = static_cast(pinValue); updated = true; } } } if (request->hasParam("pixel_count", true)) { String countStr = request->getParam("pixel_count", true)->value(); if (countStr.length() > 0) { int countValue = countStr.toInt(); if (countValue > 0 && countValue <= 65535) { config.pixelCount = static_cast(countValue); updated = true; } } } if (request->hasParam("brightness", true)) { String brightnessStr = request->getParam("brightness", true)->value(); if (brightnessStr.length() > 0) { int brightnessValue = brightnessStr.toInt(); if (brightnessValue >= 0 && brightnessValue <= 255) { config.brightness = static_cast(brightnessValue); updated = true; } } } if (request->hasParam("matrix_width", true)) { String widthStr = request->getParam("matrix_width", true)->value(); if (widthStr.length() > 0) { int widthValue = widthStr.toInt(); if (widthValue > 0 && widthValue <= 65535) { config.matrixWidth = static_cast(widthValue); updated = true; } } } if (request->hasParam("matrix_serpentine", true)) { String serpentineStr = request->getParam("matrix_serpentine", true)->value(); config.matrixSerpentine = (serpentineStr.equalsIgnoreCase("true") || serpentineStr == "1"); updated = true; } if (request->hasParam("pixel_type", true)) { String typeStr = request->getParam("pixel_type", true)->value(); if (typeStr.length() > 0) { int typeValue = typeStr.toInt(); config.pixelType = static_cast(typeValue); updated = true; } } if (!updated) { request->send(400, "application/json", "{\"error\":\"No valid configuration fields provided\"}"); return; } // Save config to file if (saveConfig(config)) { LOG_INFO("PixelStreamService", "Configuration updated and saved to pixelstream.json"); request->send(200, "application/json", "{\"status\":\"success\",\"message\":\"Configuration updated and saved\"}"); } else { LOG_ERROR("PixelStreamService", "Failed to save configuration to file"); request->send(500, "application/json", "{\"error\":\"Failed to save configuration\"}"); } } void PixelStreamService::handleGetConfigRequest(AsyncWebServerRequest* request) { PixelStreamConfig config = loadConfig(); JsonDocument doc; doc["pin"] = config.pin; doc["pixel_count"] = config.pixelCount; doc["brightness"] = config.brightness; doc["matrix_width"] = config.matrixWidth; doc["matrix_serpentine"] = config.matrixSerpentine; doc["pixel_type"] = static_cast(config.pixelType); String json; serializeJson(doc, json); request->send(200, "application/json", json); }