599 lines
22 KiB
C++
599 lines
22 KiB
C++
#include "NeoPatternService.h"
|
|
#include "spore/core/ApiServer.h"
|
|
#include "spore/util/Logging.h"
|
|
#include "spore/internal/Globals.h"
|
|
#include <ArduinoJson.h>
|
|
#include <ESP8266WiFi.h>
|
|
|
|
NeoPatternService::NeoPatternService(NodeContext& ctx, TaskManager& taskMgr, const NeoPixelConfig& config)
|
|
: taskManager(taskMgr),
|
|
ctx(ctx),
|
|
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();
|
|
registerEventHandlers();
|
|
initialized = true;
|
|
|
|
LOG_INFO("NeoPattern", "Service initialized");
|
|
}
|
|
|
|
NeoPatternService::~NeoPatternService() {
|
|
if (neoPattern) {
|
|
delete neoPattern;
|
|
}
|
|
}
|
|
|
|
void NeoPatternService::registerEndpoints(ApiServer& api) {
|
|
// Status endpoint
|
|
api.registerEndpoint("/api/neopattern/status", HTTP_GET,
|
|
[this](AsyncWebServerRequest* request) { handleStatusRequest(request); },
|
|
std::vector<ParamSpec>{});
|
|
|
|
// Patterns list endpoint
|
|
api.registerEndpoint("/api/neopattern/patterns", HTTP_GET,
|
|
[this](AsyncWebServerRequest* request) { handlePatternsRequest(request); },
|
|
std::vector<ParamSpec>{});
|
|
|
|
// Control endpoint
|
|
api.registerEndpoint("/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")},
|
|
ParamSpec{String("broadcast"), false, String("body"), String("boolean"), {}}
|
|
});
|
|
|
|
// State endpoint for complex state updates
|
|
api.registerEndpoint("/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;
|
|
|
|
// 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<JsonArray>();
|
|
|
|
// Get all patterns from registry and include metadata
|
|
auto patterns = patternRegistry.getAllPatterns();
|
|
for (const auto& pattern : patterns) {
|
|
JsonObject patternObj = arr.add<JsonObject>();
|
|
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;
|
|
bool broadcast = false;
|
|
|
|
if (request->hasParam("broadcast", true)) {
|
|
String b = request->getParam("broadcast", true)->value();
|
|
broadcast = b.equalsIgnoreCase("true") || b == "1";
|
|
}
|
|
|
|
// Build JSON payload from provided params (single source of truth)
|
|
JsonDocument payload;
|
|
bool any = false;
|
|
if (request->hasParam("pattern", true)) { payload["pattern"] = request->getParam("pattern", true)->value(); any = true; }
|
|
if (request->hasParam("color", true)) { payload["color"] = request->getParam("color", true)->value(); any = true; }
|
|
if (request->hasParam("color2", true)) { payload["color2"] = request->getParam("color2", true)->value(); any = true; }
|
|
if (request->hasParam("brightness", true)) { payload["brightness"] = request->getParam("brightness", true)->value(); any = true; }
|
|
if (request->hasParam("total_steps", true)) { payload["total_steps"] = request->getParam("total_steps", true)->value(); any = true; }
|
|
if (request->hasParam("direction", true)) { payload["direction"] = request->getParam("direction", true)->value(); any = true; }
|
|
if (request->hasParam("interval", true)) { payload["interval"] = request->getParam("interval", true)->value(); any = true; }
|
|
|
|
String payloadStr;
|
|
serializeJson(payload, payloadStr);
|
|
|
|
// Always apply locally via event so we have a single codepath for updates
|
|
if (any) {
|
|
std::string ev = "api/neopattern";
|
|
String localData = payloadStr;
|
|
LOG_INFO("NeoPattern", String("Applying local api/neopattern via event payloadLen=") + String(payloadStr.length()));
|
|
ctx.fire(ev, &localData);
|
|
updated = true;
|
|
}
|
|
|
|
// Broadcast to peers if requested (delegate to core broadcast handler)
|
|
if (broadcast && any) {
|
|
JsonDocument eventDoc;
|
|
eventDoc["event"] = "api/neopattern";
|
|
eventDoc["data"] = payloadStr; // data is JSON string
|
|
|
|
String eventJson;
|
|
serializeJson(eventDoc, eventJson);
|
|
|
|
LOG_INFO("NeoPattern", String("Submitting cluster/broadcast for api/neopattern payloadLen=") + String(payloadStr.length()));
|
|
std::string ev = "cluster/broadcast";
|
|
String eventStr = eventJson;
|
|
ctx.fire(ev, &eventStr);
|
|
}
|
|
|
|
// 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::registerEventHandlers() {
|
|
ctx.on("api/neopattern", [this](void* dataPtr) {
|
|
String* jsonStr = static_cast<String*>(dataPtr);
|
|
if (!jsonStr) {
|
|
LOG_WARN("NeoPattern", "Received api/neopattern with null dataPtr");
|
|
return;
|
|
}
|
|
LOG_INFO("NeoPattern", String("Received api/neopattern event dataLen=") + String(jsonStr->length()));
|
|
JsonDocument doc;
|
|
DeserializationError err = deserializeJson(doc, *jsonStr);
|
|
if (err) {
|
|
LOG_WARN("NeoPattern", String("Failed to parse cluster/event data: ") + err.c_str());
|
|
return;
|
|
}
|
|
JsonObject obj = doc.as<JsonObject>();
|
|
bool applied = applyControlParams(obj);
|
|
if (applied) {
|
|
LOG_INFO("NeoPattern", "Applied control from cluster/event");
|
|
}
|
|
});
|
|
|
|
// Solid color event: sets all pixels to the same color
|
|
ctx.on("api/neopattern/color", [this](void* dataPtr) {
|
|
String* jsonStr = static_cast<String*>(dataPtr);
|
|
if (!jsonStr) {
|
|
LOG_WARN("NeoPattern", "Received api/neopattern/color with null dataPtr");
|
|
return;
|
|
}
|
|
JsonDocument doc;
|
|
DeserializationError err = deserializeJson(doc, *jsonStr);
|
|
if (err) {
|
|
LOG_WARN("NeoPattern", String("Failed to parse color event data: ") + err.c_str());
|
|
return;
|
|
}
|
|
JsonObject obj = doc.as<JsonObject>();
|
|
// color can be string or number
|
|
String colorStr;
|
|
if (obj["color"].is<const char*>() || obj["color"].is<String>()) {
|
|
colorStr = obj["color"].as<String>();
|
|
} else if (obj["color"].is<long>() || obj["color"].is<int>()) {
|
|
colorStr = String(obj["color"].as<long>());
|
|
} else {
|
|
LOG_WARN("NeoPattern", "api/neopattern/color missing 'color'");
|
|
return;
|
|
}
|
|
|
|
// Optional brightness
|
|
if (obj["brightness"].is<int>() || obj["brightness"].is<long>()) {
|
|
int b = obj["brightness"].as<int>();
|
|
if (b < 0) b = 0; if (b > 255) b = 255;
|
|
setBrightness(static_cast<uint8_t>(b));
|
|
}
|
|
|
|
uint32_t color = parseColor(colorStr);
|
|
setPattern(NeoPatternType::NONE);
|
|
setColor(color);
|
|
LOG_INFO("NeoPattern", String("Set solid color ") + colorStr);
|
|
});
|
|
}
|
|
|
|
bool NeoPatternService::applyControlParams(const JsonObject& obj) {
|
|
bool updated = false;
|
|
if (obj["pattern"].is<const char*>() || obj["pattern"].is<String>()) {
|
|
String name = obj["pattern"].as<String>();
|
|
if (isValidPattern(name)) {
|
|
setPatternByName(name);
|
|
updated = true;
|
|
}
|
|
}
|
|
if (obj["color"].is<const char*>() || obj["color"].is<String>() || obj["color"].is<long>() || obj["color"].is<int>()) {
|
|
String colorStr;
|
|
if (obj["color"].is<long>() || obj["color"].is<int>()) {
|
|
colorStr = String(obj["color"].as<long>());
|
|
} else {
|
|
colorStr = obj["color"].as<String>();
|
|
}
|
|
uint32_t color = parseColor(colorStr);
|
|
setColor(color);
|
|
updated = true;
|
|
}
|
|
if (obj["color2"].is<const char*>() || obj["color2"].is<String>() || obj["color2"].is<long>() || obj["color2"].is<int>()) {
|
|
String colorStr;
|
|
if (obj["color2"].is<long>() || obj["color2"].is<int>()) {
|
|
colorStr = String(obj["color2"].as<long>());
|
|
} else {
|
|
colorStr = obj["color2"].as<String>();
|
|
}
|
|
uint32_t color = parseColor(colorStr);
|
|
setColor2(color);
|
|
updated = true;
|
|
}
|
|
if (obj["brightness"].is<int>() || obj["brightness"].is<long>() || obj["brightness"].is<const char*>() || obj["brightness"].is<String>()) {
|
|
int b = 0;
|
|
if (obj["brightness"].is<int>() || obj["brightness"].is<long>()) {
|
|
b = obj["brightness"].as<int>();
|
|
} else {
|
|
b = String(obj["brightness"].as<String>()).toInt();
|
|
}
|
|
if (b < 0) {
|
|
b = 0;
|
|
}
|
|
if (b > 255) {
|
|
b = 255;
|
|
}
|
|
setBrightness(static_cast<uint8_t>(b));
|
|
updated = true;
|
|
}
|
|
if (obj["total_steps"].is<int>() || obj["total_steps"].is<long>() || obj["total_steps"].is<const char*>() || obj["total_steps"].is<String>()) {
|
|
int steps = 0;
|
|
if (obj["total_steps"].is<int>() || obj["total_steps"].is<long>()) {
|
|
steps = obj["total_steps"].as<int>();
|
|
} else {
|
|
steps = String(obj["total_steps"].as<String>()).toInt();
|
|
}
|
|
if (steps > 0) { setTotalSteps(static_cast<uint16_t>(steps)); updated = true; }
|
|
}
|
|
if (obj["direction"].is<const char*>() || obj["direction"].is<String>()) {
|
|
String dirStr = obj["direction"].as<String>();
|
|
NeoDirection dir = (dirStr.equalsIgnoreCase("reverse")) ? NeoDirection::REVERSE : NeoDirection::FORWARD;
|
|
setDirection(dir);
|
|
updated = true;
|
|
}
|
|
if (obj["interval"].is<int>() || obj["interval"].is<long>() || obj["interval"].is<const char*>() || obj["interval"].is<String>()) {
|
|
unsigned long interval = 0;
|
|
if (obj["interval"].is<int>() || obj["interval"].is<long>()) {
|
|
interval = obj["interval"].as<unsigned long>();
|
|
} else {
|
|
interval = String(obj["interval"].as<String>()).toInt();
|
|
}
|
|
if (interval > 0) { setUpdateInterval(interval); updated = true; }
|
|
}
|
|
return updated;
|
|
}
|
|
|
|
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);
|
|
|
|
// Set up pattern-specific parameters
|
|
switch (pattern) {
|
|
case NeoPatternType::RAINBOW_CYCLE:
|
|
neoPattern->RainbowCycle(updateIntervalMs, static_cast<::direction>(direction));
|
|
break;
|
|
case NeoPatternType::THEATER_CHASE:
|
|
neoPattern->TheaterChase(currentState.color, currentState.color2, updateIntervalMs, static_cast<::direction>(direction));
|
|
break;
|
|
case NeoPatternType::COLOR_WIPE:
|
|
neoPattern->ColorWipe(currentState.color, updateIntervalMs, static_cast<::direction>(direction));
|
|
break;
|
|
case NeoPatternType::SCANNER:
|
|
neoPattern->Scanner(currentState.color, updateIntervalMs);
|
|
break;
|
|
case NeoPatternType::FADE:
|
|
neoPattern->Fade(currentState.color, currentState.color2, currentState.totalSteps, updateIntervalMs, static_cast<::direction>(direction));
|
|
break;
|
|
case NeoPatternType::FIRE:
|
|
// Fire pattern doesn't need setup
|
|
break;
|
|
case NeoPatternType::NONE:
|
|
// None pattern doesn't need setup
|
|
break;
|
|
}
|
|
}
|
|
|
|
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& taskManager) {
|
|
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<uint8_t>(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<uint8_t>(NeoPatternType::RAINBOW_CYCLE),
|
|
"Rainbow cycle pattern",
|
|
nullptr, // No initializer needed, state is set up in setPattern
|
|
[this]() { updateRainbowCycle(); },
|
|
false, // doesn't require color2
|
|
true // supports direction
|
|
);
|
|
|
|
patternRegistry.registerPattern(
|
|
"theater_chase",
|
|
static_cast<uint8_t>(NeoPatternType::THEATER_CHASE),
|
|
"Theater chase pattern",
|
|
nullptr, // No initializer needed, state is set up in setPattern
|
|
[this]() { updateTheaterChase(); },
|
|
true, // requires color2
|
|
true // supports direction
|
|
);
|
|
|
|
patternRegistry.registerPattern(
|
|
"color_wipe",
|
|
static_cast<uint8_t>(NeoPatternType::COLOR_WIPE),
|
|
"Color wipe pattern",
|
|
nullptr, // No initializer needed, state is set up in setPattern
|
|
[this]() { updateColorWipe(); },
|
|
false, // doesn't require color2
|
|
true // supports direction
|
|
);
|
|
|
|
patternRegistry.registerPattern(
|
|
"scanner",
|
|
static_cast<uint8_t>(NeoPatternType::SCANNER),
|
|
"Scanner pattern",
|
|
nullptr, // No initializer needed, state is set up in setPattern
|
|
[this]() { updateScanner(); },
|
|
false, // doesn't require color2
|
|
false // doesn't support direction
|
|
);
|
|
|
|
patternRegistry.registerPattern(
|
|
"fade",
|
|
static_cast<uint8_t>(NeoPatternType::FADE),
|
|
"Fade pattern",
|
|
nullptr, // No initializer needed, state is set up in setPattern
|
|
[this]() { updateFade(); },
|
|
true, // requires color2
|
|
true // supports direction
|
|
);
|
|
|
|
patternRegistry.registerPattern(
|
|
"fire",
|
|
static_cast<uint8_t>(NeoPatternType::FIRE),
|
|
"Fire effect pattern",
|
|
nullptr, // No initializer needed, state is set up in setPattern
|
|
[this]() { updateFire(); },
|
|
false, // doesn't require color2
|
|
false // doesn't support direction
|
|
);
|
|
}
|
|
|
|
std::vector<String> NeoPatternService::patternNamesVector() const {
|
|
return patternRegistry.getAllPatternNames();
|
|
}
|
|
|
|
String NeoPatternService::currentPatternName() const {
|
|
return patternRegistry.getPatternName(static_cast<uint8_t>(activePattern));
|
|
}
|
|
|
|
NeoPatternService::NeoPatternType NeoPatternService::nameToPattern(const String& name) const {
|
|
uint8_t type = patternRegistry.getPatternType(name);
|
|
return static_cast<NeoPatternType>(type);
|
|
}
|
|
|
|
void NeoPatternService::resetStateForPattern(NeoPatternType pattern) {
|
|
neoPattern->Index = 0;
|
|
neoPattern->Direction = static_cast<::direction>(direction);
|
|
neoPattern->completed = 0;
|
|
// Don't reset lastUpdateMs to 0, keep the current timing
|
|
}
|
|
|
|
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<uint8_t>(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<uint8_t>(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);
|
|
}
|