rpi-rgb-led-matrix-scene-composer
A high-level scene-based rendering engine for RGB LED matrices on Raspberry Pi.
Overview
This library provides a clean, composable architecture for creating animated content on RGB LED matrices. It uses a scene-graph approach with components, scenes, and an orchestrator managing the render loop.
Architecture
Clean one-way dependency flow:
Component (knows nothing)
↓
Scene (knows about Components)
↓
Orchestrator (knows about Scenes) [OPTIONAL]
Core classes:
- Component: Base class for renderable elements (text, tables, filters, etc.)
- Pure, standalone - no knowledge of Scene
- Renders to RenderBuffer when given time parameter
- Scene: Container for components with positioning and layering
- Composes components
- Can work standalone or with Orchestrator
- DisplayTarget: Abstraction for output (terminal emulator or physical matrix)
- RenderBuffer: Fixed-size RGB pixel buffer backed by numpy
Optional (for multi-scene apps):
- Orchestrator: Manages multiple scenes, transitions, and global time
- Only needed when you have multiple scenes
- Provides unified time source across scenes
Features
- Simple Scene + Component composition model
- Automatic caching for static/slow-updating content
- Built-in components: Text, Tables, Rainbow filters
- Hardware-agnostic rendering
- Terminal emulator for development (no hardware needed!)
- Optional Orchestrator for multi-scene applications
Installation
Quick Start (Recommended)
# Clone the repository
git clone https://github.com/fredrikolis/rpi-rgb-led-matrix-scene-composer.git
cd rpi-rgb-led-matrix-scene-composer
# Run demos - they auto-install dependencies!
./run_terminal_demo.sh # Terminal emulator (no hardware needed)
./run_matrix_demo.sh # Physical RGB matrix (requires hardware)
Manual Installation
# Create virtual environment
python3 -m venv .venv
source .venv/bin/activate
# Install core package
pip install -e .
# Install with PioMatter support (Raspberry Pi 5)
pip install -e ".[piomatter]"
# Install with RGB Matrix support (older Pi models)
pip install -e ".[rgbmatrix]"
# Install development tools
pip install -e ".[dev]"
Quick Start - Scene + Components (Simplest)
Components are pure - they don't know about Scene. Scene composes components.
from matrix_scene_composer import (
Scene, TableComponent, Text4pxComponent, TerminalDisplayTarget
)
import time
# Step 1: Create components (standalone, no scene reference!)
data = [
{"name": "CPU", "temp": "45C", "load": "23%"},
{"name": "GPU", "temp": "67C", "load": "89%"},
]
table = TableComponent(
data=data,
text_component=Text4pxComponent,
fgcolor=(255, 255, 255),
header_bgcolor=(32, 32, 32),
show_borders=True
)
# Step 2: Create scene and add components
scene = Scene(width=64, height=32)
scene.add_component('table', table, position=(2, 2))
# Step 3: Display
display = TerminalDisplayTarget(width=64, height=32)
with display:
start_time = time.time()
while time.time() - start_time < 5.0:
current_time = time.time() - start_time
buffer = scene.render(current_time)
display.display(buffer)
time.sleep(1/30) # 30 FPS
Advanced Example - Multi-Scene with Orchestrator
Use the Orchestrator only when you need multiple scenes with transitions.
from matrix_scene_composer import (
Orchestrator, Scene, TableComponent, RainbowFilter,
Text5pxComponent, TerminalDisplayTarget
)
import time
# ============================================================================
# Data Sources (assume these fetch real data)
# ============================================================================
def get_system_stats():
"""Return current system metrics."""
return [
{"component": "CPU", "usage": "45%", "temp": "67C"},
{"component": "RAM", "usage": "72%", "temp": "45C"},
{"component": "GPU", "usage": "89%", "temp": "82C"},
]
def get_weather_info():
"""Return current weather."""
return {
"city": "Stockholm",
"temp": "15°C",
"condition": "Cloudy"
}
# ============================================================================
# Scene Builders
# ============================================================================
def create_system_scene(width, height):
"""System monitoring scene with rainbow effect."""
# Create components first (no scene reference!)
data = get_system_stats()
table = TableComponent(
data=data,
text_component=Text4pxComponent,
fgcolor=(255, 255, 255),
header_bgcolor=(32, 32, 32),
cell_padding=1,
show_borders=True
)
# Wrap with animated rainbow effect
rainbow_table = RainbowFilter(
source_component=table,
color_key=(255, 255, 255),
direction='horizontal',
speed=0.5
)
# Create scene and add components
scene = Scene(width, height)
scene.add_component('table', rainbow_table, position=(2, 8))
return scene
def create_weather_scene(width, height):
"""Weather display scene."""
# Create components first (no scene reference!)
weather = get_weather_info()
text = f"{weather['city']}\n{weather['temp']}\n{weather['condition']}"
weather_text = Text5pxComponent(
text=text,
fgcolor=(255, 255, 255),
bgcolor=(0, 0, 64),
padding=2
)
# Optional rainbow effect
rainbow_weather = RainbowFilter(
source_component=weather_text,
color_key=(255, 255, 255),
direction='vertical',
speed=0.8
)
# Create scene and add components
scene = Scene(width, height)
scene.add_component('weather', rainbow_weather, position=(10, 5))
return scene
# ============================================================================
# Main Application - Scene Rotation
# ============================================================================
def main():
width, height = 64, 32
# Set up display (swap for RGBMatrixDisplayTarget on real hardware)
display = TerminalDisplayTarget(width, height)
# Create orchestrator
orch = Orchestrator(width, height, fps=30)
orch.set_display_callback(display.display)
# Build and register scenes (Orchestrator adopts them)
orch.add_scene('system', create_system_scene(width, height))
orch.add_scene('weather', create_weather_scene(width, height))
# Define scene rotation schedule
scenes = [
('system', 5.0), # Show system for 5 seconds
('weather', 5.0), # Show weather for 5 seconds
]
# Start display and run with scene rotation
with display:
scene_idx = 0
scene_start = time.time()
start_time = time.time()
orch.transition_to(scenes[scene_idx][0])
try:
while True:
current_time = time.time() - start_time
elapsed_in_scene = time.time() - scene_start
scene_name, duration = scenes[scene_idx]
# Check if time to switch scenes
if elapsed_in_scene >= duration:
scene_idx = (scene_idx + 1) % len(scenes)
next_scene, _ = scenes[scene_idx]
# Rebuild scene with fresh data
if next_scene == 'system':
orch.scenes['system'] = create_system_scene(width, height)
elif next_scene == 'weather':
orch.scenes['weather'] = create_weather_scene(width, height)
orch.transition_to(next_scene)
scene_start = time.time()
# Render current frame
orch.time = current_time
buffer = orch._render_frame()
display.display(buffer)
# Maintain FPS
time.sleep(1.0 / orch.fps)
except KeyboardInterrupt:
print("\nShutting down...")
if __name__ == "__main__":
main()
Key Concepts
Components are Pure and Composable
Components don't know about Scene - they're standalone, reusable building blocks.
# Components are pure (no scene parameter!)
table = TableComponent(data=data, ...)
# Wrap with filter (composition)
rainbow_table = RainbowFilter(source_component=table, ...)
# Scene composes components
scene = Scene(width=64, height=32)
scene.add_component('background', bg_text, position=(0, 0), z_index=1)
scene.add_component('foreground', fg_text, position=(10, 10), z_index=2)
Display Targets are Swappable
# Development - terminal emulator
display = TerminalDisplayTarget(64, 32)
# Production - physical matrix
display = RGBMatrixDisplayTarget(64, 32, brightness=80)
# Same code works with both!
Caching is Automatic
class MyComponent(Component):
def compute_state(self, time):
# Return same state = cached rendering
return ("static",)
# Return different state = re-render
return ("animated", int(time))
Examples
See the examples/ directory for more:
simple_scene_example.py- Start here! Simplest Scene + Components example (terminal)simple_scene_piomatter.py- Same example but for physical hardware (PioMatter)demo_rainbow_table.py- Rainbow system monitordemo_terminal_emulator.py- Terminal display demodemo_orchestrator.py- Multi-scene showcase with Orchestrator
Hardware Support
Raspberry Pi 5 + Adafruit RGB Matrix Bonnet
Uses the PioMatterDisplayTarget with the modern PioMatter library:
from matrix_scene_composer import PioMatterDisplayTarget
display = PioMatterDisplayTarget(
width=64,
height=32,
n_addr_lines=4, # 4 for 64x32 panel
brightness=1.0 # 0.0 to 1.0
)
Older Raspberry Pi Models
Uses the RGBMatrixDisplayTarget with the classic rpi-rgb-led-matrix library:
from matrix_scene_composer import RGBMatrixDisplayTarget
display = RGBMatrixDisplayTarget(
width=64,
height=32,
brightness=80
)
Design Principles
- Clean Architecture: One-way dependencies (Component → Scene → Orchestrator)
- Pure Components: Components are standalone, no knowledge of Scene
- KISS: Keep it simple - start with Scene + Components, add Orchestrator only if needed
- Fixed canvas: Everything renders to defined width × height
- Time as parameter: Time is passed to
render(time), not stored in components - Composable: Components wrap components (filters, effects, etc.)
- Hardware agnostic: Same code works on terminal or real matrix
Metadata
Release files for rpi-rgb-led-matrix-scene-composer 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| rpi_rgb_led_matrix_scene_composer-0.1.1.tar.gz | 58.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| rpi_rgb_led_matrix_scene_composer-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 105.2 kB
Release files / rpi_rgb_led_matrix_scene_composer-0.1.1.tar.gz
| Download URL | rpi_rgb_led_matrix_scene_composer-0.1.1.tar.gz |
|---|---|
| Size | 58.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
42f6381253c8464c3223cf67f8e570b5db210781a52921916fc7067671038e37
|
|
BLAKE2b-256 checksum How to use checksums |
240857d14231ffc30555a5f5b7ff4a87e799808674ef33ff412c97de0247bb83
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 30, 2025.
Transparency logRelease files / rpi_rgb_led_matrix_scene_composer-0.1.1-py3-none-any.whl
| Download URL | rpi_rgb_led_matrix_scene_composer-0.1.1-py3-none-any.whl |
|---|---|
| Size | 47.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
34a1481ebfd546d038885a3739f362eb3cc19e849053efe38e136e085b1aff31
|
|
BLAKE2b-256 checksum How to use checksums |
bd791a3e7cb25eb1ec08ea5201484136ef461855b85accdd4329da6f7d8485a8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 30, 2025.
Transparency log