feat: configurable PixelStream
This commit is contained in:
202
examples/pixelstream/PixelStreamService.cpp
Normal file
202
examples/pixelstream/PixelStreamService.cpp
Normal file
@@ -0,0 +1,202 @@
|
||||
#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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user