From c11652c1236c3b1b9a8bdb5e0761ffc47ca54702 Mon Sep 17 00:00:00 2001 From: Patrick Balsiger Date: Sat, 20 Sep 2025 22:05:52 +0200 Subject: [PATCH] feat: optimize neopattern example --- examples/neopattern/NeoPattern.cpp | 376 ++++++++++++++++++ examples/neopattern/NeoPattern.h | 462 +++------------------- examples/neopattern/NeoPatternService.cpp | 167 ++++++-- examples/neopattern/NeoPatternService.h | 12 +- examples/neopattern/PatternRegistry.cpp | 101 +++++ examples/neopattern/PatternRegistry.h | 73 ++++ examples/neopattern/README.md | 11 +- examples/neopattern/config.h | 22 -- 8 files changed, 737 insertions(+), 487 deletions(-) create mode 100644 examples/neopattern/NeoPattern.cpp create mode 100644 examples/neopattern/PatternRegistry.cpp create mode 100644 examples/neopattern/PatternRegistry.h delete mode 100644 examples/neopattern/config.h diff --git a/examples/neopattern/NeoPattern.cpp b/examples/neopattern/NeoPattern.cpp new file mode 100644 index 0000000..9e21593 --- /dev/null +++ b/examples/neopattern/NeoPattern.cpp @@ -0,0 +1,376 @@ +#include "NeoPattern.h" + +// Constructor - calls base-class constructor to initialize strip +NeoPattern::NeoPattern(uint16_t pixels, uint8_t pin, uint8_t type, void (*callback)(int)) + : Adafruit_NeoPixel(pixels, pin, type) +{ + frameBuffer = (uint8_t *)malloc(768); + OnComplete = callback; + TotalSteps = numPixels(); + begin(); +} + +NeoPattern::NeoPattern(uint16_t pixels, uint8_t pin, uint8_t type) + : Adafruit_NeoPixel(pixels, pin, type) +{ + frameBuffer = (uint8_t *)malloc(768); + TotalSteps = numPixels(); + begin(); +} + +NeoPattern::~NeoPattern() { + if (frameBuffer) { + free(frameBuffer); + } +} + +void NeoPattern::handleStream(uint8_t *data, size_t len) +{ + //const uint16_t *data16 = (uint16_t *)data; + bufferSize = len; + memcpy(frameBuffer, data, len); +} + +void NeoPattern::drawFrameBuffer(int w, uint8_t *frame, int length) +{ + for (int i = 0; i < length; i++) + { + uint8_t r = frame[i]; + uint8_t g = frame[i + 1]; + uint8_t b = frame[i + 2]; + setPixelColor(i, r, g, b); + } +} + +void NeoPattern::onCompleteDefault(int pixels) +{ + //Serial.println("onCompleteDefault"); + // FIXME no specific code + if (ActivePattern == THEATER_CHASE) + { + return; + } + Reverse(); + //Serial.println("pattern completed"); +} + +// Increment the Index and reset at the end +void NeoPattern::Increment() +{ + completed = 0; + if (Direction == FORWARD) + { + Index++; + if (Index >= TotalSteps) + { + Index = 0; + completed = 1; + if (OnComplete != NULL) + { + OnComplete(numPixels()); // call the comlpetion callback + } + else + { + onCompleteDefault(numPixels()); + } + } + } + else // Direction == REVERSE + { + --Index; + if (Index <= 0) + { + Index = TotalSteps - 1; + completed = 1; + if (OnComplete != NULL) + { + OnComplete(numPixels()); // call the comlpetion callback + } + else + { + onCompleteDefault(numPixels()); + } + } + } +} + +// Reverse pattern direction +void NeoPattern::Reverse() +{ + if (Direction == FORWARD) + { + Direction = REVERSE; + Index = TotalSteps - 1; + } + else + { + Direction = FORWARD; + Index = 0; + } +} + +// Initialize for a RainbowCycle +void NeoPattern::RainbowCycle(uint8_t interval, direction dir) +{ + ActivePattern = RAINBOW_CYCLE; + Interval = interval; + TotalSteps = 255; + Index = 0; + Direction = dir; +} + +// Update the Rainbow Cycle Pattern +void NeoPattern::RainbowCycleUpdate() +{ + for (int i = 0; i < numPixels(); i++) + { + setPixelColor(i, Wheel(((i * 256 / numPixels()) + Index) & 255)); + } + show(); + Increment(); +} + +// Initialize for a Theater Chase +void NeoPattern::TheaterChase(uint32_t color1, uint32_t color2, uint16_t interval, direction dir) +{ + ActivePattern = THEATER_CHASE; + Interval = interval; + TotalSteps = numPixels(); + Color1 = color1; + Color2 = color2; + Index = 0; + Direction = dir; +} + +// Update the Theater Chase Pattern +void NeoPattern::TheaterChaseUpdate() +{ + for (int i = 0; i < numPixels(); i++) + { + if ((i + Index) % 3 == 0) + { + setPixelColor(i, Color1); + } + else + { + setPixelColor(i, Color2); + } + } + show(); + Increment(); +} + +// Initialize for a ColorWipe +void NeoPattern::ColorWipe(uint32_t color, uint8_t interval, direction dir) +{ + ActivePattern = COLOR_WIPE; + Interval = interval; + TotalSteps = numPixels(); + Color1 = color; + Index = 0; + Direction = dir; +} + +// Update the Color Wipe Pattern +void NeoPattern::ColorWipeUpdate() +{ + setPixelColor(Index, Color1); + show(); + Increment(); +} + +// Initialize for a SCANNNER +void NeoPattern::Scanner(uint32_t color1, uint8_t interval) +{ + ActivePattern = SCANNER; + Interval = interval; + TotalSteps = (numPixels() - 1) * 2; + Color1 = color1; + Index = 0; +} + +// Update the Scanner Pattern +void NeoPattern::ScannerUpdate() +{ + for (int i = 0; i < numPixels(); i++) + { + if (i == Index) // Scan Pixel to the right + { + setPixelColor(i, Color1); + } + else if (i == TotalSteps - Index) // Scan Pixel to the left + { + setPixelColor(i, Color1); + } + else // Fading tail + { + setPixelColor(i, DimColor(getPixelColor(i))); + } + } + show(); + Increment(); +} + +// Initialize for a Fade +void NeoPattern::Fade(uint32_t color1, uint32_t color2, uint16_t steps, uint8_t interval, direction dir) +{ + ActivePattern = FADE; + Interval = interval; + TotalSteps = steps; + Color1 = color1; + Color2 = color2; + Index = 0; + Direction = dir; +} + +// Update the Fade Pattern +void NeoPattern::FadeUpdate() +{ + // Calculate linear interpolation between Color1 and Color2 + // Optimise order of operations to minimize truncation error + uint8_t red = ((Red(Color1) * (TotalSteps - Index)) + (Red(Color2) * Index)) / TotalSteps; + uint8_t green = ((Green(Color1) * (TotalSteps - Index)) + (Green(Color2) * Index)) / TotalSteps; + uint8_t blue = ((Blue(Color1) * (TotalSteps - Index)) + (Blue(Color2) * Index)) / TotalSteps; + + ColorSet(Color(red, green, blue)); + show(); + Increment(); +} + +// Calculate 50% dimmed version of a color (used by ScannerUpdate) +uint32_t NeoPattern::DimColor(uint32_t color) +{ + // Shift R, G and B components one bit to the right + uint32_t dimColor = Color(Red(color) >> 1, Green(color) >> 1, Blue(color) >> 1); + return dimColor; +} + +// Set all pixels to a color (synchronously) +void NeoPattern::ColorSet(uint32_t color) +{ + for (int i = 0; i < numPixels(); i++) + { + setPixelColor(i, color); + } + show(); +} + +// Returns the Red component of a 32-bit color +uint8_t NeoPattern::Red(uint32_t color) +{ + return (color >> 16) & 0xFF; +} + +// Returns the Green component of a 32-bit color +uint8_t NeoPattern::Green(uint32_t color) +{ + return (color >> 8) & 0xFF; +} + +// Returns the Blue component of a 32-bit color +uint8_t NeoPattern::Blue(uint32_t color) +{ + return color & 0xFF; +} + +// Input a value 0 to 255 to get a color value. +// The colours are a transition r - g - b - back to r. +uint32_t NeoPattern::Wheel(uint8_t WheelPos) +{ + //if(WheelPos == 0) return Color(0,0,0); + WheelPos = 255 - WheelPos; + if (WheelPos < 85) + { + return Color(255 - WheelPos * 3, 0, WheelPos * 3); + } + else if (WheelPos < 170) + { + WheelPos -= 85; + return Color(0, WheelPos * 3, 255 - WheelPos * 3); + } + else + { + WheelPos -= 170; + return Color(WheelPos * 3, 255 - WheelPos * 3, 0); + } +} + +/** + * Effects from https://www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/ + */ +void NeoPattern::Fire(int Cooling, int Sparking) +{ + uint8_t heat[numPixels()]; + int cooldown; + + // Step 1. Cool down every cell a little + for (int i = 0; i < numPixels(); i++) + { + cooldown = random(0, ((Cooling * 10) / numPixels()) + 2); + + if (cooldown > heat[i]) + { + heat[i] = 0; + } + else + { + heat[i] = heat[i] - cooldown; + } + } + + // Step 2. Heat from each cell drifts 'up' and diffuses a little + for (int k = numPixels() - 1; k >= 2; k--) + { + heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2]) / 3; + } + + // Step 3. Randomly ignite new 'sparks' near the bottom + if (random(255) < Sparking) + { + int y = random(7); + heat[y] = heat[y] + random(160, 255); + //heat[y] = random(160,255); + } + + // Step 4. Convert heat to LED colors + for (int j = 0; j < numPixels(); j++) + { + setPixelHeatColor(j, heat[j]); + } + + showStrip(); +} + +void NeoPattern::setPixelHeatColor(int Pixel, uint8_t temperature) +{ + // Scale 'heat' down from 0-255 to 0-191 + uint8_t t192 = round((temperature / 255.0) * 191); + + // calculate ramp up from + uint8_t heatramp = t192 & 0x3F; // 0..63 + heatramp <<= 2; // scale up to 0..252 + + // figure out which third of the spectrum we're in: + if (t192 > 0x80) + { // hottest + setPixel(Pixel, 255, 255, heatramp); + } + else if (t192 > 0x40) + { // middle + setPixel(Pixel, 255, heatramp, 0); + } + else + { // coolest + setPixel(Pixel, heatramp, 0, 0); + } +} + +void NeoPattern::setPixel(int Pixel, uint8_t red, uint8_t green, uint8_t blue) +{ + setPixelColor(Pixel, Color(red, green, blue)); +} + +void NeoPattern::showStrip() +{ + show(); +} diff --git a/examples/neopattern/NeoPattern.h b/examples/neopattern/NeoPattern.h index 1f27ad2..505327d 100644 --- a/examples/neopattern/NeoPattern.h +++ b/examples/neopattern/NeoPattern.h @@ -56,424 +56,54 @@ class NeoPattern : public Adafruit_NeoPixel int bufferSize = 0; // Constructor - calls base-class constructor to initialize strip - NeoPattern(uint16_t pixels, uint8_t pin, uint8_t type, void (*callback)(int)) - : Adafruit_NeoPixel(pixels, pin, type) - { - frameBuffer = (uint8_t *)malloc(768); - OnComplete = callback; - TotalSteps = numPixels(); - begin(); - } + NeoPattern(uint16_t pixels, uint8_t pin, uint8_t type, void (*callback)(int)); + NeoPattern(uint16_t pixels, uint8_t pin, uint8_t type); + ~NeoPattern(); - NeoPattern(uint16_t pixels, uint8_t pin, uint8_t type) - : Adafruit_NeoPixel(pixels, pin, type) - { - frameBuffer = (uint8_t *)malloc(768); - TotalSteps = numPixels(); - begin(); - } + // Stream handling + void handleStream(uint8_t *data, size_t len); + void drawFrameBuffer(int w, uint8_t *frame, int length); - ~NeoPattern() { - if (frameBuffer) { - free(frameBuffer); - } - } - - - // Implementation starts here (inline) - void handleStream(uint8_t *data, size_t len) - { - //const uint16_t *data16 = (uint16_t *)data; - bufferSize = len; - memcpy(frameBuffer, data, len); - } - - void drawFrameBuffer(int w, uint8_t *frame, int length) - { - for (int i = 0; i < length; i++) - { - uint8_t r = frame[i]; - uint8_t g = frame[i + 1]; - uint8_t b = frame[i + 2]; - setPixelColor(i, r, g, b); - } - } - - void onCompleteDefault(int pixels) - { - //Serial.println("onCompleteDefault"); - // FIXME no specific code - if (ActivePattern == THEATER_CHASE) - { - return; - } - Reverse(); - //Serial.println("pattern completed"); - } - - // Update the pattern - void Update() - { - switch (ActivePattern) - { - case RAINBOW_CYCLE: - RainbowCycleUpdate(); - break; - case THEATER_CHASE: - TheaterChaseUpdate(); - break; - case COLOR_WIPE: - ColorWipeUpdate(); - break; - case SCANNER: - ScannerUpdate(); - break; - case FADE: - FadeUpdate(); - break; - case FIRE: - Fire(50, 120); - break; - case NONE: - // For NONE pattern, just maintain current state - break; - default: - if (bufferSize > 0) - { - drawFrameBuffer(TotalSteps, frameBuffer, bufferSize); - } - break; - } - } - - void UpdateScheduled() - { - if ((millis() - lastUpdate) > Interval) // time to update - { - lastUpdate = millis(); - Update(); - } - } - - // Increment the Index and reset at the end - void Increment() - { - completed = 0; - if (Direction == FORWARD) - { - Index++; - if (Index >= TotalSteps) - { - Index = 0; - completed = 1; - if (OnComplete != NULL) - { - OnComplete(numPixels()); // call the comlpetion callback - } - else - { - onCompleteDefault(numPixels()); - } - } - } - else // Direction == REVERSE - { - --Index; - if (Index <= 0) - { - Index = TotalSteps - 1; - completed = 1; - if (OnComplete != NULL) - { - OnComplete(numPixels()); // call the comlpetion callback - } - else - { - onCompleteDefault(numPixels()); - } - } - } - } - - // Reverse pattern direction - void Reverse() - { - if (Direction == FORWARD) - { - Direction = REVERSE; - Index = TotalSteps - 1; - } - else - { - Direction = FORWARD; - Index = 0; - } - } - - // Initialize for a RainbowCycle - void RainbowCycle(uint8_t interval, direction dir = FORWARD) - { - ActivePattern = RAINBOW_CYCLE; - Interval = interval; - TotalSteps = 255; - Index = 0; - Direction = dir; - } - - // Update the Rainbow Cycle Pattern - void RainbowCycleUpdate() - { - for (int i = 0; i < numPixels(); i++) - { - setPixelColor(i, Wheel(((i * 256 / numPixels()) + Index) & 255)); - } - show(); - Increment(); - } - - // Initialize for a Theater Chase - void TheaterChase(uint32_t color1, uint32_t color2, uint16_t interval, direction dir = FORWARD) - { - ActivePattern = THEATER_CHASE; - Interval = interval; - TotalSteps = numPixels(); - Color1 = color1; - Color2 = color2; - Index = 0; - Direction = dir; - } - - // Update the Theater Chase Pattern - void TheaterChaseUpdate() - { - for (int i = 0; i < numPixels(); i++) - { - if ((i + Index) % 3 == 0) - { - setPixelColor(i, Color1); - } - else - { - setPixelColor(i, Color2); - } - } - show(); - Increment(); - } - - // Initialize for a ColorWipe - void ColorWipe(uint32_t color, uint8_t interval, direction dir = FORWARD) - { - ActivePattern = COLOR_WIPE; - Interval = interval; - TotalSteps = numPixels(); - Color1 = color; - Index = 0; - Direction = dir; - } - - // Update the Color Wipe Pattern - void ColorWipeUpdate() - { - setPixelColor(Index, Color1); - show(); - Increment(); - } - - // Initialize for a SCANNNER - void Scanner(uint32_t color1, uint8_t interval) - { - ActivePattern = SCANNER; - Interval = interval; - TotalSteps = (numPixels() - 1) * 2; - Color1 = color1; - Index = 0; - } - - // Update the Scanner Pattern - void ScannerUpdate() - { - for (int i = 0; i < numPixels(); i++) - { - if (i == Index) // Scan Pixel to the right - { - setPixelColor(i, Color1); - } - else if (i == TotalSteps - Index) // Scan Pixel to the left - { - setPixelColor(i, Color1); - } - else // Fading tail - { - setPixelColor(i, DimColor(getPixelColor(i))); - } - } - show(); - Increment(); - } - - // Initialize for a Fade - void Fade(uint32_t color1, uint32_t color2, uint16_t steps, uint8_t interval, direction dir = FORWARD) - { - ActivePattern = FADE; - Interval = interval; - TotalSteps = steps; - Color1 = color1; - Color2 = color2; - Index = 0; - Direction = dir; - } - - // Update the Fade Pattern - void FadeUpdate() - { - // Calculate linear interpolation between Color1 and Color2 - // Optimise order of operations to minimize truncation error - uint8_t red = ((Red(Color1) * (TotalSteps - Index)) + (Red(Color2) * Index)) / TotalSteps; - uint8_t green = ((Green(Color1) * (TotalSteps - Index)) + (Green(Color2) * Index)) / TotalSteps; - uint8_t blue = ((Blue(Color1) * (TotalSteps - Index)) + (Blue(Color2) * Index)) / TotalSteps; - - ColorSet(Color(red, green, blue)); - show(); - Increment(); - } - - // Calculate 50% dimmed version of a color (used by ScannerUpdate) - uint32_t DimColor(uint32_t color) - { - // Shift R, G and B components one bit to the right - uint32_t dimColor = Color(Red(color) >> 1, Green(color) >> 1, Blue(color) >> 1); - return dimColor; - } - - // Set all pixels to a color (synchronously) - void ColorSet(uint32_t color) - { - for (int i = 0; i < numPixels(); i++) - { - setPixelColor(i, color); - } - show(); - } - - // Returns the Red component of a 32-bit color - uint8_t Red(uint32_t color) - { - return (color >> 16) & 0xFF; - } - - // Returns the Green component of a 32-bit color - uint8_t Green(uint32_t color) - { - return (color >> 8) & 0xFF; - } - - // Returns the Blue component of a 32-bit color - uint8_t Blue(uint32_t color) - { - return color & 0xFF; - } - - // Input a value 0 to 255 to get a color value. - // The colours are a transition r - g - b - back to r. - uint32_t Wheel(uint8_t WheelPos) - { - //if(WheelPos == 0) return Color(0,0,0); - WheelPos = 255 - WheelPos; - if (WheelPos < 85) - { - return Color(255 - WheelPos * 3, 0, WheelPos * 3); - } - else if (WheelPos < 170) - { - WheelPos -= 85; - return Color(0, WheelPos * 3, 255 - WheelPos * 3); - } - else - { - WheelPos -= 170; - return Color(WheelPos * 3, 255 - WheelPos * 3, 0); - } - } - /** - * Effects from https://www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/ - */ - void Fire(int Cooling, int Sparking) - { - uint8_t heat[numPixels()]; - int cooldown; - - // Step 1. Cool down every cell a little - for (int i = 0; i < numPixels(); i++) - { - cooldown = random(0, ((Cooling * 10) / numPixels()) + 2); - - if (cooldown > heat[i]) - { - heat[i] = 0; - } - else - { - heat[i] = heat[i] - cooldown; - } - } - - // Step 2. Heat from each cell drifts 'up' and diffuses a little - for (int k = numPixels() - 1; k >= 2; k--) - { - heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2]) / 3; - } - - // Step 3. Randomly ignite new 'sparks' near the bottom - if (random(255) < Sparking) - { - int y = random(7); - heat[y] = heat[y] + random(160, 255); - //heat[y] = random(160,255); - } - - // Step 4. Convert heat to LED colors - for (int j = 0; j < numPixels(); j++) - { - setPixelHeatColor(j, heat[j]); - } - - showStrip(); - } - - void setPixelHeatColor(int Pixel, uint8_t temperature) - { - // Scale 'heat' down from 0-255 to 0-191 - uint8_t t192 = round((temperature / 255.0) * 191); - - // calculate ramp up from - uint8_t heatramp = t192 & 0x3F; // 0..63 - heatramp <<= 2; // scale up to 0..252 - - // figure out which third of the spectrum we're in: - if (t192 > 0x80) - { // hottest - setPixel(Pixel, 255, 255, heatramp); - } - else if (t192 > 0x40) - { // middle - setPixel(Pixel, 255, heatramp, 0); - } - else - { // coolest - setPixel(Pixel, heatramp, 0, 0); - } - } - - void setPixel(int Pixel, uint8_t red, uint8_t green, uint8_t blue) - { - setPixelColor(Pixel, Color(red, green, blue)); - } + // Pattern completion + void onCompleteDefault(int pixels); - void showStrip() - { - show(); - } + // Pattern control + void Increment(); + void Reverse(); + + // Rainbow Cycle + void RainbowCycle(uint8_t interval, direction dir = FORWARD); + void RainbowCycleUpdate(); + + // Theater Chase + void TheaterChase(uint32_t color1, uint32_t color2, uint16_t interval, direction dir = FORWARD); + void TheaterChaseUpdate(); + + // Color Wipe + void ColorWipe(uint32_t color, uint8_t interval, direction dir = FORWARD); + void ColorWipeUpdate(); + + // Scanner + void Scanner(uint32_t color1, uint8_t interval); + void ScannerUpdate(); + + // Fade + void Fade(uint32_t color1, uint32_t color2, uint16_t steps, uint8_t interval, direction dir = FORWARD); + void FadeUpdate(); + + // Fire effect + void Fire(int Cooling, int Sparking); + void setPixelHeatColor(int Pixel, uint8_t temperature); + void setPixel(int Pixel, uint8_t red, uint8_t green, uint8_t blue); + void showStrip(); + + // Utility methods + uint32_t DimColor(uint32_t color); + void ColorSet(uint32_t color); + uint8_t Red(uint32_t color); + uint8_t Green(uint32_t color); + uint8_t Blue(uint32_t color); + uint32_t Wheel(uint8_t WheelPos); }; -#endif +#endif \ No newline at end of file diff --git a/examples/neopattern/NeoPatternService.cpp b/examples/neopattern/NeoPatternService.cpp index a76633c..99e52ff 100644 --- a/examples/neopattern/NeoPatternService.cpp +++ b/examples/neopattern/NeoPatternService.cpp @@ -86,6 +86,12 @@ void NeoPatternService::handleStatusRequest(AsyncWebServerRequest* request) { 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); @@ -94,8 +100,16 @@ void NeoPatternService::handleStatusRequest(AsyncWebServerRequest* request) { void NeoPatternService::handlePatternsRequest(AsyncWebServerRequest* request) { JsonDocument doc; JsonArray arr = doc.to(); - for (const auto& kv : patternUpdaters) { - arr.add(kv.first); + + // 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; @@ -108,8 +122,13 @@ void NeoPatternService::handleControlRequest(AsyncWebServerRequest* request) { if (request->hasParam("pattern", true)) { String name = request->getParam("pattern", true)->value(); - setPatternByName(name); - updated = true; + 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)) { @@ -195,6 +214,9 @@ void NeoPatternService::setPattern(NeoPatternType 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) { @@ -263,46 +285,89 @@ void NeoPatternService::registerTasks() { } 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; + // 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 { - std::vector names; - names.reserve(patternUpdaters.size()); - for (const auto& kv : patternUpdaters) { - names.push_back(kv.first); - } - return names; + return patternRegistry.getAllPatternNames(); } String NeoPatternService::currentPatternName() const { - for (const auto& kv : nameToPatternMap) { - if (kv.second == activePattern) { - return kv.first; - } - } - return "none"; + return patternRegistry.getPatternName(static_cast(activePattern)); } NeoPatternService::NeoPatternType NeoPatternService::nameToPattern(const String& name) const { - auto it = nameToPatternMap.find(name); - return (it != nameToPatternMap.end()) ? it->second : NeoPatternType::NONE; + uint8_t type = patternRegistry.getPatternType(name); + return static_cast(type); } void NeoPatternService::resetStateForPattern(NeoPatternType pattern) { @@ -322,6 +387,29 @@ uint32_t NeoPatternService::parseColor(const String& colorStr) const { } } +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; @@ -329,13 +417,8 @@ void NeoPatternService::update() { //if (now - lastUpdateMs < updateIntervalMs) return; //lastUpdateMs = now; - const String name = currentPatternName(); - auto it = patternUpdaters.find(name); - if (it != patternUpdaters.end()) { - it->second(); - } else { - updateNone(); - } + // Use pattern registry to execute the current pattern + patternRegistry.executePattern(static_cast(activePattern)); } void NeoPatternService::updateRainbowCycle() { diff --git a/examples/neopattern/NeoPatternService.h b/examples/neopattern/NeoPatternService.h index 9476693..3103911 100644 --- a/examples/neopattern/NeoPatternService.h +++ b/examples/neopattern/NeoPatternService.h @@ -4,6 +4,7 @@ #include "NeoPattern.h" #include "NeoPatternState.h" #include "NeoPixelConfig.h" +#include "PatternRegistry.h" #include #include @@ -70,14 +71,21 @@ private: NeoPatternType nameToPattern(const String& name) const; void resetStateForPattern(NeoPatternType pattern); uint32_t parseColor(const String& colorStr) const; + + // Pattern validation methods + bool isValidPattern(const String& name) const; + bool isValidPattern(NeoPatternType type) const; + bool patternRequiresColor2(const String& name) const; + bool patternSupportsDirection(const String& name) const; + String getPatternDescription(const String& name) const; TaskManager& taskManager; NeoPattern* neoPattern; NeoPixelConfig config; NeoPatternState currentState; - std::map> patternUpdaters; - std::map nameToPatternMap; + // Pattern registry for centralized pattern management + PatternRegistry patternRegistry; NeoPatternType activePattern; NeoDirection direction; diff --git a/examples/neopattern/PatternRegistry.cpp b/examples/neopattern/PatternRegistry.cpp new file mode 100644 index 0000000..5f06596 --- /dev/null +++ b/examples/neopattern/PatternRegistry.cpp @@ -0,0 +1,101 @@ +#include "PatternRegistry.h" + +PatternRegistry::PatternRegistry() { + // Constructor - patterns will be registered by the service +} + +void PatternRegistry::registerPattern(const PatternInfo& pattern) { + patternMap_[pattern.name] = pattern; + typeToNameMap_[pattern.type] = pattern.name; +} + +void PatternRegistry::registerPattern(const String& name, uint8_t type, const String& description, + std::function initializer, std::function updater, + bool requiresColor2, bool supportsDirection) { + PatternInfo info(name, type, description, initializer, updater, requiresColor2, supportsDirection); + registerPattern(info); +} + +const PatternInfo* PatternRegistry::getPattern(const String& name) const { + auto it = patternMap_.find(name); + return (it != patternMap_.end()) ? &it->second : nullptr; +} + +const PatternInfo* PatternRegistry::getPattern(uint8_t type) const { + auto typeIt = typeToNameMap_.find(type); + if (typeIt == typeToNameMap_.end()) { + return nullptr; + } + return getPattern(typeIt->second); +} + +String PatternRegistry::getPatternName(uint8_t type) const { + auto it = typeToNameMap_.find(type); + return (it != typeToNameMap_.end()) ? it->second : ""; +} + +uint8_t PatternRegistry::getPatternType(const String& name) const { + const PatternInfo* info = getPattern(name); + return info ? info->type : 0; +} + +std::vector PatternRegistry::getAllPatternNames() const { + std::vector names; + names.reserve(patternMap_.size()); + for (const auto& pair : patternMap_) { + names.push_back(pair.first); + } + return names; +} + +std::vector PatternRegistry::getAllPatterns() const { + std::vector patterns; + patterns.reserve(patternMap_.size()); + for (const auto& pair : patternMap_) { + patterns.push_back(pair.second); + } + return patterns; +} + +bool PatternRegistry::isValidPattern(const String& name) const { + return patternMap_.find(name) != patternMap_.end(); +} + +bool PatternRegistry::isValidPattern(uint8_t type) const { + return typeToNameMap_.find(type) != typeToNameMap_.end(); +} + +void PatternRegistry::executePattern(const String& name) const { + const PatternInfo* info = getPattern(name); + if (info && info->updater) { + info->updater(); + } +} + +void PatternRegistry::executePattern(uint8_t type) const { + const PatternInfo* info = getPattern(type); + if (info && info->updater) { + info->updater(); + } +} + +void PatternRegistry::initializePattern(const String& name) const { + const PatternInfo* info = getPattern(name); + if (info && info->initializer) { + info->initializer(); + } +} + +void PatternRegistry::initializePattern(uint8_t type) const { + const PatternInfo* info = getPattern(type); + if (info && info->initializer) { + info->initializer(); + } +} + +void PatternRegistry::updateTypeMap() { + typeToNameMap_.clear(); + for (const auto& pair : patternMap_) { + typeToNameMap_[pair.second.type] = pair.first; + } +} diff --git a/examples/neopattern/PatternRegistry.h b/examples/neopattern/PatternRegistry.h new file mode 100644 index 0000000..1484be8 --- /dev/null +++ b/examples/neopattern/PatternRegistry.h @@ -0,0 +1,73 @@ +#pragma once +#include +#include +#include +#include + +/** + * PatternInfo structure containing all information needed for a pattern + */ +struct PatternInfo { + String name; + uint8_t type; + String description; + std::function updater; + std::function initializer; + bool requiresColor2; + bool supportsDirection; + + // Default constructor for std::map compatibility + PatternInfo() : type(0), requiresColor2(false), supportsDirection(false) {} + + // Parameterized constructor + PatternInfo(const String& n, uint8_t t, const String& desc, + std::function initFunc, std::function updateFunc = nullptr, + bool needsColor2 = false, bool supportsDir = true) + : name(n), type(t), description(desc), updater(updateFunc), + initializer(initFunc), requiresColor2(needsColor2), supportsDirection(supportsDir) {} +}; + +/** + * PatternRegistry class for centralized pattern management + */ +class PatternRegistry { +public: + using PatternMap = std::map; + using PatternTypeMap = std::map; + + PatternRegistry(); + ~PatternRegistry() = default; + + // Pattern registration + void registerPattern(const PatternInfo& pattern); + void registerPattern(const String& name, uint8_t type, const String& description, + std::function initializer, std::function updater = nullptr, + bool requiresColor2 = false, bool supportsDirection = true); + + // Pattern lookup + const PatternInfo* getPattern(const String& name) const; + const PatternInfo* getPattern(uint8_t type) const; + String getPatternName(uint8_t type) const; + uint8_t getPatternType(const String& name) const; + + // Pattern enumeration + std::vector getAllPatternNames() const; + std::vector getAllPatterns() const; + const PatternMap& getPatternMap() const { return patternMap_; } + + // Pattern validation + bool isValidPattern(const String& name) const; + bool isValidPattern(uint8_t type) const; + + // Pattern execution + void executePattern(const String& name) const; + void executePattern(uint8_t type) const; + void initializePattern(const String& name) const; + void initializePattern(uint8_t type) const; + +private: + PatternMap patternMap_; + PatternTypeMap typeToNameMap_; + + void updateTypeMap(); +}; diff --git a/examples/neopattern/README.md b/examples/neopattern/README.md index febe439..fb3ec84 100644 --- a/examples/neopattern/README.md +++ b/examples/neopattern/README.md @@ -18,13 +18,14 @@ This example demonstrates how to integrate a NeoPixel pattern service with the S ## Configuration -Edit `config.h` to configure your setup: +Edit `NeoPixelConfig.h` to configure your setup: ```cpp -#define NEOPIXEL_PIN 4 // GPIO pin connected to LED strip -#define NEOPIXEL_LENGTH 8 // Number of LEDs in your strip -#define NEOPIXEL_BRIGHTNESS 100 // Initial brightness (0-255) -#define NEOPIXEL_UPDATE_INTERVAL 100 // Update interval in milliseconds +static constexpr int DEFAULT_PIN = 2; +static constexpr int DEFAULT_LENGTH = 8; +static constexpr int DEFAULT_BRIGHTNESS = 100; +static constexpr int DEFAULT_UPDATE_INTERVAL = 100; +static constexpr int DEFAULT_COLOR = 0xFF0000; // Red ``` ## API Endpoints diff --git a/examples/neopattern/config.h b/examples/neopattern/config.h deleted file mode 100644 index 5a2fadf..0000000 --- a/examples/neopattern/config.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef __NPX_CONFIG__ -#define __NPX_CONFIG__ - -// NeoPixel Configuration -#define NEOPIXEL_PIN 2 -#define NEOPIXEL_LENGTH 4 -#define NEOPIXEL_BRIGHTNESS 100 -#define NEOPIXEL_UPDATE_INTERVAL 100 - -// Spore Framework Configuration -#define SPORE_APP_NAME "neopattern" -#define SPORE_DEVICE_TYPE "led_strip" - -// Network Configuration (if needed) -#define WIFI_SSID "your_wifi_ssid" -#define WIFI_PASSWORD "your_wifi_password" - -// Debug Configuration -#define DEBUG_SERIAL_BAUD 115200 -#define DEBUG_ENABLED true - -#endif