Skip to content

Architecture

Daniel Kulp edited this page Apr 1, 2026 · 2 revisions

Falcon Player (FPP) Architectural Documentation

Executive Summary

Falcon Player (FPP) is a high-performance sequence player for LED lighting control, designed for Raspberry Pi and BeagleBone SBCs. It supports E1.31, DDP, DMX, ArtNet, KiNet, Pixelnet, and Renard protocols, with hardware cape support for direct GPIO driving.

Key Architectural Principles:

  • Event-driven architecture using EPoll/kqueue for efficient I/O multiplexing
  • Single-buffer data pipeline with in-place transformations
  • Plugin-based extensibility for channel outputs and custom functionality
  • Lock-free bridge data for external DMX controller input
  • Real-time processing with 10ms-50ms timing loops

High-Level Architecture

┌─────────────────────────────────────────────────────────────────────────────┐
│                              fppd Daemon (PID 1)                              │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐   │
│  │   Command    │  │    Player    │  │   Sequence   │  │  Scheduler   │   │
│  │   Manager    │  │   Instance   │  │   Instance   │  │   Instance   │   │
│  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘   │
│         │                  │                  │                  │           │
│         └──────────────────┼──────────────────┼──────────────────┘           │
│                            │                  │                                │
│         ┌──────────────────┼──────────────────┼──────────────────┐           │
│         │                  │                  │                  │           │
│  ┌──────▼───────┐  ┌──────▼───────┐  ┌──────▼───────┐  ┌──────▼───────┐   │
│  │  Plugin      │  │  MultiSync   │  │  Channel     │  │   E1.31    │   │
│  │  Manager     │  │              │  │  Output      │  │  Bridge    │   │
│  │              │  │              │  │  Thread      │  │            │   │
│  └──────────────┘  └──────────────┘  └──────────────┘  └──────────────┘   │
│                                                                             │
│  ┌───────────────────────────────────────────────────────────────────┐    │
│  │                      EPoll/kqueue Event Loop                       │    │
│  │  (10ms when playing, 50ms idle)                                    │    │
│  └───────────────────────────────────────────────────────────────────┘    │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Core Components

1. fppd Daemon

Entry Point: src/fppd.cpp (1167 lines)

Responsibilities:

  • Main event loop using EPoll (Linux) or kqueue (macOS)
  • Daemonization (fork, setsid)
  • Signal handling (SIGTERM, SIGINT, SIGSEGV, etc.)
  • Subsystem coordination (Player, Scheduler, Sequence, MultiSync)
  • Graceful shutdown and auto-restart

Startup Sequence:

1. setupExceptionHandlers()           // Crash handlers
2. LoadSettings()                     // Load /media/settings
3. ParseArguments()                   // Command-line args
4. CreateDaemon()                     // Fork if configured
5. GPIO::Init()                       // Initialize GPIO
6. CommandManager::Init()            // Web/API commands
7. MQTT::init()                       // MQTT client
8. new Scheduler()                   // Schedule manager
9. new Playlist()                    // Playlist system
10. new Sequence()                   // Sequence player
11. MultiSync::Init()                // Remote sync
12. Player::Init()                   // Player state machine
13. PluginManager::init()           // Plugin system
14. InitializeChannelOutputs()      // Load channel outputs
15. MainLoop()                       // Start event loop

Main Event Loop: MainLoop() (fppd.cpp:862)

while (runMainFPPDLoop) {
    // Wait for events on file descriptors
    EPollManager::INSTANCE.waitForEvents(sleepms);  // 10ms playing, 50ms idle

    // If events: push bridge data
    if (epollresult == EPollManager::WaitResult::SOME_TRUE) {
        pushBridgeData();
    }

    // Process Player state machine
    if (Player::INSTANCE.IsPlaying()) {
        Player::INSTANCE.Process();
    }

    // Check scheduler
    scheduler->CheckIfShouldBePlayingNow();

    // Force channel output if needed
    forceChannelOutput();

    // Periodic ping every 1 second
    multiSync->PeriodicPing();

    // Fire timers
    EPollManager::INSTANCE.fireTimers();

    // Process curl requests
    curlManager->processPendingRequests();
}

Threading Model:

Thread Purpose
Main Event loop, subsystem coordination
ChannelOutput Background thread for async output
WarningHolder Notify thread for warnings
PublishStats Detached thread for stats upload (every 10 days)

2. libfpp Shared Library

Build Target: src/libfpp.so (or .dylib on macOS)

Purpose: Core FPP functionality shared by fppd and CLI tools

Key Components:

2.1 Sequence Player (Sequence.h/cpp)

  • Buffer: 8MB m_seqData (FPPD_MAX_CHANNELS = 2097152 channels × 3 bytes)
  • Bridge Data: m_bridgeData for E1.31/DDP/ArtNet input
  • Frame Cache: frameCache for seek operations

Data Pipeline:

FSEQ File on Disk
        │
        ▼
ReadFramesLoop()              // Background thread
  FSEQFile::getFrame()           decompresses zstd/zlib
  caches into frameCache
        │
        ▼
ReadSequenceData()            // Channel output thread
  data->readFrame()
  writes into m_seqData
        │
        ▼
ProcessSequenceData()
  1. Bridge/E1.31 merge      memcpy non-expired m_bridgeData
  2. PluginManager::modifySequenceData()
  3. OverlayEffects()          legacy effects
  4. SDLOutput::ProcessVideoOverlay()
  5. PixelOverlayManager::doOverlays()
  6. ChannelTester::OverlayTestData()
  7. PluginManager::modifyChannelData()
  8. PrepareChannelData()      // Output processors chain
        │
        ▼
SendSequenceData()
  Execute frame-triggered commands
  SendChannelData(m_seqData)
        │
        ▼
Channel Output Plugins

2.2 Plugin System (Plugins.h)

class PluginManager {
public:
    void init(void);
    void loadUserPlugins();
    void Cleanup();

    void mediaCallback(...)        // Playlist media events
    void playlistCallback(...)     // Playlist state changes
    void multiSyncData(...)        // Remote sync data
    void modifySequenceData(...)   // Before overlays
    void modifyChannelData(...)    // After overlays

    FPPPlugins::Plugin* findPlugin(...)

private:
    std::vector<FPPPlugins::Plugin*> mPlugins;
    std::vector<FPPPlugins::PlaylistEventPlugin*> mPlaylistPlugins;
    std::vector<FPPPlugins::ChannelOutputPlugin*> mChannelOutputPlugins;
    std::vector<FPPPlugins::ChannelDataPlugin*> mChannelDataPlugins;
    std::vector<FPPPlugins::APIProviderPlugin*> mAPIProviderPlugins;
};

Plugin Interfaces (Plugin.h):

Plugin Type Purpose Callback Methods
Plugin Base plugin multiSyncData(), settingChanged()
ChannelOutputPlugin Channel output drivers createChannelOutput()
PlaylistEventPlugin Playlist hooks eventCallback(), mediaCallback(), playlistCallback()
ChannelDataPlugin Channel data modification modifySequenceData(), modifyChannelData()
APIProviderPlugin Web API extension registerApis(), addControlCallbacks()

2.3 Channel Output System (channeloutput/)

Base Classes:

// ChannelOutput.h
class ChannelOutput {
public:
    virtual void Init(int startChannel, int channelCount) = 0;
    virtual void SendData(const uint8_t* data) = 0;
    virtual void PrepData(uint8_t* data) = 0;
    virtual std::vector<std::pair<int, int>> GetRequiredChannelRanges() = 0;
};

// ThreadedChannelOutput.h
class ThreadedChannelOutput : public ChannelOutput {
protected:
    uint8_t* writeBuffer;    // Double-buffer for async send
    uint8_t* sendBuffer;
    std::thread sendThread;
    std::mutex sendMutex;
    std::condition_variable sendCv;

    void SendLoop();   // Background thread
};

Channel Output Plugins: 41+ plugins in src/channeloutput/

  • UDPOutput.cpp - UDP multicast/unicast
  • E131.cpp - E1.31 sender
  • ArtNet.cpp - ArtNet sender
  • KiNet.cpp - KiNet sender
  • DDP.cpp - DDP sender
  • BBB48String.cpp - Bit-banged GPIO (BeagleBone)
  • rpi_ws281x.cpp - WS2811/WS2812 driver (Raspberry Pi)
  • RGBMatrix.cpp - SPI framebuffer (Raspberry Pi)
  • MQTTOutput.cpp - MQTT remote control
  • VirtualDisplay.cpp - Virtual display outputs

Output Processor Chain (processors/OutputProcessor.h):

class OutputProcessors {
public:
    void addProcessor(OutputProcessor* processor);
    void process(int channel, uint8_t* data);

private:
    std::vector<OutputProcessor*> processors;
    std::mutex processorsLock;
};

Processor Types (executed in order during PrepareChannelData):

  1. RemapOutputProcessor - Channel remapping
  2. SetValueOutputProcessor - Set all channels to value
  3. BrightnessOutputProcessor - Brightness + gamma correction
  4. ColorOrderOutputProcessor - RGB/BGR swap
  5. HoldValueOutputProcessor - Hold last value
  6. ThreeToFourOutputProcessor - R → RGB expansion
  7. OverrideZeroOutputProcessor - Override zero channels
  8. FoldOutputProcessor - Fold channel ranges
  9. ClampValueOutputProcessor - Clamp to range
  10. ScaleValueOutputProcessor - Scale values

Example: ThreeToFourOutputProcessor (processors/ThreeToFourOutputProcessor.cpp):

int ThreeToFourOutputProcessor::process(int value) {
    if (inputChannel % 3 == 0) {   // R only
        redValue = value;
        return 0;
    } else if (inputChannel % 3 == 1) {   // G
        greenValue = value;
        return 0;
    } else {   // B + return R
        blueValue = value;
        return redValue;
    }
}

2.4 E1.31 Bridge (e131bridge.cpp/h)

Purpose: Receive E1.31, DDP, ArtNet UDP packets and merge into sequence data

Event-Driven Architecture:

// Socket initialization
bridgeSock = socket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, 0);
bind(bridgeSock, &addr, sizeof(addr));   // Port 1935

// Join multicast groups
for (each universe) {
    setsockopt(bridgeSock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq);
}

// Register with EPollManager
std::function<bool(int)> f = [](int i) { return Bridge_ReceiveE131Data(); };
EPollManager::INSTANCE.addFileDescriptor(bridgeSock, f);

Data Flow:

UDP Packets Arrive
        │
        ▼
EPollManager detects readable FDs
        │
        ▼
Bridge_ReceiveE131Data()      // Uses recvmmsg() for batch receive
        │
        ▼
Bridge_StoreData()            // Parse E1.31 header
        │                      // Extract universe, sequence #, DMX data
        ▼
SetBridgeData()               // Thread-safe write
        │                      // m_bridgeData[startChannel:start+len] = packetData
        │                      // timestamp = packetTime + 1000ms expiry
        ▼
sequence->SetBridgeData()     // Mark as "dirty" for merge

Merge Process (Sequence::ProcessSequenceData step 1):

// memcpy non-expired bridge data into m_seqData
for (int i = start; i < start + length; i++) {
    if (m_bridgeDataExpiry[i] > now) {
        memcpy(&m_seqData[i], &m_bridgeData[i], 3);   // 3 bytes per channel
    }
}

2.5 Pixel Overlay System (overlays/PixelOverlay.h/cpp)

Purpose: Apply overlay effects to sequence data

Two Buffers Per Model:

  1. Overlay Buffer (/FPP-Model-Overlay-Buffer-{name}): Full RGB image, effects render here
  2. Channel Data Buffer (/FPP-Model-Data-{name}): Sparse channel-mapped output

Effect Update Thread:

  • Background thread ("FPP-OverlayME")
  • Wakes at precise ms deadlines
  • Calls model->updateRunningEffects()RunningEffect::update()

Return Values:

  • > 0: Reschedule in N milliseconds
  • 0 (EFFECT_DONE): Effect finished, delete
  • -1 (EFFECT_AFTER_NEXT_OUTPUT): Defer to phase 5

doOverlays() Phases:

Phase 1: Flush externally-dirtied overlay buffers
         for each active model: if overlayBufferIsDirty() -> flushOverlayBuffer()

Phase 2: Apply sub-models (type "Sub") to m_seqData
         sub-models have X/Y offset within a parent

Phase 3: Apply non-sub models to m_seqData
         copy channelData -> m_seqData based on overlay state:
           Enabled(1)=opaque, Transparent(2)=non-zero only,
           TransparentRGB(3)=all-RGB-non-zero only

Phase 4: Apply activeRanges (channel range overwrites)

Phase 5: Process AFTER_NEXT_OUTPUT effects (after lock release)
         calls updateRunningEffects() for deferred models

2.6 Player State Machine (Player.h)

Purpose: Manage playlist playback state

Key Methods:

void Init();
int StartPlaylist(const std::string& name, ...);
int StopNow(int forceStop = 0);
int StopGracefully(int forceStop = 0, int afterCurrentLoop = 0);
int Process();                    // State machine update
void ProcessMedia();              // Media player control
PlaylistStatus GetStatus();       // IDLE, PLAYING, PAUSED, STOPPED
int IsPlaying();                  // Boolean

State Machine:

IDLE ──StartPlaylist()──> PLAYING ──StopNow()──> IDLE
  │                          │                       │
  │                          ▼                       │
  └──────────StopGracefully()───────────────────────┘

2.7 Scheduler (Scheduler.h)

Purpose: Schedule playlist playback based on time/date

Key Methods:

void ScheduleProc();
void CheckIfShouldBePlayingNow();

3. CLI Tools

Tool Purpose Build Target
fppd Main daemon fppd
fpp CLI client fpp
fppmm Memory map utility fppmm
fppoled OLED display driver fppoled (Pi/BBB only)
fppcapedetect Hardware cape detection fppcapedetect (Pi/BBB only)
fpprtc Real-time clock utility fpprtc (Pi/BBB only)
fppinit FPP initialization fppinit

Build System

Makefile Structure

Primary Makefile: src/Makefile

# Include platform fragments
include ${SRCDIR}/makefiles/platform/*.mk

# Include build targets
include ${SRCDIR}/makefiles/*.mk

Platform Fragments:

Platform File Defines Notes
Raspberry Pi pi.mk PLATFORM_PI libgpiod, builds all external submodules
BeagleBone bb.mk PLATFORM_BBB or PLATFORM_BB64 PRU support, NEON SIMD
macOS osx.mk PLATFORM_OSX clang++, CoreAudio framework, .dylib
Linux linux.mk PLATFORM_DEBIAN/PLATFORM_UBUNTU Docker detection skips OLED/cape/RTC

Build Targets:

make               # default optimized build (-O3, -g1)
make debug         # debug build (-g -DDEBUG)
make asan          # address sanitizer build
make tsan          # thread sanitizer build
make clean         # remove all build artifacts

Key Build Artifacts:

  • libfpp.so - Core shared library
  • fppd - Main daemon
  • fpp - CLI tool
  • fppoled, fppcapedetect, fpprtc - Platform-specific utilities
  • Channel output plugins: libfpp-co-*.so

Data Flow Architecture

Channel Data Pipeline

FSEQ File on Disk
        │
        ▼
ReadFramesLoop()               // Background thread
  FSEQFile::getFrame()           decompresses zstd/zlib
  caches into frameCache
        │
        ▼
ReadSequenceData()             // Channel output thread
  data->readFrame()
  writes into m_seqData        // 8MB buffer (2097152 channels × 3 bytes)
        │
        ▼
ProcessSequenceData()
  1. Bridge/E1.31 merge       memcpy non-expired m_bridgeData
  2. PluginManager::modifySequenceData()
  3. OverlayEffects()           legacy effects
  4. SDLOutput::ProcessVideoOverlay()
  5. PixelOverlayManager::doOverlays()
  6. ChannelTester::OverlayTestData()
  7. PluginManager::modifyChannelData()
  8. PrepareChannelData()       // Output processors chain
        │
        ▼
SendSequenceData()
  Execute frame-triggered commands
  SendChannelData(m_seqData)
        │
        ▼
Channel Output Plugins

Key Implementation Details

  1. In-Place Buffer Transformations: All transforms happen in-place on the single m_seqData buffer
  2. Bridge Data Priority: Bridge/E1.31 input is merged first, before any overlays
  3. Test Data Overlay: Test data overlays after pixel overlays but before output processors
  4. Output Processor Finalization: Output processors modify the buffer right before sending

Synchronization Model

Primary Locks

Lock Protects Location
m_sequenceLock (recursive_mutex) FSEQ file operations, bridge data access Sequence.h:108
frameCacheLock (mutex) Frame cache between read and output threads Sequence.h:118
m_bridgeRangesLock (mutex) Bridge data ranges and expiry timestamps Sequence.h:91
outputProcessors.processorsLock (mutex) Output processor chain during execution ChannelOutputSetup.h:74
activeModelsLock (recursive_mutex) Active overlay model list during doOverlays PixelOverlay.h

Effect-Specific Locks

Lock Protects
effectLock per model (recursive_mutex) runningEffect pointer during update/replace
threadLock (mutex) updates map, afterOverlayModels list

Plugin Architecture

Plugin Types

Type Purpose Callback Methods
Plugin Base plugin multiSyncData(), settingChanged()
ChannelOutputPlugin Channel output drivers createChannelOutput()
PlaylistEventPlugin Playlist hooks eventCallback(), mediaCallback(), playlistCallback(), playlistInserted()
ChannelDataPlugin Channel data modification modifySequenceData(), modifyChannelData()
APIProviderPlugin Web API extension registerApis(), unregisterApis(), addControlCallbacks()

Plugin Loading

// PluginManager::loadUserPlugins()
for (DIR* dir = opendir("/home/fpp/media/plugins"); ...) {
    if (isPluginDirectory(dir)) {
        Plugin* plugin = loadUserPlugin(name);
        addPlugin(plugin);
    }
}

Error Handling

Crash Handler (fppd.cpp:266-389)

Signals Handled:

  • SIGFPE - Floating-point exception
  • SIGILL - Illegal instruction
  • SIGBUS - Bus error
  • SIGSEGV - Segmentation fault
  • SIGABRT - Abort

Actions:

  1. Log crash info with sequence state
  2. Attempt gdb backtrace (if debugger present)
  3. Fallback to backtrace() and write to /tmp/fppd_crash.log
  4. Upload to crash server (if ShareCrashData >= 1)
  5. Set warning flag and exit

Signal Handling

// command.cpp:55-65
static void exit_handler(int signum) {
    LogInfo("Caught signal %d\n", signum)
    if (mediaOutputStatus.status == MEDIAOUTPUTSTATUS_PLAYING) {
        CloseMediaOutput()
    }
    ShutdownFPPD()   // Triggers SHUTDOWN_HOOK
    sleep(1)
    CloseCommand()
}

signal(SIGINT, exit_handler)
signal(SIGTERM, exit_handler)

Web UI Layer

Technology Stack:

  • PHP 7.x - Server-side rendering
  • Limonade - Micro-framework (v0.5.0) for routing
  • Bootstrap 3.x - UI framework
  • jQuery - DOM manipulation
  • jQuery TableSorter - Table sorting/filtering
  • ZeroClipboard - Clipboard functionality
  • Zebra_Pin - Sticky positioning

Directory Structure:

www/
  api/
    controllers/     # 25 API controllers (REST-like)
    lib/             # Limonade framework, custom libs
  common/            # Shared PHP utilities
  help/              # Help pages
  images/            # Static assets
  jquery/            # jQuery plugins
  media/             # Dynamic content
  phpqrcode/         # QR code generation

API Controllers (25 total):

Controller Purpose
backups.php Backup/restore management
cape.php Hardware cape configuration
channel.php Channel output configuration
configfile.php Config file management
email.php Email settings
effects.php Effect configuration
events.php Event triggers
files.php File operations
git.php Git version control
help.php Help content
media.php Media library
network.php Network configuration
options.php System options
playlist.php Playlist management
plugin.php Plugin management
proxies.php Proxy configuration
schedule.php Schedule management
scripts.php Script browser
sequence.php Sequence management
settings.php Settings management
stats.php Statistics
system.php System operations
testmode.php Test patterns

Key Files:

  • www/config.php - Configuration, media directory, settings
  • www/common.php - Shared utilities ( ScrubFile, ReadSettingFromFile)
  • www/index.php - Main dashboard page

Settings System:

// www/config.php
$settingsFile = $mediaDirectory . "/settings";  // /media/settings

// Key-value store loaded at runtime
$settings = array();
$settings['fppMode'] = "player";

// Helper function
function GetSettingValue($setting, $default = '', $prefix = '', $suffix = '') {
    global $settings;
    if (isset($settings[$setting]) && $settings[$setting] != '') {
        return $prefix . $settings[$setting] . $suffix;
    }
    return $default;
}

API Pattern:

// www/api/controllers/sequence.php
require_once '../common.php';
require_once '../../common/settings.php';

function sequence_GetInfo() {
    global $sequence;
    $info = array();
    $info['filename'] = $sequence->m_seqFilename;
    $info['elapsed'] = $sequence->m_seqMSElapsed;
    return $info;
}

UI Components:

  • Dashboard with playlist, player controls, sync stats
  • Settings pages (input, output, playback, system, email, logs, privacy)
  • Configuration editors (channel outputs, models, command presets)
  • File browser
  • Script browser
  • Help system

Configuration System

Configuration Files

File Purpose
config/channeloutputs.json Output type, startChannel, channelCount
config/model-overlays.json Pixel grid definitions
config/commandPresets.json Named command sequences
/media/settings Key=value settings file
www/settings.json Declarative settings metadata with UI types
capes/ GPIO pin mappings, output channel definitions
etc/asoundrc.* ALSA configurations
ci-universes.json E1.31/DDP/ArtNet input configuration

Areas for Improvement

1. Code Organization

Current Issues:

  • Large monolithic fppd.cpp file (1167 lines)
  • Mixed concerns in Sequence.cpp (file I/O, bridge data, overlays, processing)
  • Global singleton instances (PluginManager::INSTANCE, Sequence::sequence, etc.)

Suggestions:

src/
  daemon/
    fppd.cpp           # Main entry point
    DaemonManager.cpp  # Daemon lifecycle
    SignalHandler.cpp  # Signal handling
  player/
    Player.cpp         # Playlist state machine
    Scheduler.cpp      # Time-based scheduling
  sequence/
    Sequence.cpp       # FSEQ playback
    FSEQFile.cpp       # File I/O
  bridge/
    E131Bridge.cpp     # E1.31/DDP/ArtNet
    BridgeManager.cpp  # Bridge lifecycle
  plugins/
    PluginManager.cpp  # Plugin system
    PluginRegistry.cpp # Plugin registration
  channeloutput/
    ChannelOutput.cpp  # Base classes
    ChannelOutputSetup.cpp # Setup
    ChannelOutputThread.cpp # Output thread
    [plugins/]         # Individual plugins
  overlays/
    PixelOverlay.cpp   # Overlay system
    PixelOverlayEffects.cpp # Effects
  config/
    ConfigManager.cpp  # Configuration loading
    Settings.cpp       # Settings management

2. Threading Model

Current Issues:

  • Single main event loop blocks on EPoll
  • Channel output thread is optional
  • Detached statistics thread

Suggestions:

  • Separate threads for:
    • Event loop (EPoll)
    • Sequence playback
    • Channel output
    • Bridge processing
    • Overlay effects

3. Memory Management

Current Issues:

  • Raw pointers throughout
  • Manual memory management in plugins
  • Shared memory for overlays

Suggestions:

  • Use std::unique_ptr where possible
  • Smart pointers for plugin management
  • RAII for shared memory

4. Error Handling

Current Issues:

  • Crash handler uploads to external server
  • Limited error recovery
  • Silent failures in some areas

Suggestions:

  • Structured error types
  • Error propagation instead of logging
  • Recovery mechanisms for common failures

5. Testing Infrastructure

Current Issues:

  • No visible unit tests
  • Integration testing via CLI
  • Manual testing of channel outputs

Suggestions:

  • Unit tests for core algorithms
  • Mock channel outputs for testing
  • Automated integration tests

6. Documentation

Current Issues:

  • Minimal inline documentation
  • No API documentation
  • Architecture not well documented

Suggestions:

  • Doxygen comments for public APIs
  • Architecture decision records (ADRs)
  • User-facing plugin documentation

7. Configuration Validation

Current Issues:

  • JSON files loaded without validation
  • No schema validation
  • Runtime errors for bad config

Suggestions:

  • Schema validation on load
  • Config validation CLI tool
  • Better error messages

8. Plugin Interface

Current Issues:

  • Virtual functions in base classes
  • Dynamic loading via dlopen
  • No versioning mechanism

Suggestions:

  • Versioned plugin interfaces
  • Plugin capability negotiation
  • Plugin sandboxing (future)

Recommendations for Reorganization

Phase 1: Extract Core Components

  1. Extract Sequence class to src/sequence/
  2. Extract E131Bridge to src/bridge/
  3. Extract PluginManager to src/plugins/

Phase 2: Improve Threading

  1. Separate event loop from sequence playback
  2. Add dedicated bridge processing thread
  3. Standardize thread lifecycle

Phase 3: Memory Management

  1. Replace raw pointers with smart pointers
  2. Add RAII wrappers for shared memory
  3. Add memory ownership documentation

Phase 4: Testing

  1. Add unit tests for core algorithms
  2. Create mock channel outputs
  3. Add integration test framework

Phase 5: Documentation

  1. Add Doxygen comments
  2. Create architecture diagrams
  3. Write plugin developer guide

Summary

FPP is a well-engineered real-time system with:

  • Event-driven architecture using EPoll/kqueue
  • Plugin-based extensibility for channel outputs
  • Efficient data pipeline with in-place transformations
  • Robust error handling with crash reporting

Key strengths:

  • Low-latency processing (10ms-50ms loops)
  • Extensible plugin system
  • Multi-platform support (Pi, BBB, Linux, macOS)

Key areas for improvement:

  • Code organization (reduce monolithic files)
  • Threading model (more parallelism)
  • Memory management (smart pointers)
  • Testing infrastructure
  • Documentation

Generated: 2026-04-01

Clone this wiki locally