Files
spore/examples/neopattern/NeoPatternService.cpp

369 lines
12 KiB
C++

#include "NeoPatternService.h"
#include "spore/core/ApiServer.h"
#include "spore/util/Logging.h"
#include <ArduinoJson.h>
NeoPatternService::NeoPatternService(TaskManager& taskMgr, const NeoPixelConfig& config)
: taskManager(taskMgr),
config(config),
activePattern(NeoPatternType::RAINBOW_CYCLE),
direction(NeoDirection::FORWARD),
updateIntervalMs(config.updateInterval),
lastUpdateMs(0),
initialized(false) {
// Initialize NeoPattern
neoPattern = new NeoPattern(config.length, config.pin, NEO_GRB + NEO_KHZ800);
neoPattern->setBrightness(config.brightness);
// Initialize state
currentState.pattern = static_cast<uint>(activePattern);
currentState.color = 0xFF0000; // Red
currentState.color2 = 0x0000FF; // Blue
currentState.totalSteps = 16;
currentState.brightness = config.brightness;
// Set initial pattern
neoPattern->Color1 = currentState.color;
neoPattern->Color2 = currentState.color2;
neoPattern->TotalSteps = currentState.totalSteps;
neoPattern->ActivePattern = static_cast<::pattern>(activePattern);
neoPattern->Direction = static_cast<::direction>(direction);
registerPatterns();
registerTasks();
initialized = true;
LOG_INFO("NeoPattern", "Service initialized");
}
NeoPatternService::~NeoPatternService() {
if (neoPattern) {
delete neoPattern;
}
}
void NeoPatternService::registerEndpoints(ApiServer& api) {
// Status endpoint
api.addEndpoint("/api/neopattern/status", HTTP_GET,
[this](AsyncWebServerRequest* request) { handleStatusRequest(request); },
std::vector<ParamSpec>{});
// Patterns list endpoint
api.addEndpoint("/api/neopattern/patterns", HTTP_GET,
[this](AsyncWebServerRequest* request) { handlePatternsRequest(request); },
std::vector<ParamSpec>{});
// Control endpoint
api.addEndpoint("/api/neopattern", HTTP_POST,
[this](AsyncWebServerRequest* request) { handleControlRequest(request); },
std::vector<ParamSpec>{
ParamSpec{String("pattern"), false, String("body"), String("string"), patternNamesVector()},
ParamSpec{String("color"), false, String("body"), String("color"), {}},
ParamSpec{String("color2"), false, String("body"), String("color"), {}},
ParamSpec{String("brightness"), false, String("body"), String("numberRange"), {}, String("80")},
ParamSpec{String("total_steps"), false, String("body"), String("numberRange"), {}, String("16")},
ParamSpec{String("direction"), false, String("body"), String("string"), {String("forward"), String("reverse")}},
ParamSpec{String("interval"), false, String("body"), String("number"), {}, String("100")}
});
// State endpoint for complex state updates
api.addEndpoint("/api/neopattern/state", HTTP_POST,
[this](AsyncWebServerRequest* request) { handleStateRequest(request); },
std::vector<ParamSpec>{});
}
void NeoPatternService::handleStatusRequest(AsyncWebServerRequest* request) {
JsonDocument doc;
doc["pin"] = config.pin;
doc["length"] = config.length;
doc["brightness"] = currentState.brightness;
doc["pattern"] = currentPatternName();
doc["color"] = String(currentState.color, HEX);
doc["color2"] = String(currentState.color2, HEX);
doc["total_steps"] = currentState.totalSteps;
doc["direction"] = (direction == NeoDirection::FORWARD) ? "forward" : "reverse";
doc["interval"] = updateIntervalMs;
doc["active"] = initialized;
String json;
serializeJson(doc, json);
request->send(200, "application/json", json);
}
void NeoPatternService::handlePatternsRequest(AsyncWebServerRequest* request) {
JsonDocument doc;
JsonArray arr = doc.to<JsonArray>();
for (const auto& kv : patternUpdaters) {
arr.add(kv.first);
}
String json;
serializeJson(doc, json);
request->send(200, "application/json", json);
}
void NeoPatternService::handleControlRequest(AsyncWebServerRequest* request) {
bool updated = false;
if (request->hasParam("pattern", true)) {
String name = request->getParam("pattern", true)->value();
setPatternByName(name);
updated = true;
}
if (request->hasParam("color", true)) {
String colorStr = request->getParam("color", true)->value();
uint32_t color = parseColor(colorStr);
setColor(color);
updated = true;
}
if (request->hasParam("color2", true)) {
String colorStr = request->getParam("color2", true)->value();
uint32_t color = parseColor(colorStr);
setColor2(color);
updated = true;
}
if (request->hasParam("brightness", true)) {
int b = request->getParam("brightness", true)->value().toInt();
if (b < 0) b = 0;
if (b > 255) b = 255;
setBrightness(static_cast<uint8_t>(b));
updated = true;
}
if (request->hasParam("total_steps", true)) {
int steps = request->getParam("total_steps", true)->value().toInt();
if (steps > 0) {
setTotalSteps(static_cast<uint16_t>(steps));
updated = true;
}
}
if (request->hasParam("direction", true)) {
String dirStr = request->getParam("direction", true)->value();
NeoDirection dir = (dirStr.equalsIgnoreCase("reverse")) ? NeoDirection::REVERSE : NeoDirection::FORWARD;
setDirection(dir);
updated = true;
}
if (request->hasParam("interval", true)) {
unsigned long interval = request->getParam("interval", true)->value().toInt();
if (interval > 0) {
setUpdateInterval(interval);
updated = true;
}
}
// Return current state
JsonDocument resp;
resp["ok"] = true;
resp["pattern"] = currentPatternName();
resp["color"] = String(currentState.color, HEX);
resp["color2"] = String(currentState.color2, HEX);
resp["brightness"] = currentState.brightness;
resp["total_steps"] = currentState.totalSteps;
resp["direction"] = (direction == NeoDirection::FORWARD) ? "forward" : "reverse";
resp["interval"] = updateIntervalMs;
resp["updated"] = updated;
String json;
serializeJson(resp, json);
request->send(200, "application/json", json);
}
void NeoPatternService::handleStateRequest(AsyncWebServerRequest* request) {
if (request->contentType() != "application/json") {
request->send(400, "application/json", "{\"error\":\"Content-Type must be application/json\"}");
return;
}
// Note: AsyncWebServerRequest doesn't have getBody() method
// For now, we'll skip JSON body parsing and use form parameters
request->send(400, "application/json", "{\"error\":\"JSON body parsing not implemented\"}");
return;
// JSON body parsing not implemented for AsyncWebServerRequest
// This would need to be implemented differently
request->send(501, "application/json", "{\"error\":\"Not implemented\"}");
}
void NeoPatternService::setPattern(NeoPatternType pattern) {
activePattern = pattern;
currentState.pattern = static_cast<uint>(pattern);
neoPattern->ActivePattern = static_cast<::pattern>(pattern);
resetStateForPattern(pattern);
}
void NeoPatternService::setPatternByName(const String& name) {
NeoPatternType pattern = nameToPattern(name);
setPattern(pattern);
}
void NeoPatternService::setColor(uint32_t color) {
currentState.color = color;
neoPattern->Color1 = color;
if (activePattern == NeoPatternType::NONE) {
neoPattern->ColorSet(color);
}
}
void NeoPatternService::setColor2(uint32_t color) {
currentState.color2 = color;
neoPattern->Color2 = color;
}
void NeoPatternService::setBrightness(uint8_t brightness) {
currentState.brightness = brightness;
neoPattern->setBrightness(brightness);
neoPattern->show();
}
void NeoPatternService::setTotalSteps(uint16_t steps) {
currentState.totalSteps = steps;
neoPattern->TotalSteps = steps;
}
void NeoPatternService::setDirection(NeoDirection dir) {
direction = dir;
neoPattern->Direction = static_cast<::direction>(dir);
}
void NeoPatternService::setUpdateInterval(unsigned long interval) {
updateIntervalMs = interval;
taskManager.setTaskInterval("neopattern_update", interval);
}
void NeoPatternService::setState(const NeoPatternState& state) {
currentState = state;
// Apply state to NeoPattern
neoPattern->Index = 0;
neoPattern->Color1 = state.color;
neoPattern->Color2 = state.color2;
neoPattern->TotalSteps = state.totalSteps;
neoPattern->ActivePattern = static_cast<::pattern>(state.pattern);
neoPattern->Direction = FORWARD;
setBrightness(state.brightness);
setPattern(static_cast<NeoPatternType>(state.pattern));
}
NeoPatternState NeoPatternService::getState() const {
return currentState;
}
void NeoPatternService::registerTasks() {
taskManager.registerTask("neopattern_update", updateIntervalMs, [this]() { update(); });
taskManager.registerTask("neopattern_status_print", 10000, [this]() {
LOG_INFO("NeoPattern", "Status update");
});
}
void NeoPatternService::registerPatterns() {
// Register pattern updaters
patternUpdaters["none"] = [this]() { updateNone(); };
patternUpdaters["rainbow_cycle"] = [this]() { updateRainbowCycle(); };
patternUpdaters["theater_chase"] = [this]() { updateTheaterChase(); };
patternUpdaters["color_wipe"] = [this]() { updateColorWipe(); };
patternUpdaters["scanner"] = [this]() { updateScanner(); };
patternUpdaters["fade"] = [this]() { updateFade(); };
patternUpdaters["fire"] = [this]() { updateFire(); };
// Register name to pattern mapping
nameToPatternMap["none"] = NeoPatternType::NONE;
nameToPatternMap["rainbow_cycle"] = NeoPatternType::RAINBOW_CYCLE;
nameToPatternMap["theater_chase"] = NeoPatternType::THEATER_CHASE;
nameToPatternMap["color_wipe"] = NeoPatternType::COLOR_WIPE;
nameToPatternMap["scanner"] = NeoPatternType::SCANNER;
nameToPatternMap["fade"] = NeoPatternType::FADE;
nameToPatternMap["fire"] = NeoPatternType::FIRE;
}
std::vector<String> NeoPatternService::patternNamesVector() const {
std::vector<String> names;
names.reserve(patternUpdaters.size());
for (const auto& kv : patternUpdaters) {
names.push_back(kv.first);
}
return names;
}
String NeoPatternService::currentPatternName() const {
for (const auto& kv : nameToPatternMap) {
if (kv.second == activePattern) {
return kv.first;
}
}
return "none";
}
NeoPatternService::NeoPatternType NeoPatternService::nameToPattern(const String& name) const {
auto it = nameToPatternMap.find(name);
return (it != nameToPatternMap.end()) ? it->second : NeoPatternType::NONE;
}
void NeoPatternService::resetStateForPattern(NeoPatternType pattern) {
neoPattern->Index = 0;
neoPattern->Direction = static_cast<::direction>(direction);
neoPattern->completed = 0;
lastUpdateMs = 0;
}
uint32_t NeoPatternService::parseColor(const String& colorStr) const {
if (colorStr.startsWith("#")) {
return strtoul(colorStr.substring(1).c_str(), nullptr, 16);
} else if (colorStr.startsWith("0x") || colorStr.startsWith("0X")) {
return strtoul(colorStr.c_str(), nullptr, 16);
} else {
return strtoul(colorStr.c_str(), nullptr, 10);
}
}
void NeoPatternService::update() {
if (!initialized) return;
//unsigned long now = millis();
//if (now - lastUpdateMs < updateIntervalMs) return;
//lastUpdateMs = now;
const String name = currentPatternName();
auto it = patternUpdaters.find(name);
if (it != patternUpdaters.end()) {
it->second();
} else {
updateNone();
}
}
void NeoPatternService::updateRainbowCycle() {
neoPattern->RainbowCycleUpdate();
}
void NeoPatternService::updateTheaterChase() {
neoPattern->TheaterChaseUpdate();
}
void NeoPatternService::updateColorWipe() {
neoPattern->ColorWipeUpdate();
}
void NeoPatternService::updateScanner() {
neoPattern->ScannerUpdate();
}
void NeoPatternService::updateFade() {
neoPattern->FadeUpdate();
}
void NeoPatternService::updateFire() {
neoPattern->Fire(50, 120);
}
void NeoPatternService::updateNone() {
// For NONE pattern, just show the current color
neoPattern->ColorSet(neoPattern->Color1);
}