#include "NeoPatternService.h" #include "spore/core/ApiServer.h" NeoPatternService::NeoPatternService(TaskManager& taskMgr, uint16_t numPixels, uint8_t pin, uint8_t type) : taskManager(taskMgr), pixels(numPixels, pin, type), updateIntervalMs(100), brightness(48) { pixels.setBrightness(brightness); pixels.show(); registerTasks(); setPatternByName("rainbow_cycle"); } void NeoPatternService::registerEndpoints(ApiServer& api) { api.addEndpoint("/api/neopattern/status", HTTP_GET, [this](AsyncWebServerRequest* request) { handleStatusRequest(request); }, std::vector{}); api.addEndpoint("/api/neopattern/patterns", HTTP_GET, [this](AsyncWebServerRequest* request) { handlePatternsRequest(request); }, std::vector{}); api.addEndpoint("/api/neopattern", HTTP_POST, [this](AsyncWebServerRequest* request) { handleControlRequest(request); }, std::vector{ ParamSpec{String("pattern"), false, String("body"), String("string"), patternNamesVector()}, ParamSpec{String("interval_ms"), false, String("body"), String("number"), {}, String("100")}, ParamSpec{String("brightness"), false, String("body"), String("number"), {}, String("50")}, ParamSpec{String("color"), false, String("body"), String("color"), {}}, ParamSpec{String("color2"), false, String("body"), String("color"), {}}, ParamSpec{String("r"), false, String("body"), String("number"), {}}, ParamSpec{String("g"), false, String("body"), String("number"), {}}, ParamSpec{String("b"), false, String("body"), String("number"), {}}, ParamSpec{String("r2"), false, String("body"), String("number"), {}}, ParamSpec{String("g2"), false, String("body"), String("number"), {}}, ParamSpec{String("b2"), false, String("body"), String("number"), {}}, ParamSpec{String("total_steps"), false, String("body"), String("number"), {}}, ParamSpec{String("direction"), false, String("body"), String("string"), {String("forward"), String("reverse")}} }); } void NeoPatternService::handleStatusRequest(AsyncWebServerRequest* request) { JsonDocument doc; doc["pin"] = pixels.getPin(); doc["count"] = pixels.numPixels(); doc["interval_ms"] = updateIntervalMs; doc["brightness"] = brightness; doc["pattern"] = currentPatternName(); doc["total_steps"] = pixels.TotalSteps; doc["color1"] = pixels.Color1; doc["color2"] = pixels.Color2; String json; serializeJson(doc, json); request->send(200, "application/json", json); } void NeoPatternService::handlePatternsRequest(AsyncWebServerRequest* request) { JsonDocument doc; JsonArray arr = doc.to(); for (auto& kv : patternSetters) arr.add(kv.first); String json; serializeJson(doc, json); request->send(200, "application/json", json); } void NeoPatternService::handleControlRequest(AsyncWebServerRequest* request) { if (request->hasParam("pattern", true)) { String name = request->getParam("pattern", true)->value(); setPatternByName(name); } if (request->hasParam("interval_ms", true)) { unsigned long v = request->getParam("interval_ms", true)->value().toInt(); if (v < 1) v = 1; updateIntervalMs = v; taskManager.setTaskInterval("neopattern_update", updateIntervalMs); } if (request->hasParam("brightness", true)) { int b = request->getParam("brightness", true)->value().toInt(); if (b < 0) b = 0; if (b > 255) b = 255; setBrightness((uint8_t)b); } // Accept packed color ints or r,g,b triplets if (request->hasParam("color", true)) { pixels.Color1 = (uint32_t)strtoul(request->getParam("color", true)->value().c_str(), nullptr, 0); } if (request->hasParam("color2", true)) { pixels.Color2 = (uint32_t)strtoul(request->getParam("color2", true)->value().c_str(), nullptr, 0); } if (request->hasParam("r", true) || request->hasParam("g", true) || request->hasParam("b", true)) { int r = request->hasParam("r", true) ? request->getParam("r", true)->value().toInt() : 0; int g = request->hasParam("g", true) ? request->getParam("g", true)->value().toInt() : 0; int b = request->hasParam("b", true) ? request->getParam("b", true)->value().toInt() : 0; pixels.Color1 = pixels.Color(r, g, b); } if (request->hasParam("r2", true) || request->hasParam("g2", true) || request->hasParam("b2", true)) { int r = request->hasParam("r2", true) ? request->getParam("r2", true)->value().toInt() : 0; int g = request->hasParam("g2", true) ? request->getParam("g2", true)->value().toInt() : 0; int b = request->hasParam("b2", true) ? request->getParam("b2", true)->value().toInt() : 0; pixels.Color2 = pixels.Color(r, g, b); } if (request->hasParam("total_steps", true)) { pixels.TotalSteps = request->getParam("total_steps", true)->value().toInt(); } if (request->hasParam("direction", true)) { String dir = request->getParam("direction", true)->value(); if (dir.equalsIgnoreCase("forward")) pixels.Direction = FORWARD; else if (dir.equalsIgnoreCase("reverse")) pixels.Direction = REVERSE; } JsonDocument resp; resp["ok"] = true; resp["pattern"] = currentPatternName(); resp["interval_ms"] = updateIntervalMs; resp["brightness"] = brightness; String json; serializeJson(resp, json); request->send(200, "application/json", json); } void NeoPatternService::setBrightness(uint8_t b) { brightness = b; pixels.setBrightness(brightness); pixels.show(); } void NeoPatternService::registerTasks() { taskManager.registerTask("neopattern_update", updateIntervalMs, [this]() { update(); }); taskManager.registerTask("neopattern_status_print", 10000, [this]() { Serial.printf("[NeoPattern] pattern=%s interval=%lu ms brightness=%u\n", currentPatternName().c_str(), updateIntervalMs, brightness); }); } void NeoPatternService::registerPatterns() { patternSetters["off"] = [this]() { pixels.ActivePattern = NONE; }; patternSetters["rainbow_cycle"] = [this]() { pixels.RainbowCycle(updateIntervalMs); }; patternSetters["theater_chase"] = [this]() { pixels.TheaterChase(pixels.Color1 ? pixels.Color1 : pixels.Color(127,127,127), pixels.Color2, updateIntervalMs); }; patternSetters["color_wipe"] = [this]() { pixels.ColorWipe(pixels.Color1 ? pixels.Color1 : pixels.Color(255,0,0), updateIntervalMs); }; patternSetters["scanner"] = [this]() { pixels.Scanner(pixels.Color1 ? pixels.Color1 : pixels.Color(0,0,255), updateIntervalMs); }; patternSetters["fade"] = [this]() { pixels.Fade(pixels.Color1, pixels.Color2, pixels.TotalSteps ? pixels.TotalSteps : 32, updateIntervalMs); }; patternSetters["fire"] = [this]() { pixels.ActivePattern = FIRE; pixels.Interval = updateIntervalMs; }; } std::vector NeoPatternService::patternNamesVector() { if (patternSetters.empty()) registerPatterns(); std::vector v; v.reserve(patternSetters.size()); for (const auto& kv : patternSetters) v.push_back(kv.first); return v; } String NeoPatternService::currentPatternName() { switch (pixels.ActivePattern) { case NONE: return String("off"); case RAINBOW_CYCLE: return String("rainbow_cycle"); case THEATER_CHASE: return String("theater_chase"); case COLOR_WIPE: return String("color_wipe"); case SCANNER: return String("scanner"); case FADE: return String("fade"); case FIRE: return String("fire"); } return String("off"); } void NeoPatternService::setPatternByName(const String& name) { if (patternSetters.empty()) registerPatterns(); auto it = patternSetters.find(name); if (it != patternSetters.end()) { pixels.Index = 0; pixels.Direction = FORWARD; it->second(); } } void NeoPatternService::update() { pixels.Update(); }