#include "NeoPatternService.h" #include "spore/core/ApiServer.h" #include "spore/util/Logging.h" #include 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(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{}); // Patterns list endpoint api.addEndpoint("/api/neopattern/patterns", HTTP_GET, [this](AsyncWebServerRequest* request) { handlePatternsRequest(request); }, std::vector{}); // Control endpoint api.addEndpoint("/api/neopattern", HTTP_POST, [this](AsyncWebServerRequest* request) { handleControlRequest(request); }, std::vector{ ParamSpec{String("pattern"), false, String("body"), String("string"), patternNamesVector()}, ParamSpec{String("color"), false, String("body"), String("string"), {}}, ParamSpec{String("color2"), false, String("body"), String("string"), {}}, ParamSpec{String("brightness"), false, String("body"), String("number"), {}, String("100")}, ParamSpec{String("total_steps"), false, String("body"), String("number"), {}, 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{}); } 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(); 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(b)); updated = true; } if (request->hasParam("total_steps", true)) { int steps = request->getParam("total_steps", true)->value().toInt(); if (steps > 0) { setTotalSteps(static_cast(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(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(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 NeoPatternService::patternNamesVector() const { std::vector 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); }