#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("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{}); } 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; // Add pattern metadata String currentPattern = currentPatternName(); doc["pattern_description"] = getPatternDescription(currentPattern); doc["pattern_requires_color2"] = patternRequiresColor2(currentPattern); doc["pattern_supports_direction"] = patternSupportsDirection(currentPattern); String json; serializeJson(doc, json); request->send(200, "application/json", json); } void NeoPatternService::handlePatternsRequest(AsyncWebServerRequest* request) { JsonDocument doc; JsonArray arr = doc.to(); // Get all patterns from registry and include metadata auto patterns = patternRegistry.getAllPatterns(); for (const auto& pattern : patterns) { JsonObject patternObj = arr.add(); patternObj["name"] = pattern.name; patternObj["type"] = pattern.type; patternObj["description"] = pattern.description; patternObj["requires_color2"] = pattern.requiresColor2; patternObj["supports_direction"] = pattern.supportsDirection; } 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(); if (isValidPattern(name)) { setPatternByName(name); updated = true; } else { // Invalid pattern name - could add error handling here LOG_WARN("NeoPattern", "Invalid pattern name: " + name); } } 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); // Initialize the pattern using the registry patternRegistry.initializePattern(static_cast(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 all patterns with their metadata and callbacks patternRegistry.registerPattern( "none", static_cast(NeoPatternType::NONE), "No pattern - solid color", [this]() { /* No initialization needed */ }, [this]() { updateNone(); }, false, // doesn't require color2 false // doesn't support direction ); patternRegistry.registerPattern( "rainbow_cycle", static_cast(NeoPatternType::RAINBOW_CYCLE), "Rainbow cycle pattern", [this]() { neoPattern->RainbowCycle(updateIntervalMs, static_cast<::direction>(direction)); }, [this]() { updateRainbowCycle(); }, false, // doesn't require color2 true // supports direction ); patternRegistry.registerPattern( "theater_chase", static_cast(NeoPatternType::THEATER_CHASE), "Theater chase pattern", [this]() { neoPattern->TheaterChase(currentState.color, currentState.color2, updateIntervalMs, static_cast<::direction>(direction)); }, [this]() { updateTheaterChase(); }, true, // requires color2 true // supports direction ); patternRegistry.registerPattern( "color_wipe", static_cast(NeoPatternType::COLOR_WIPE), "Color wipe pattern", [this]() { neoPattern->ColorWipe(currentState.color, updateIntervalMs, static_cast<::direction>(direction)); }, [this]() { updateColorWipe(); }, false, // doesn't require color2 true // supports direction ); patternRegistry.registerPattern( "scanner", static_cast(NeoPatternType::SCANNER), "Scanner pattern", [this]() { neoPattern->Scanner(currentState.color, updateIntervalMs); }, [this]() { updateScanner(); }, false, // doesn't require color2 false // doesn't support direction ); patternRegistry.registerPattern( "fade", static_cast(NeoPatternType::FADE), "Fade pattern", [this]() { neoPattern->Fade(currentState.color, currentState.color2, currentState.totalSteps, updateIntervalMs, static_cast<::direction>(direction)); }, [this]() { updateFade(); }, true, // requires color2 true // supports direction ); patternRegistry.registerPattern( "fire", static_cast(NeoPatternType::FIRE), "Fire effect pattern", [this]() { neoPattern->Fire(50, 120); }, [this]() { updateFire(); }, false, // doesn't require color2 false // doesn't support direction ); } std::vector NeoPatternService::patternNamesVector() const { return patternRegistry.getAllPatternNames(); } String NeoPatternService::currentPatternName() const { return patternRegistry.getPatternName(static_cast(activePattern)); } NeoPatternService::NeoPatternType NeoPatternService::nameToPattern(const String& name) const { uint8_t type = patternRegistry.getPatternType(name); return static_cast(type); } 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); } } bool NeoPatternService::isValidPattern(const String& name) const { return patternRegistry.isValidPattern(name); } bool NeoPatternService::isValidPattern(NeoPatternType type) const { return patternRegistry.isValidPattern(static_cast(type)); } bool NeoPatternService::patternRequiresColor2(const String& name) const { const PatternInfo* info = patternRegistry.getPattern(name); return info ? info->requiresColor2 : false; } bool NeoPatternService::patternSupportsDirection(const String& name) const { const PatternInfo* info = patternRegistry.getPattern(name); return info ? info->supportsDirection : false; } String NeoPatternService::getPatternDescription(const String& name) const { const PatternInfo* info = patternRegistry.getPattern(name); return info ? info->description : ""; } void NeoPatternService::update() { if (!initialized) return; //unsigned long now = millis(); //if (now - lastUpdateMs < updateIntervalMs) return; //lastUpdateMs = now; // Use pattern registry to execute the current pattern patternRegistry.executePattern(static_cast(activePattern)); } 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); }