Version: 0.4.0
Date: June 2026
Author: Erkan Colak
Complete API reference and development guide for OFM-NeoPixel library.
- README - Project overview and features
- Quickstart Guide - Get started quickly
- Architecture & Flow Diagrams - Detailed system architecture
- Effects Porting Guide - Port FastLED effects
- Architecture Overview
- Module Integration
- API Reference
- Console Commands
- Testing & Benchmarking
- Build Configuration
- Advanced Topics
Application Layer (main.cpp)
│
└──> NeoPixel Module (OpenKNX integration)
│
└──> NeoPixelManager (Core orchestration)
│
├──> PhysicalStrip (Hardware abstraction)
│ └──> IHardwareDriver (PIO/RMT/SPI)
│
├──> VirtualStrip (Multi-strip aggregation)
│
└──> Segment (Effect zones)
└──> Effect (Animations)
src/
├── NeoPixel.h/cpp # OpenKNX Module wrapper
├── NeoPixelConsole.cpp # Console command handlers
├── NeoPixelManager.h/cpp # Core manager
├── PhysicalStrip.h/cpp # Hardware strip wrapper
├── VirtualStrip.h/cpp # Multi-strip composition
├── Segment.h/cpp # LED range + effect
├── LedState.h # State machine
├── IHardwareDriver.h # Driver interface
│
├── effects/
│ ├── Effect.h # Base class
│ ├── EffectPool.h/cpp # Effect registry
│ └── (various effects)
│
├── hal/
│ ├── DriverFactory.h # Auto driver selection
│ └── HW_NeoPixel_SPI.h # Hardware SPI driver
│
├── pio/ # RP2040/RP2350 PIO drivers
└── rmt/ # ESP32 RMT drivers
OFM-NeoPixel integrates with OpenKNX through the Module interface.
In platformio.ini:
build_flags =
-DNEOPIXEL_MODULE # Enable NeoPixel module
-DOPENKNX_NEOPIXEL_TESTS # Optional: Enable test system
-DOPENKNX_NEOPIXEL_BENCHMARK # Optional: Enable benchmarksIn main.cpp:
#ifdef NEOPIXEL_MODULE
#include "NeoPixel.h"
#endif
void setup() {
openknx.init(0);
#ifdef NEOPIXEL_MODULE
openknx.addModule(13, neoPixelModule); // Module ID 13
#endif
openknx.setup();
}
void loop() {
openknx.loop(); // Calls neoPixelModule.loop() automatically
}The module is automatically instantiated as a global neoPixelModule object.
Main module class providing OpenKNX integration and high-level API.
#include "NeoPixel.h"class NeoPixel : public OpenKNX::Module
{
public:
// OpenKNX Module interface
const std::string name() override; // Returns "NeoPixel"
const std::string version() override; // Returns "1.0.0"
void init(); // Initialize module
void setup(bool configured) override; // Setup LED strips
void loop(bool configured) override; // Auto-update loop
void processInputKo(GroupObject& ko) override; // Process GroupObjects
void showHelp() override; // Console help
bool processCommand(const std::string command, bool diagnose) override;
};// Add physical strip (1-wire protocol)
PhysicalStrip* addStrip(uint32_t pin, uint16_t ledCount,
LedProtocol protocol = LedProtocol::WS2812B);
// Add virtual strip
VirtualStrip* addVirtualStrip(uint16_t totalLeds,
ColorOrder colorOrder = ColorOrder::GRB);Example:
// Single strip
auto strip = neoPixelModule.addStrip(9, 64, LedProtocol::WS2812B);
// Multi-strip with virtual
neoPixelModule.addStrip(9, 8, LedProtocol::WS2812B); // Physical 0
neoPixelModule.addStrip(22, 64, LedProtocol::WS2812B); // Physical 1
auto vstrip = neoPixelModule.addVirtualStrip(72, ColorOrder::GRB);void updateAll(); // Manual update all strips
void clearAll(); // Clear all LEDs
void setUpdateSpeed(UpdateSpeed speed); // Set update interval
void setAutoUpdate(bool enabled); // Enable/disable auto-update
bool getAutoUpdate() const; // Get auto-update state
uint32_t getUpdateInterval() const; // Get update interval (ms)Update Speed Presets:
enum class UpdateSpeed : uint8_t
{
SLOW = 100, // 10 FPS
NORMAL = 50, // 20 FPS (default)
FAST = 33, // 30 FPS
MAX = 20, // 50 FPS
EXTREME = 12, // 80 FPS
LUDICROUS = 4, // 120 FPS
FTL = 0 // 240 FPS
};Example:
void setup() {
// ...
neoPixelModule.setUpdateSpeed(UpdateSpeed::FAST); // 30 FPS
neoPixelModule.setAutoUpdate(true); // Auto-update in loop()
}bool isInitialized() const; // Initialization status
uint32_t getStripCount() const; // Number of physical strips
uint32_t getTotalLeds() const; // Total LED count
NeoPixelManager* getManager(); // Access core managerCore manager orchestrating all strips, virtual strips, and segments.
#include "NeoPixelManager.h"Configure in platformio.ini:
build_flags =
-DNEOPIXEL_MAX_PHYSICAL_STRIPS=12 # Max physical strips
-DNEOPIXEL_MAX_VIRTUAL_STRIPS=6 # Max virtual strips
-DNEOPIXEL_MAX_SEGMENTS=32 # Max segments
-DNEOPIXEL_ENFORCE_LIMITS=1 # Enable enforcement// Add 1-wire strip
PhysicalStrip* addStrip(uint32_t pin, uint16_t ledCount,
LedProtocol protocol = LedProtocol::WS2812B);
// Add 1-wire strip with specific driver
PhysicalStrip* addStrip(uint32_t pin, uint16_t ledCount,
LedProtocol protocol, DriverType driverType);
// Add SPI strip
PhysicalStrip* addSpiStrip(uint32_t mosiPin, uint32_t sckPin,
uint16_t ledCount, LedProtocol protocol);
// Add SPI strip with specific driver
PhysicalStrip* addSpiStrip(uint32_t mosiPin, uint32_t sckPin,
uint16_t ledCount, LedProtocol protocol,
DriverType driverType);
// Remove strip
bool removeStrip(PhysicalStrip* strip);
// Access strips
PhysicalStrip* getStrip(uint32_t index);
PhysicalStrip* findStripByPin(uint32_t pin);
uint32_t getStripCount() const;Example:
auto mgr = neoPixelModule.getManager();
// Auto driver selection
auto strip1 = mgr->addStrip(9, 64, LedProtocol::WS2812B);
// Force PIO driver on RP2040
auto strip2 = mgr->addStrip(22, 100, LedProtocol::WS2812B,
DriverType::SERIAL_1WIRE);
// SPI strip (APA102)
auto strip3 = mgr->addSpiStrip(11, 10, 50, LedProtocol::APA102);// Create virtual strip
VirtualStrip* addVirtualStrip(uint16_t totalLeds,
ColorOrder colorOrder = ColorOrder::RGB);
// Remove virtual strip
bool removeVirtualStrip(VirtualStrip* vstrip);
// Access virtual strips
VirtualStrip* getVirtualStrip(uint32_t index);
uint32_t getVirtualStripCount() const;
// Attach/detach physical strips
bool attachPhysicalToVirtual(VirtualStrip* vstrip, PhysicalStrip* pstrip,
uint16_t offset);
bool detachPhysicalFromVirtual(VirtualStrip* vstrip, PhysicalStrip* pstrip);Example:
// Create two physical strips
auto phys0 = mgr->addStrip(3, 100, LedProtocol::WS2812B);
auto phys1 = mgr->addStrip(7, 100, LedProtocol::WS2812B);
// Create virtual strip combining both
auto vstrip = mgr->addVirtualStrip(200, ColorOrder::GRB);
// Attach physical strips
mgr->attachPhysicalToVirtual(vstrip, phys0, 0); // Offset 0
mgr->attachPhysicalToVirtual(vstrip, phys1, 100); // Offset 100
// Now vstrip[0-99] maps to phys0, vstrip[100-199] maps to phys1// Create segment
Segment* addSegment(VirtualStrip* vstrip, uint16_t startLed, uint16_t endLed);
// Remove segment
bool removeSegment(Segment* segment);
// Access segments
Segment* getSegment(uint32_t index);
uint32_t getSegmentCount() const;
// Effect assignment
bool attachEffect(Segment* segment, Effect* effect);
bool detachEffect(Segment* segment);Example:
// Create segments on virtual strip
auto seg1 = mgr->addSegment(vstrip, 0, 99); // First half
auto seg2 = mgr->addSegment(vstrip, 100, 199); // Second half
// Attach different effects
mgr->attachEffect(seg1, new EffectSolid(255, 0, 0)); // Red
mgr->attachEffect(seg2, new EffectRainbow()); // Rainbowbool init(); // Initialize all strips
bool isInitialized() const; // Check initialization
void reset(); // Reset all strips
uint32_t getErrorCount() const; // Get error countvoid update(uint32_t deltaTime); // Update effects (deltaTime in ms)
bool updateAll(); // Send buffers to hardware
bool waitForAll(uint32_t timeoutMs = 0); // Wait for transfers
bool waitForStrip(PhysicalStrip* strip, uint32_t timeoutMs = 0);
bool isAnyBusy() const; // Check if any strip busy
bool areAllReady() const; // Check if all strips readyExample:
void loop() {
static uint32_t lastUpdate = 0;
uint32_t now = millis();
uint32_t deltaTime = now - lastUpdate;
// Update effects
mgr->update(deltaTime);
// Send to hardware
mgr->updateAll();
lastUpdate = now;
delay(20); // 50 FPS
}uint32_t getTotalLedCount() const; // Total LEDs across all strips
void printStats(); // Print statistics to consoleHardware abstraction for a single physical LED strip.
#include "PhysicalStrip.h"// 1-wire protocol
PhysicalStrip(uint32_t pin, uint16_t ledCount,
LedProtocol protocol = LedProtocol::WS2812B,
DriverType driverType = DriverType::AUTO);
// SPI protocol
PhysicalStrip(uint32_t pin, uint16_t ledCount,
LedProtocol protocol, uint32_t sckPin,
DriverType driverType = DriverType::AUTO);bool init(); // Initialize hardware driver
bool isInitialized() const; // Check initialization status// RGB (3-channel)
bool setPixel(uint16_t index, uint8_t r, uint8_t g, uint8_t b);
void setAll(uint8_t r, uint8_t g, uint8_t b);
// RGBW (4-channel)
bool setPixel(uint16_t index, uint8_t r, uint8_t g, uint8_t b, uint8_t w);
void setAll(uint8_t r, uint8_t g, uint8_t b, uint8_t w);
// Clear
void clear();Example:
auto strip = mgr->addStrip(9, 64, LedProtocol::WS2812B);
strip->init();
strip->setPixel(0, 255, 0, 0); // First LED red
strip->setPixel(1, 0, 255, 0); // Second LED green
strip->setAll(0, 0, 255); // All blue
strip->clear(); // All offbool show(); // Send buffer to LEDs (non-blocking)
bool waitForTransfer(uint32_t timeoutMs = 0); // Wait for completion
bool isBusy() const; // Check if transfer in progressExample:
strip->setPixel(0, 255, 0, 0);
strip->show(); // Start DMA transfer
// Non-blocking - can continue immediately
doOtherWork();
// Optional: wait for completion
strip->waitForTransfer(100); // 100ms timeoutuint8_t* getBuffer(); // Direct buffer access
size_t getBufferSize() const; // Buffer size in bytesExample:
// Direct buffer manipulation
uint8_t* buf = strip->getBuffer();
size_t size = strip->getBufferSize();
// Manual RGB fill (assuming GRB order)
for (size_t i = 0; i < size; i += 3) {
buf[i] = 128; // G
buf[i + 1] = 255; // R
buf[i + 2] = 0; // B
}
strip->show();uint16_t getLedCount() const; // Number of LEDs
LedProtocol getProtocol() const; // Protocol type
uint32_t getDataPin() const; // Data pin (MOSI for SPI)
uint32_t getClockPin() const; // Clock pin (SPI only)
DriverCapabilities getCapabilities() const; // Driver capabilities
const char* getDriverName() const; // Driver name stringbool setUpdateFrequency(uint32_t frequencyHz); // Set update frequency
IHardwareDriver* getDriver() const; // Access raw driverLogical strip combining multiple physical strips.
#include "VirtualStrip.h"VirtualStrip(uint16_t totalLeds, ColorOrder colorOrder = ColorOrder::RGB);Color Order Options:
enum class ColorOrder : uint8_t
{
RGB, // Red, Green, Blue
RBG, // Red, Blue, Green
GRB, // Green, Red, Blue (WS2812B default)
GBR, // Green, Blue, Red
BRG, // Blue, Red, Green
BGR, // Blue, Green, Red
RGBW, // Red, Green, Blue, White
GRBW, // Green, Red, Blue, White (SK6812 default)
// ... more variants
};// Attach physical strip
bool attachPhysicalStrip(PhysicalStrip* physicalStrip, uint16_t offset);
// Detach physical strip
bool detachPhysicalStrip(PhysicalStrip* physicalStrip);
// Access
uint16_t getPhysicalStripCount() const;
PhysicalStrip* getPhysicalStrip(uint16_t index) const;Example:
auto phys0 = mgr->addStrip(3, 50, LedProtocol::WS2812B);
auto phys1 = mgr->addStrip(7, 50, LedProtocol::WS2812B);
auto vstrip = new VirtualStrip(100, ColorOrder::GRB);
vstrip->attachPhysicalStrip(phys0, 0); // vstrip[0-49] -> phys0
vstrip->attachPhysicalStrip(phys1, 50); // vstrip[50-99] -> phys1// Set brightness (for APA102)
void setBrightness(uint8_t brightness);
uint8_t getBrightness() const;
// Set pixels (RGB)
bool setPixel(uint16_t index, uint8_t r, uint8_t g, uint8_t b);
void setRange(uint16_t startIndex, uint16_t length, uint8_t r, uint8_t g, uint8_t b);
void setAll(uint8_t r, uint8_t g, uint8_t b);
void clear();
// Set pixels (RGBW)
bool setPixel(uint16_t index, uint8_t r, uint8_t g, uint8_t b, uint8_t w);
// Get pixels
bool getPixel(uint16_t index, uint8_t& r, uint8_t& g, uint8_t& b) const;
bool getPixel(uint16_t index, uint8_t& r, uint8_t& g, uint8_t& b, uint8_t& w) const;Example:
vstrip->setPixel(0, 255, 0, 0); // First LED on phys0
vstrip->setPixel(75, 0, 255, 0); // LED 25 on phys1
vstrip->setRange(10, 5, 0, 0, 255); // 5 blue LEDs starting at 10
vstrip->setAll(128, 128, 128); // All grayuint8_t* getBuffer(); // Unified virtual buffer
const uint8_t* getBuffer() const;
size_t getBufferSize() const; // Buffer size
uint8_t getBytesPerLed() const; // Bytes per LED (3 or 4)// Sync virtual buffer to physical buffers
bool syncToPhysical();
// Show on all physical strips
bool show();
// Wait for completion
bool waitForCompletion(uint32_t timeoutMs = 0);Example:
// Modify virtual buffer
vstrip->setPixel(0, 255, 0, 0);
vstrip->setPixel(60, 0, 255, 0);
// Sync to physical buffers
vstrip->syncToPhysical();
// Send to hardware
vstrip->show();uint16_t getLedCount() const; // Total virtual LEDs
ColorOrder getColorOrder() const; // Color order
bool isDirty() const; // Buffer modified since sync
void markDirty(); // Mark buffer as modified
void clearDirty(); // Clear dirty flagLED range within a VirtualStrip with its own effect.
#include "Segment.h"Segment(VirtualStrip* virtualStrip, uint16_t startLed, uint16_t endLed);uint16_t getStartLed() const; // Start LED index
uint16_t getEndLed() const; // End LED index (inclusive)
uint16_t getLength() const; // Segment length
VirtualStrip* getVirtualStrip() const; // Parent virtual stripvoid setEffect(Effect* effect); // Assign effect
Effect* getEffect() const; // Get current effect
bool hasEffect() const; // Check if effect assigned
void clearEffect(); // Remove effectExample:
auto seg = mgr->addSegment(vstrip, 0, 49);
seg->setEffect(new EffectRainbow());LedState getLedState() const; // Get state (IDLE, RUNNING, etc.)
void setState(LedState state); // Set state
bool isRunning() const; // Check if effect running
bool isPaused() const; // Check if paused
void pause(); // Pause effect
void resume(); // Resume effect
void stop(); // Stop effect
void start(); // Start effectLED States:
enum class LedState : uint8_t
{
IDLE, // No effect
EFFECT_RUNNING, // Effect active
TRANSITIONING, // Switching effects
ERROR // Error state
};EffectConfig& getConfig(); // Get effect config (mutable)
const EffectConfig& getConfig() const; // Get effect config (const)EffectConfig Structure:
struct EffectConfig
{
uint8_t speed; // Speed 1-255
uint8_t intensity; // Intensity 1-255
uint8_t brightness; // Segment brightness 0-255
uint8_t apa102Brightness; // APA102 hardware brightness
uint32_t primaryRGBW; // Primary color (packed RGBW)
uint32_t secondaryRGBW; // Secondary color (packed RGBW)
uint8_t reverse; // Reverse direction
uint8_t count; // Count parameter
uint8_t fade; // Fade amount
uint8_t mode; // Effect mode
uint32_t option1; // Additional parameter 1
uint32_t option2; // Additional parameter 2
};Example:
auto seg = mgr->addSegment(vstrip, 0, 49);
seg->setEffect(new EffectRainbow());
EffectConfig& config = seg->getConfig();
config.speed = 200;
config.brightness = 128;
config.primaryRGBW = 0xFF0000FF; // Redvoid update(uint32_t deltaTime); // Update effect (called by manager)bool setPixel(uint16_t localIndex, uint8_t r, uint8_t g, uint8_t b);
bool setPixel(uint16_t localIndex, uint8_t r, uint8_t g, uint8_t b, uint8_t w);
bool getPixel(uint16_t localIndex, uint8_t& r, uint8_t& g, uint8_t& b) const;
void fill(uint8_t r, uint8_t g, uint8_t b);
void clear();Note: Pixel indices are relative to segment start (0 = startLed).
A Segment can be declared as a 2D or 3D matrix. The geometry is a pure software interpretation layer — the physical LED chain stays 1D. Coordinate setters map (x,y) / (x,y,z) to a linear index using the configured LedTopology.
// 2D matrix (width × height), default ROWS_SERPENTINE
void setGeometry(uint8_t width, uint8_t height,
LedTopology t = LedTopology::ROWS_SERPENTINE);
// 3D volume (width × height × depth)
void setGeometry(uint8_t width, uint8_t height, uint8_t depth,
LedTopology t = LedTopology::ROWS_SERPENTINE_3D);
const LedGeometry& getGeometry() const;
bool is2D() const;
bool is3D() const;Topologies (LedTopology):
| Value | Wiring |
|---|---|
LINEAR_1D |
No matrix (default, 1D strip) |
ROWS_SERPENTINE |
→→ ←← →→ (most WS2812B panels) |
ROWS_LINEAR |
→→ →→ →→ (all rows same direction) |
COLS_SERPENTINE |
↓↑↓↑ (column-major, alternating) |
COLS_LINEAR |
↓↓↓↓ (column-major, same direction) |
ROWS_SERPENTINE_3D |
3D volume, serpentine rows |
COLS_LINEAR_TILED |
Tiled panel chain, columns linear per block |
COLS_SERP_TILED |
Tiled panel chain, columns serpentine per block |
bool setPixelXY(uint8_t x, uint8_t y, uint8_t r, uint8_t g, uint8_t b);
bool setPixelXY(uint8_t x, uint8_t y, uint8_t r, uint8_t g, uint8_t b, uint8_t w);
bool setPixelXYZ(uint8_t x, uint8_t y, uint8_t z, uint8_t r, uint8_t g, uint8_t b);
uint16_t xyToIndex(uint8_t x, uint8_t y) const; // 0xFFFF if out of bounds
uint16_t xyzToIndex(uint8_t x, uint8_t y, uint8_t z) const;// Global-coordinate setters used by distributed 2D effects across multiple devices
bool setPixelGlobalXY(uint16_t x, uint16_t y, uint8_t r, uint8_t g, uint8_t b);
bool setPixelGlobalXY(uint16_t x, uint16_t y, uint8_t r, uint8_t g, uint8_t b, uint8_t w);
uint16_t getRenderWidth() const; // global matrix width in band mode
uint16_t getRenderHeight() const;Example: 16×16 serpentine panel
auto seg = mgr->addSegment(vstrip, 0, 255);
seg->setGeometry(16, 16, LedTopology::ROWS_SERPENTINE);
seg->setPixelXY(0, 0, 0, 255, 0); // top-left = green
seg->setPixelXY(3, 7, 255, 0, 0); // (col=3, row=7) = red
// Assign a 2D-aware effect
seg->setEffect(EffectPool::getFire());class MyEffect : public Effect {
uint8_t getCapabilities() const override { return DIM_1D | DIM_2D; }
void update(Segment* seg, uint32_t dt) override { /* 1D fallback */ }
void update2D(Segment* seg, uint32_t dt) override {
const auto& geo = seg->getGeometry();
for (uint8_t y = 0; y < geo.height; y++)
for (uint8_t x = 0; x < geo.width; x++)
seg->setPixelXY(x, y, x * 16, y * 16, 0);
}
};Segment::update() dispatches automatically: is3D() + 3D-capable → update3D(), is2D() + 2D-capable → update2D(), otherwise → update() (1D, rendered line by line).
Base class for creating custom effects.
#include "effects/Effect.h"class Effect
{
public:
virtual ~Effect() = default;
// Main update function (REQUIRED)
virtual void update(Segment* segment, EffectConfig& config,
EffectState& state, uint32_t deltaTime) = 0;
// Effect metadata
virtual const char* getName() const = 0;
virtual uint8_t getEffectId() const = 0;
// Optional: initialization/cleanup
virtual void init(Segment* segment, EffectConfig& config, EffectState& state) {}
virtual void cleanup(Segment* segment, EffectState& state) {}
};Example: Solid Color Effect
class EffectSolid : public Effect
{
public:
EffectSolid(uint8_t r, uint8_t g, uint8_t b)
: _r(r), _g(g), _b(b) {}
void update(Segment* segment, EffectConfig& config,
EffectState& state, uint32_t deltaTime) override
{
// Fill segment with solid color
segment->fill(_r, _g, _b);
}
const char* getName() const override { return "Solid"; }
uint8_t getEffectId() const override { return 0; }
private:
uint8_t _r, _g, _b;
};Example: Animation Effect
class EffectChase : public Effect
{
public:
void update(Segment* segment, EffectConfig& config,
EffectState& state, uint32_t deltaTime) override
{
// Clear segment
segment->clear();
// Update position
state.position = (state.position + config.speed / 50) % segment->getLength();
// Set moving pixel
uint8_t r = (config.primaryRGBW >> 24) & 0xFF;
uint8_t g = (config.primaryRGBW >> 16) & 0xFF;
uint8_t b = (config.primaryRGBW >> 8) & 0xFF;
segment->setPixel(state.position, r, g, b);
}
const char* getName() const override { return "Chase"; }
uint8_t getEffectId() const override { return 10; }
};Pre-registered effects accessible by ID.
#include "effects/EffectPool.h"
// Get effect by ID
Effect* effect = EffectPool::getEffect(2); // Rainbow
// First eight of 33 effects (IDs 0-32, see doc/Effects.md):
// 0 = Solid
// 1 = Wipe
// 2 = Rainbow
// 3 = Pride2015
// 4 = Juggle
// 5 = BPM
// 6 = Cylon
// 7 = TestThe Effektmanager (EM) is a per-segment sequencer that applies a chain of effect presets (cues) over time. EM data is stored in KNX flash (configured via ETS); each segment owns an EffektManagerController that drives playback.
#include "EffektManager.h"constexpr uint8_t EM_COUNT = 16; // Number of Effektmanager instances
constexpr uint8_t EM_CUE_COUNT = 10; // Max cues per EM (ETS: Cue 1..10)
constexpr uint8_t EM_PARAM_COUNT = 10; // Max effect parameters per cue
constexpr uint8_t EM_TEXT_LEN = 14; // Cue/effect text length (DPT 16)
constexpr uint8_t EM_NONE = 0; // EM id 0 = "no EM / stop"struct EffektCue // 48 bytes
{
uint8_t effectId; // Effect ID (0 = Solid/Off)
uint8_t params[10]; // Effect parameters
uint8_t r, g, b, w; // Primary colour
uint8_t brightness; // 0-255
uint16_t durationSec; // 0 = hold until next trigger
uint16_t fadeMs; // Fade-out before next cue (0 = hard cut)
char cueName[14]; // Cue label
char effectText[14]; // Effect-specific text (e.g. ScrollText)
};
struct EffektManagerHeader // 20 bytes
{
char name[16]; // ETS description
uint8_t cueCount; // Active cues (1-10)
uint8_t loop : 1; // Restart at cue 1 when finished
uint8_t nextEmId; // Chain target (0 = stop, 1-16)
uint8_t enabled; // 0 = inactive, 1 = active, 2 = paused
bool isEnabled() const; // runnable: active (1) AND cueCount > 0
bool isPaused() const; // paused (2): keeps config + KOs, does not render
bool isConfigured() const; // active or paused (enabled != 0)
};
struct EffektManagerData // header + cues[10]
{
EffektManagerHeader header;
EffektCue cues[EM_CUE_COUNT];
};class EffektManagerController
{
public:
// Start EM <emId> (1-16) on a segment; EM_NONE stops. Interrupts any running EM.
void start(uint8_t emId, Segment* segment, const EffektManagerData* emData);
// Stop the running EM and restore normal operation.
void stop(Segment* segment);
// Advance the sequencer — call every loop() tick.
void tick(Segment* segment, const EffektManagerData* emData);
// Apply one cue immediately (effect + params + colour + brightness + text).
void applyCue(const EffektCue& cue, Segment* segment);
// Jump to a specific 1-based cue of the active EM.
bool triggerCue(uint8_t cueNum, Segment* segment, const EffektManagerData* emData);
bool isRunning() const; // EM currently active?
uint8_t activeEmId() const; // 0 if idle
uint8_t activeCueNum() const; // 1-based, 0 if idle
// Power-off persistence
void saveState();
void restoreState(Segment* segment, const EffektManagerData* emData);
uint8_t lastEmId() const;
void setLastEmId(uint8_t emId);
};Behaviour highlights:
start()interrupts any running EM immediately (Variante A).- If the segment is part of an Effektkette, the chain is paused while the EM runs and resumes on
stop(). - When a cue's
durationSecelapses, the controller fades out (fadeMs) and advances; on the last cue it loops, chains tonextEmId, or stops. saveState()/restoreState()persist and restart the active EM (from cue 1) across reboots.
Complete reference for all neo console commands.
neo <category> <action> [parameters]
| Command | Description |
|---|---|
neo |
Show module info |
neo ? |
Show help |
neo list |
List all strips, virtual strips, segments |
neo info |
Detailed system information |
neo perf |
Performance statistics |
| Command | Description | Example |
|---|---|---|
neo phys add <pin> <leds> [protocol] |
Add physical strip | neo phys add 9 64 WS2812B |
neo phys del <index> |
Delete strip | neo phys del 0 |
neo phys list |
List strips | neo phys list |
neo phys timings |
List all 11 timing modes + clone profiles | neo phys timings |
neo phys timing <i> |
Show current timing mode for strip | neo phys timing 0 |
neo phys timing <i> <mode> |
Set timing mode | neo phys timing 0 legacy |
neo phys timing <i> info |
Detailed timing info | neo phys timing 0 info |
neo phys timing <i> custom <t0h> <t0l> <t1h> <t1l> |
Custom bit timing (ns) | neo phys timing 0 custom 350 900 900 350 |
neo phys timing <i> reset |
Revert to AUTO | neo phys timing 0 reset |
neo phys timing <i> qualify |
Interactive clone-qualify scan (next/apply/stop) | neo phys timing 0 qualify |
Timing Modes (11): auto (AUTO ≈ 800 kHz), legacy (AUTO_LEGACY ≈ 960 kHz, WS2812C/D onboard LEDs),
slow5/slow10/slow15/slow20 (SLOW_5PCT … SLOW_20PCT, down to ~640 kHz for weak/long chains),
fast5/fast10/fast15/fast20/fast25 (FAST_5PCT … FAST_25PCT, up to ~1000 kHz).
Replaces the old raw-frequency setting — the mode picks the PIO clock divider for you.
Supported Protocols:
- WS2812, WS2812B, WS2813, WS2815
- SK6812, SK6805
- APA102, WS2801
| Command | Description | Example |
|---|---|---|
neo virt add <leds> [order] |
Create virtual strip | neo virt add 72 GRB |
neo virt del <index> |
Delete virtual strip | neo virt del 0 |
neo virt list |
List virtual strips | neo virt list |
neo virt attach <virt> <phys> |
Attach physical to virtual | neo virt attach 0 0 |
neo virt detach <virt> <phys> |
Detach physical | neo virt detach 0 0 |
neo virt order <virt> <phys> <order> |
Set mapping order | neo virt order 0 0 1 |
Color Orders:
- RGB, RBG, GRB, GBR, BRG, BGR
- RGBW, GRBW, BRGW, BGRW, etc.
| Command | Description | Example |
|---|---|---|
neo seg add <virt> <start> <end> |
Create segment | neo seg add 0 0 35 |
neo seg del <index> |
Delete segment | neo seg del 0 |
neo seg list |
List segments | neo seg list |
neo seg pause <index> |
Pause effect | neo seg pause 0 |
neo seg resume <index> |
Resume effect | neo seg resume 0 |
neo seg stop <index> |
Stop effect | neo seg stop 0 |
neo seg geo <id> <w> <h> [topo] |
Set 2D geometry (topo 1=rows-serp … 7=cols-serp-tiled, 0=back to 1D) | neo seg geo 0 32 16 1 |
neo seg geo <id> <w> <h> <tile> <topo> |
Tiled panel (tile = tile height) | neo seg geo 0 32 16 8 7 |
| Command | Description | Example |
|---|---|---|
neo effect set <seg> <id|name> |
Assign effect by ID or name | neo effect set 0 23 |
neo effect stop <seg> |
Stop effect on segment | neo effect stop 0 |
neo effect clear <seg> |
Remove effect from segment | neo effect clear 0 |
neo effect pause <seg> / resume <seg> |
Freeze / resume effect | neo effect pause 0 |
neo effect config <seg> |
Show effect parameters (index/name/value/default) | neo effect config 0 |
neo effect config <seg> get <i> |
Read one parameter | neo effect config 0 get 0 |
neo effect config <seg> set <i> <v> |
Set one parameter live | neo effect config 0 set 0 255 |
neo color <seg> <r> <g> <b> [w] [cw] |
Set color (0-255) | neo color 0 255 0 0 |
neo brightness <seg> <value> |
Software brightness 0-255 — also sets the segment master brightness that EM cues scale against (cue stays relative to it across switches) | neo brightness 0 128 |
neo hwbrightness <seg> <value> |
Hardware brightness (APA102/SK9822 only) | neo hwbrightness 0 31 |
Segment indices are 0-based.
| Command | Description | Example |
|---|---|---|
neo em / neo em status [seg] |
EM status table | neo em status |
neo em dump <seg> |
Active EM header + runtime state | neo em dump 0 |
neo em start <seg> <em> |
Start EM (1-16) on segment | neo em start 0 3 |
neo em stop <seg> |
Stop EM on segment | neo em stop 0 |
neo em cue <seg> <cue> |
Trigger cue of active EM | neo em cue 0 2 |
neo cue / neo cue list [all|seg] |
Cue table: active EMs (default), all configured EMs (all), or one segment |
neo cue list all |
neo cue <seg> <cue> |
Trigger cue (alias) | neo cue 0 2 |
Build EMs/cues entirely from the console — handy for bench tests and commissioning. EM id is 1-based; edits take effect on the running EM at the next cycle. Runtime-only: not persisted, lost on reboot — ETS remains the permanent config source.
| Command | Description | Example |
|---|---|---|
neo init |
Bring the module up without an ETS download (test mode; OAM) | neo init |
neo em bind <managerSeg> |
Wrap a console segment into the EM system | neo em bind 0 |
neo em config <em> <loop 0|1> [nextEm] |
Configure + enable an EM | neo em config 1 1 |
neo cue set <em> <cue> <effectId> <durSec> <fadeMs> [bri] [r g b] |
Define/overwrite a cue (params auto-seeded with the effect's defaults; string effects get their default text) | neo cue set 1 1 23 5 300 |
neo cue param <em> <cue> <paramIdx> <value> |
Override one effect parameter of an existing cue (clamped to the effect's range) | neo cue param 1 1 0 255 |
neo cue text <em> <cue> <text> |
Set a cue's text for string effects (e.g. Scroll Text). Full ASCII + ISO-8859-1 umlauts; quotes optional, \" = literal quote. ≤13 chars are stored in the cue; longer text is kept in a RAM side-store and applied when the cue activates (Scroll Text up to ~240 chars; runtime-only, lost on reboot). |
neo cue text 1 7 "Hallo \"Welt\" äöü" |
neo cue clear <em> |
Clear all cues of an EM | neo cue clear 1 |
Segment indices are 0-based.
| Command | Description | Example |
|---|---|---|
neo chain / neo chain status [seg] |
Chain status table | neo chain status |
neo chain set <seg> <off|master|slave> |
Set chain mode | neo chain set 0 master |
neo chain override <seg> <0|1> |
Slave local-override flag | neo chain override 0 1 |
neo chain trigger <seg> |
Send sync telegram now (master) | neo chain trigger 0 |
| Command | Description | Example |
|---|---|---|
neo update |
Manual update | neo update |
neo clear |
Clear all LEDs | neo clear |
neo speed <ms> |
Set interval | neo speed 50 |
neo speed slow/normal/fast/max |
Speed preset | neo speed fast |
neo auto on/off |
Auto-update | neo auto on |
| Command | Description | Example |
|---|---|---|
neo test |
Animation test | neo test |
neo simpletest start |
Start simple test | neo simpletest start |
neo simpletest stop |
Stop simple test | neo simpletest stop |
neo animtest start |
Start animation test | neo animtest start |
neo animtest stop |
Stop animation test | neo animtest stop |
Enable tests in platformio.ini:
build_flags =
-DOPENKNX_NEOPIXEL_TESTS
-DOPENKNX_NEOPIXEL_AUTO_TEST # Auto-start on bootneo test # Run animation test
neo animtest start # Start animation test
neo animtest stop # Stop animation test
neo simpletest start # Start simple test
neo simpletest stop # Stop simple testEnable benchmarks:
build_flags =
-DOPENKNX_NEOPIXEL_BENCHMARKneo benchmark run # Run all benchmarks
neo benchmark speed [strip] # Update speed test
neo benchmark colors [strip] # Color pattern test
neo benchmark size # LED count scaling
neo benchmark compare # Protocol comparison
neo benchmark stability [strip] # Stability test
neo benchmark cpu [strip] # CPU usage analysis
neo benchmark dma # DMA comparisonbuild_flags =
# Core
-DNEOPIXEL_MODULE # Enable module
# Resource limits
-DNEOPIXEL_MAX_PHYSICAL_STRIPS=12
-DNEOPIXEL_MAX_VIRTUAL_STRIPS=6
-DNEOPIXEL_MAX_SEGMENTS=32
-DNEOPIXEL_ENFORCE_LIMITS=1
# Optional features
-DOPENKNX_NEOPIXEL_TESTS # Test system
-DOPENKNX_NEOPIXEL_AUTO_TEST # Auto-start tests
-DOPENKNX_NEOPIXEL_BENCHMARK # Benchmark system[env:pico]
platform = raspberrypi
board = pico
framework = arduino
lib_deps =
OpenKNX/OFM-NeoPixel
build_flags =
-DNEOPIXEL_MODULE
-DARDUINO_ARCH_RP2040[env:esp32s3]
platform = espressif32
board = esp32-s3-devkitc-1
framework = arduino
lib_deps =
OpenKNX/OFM-NeoPixel
build_flags =
-DNEOPIXEL_MODULE
-DARDUINO_ARCH_ESP32For maximum performance, you can manipulate LED buffers directly:
auto vstrip = neoPixelModule.addVirtualStrip(100, ColorOrder::GRB);
uint8_t* buf = vstrip->getBuffer();
size_t size = vstrip->getBufferSize();
// Assuming GRB order, 3 bytes per LED
for (size_t i = 0; i < size; i += 3) {
buf[i] = 128; // G
buf[i + 1] = 255; // R
buf[i + 2] = 0; // B
}
vstrip->syncToPhysical();
vstrip->show();Implement IHardwareDriver interface for custom drivers:
class MyCustomDriver : public IHardwareDriver
{
public:
bool init(const DriverConfig& config) override;
bool setPixel(uint16_t index, uint8_t r, uint8_t g, uint8_t b) override;
bool show() override;
// ... implement other methods
};Tips:
- Use auto-update mode instead of manual
updateAll()calls - Keep segment count reasonable (< 16)
- Use hardware SPI for APA102 strips when possible
- Avoid frequent virtual strip recomposition
- Batch pixel updates before calling
show()
Monitoring:
// Check performance
neo perf
// Output:
// Update time: 22µs
// FPS: 30
// CPU usage: 0.07%See Effects Porting Guide for detailed migration instructions.
Quick reference:
// FastLED
FastLED.addLeds<WS2812B, 9, GRB>(leds, 64);
leds[0] = CRGB::Red;
FastLED.show();
// OFM-NeoPixel
auto strip = neoPixelModule.addStrip(9, 64, LedProtocol::WS2812B);
strip->setPixel(0, 255, 0, 0);
strip->show();// Adafruit
Adafruit_NeoPixel strip(64, 9, NEO_GRB + NEO_KHZ800);
strip.begin();
strip.setPixelColor(0, strip.Color(255, 0, 0));
strip.show();
// OFM-NeoPixel
auto strip = neoPixelModule.addStrip(9, 64, LedProtocol::WS2812B);
strip->setPixel(0, 255, 0, 0);
strip->show();LEDs not lighting up:
- Check power supply (5V/12V depending on strip)
- Verify wiring (DIN, GND, VCC)
- Check GPIO pin number
- Try different protocol (WS2812 vs WS2812B)
Flickering:
- Add 1000µF capacitor near strip
- Add 470Ω resistor on data line
- Check power supply capacity
- Reduce update frequency
Wrong colors:
- Check color order (RGB vs GRB)
- Try different ColorOrder setting
- Verify protocol matches strip
Performance issues:
- Reduce segment count
- Lower update frequency
- Use hardware acceleration (PIO/RMT)
- Check
neo perfoutput
Enable debug output:
build_flags =
-DDEBUGThen check console for debug messages during initialization and updates.
Documentation:
Repository: https://github.com/OpenKNX/OFM-NeoPixel
Issues: https://github.com/OpenKNX/OFM-NeoPixel/issues
Last Updated: June 2026
Version: 0.4.0