feat: optimize neopattern example

This commit is contained in:
2025-09-20 22:05:52 +02:00
parent 51d4d4bc94
commit c11652c123
8 changed files with 737 additions and 487 deletions

View File

@@ -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();
}

View File

@@ -56,424 +56,54 @@ class NeoPattern : public Adafruit_NeoPixel
int bufferSize = 0; int bufferSize = 0;
// Constructor - calls base-class constructor to initialize strip // Constructor - calls base-class constructor to initialize strip
NeoPattern(uint16_t pixels, uint8_t pin, uint8_t type, void (*callback)(int)) NeoPattern(uint16_t pixels, uint8_t pin, uint8_t type, void (*callback)(int));
: Adafruit_NeoPixel(pixels, pin, type) NeoPattern(uint16_t pixels, uint8_t pin, uint8_t type);
{ ~NeoPattern();
frameBuffer = (uint8_t *)malloc(768);
OnComplete = callback;
TotalSteps = numPixels();
begin();
}
NeoPattern(uint16_t pixels, uint8_t pin, uint8_t type) // Stream handling
: Adafruit_NeoPixel(pixels, pin, type) void handleStream(uint8_t *data, size_t len);
{ void drawFrameBuffer(int w, uint8_t *frame, int length);
frameBuffer = (uint8_t *)malloc(768);
TotalSteps = numPixels();
begin();
}
~NeoPattern() { // Pattern completion
if (frameBuffer) { void onCompleteDefault(int pixels);
free(frameBuffer);
}
}
// Pattern control
void Increment();
void Reverse();
// Implementation starts here (inline) // Rainbow Cycle
void handleStream(uint8_t *data, size_t len) void RainbowCycle(uint8_t interval, direction dir = FORWARD);
{ void RainbowCycleUpdate();
//const uint16_t *data16 = (uint16_t *)data;
bufferSize = len;
memcpy(frameBuffer, data, len);
}
void drawFrameBuffer(int w, uint8_t *frame, int length) // Theater Chase
{ void TheaterChase(uint32_t color1, uint32_t color2, uint16_t interval, direction dir = FORWARD);
for (int i = 0; i < length; i++) void TheaterChaseUpdate();
{
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) // Color Wipe
{ void ColorWipe(uint32_t color, uint8_t interval, direction dir = FORWARD);
//Serial.println("onCompleteDefault"); void ColorWipeUpdate();
// FIXME no specific code
if (ActivePattern == THEATER_CHASE)
{
return;
}
Reverse();
//Serial.println("pattern completed");
}
// Update the pattern // Scanner
void Update() void Scanner(uint32_t color1, uint8_t interval);
{ void ScannerUpdate();
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() // Fade
{ void Fade(uint32_t color1, uint32_t color2, uint16_t steps, uint8_t interval, direction dir = FORWARD);
if ((millis() - lastUpdate) > Interval) // time to update void FadeUpdate();
{
lastUpdate = millis();
Update();
}
}
// Increment the Index and reset at the end // Fire effect
void Increment() void Fire(int Cooling, int Sparking);
{ void setPixelHeatColor(int Pixel, uint8_t temperature);
completed = 0; void setPixel(int Pixel, uint8_t red, uint8_t green, uint8_t blue);
if (Direction == FORWARD) void showStrip();
{
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 // Utility methods
void Reverse() uint32_t DimColor(uint32_t color);
{ void ColorSet(uint32_t color);
if (Direction == FORWARD) uint8_t Red(uint32_t color);
{ uint8_t Green(uint32_t color);
Direction = REVERSE; uint8_t Blue(uint32_t color);
Index = TotalSteps - 1; uint32_t Wheel(uint8_t WheelPos);
}
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));
}
void showStrip()
{
show();
}
}; };
#endif #endif

View File

@@ -86,6 +86,12 @@ void NeoPatternService::handleStatusRequest(AsyncWebServerRequest* request) {
doc["interval"] = updateIntervalMs; doc["interval"] = updateIntervalMs;
doc["active"] = initialized; 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; String json;
serializeJson(doc, json); serializeJson(doc, json);
request->send(200, "application/json", json); request->send(200, "application/json", json);
@@ -94,8 +100,16 @@ void NeoPatternService::handleStatusRequest(AsyncWebServerRequest* request) {
void NeoPatternService::handlePatternsRequest(AsyncWebServerRequest* request) { void NeoPatternService::handlePatternsRequest(AsyncWebServerRequest* request) {
JsonDocument doc; JsonDocument doc;
JsonArray arr = doc.to<JsonArray>(); JsonArray arr = doc.to<JsonArray>();
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<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; String json;
@@ -108,8 +122,13 @@ void NeoPatternService::handleControlRequest(AsyncWebServerRequest* request) {
if (request->hasParam("pattern", true)) { if (request->hasParam("pattern", true)) {
String name = request->getParam("pattern", true)->value(); String name = request->getParam("pattern", true)->value();
setPatternByName(name); if (isValidPattern(name)) {
updated = true; 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)) { if (request->hasParam("color", true)) {
@@ -195,6 +214,9 @@ void NeoPatternService::setPattern(NeoPatternType pattern) {
currentState.pattern = static_cast<uint>(pattern); currentState.pattern = static_cast<uint>(pattern);
neoPattern->ActivePattern = static_cast<::pattern>(pattern); neoPattern->ActivePattern = static_cast<::pattern>(pattern);
resetStateForPattern(pattern); resetStateForPattern(pattern);
// Initialize the pattern using the registry
patternRegistry.initializePattern(static_cast<uint8_t>(pattern));
} }
void NeoPatternService::setPatternByName(const String& name) { void NeoPatternService::setPatternByName(const String& name) {
@@ -263,46 +285,89 @@ void NeoPatternService::registerTasks() {
} }
void NeoPatternService::registerPatterns() { void NeoPatternService::registerPatterns() {
// Register pattern updaters // Register all patterns with their metadata and callbacks
patternUpdaters["none"] = [this]() { updateNone(); }; patternRegistry.registerPattern(
patternUpdaters["rainbow_cycle"] = [this]() { updateRainbowCycle(); }; "none",
patternUpdaters["theater_chase"] = [this]() { updateTheaterChase(); }; static_cast<uint8_t>(NeoPatternType::NONE),
patternUpdaters["color_wipe"] = [this]() { updateColorWipe(); }; "No pattern - solid color",
patternUpdaters["scanner"] = [this]() { updateScanner(); }; [this]() { /* No initialization needed */ },
patternUpdaters["fade"] = [this]() { updateFade(); }; [this]() { updateNone(); },
patternUpdaters["fire"] = [this]() { updateFire(); }; false, // doesn't require color2
false // doesn't support direction
);
// Register name to pattern mapping patternRegistry.registerPattern(
nameToPatternMap["none"] = NeoPatternType::NONE; "rainbow_cycle",
nameToPatternMap["rainbow_cycle"] = NeoPatternType::RAINBOW_CYCLE; static_cast<uint8_t>(NeoPatternType::RAINBOW_CYCLE),
nameToPatternMap["theater_chase"] = NeoPatternType::THEATER_CHASE; "Rainbow cycle pattern",
nameToPatternMap["color_wipe"] = NeoPatternType::COLOR_WIPE; [this]() { neoPattern->RainbowCycle(updateIntervalMs, static_cast<::direction>(direction)); },
nameToPatternMap["scanner"] = NeoPatternType::SCANNER; [this]() { updateRainbowCycle(); },
nameToPatternMap["fade"] = NeoPatternType::FADE; false, // doesn't require color2
nameToPatternMap["fire"] = NeoPatternType::FIRE; true // supports direction
);
patternRegistry.registerPattern(
"theater_chase",
static_cast<uint8_t>(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<uint8_t>(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<uint8_t>(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<uint8_t>(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<uint8_t>(NeoPatternType::FIRE),
"Fire effect pattern",
[this]() { neoPattern->Fire(50, 120); },
[this]() { updateFire(); },
false, // doesn't require color2
false // doesn't support direction
);
} }
std::vector<String> NeoPatternService::patternNamesVector() const { std::vector<String> NeoPatternService::patternNamesVector() const {
std::vector<String> names; return patternRegistry.getAllPatternNames();
names.reserve(patternUpdaters.size());
for (const auto& kv : patternUpdaters) {
names.push_back(kv.first);
}
return names;
} }
String NeoPatternService::currentPatternName() const { String NeoPatternService::currentPatternName() const {
for (const auto& kv : nameToPatternMap) { return patternRegistry.getPatternName(static_cast<uint8_t>(activePattern));
if (kv.second == activePattern) {
return kv.first;
}
}
return "none";
} }
NeoPatternService::NeoPatternType NeoPatternService::nameToPattern(const String& name) const { NeoPatternService::NeoPatternType NeoPatternService::nameToPattern(const String& name) const {
auto it = nameToPatternMap.find(name); uint8_t type = patternRegistry.getPatternType(name);
return (it != nameToPatternMap.end()) ? it->second : NeoPatternType::NONE; return static_cast<NeoPatternType>(type);
} }
void NeoPatternService::resetStateForPattern(NeoPatternType pattern) { 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<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() { void NeoPatternService::update() {
if (!initialized) return; if (!initialized) return;
@@ -329,13 +417,8 @@ void NeoPatternService::update() {
//if (now - lastUpdateMs < updateIntervalMs) return; //if (now - lastUpdateMs < updateIntervalMs) return;
//lastUpdateMs = now; //lastUpdateMs = now;
const String name = currentPatternName(); // Use pattern registry to execute the current pattern
auto it = patternUpdaters.find(name); patternRegistry.executePattern(static_cast<uint8_t>(activePattern));
if (it != patternUpdaters.end()) {
it->second();
} else {
updateNone();
}
} }
void NeoPatternService::updateRainbowCycle() { void NeoPatternService::updateRainbowCycle() {

View File

@@ -4,6 +4,7 @@
#include "NeoPattern.h" #include "NeoPattern.h"
#include "NeoPatternState.h" #include "NeoPatternState.h"
#include "NeoPixelConfig.h" #include "NeoPixelConfig.h"
#include "PatternRegistry.h"
#include <map> #include <map>
#include <functional> #include <functional>
@@ -71,13 +72,20 @@ private:
void resetStateForPattern(NeoPatternType pattern); void resetStateForPattern(NeoPatternType pattern);
uint32_t parseColor(const String& colorStr) const; 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; TaskManager& taskManager;
NeoPattern* neoPattern; NeoPattern* neoPattern;
NeoPixelConfig config; NeoPixelConfig config;
NeoPatternState currentState; NeoPatternState currentState;
std::map<String, std::function<void()>> patternUpdaters; // Pattern registry for centralized pattern management
std::map<String, NeoPatternType> nameToPatternMap; PatternRegistry patternRegistry;
NeoPatternType activePattern; NeoPatternType activePattern;
NeoDirection direction; NeoDirection direction;

View File

@@ -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<void()> initializer, std::function<void()> 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<String> PatternRegistry::getAllPatternNames() const {
std::vector<String> names;
names.reserve(patternMap_.size());
for (const auto& pair : patternMap_) {
names.push_back(pair.first);
}
return names;
}
std::vector<PatternInfo> PatternRegistry::getAllPatterns() const {
std::vector<PatternInfo> 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;
}
}

View File

@@ -0,0 +1,73 @@
#pragma once
#include <map>
#include <functional>
#include <vector>
#include <Arduino.h>
/**
* PatternInfo structure containing all information needed for a pattern
*/
struct PatternInfo {
String name;
uint8_t type;
String description;
std::function<void()> updater;
std::function<void()> 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<void()> initFunc, std::function<void()> 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<String, PatternInfo>;
using PatternTypeMap = std::map<uint8_t, String>;
PatternRegistry();
~PatternRegistry() = default;
// Pattern registration
void registerPattern(const PatternInfo& pattern);
void registerPattern(const String& name, uint8_t type, const String& description,
std::function<void()> initializer, std::function<void()> 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<String> getAllPatternNames() const;
std::vector<PatternInfo> 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();
};

View File

@@ -18,13 +18,14 @@ This example demonstrates how to integrate a NeoPixel pattern service with the S
## Configuration ## Configuration
Edit `config.h` to configure your setup: Edit `NeoPixelConfig.h` to configure your setup:
```cpp ```cpp
#define NEOPIXEL_PIN 4 // GPIO pin connected to LED strip static constexpr int DEFAULT_PIN = 2;
#define NEOPIXEL_LENGTH 8 // Number of LEDs in your strip static constexpr int DEFAULT_LENGTH = 8;
#define NEOPIXEL_BRIGHTNESS 100 // Initial brightness (0-255) static constexpr int DEFAULT_BRIGHTNESS = 100;
#define NEOPIXEL_UPDATE_INTERVAL 100 // Update interval in milliseconds static constexpr int DEFAULT_UPDATE_INTERVAL = 100;
static constexpr int DEFAULT_COLOR = 0xFF0000; // Red
``` ```
## API Endpoints ## API Endpoints

View File

@@ -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