Files
spore/examples/pixelstream/PixelStreamService.cpp
2025-10-28 21:05:02 +01:00

203 lines
7.9 KiB
C++

#include "PixelStreamService.h"
#include "spore/util/Logging.h"
#include <Adafruit_NeoPixel.h>
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>{
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<ParamSpec>{});
}
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<uint8_t>()) config.pin = doc["pin"].as<uint8_t>();
if (doc["pixel_count"].is<uint16_t>()) config.pixelCount = doc["pixel_count"].as<uint16_t>();
if (doc["brightness"].is<uint8_t>()) config.brightness = doc["brightness"].as<uint8_t>();
if (doc["matrix_width"].is<uint16_t>()) config.matrixWidth = doc["matrix_width"].as<uint16_t>();
if (doc["matrix_serpentine"].is<bool>()) config.matrixSerpentine = doc["matrix_serpentine"].as<bool>();
if (doc["pixel_type"].is<uint8_t>()) config.pixelType = static_cast<neoPixelType>(doc["pixel_type"].as<uint8_t>());
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<uint8_t>(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<uint8_t>(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<uint16_t>(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<uint8_t>(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<uint16_t>(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<neoPixelType>(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<uint8_t>(config.pixelType);
String json;
serializeJson(doc, json);
request->send(200, "application/json", json);
}