A pure Python implementation of the Waveshare IT8951 e-paper controller driver for Raspberry Pi. This driver provides a clean, modern Python interface for controlling e-paper displays using the IT8951 controller chip.
New in v0.14.0: Performance and memory optimizations! Pre-allocated arrays in hot paths, zero-copy SPI transfers, comprehensive memory monitoring utilities, and performance profiling tools. Test coverage increased to 99%.
v0.13.0: Enhanced debug mode with 6 verbosity levels, component-specific debugging, and diagnostic context in error messages.
v0.12.0: Comprehensive integration test suite with multi-feature workflows, error recovery, and memory management tests.
- 🐍 Pure Python - No C dependencies, runs on any platform with mock mode
- 🔋 Power Management - Standby/sleep modes with auto-sleep timeout for battery-powered devices
- 🎯 Smart Defaults - 4bpp mode by default (50% less data, same quality as 8bpp)
- 🛡️ Memory Safety - Progressive loading for large images with automatic memory warnings
- 🧪 Development-Friendly - 99.18% test coverage, type hints, and mock SPI for testing without hardware
- ⚡ Production-Ready - Auto-alignment, VCOM calibration, A2 ghosting prevention, and comprehensive error handling
- 📊 Performance Testing - Built-in benchmarks for pixel packing, display operations, and memory usage
- 🔍 Troubleshooting Tools - Interactive diagnostics, register dumps, and guided problem resolution
- Python 3.11 or later (supports 3.11 and 3.12)
- Raspberry Pi with SPI enabled (for hardware usage)
- Waveshare 10.3" e-paper HAT with IT8951 controller
- pydantic>= 2.9 - Data validation and models
- pillow>= 10.4 - Image processing
- numpy>= 1.26,<2.0 - Numerical operations (stays on 1.x to avoid breaking changes)
- spidev>= 3.6 - SPI communication (Raspberry Pi only)
- RPi.GPIO>= 0.7.1 - GPIO control (optional, Raspberry Pi only)
This library supports multiple platforms:
- Raspberry Pi (ARM/ARM64) - Full hardware support
- Linux (x86_64) - Development with MockSPI
- macOS (Intel/Apple Silicon) - Development with MockSPI
- Windows - Basic compatibility with MockSPI
See Platform Support Guide for detailed platform-specific instructions.
git clone https://github.com/sjnims/IT8951_ePaper_Py.git
cd IT8951_ePaper_Py
poetry install
# For Raspberry Pi users, install with GPIO support:
poetry install -E rpigit clone https://github.com/sjnims/IT8951_ePaper_Py.git
cd IT8951_ePaper_Py
pip install -e .
# For Raspberry Pi with GPIO:
pip install -e ".[rpi]"from IT8951_ePaper_Py import EPaperDisplay
from IT8951_ePaper_Py.constants import DisplayMode
# Initialize display with VCOM voltage (check your display's FPC cable sticker)
display = EPaperDisplay(vcom=-2.0)  # Replace with your display's VCOM value
try:
    # Initialize and get display dimensions
    width, height = display.init()
    print(f"Display size: {width}x{height}")
    # Clear display to white
    display.clear(color=0xFF)
    # Display an image
    from PIL import Image
    img = Image.open("example.jpg")
    display.display_image(img, x=0, y=0, mode=DisplayMode.GC16)
finally:
    display.close()Use the context manager for automatic power management:
# Auto-sleep after 30 seconds of inactivity
with EPaperDisplay(vcom=-2.0) as display:
    display.set_auto_sleep_timeout(30.0)
    width, height = display.init()
    # Display your content
    display.display_image(img)
    # Get device status including power state
    status = display.get_device_status()
    print(f"Power state: {status['power_state']}")
# Display automatically enters sleep mode on exitThe driver supports multiple pixel formats for different use cases:
from IT8951_ePaper_Py.constants import PixelFormat
# Default: 4bpp (recommended) - 16 grayscale levels, 50% data reduction
display.display_image(img)
# 8bpp - Full 256 grayscale levels
display.display_image(img, pixel_format=PixelFormat.BPP_8)
# 2bpp - 4 grayscale levels, good for simple graphics
display.display_image(img, pixel_format=PixelFormat.BPP_2)
# 1bpp - Binary (black/white), fastest updates for text/QR codes
display.display_image(img, pixel_format=PixelFormat.BPP_1)Performance comparison:
- 1bpp: 1/8 data of 8bpp - ideal for text, QR codes, line art
- 2bpp: 1/4 data of 8bpp - good for simple graphics with 4 gray levels
- 4bpp: 1/2 data of 8bpp - best balance of quality and speed (default)
- 8bpp: Full quality - use when maximum grayscale fidelity is needed
The driver automatically detects your Raspberry Pi version and selects the optimal SPI speed:
- Raspberry Pi 3 and below: 15.625 MHz (faster)
- Raspberry Pi 4 and above: 7.8125 MHz (more stable)
You can also manually override the SPI speed:
# Manual speed override (10 MHz)
display = EPaperDisplay(vcom=-2.0, spi_speed_hz=10000000)
# Use default auto-detection
display = EPaperDisplay(vcom=-2.0)Note: These speeds are based on Waveshare's recommendations. Pi 4+ requires slower speeds due to hardware differences.
# See examples/basic_display.py
python examples/basic_display.py# Display an image file
python examples/image_display.py path/to/image.jpg -2.0# Fast partial updates for dynamic content
python examples/partial_update.py# Find optimal VCOM voltage for your display
python examples/vcom_calibration.py# Demonstrate power management features
python examples/power_management_demo.py# 4bpp optimized display (50% data reduction)
python examples/performance_4bpp.py
# 1bpp binary display for text/QR codes
python examples/binary_1bpp_demo.py
# Progressive loading for large images
python examples/progressive_loading_demo.py# Comprehensive battery-powered device example
python examples/battery_powered_demo.py# Interactive troubleshooting guide with diagnostics
python examples/troubleshooting_demo.pySee all examples in the examples/ directory.
The driver follows a layered architecture:
- Hardware Abstraction Layer (spi_interface.py)- SPIInterface- Abstract base class
- RaspberryPiSPI- Hardware implementation
- MockSPI- Mock implementation for testing
 
- Core Driver (it8951.py)- Low-level IT8951 controller communication
- Register operations and command execution
 
- High-Level Display (display.py)- User-friendly display interface
- Image processing and alignment
- Automatic format conversion
 
- Data Models (models.py)- Type-safe configuration with Pydantic
- Validation and data structures
 
- Utilities and Helpers
- alignment.py- Pixel alignment operations
- buffer_pool.py- Memory buffer management
- command_utils.py- Command validation
- pixel_packing.py- Numpy-optimized pixel packing
- vcom_calibration.py- VCOM calibration logic
 
- Exception Hierarchy (exceptions.py)- IT8951Error- Base exception
- CommunicationError- SPI communication failures
- DeviceError- Device-reported errors
- InitializationError- Initialization failures
- DisplayError- Display operation errors
- IT8951MemoryError- Memory operation failures
- IT8951TimeoutError- Operation timeouts
- InvalidParameterError- Invalid parameters
- VCOMError- VCOM voltage configuration errors
 
Important: The base EPaperDisplay class is NOT thread-safe. The IT8951 controller and SPI communication protocol do not support concurrent operations.
For multi-threaded applications, use the provided ThreadSafeEPaperDisplay wrapper:
from IT8951_ePaper_Py import ThreadSafeEPaperDisplay
import threading
# Create thread-safe display instance
display = ThreadSafeEPaperDisplay(vcom=-2.0)
# Can be safely used from multiple threads
def worker(thread_id):
    display.display_image(image, x=thread_id * 100, y=0)
threads = [threading.Thread(target=worker, args=(i,)) for i in range(4)]
for t in threads:
    t.start()
for t in threads:
    t.join()The ThreadSafeEPaperDisplay class:
- Provides automatic thread synchronization using a reentrant lock
- Has identical API to EPaperDisplay- just change the class name
- Allows nested method calls within the same thread
- Protects all public methods and properties
If you prefer manual control, implement your own synchronization:
import threading
from IT8951_ePaper_Py import EPaperDisplay
display = EPaperDisplay(vcom=-2.0)
display_lock = threading.Lock()
# In each thread:
with display_lock:
    display.display_image(image)Thread safety issues to be aware of:
- SPI transactions must be atomic (chip select, data transfer)
- Command/data sequences can be corrupted by concurrent access
- Power state changes affect all operations
- The busy wait mechanism assumes single-threaded access
See Thread Safety Guide for detailed documentation and patterns.
- INIT(0) - Full initialization mode
- DU(1) - Direct update (fast, monochrome)
- GC16(2) - 16-level grayscale (high quality)
- GL16(3) - 16-level grayscale with flashing
- A2(4) - 2-level fast update
- GLR16(5) - Ghost reduction 16-level (reduces ghosting artifacts)
- GLD16(6) - Ghost level detection 16 (adaptive ghost compensation)
- DU4(7) - Direct update 4-level (fast 4-grayscale mode)
The driver includes a mock SPI interface that allows development and testing on non-Raspberry Pi systems:
# The driver automatically uses MockSPI when not on a Raspberry Pi
from IT8951_ePaper_Py import EPaperDisplay
# Works on macOS, Windows, Linux without hardware
display = EPaperDisplay(vcom=-2.0)# Install all development dependencies
poetry install --with dev
# Run tests
poetry run pytest
# Run type checking
poetry run pyright
# Run linting
poetry run ruff check .
# Format code
poetry run ruff format .
# Check code complexity
poetry run radon cc src/ -a# Run all tests
poetry run pytest
# Run with coverage
poetry run pytest --cov
# Run specific test file
poetry run pytest tests/test_display.py
# Run performance benchmarks
poetry run pytest tests/test_performance.py -v
# Run numpy pixel packing benchmarks
poetry run pytest tests/test_numpy_pixel_packing.py -v
# Run with detailed timing information
poetry run pytest --durations=10The test suite includes:
- Unit tests for all modules with 98.35% coverage
- Performance benchmarks for critical operations
- Pixel packing tests for all bit depths (1bpp, 2bpp, 4bpp, 8bpp)
- Power management tests for battery optimization
- Alignment tests for edge cases and hardware requirements
- Extended display mode tests for GLR16, GLD16, and DU4 modes
IT8951_ePaper_Py/
├── src/
│   └── IT8951_ePaper_Py/
│       ├── __init__.py          # Package initialization
│       ├── constants.py         # Hardware constants
│       ├── exceptions.py        # Custom exceptions
│       ├── models.py            # Pydantic data models
│       ├── spi_interface.py     # SPI abstraction layer
│       ├── it8951.py            # Core driver
│       ├── display.py           # High-level interface
│       ├── alignment.py         # Pixel alignment utilities
│       ├── buffer_pool.py       # Memory buffer management
│       ├── command_utils.py     # Command validation helpers
│       ├── pixel_packing.py     # Numpy-optimized pixel packing
│       ├── utils.py             # General utilities
│       └── vcom_calibration.py  # VCOM calibration logic
├── tests/                       # Test suite
├── examples/                    # Example scripts
├── stubs/                       # Type stubs for external libs
├── docs/                        # Documentation
├── ROADMAP.md                   # Development roadmap
├── CLAUDE.md                    # AI assistant instructions
├── CHANGELOG.md                 # Version history
├── CONTRIBUTING.md              # Contribution guidelines
└── pyproject.toml               # Project configuration
The driver includes comprehensive error recovery capabilities:
from IT8951_ePaper_Py import RetryPolicy, BackoffStrategy, create_retry_spi_interface
# Exponential backoff (default)
policy = RetryPolicy(
    max_attempts=5,
    delay=0.1,
    backoff_factor=2.0,
    backoff_strategy=BackoffStrategy.EXPONENTIAL
)
# Linear backoff for gradual retry
linear_policy = RetryPolicy(
    backoff_strategy=BackoffStrategy.LINEAR,
    max_delay=5.0  # Cap maximum delay
)
# Fixed delay with jitter to prevent thundering herd
jitter_policy = RetryPolicy(
    backoff_strategy=BackoffStrategy.JITTER,
    jitter_range=0.1  # ±10% randomness
)
# Create display with retry-enabled SPI
spi = create_retry_spi_interface(retry_policy=policy)
display = EPaperDisplay(vcom=-2.0, spi_interface=spi)# See comprehensive error recovery demonstration
python examples/error_recovery_demo.py
# Basic retry mechanism demo
python examples/retry_demo.pySee Error Recovery Guide for detailed recovery procedures and best practices.
The driver includes comprehensive debug logging for troubleshooting and development:
from IT8951_ePaper_Py import enable_debug, disable_debug, DebugLevel
# Enable debug logging
enable_debug(DebugLevel.DEBUG)
# Use the display - will show debug output
display = EPaperDisplay(vcom=-2.0)
display.init()
# Disable when done
disable_debug()from IT8951_ePaper_Py import set_component_debug, DebugLevel
# Set different levels for different components
set_component_debug("spi", DebugLevel.TRACE)      # Very verbose SPI logging
set_component_debug("display", DebugLevel.INFO)   # General display info
set_component_debug("power", DebugLevel.DEBUG)    # Detailed power managementConfigure debug mode via environment:
# Global debug level
export IT8951_DEBUG=INFO
# Component-specific levels
export IT8951_DEBUG_SPI=TRACE
export IT8951_DEBUG_DISPLAY=DEBUG
python your_script.py- OFF- No debug output (default)
- ERROR- Only errors
- WARNING- Warnings and errors
- INFO- General information
- DEBUG- Detailed debug info
- TRACE- Very detailed trace info
See examples/debug_mode_demo.py for comprehensive examples.
- Platform Support - Multi-platform installation and compatibility
- Performance Guide - Optimization tips and benchmarks
- Display Modes - Detailed explanation of all display modes
- Power Management - Battery optimization and power states
- Memory Safety - Memory management best practices
- Bit Depth Support - Using different pixel formats
- Thread Safety - Multi-threading considerations and solutions
- Error Recovery - Retry policies and recovery procedures
- Hardware Setup - Raspberry Pi GPIO connections and troubleshooting
- Migration Guide - Migrating from the C driver to Python
- Best Practices - Production deployment and optimization tips
- ATS Monitoring - Automated Test Selection CI/CD optimization
- Docstring Style Guide - Examples of Google-style docstrings for contributors
See our Development Roadmap for completed and planned features. Phase 13.2 diagnostic enhancements are now complete, featuring debug mode with configurable verbosity levels and enhanced error messages with diagnostic context.
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch (git checkout -b feature/amazing-feature)
- Commit your changes (git commit -m 'Add amazing feature')
- Push to the branch (git push origin feature/amazing-feature)
- Open a Pull Request
- Follow PEP 8
- Use type hints for all functions
- Add docstrings (Google style)
- Run ruff checkandruff formatbefore committing
- Ensure all tests pass before submitting PR
This project uses GitHub Actions for continuous integration:
- Linting: ruff (linting + formatting), pyright
- Testing: pytest with coverage on multiple platforms
- Ubuntu (x86_64) - Python 3.11, 3.12, 3.13
- macOS (Intel/ARM) - Python 3.13
- ARM64 (via QEMU) - Python 3.11
- Windows - Basic compatibility check
 
- Security: CodeQL for comprehensive security analysis
- Complexity: radon for maintainability metrics
- Performance: Benchmark tests for critical paths
- Coverage: 98.35% code coverage maintained
PRs must pass all checks before merging.
- 
Check VCOM voltage # VCOM must match your display's specification (check FPC cable sticker) display = EPaperDisplay(vcom=-2.0) # Replace with your display's value 
- 
Verify SPI is enabled # Enable SPI on Raspberry Pi sudo raspi-config # Navigate to Interface Options > SPI > Enable # Verify SPI devices exist ls /dev/spi* # Should show: /dev/spidev0.0 /dev/spidev0.1 
- 
Check connections - Ensure HAT is properly seated on GPIO pins
- Verify FPC cable is fully inserted and locked
 
Enable enhanced driving mode for long cables or display quality issues:
display = EPaperDisplay(vcom=-2.0, enhance_driving=True)- 
With A2 mode (fast updates) # Enable auto-clear to prevent ghosting display = EPaperDisplay(vcom=-2.0, a2_refresh_limit=10) 
- 
General ghosting # Perform full clear display.clear() 
# Add user to spi and gpio groups
sudo usermod -a -G spi,gpio $USER
# Logout and login again for changes to take effect
# Alternative: run with sudo (not recommended for production)
sudo python your_script.pyThe IT8951 requires specific pixel alignment:
# For 1bpp mode, use 32-pixel alignment
x = (x // 32) * 32
width = ((width + 31) // 32) * 32
# For other modes, use 4-pixel alignment (handled automatically)For large images or limited memory:
# Use lower bit depth
display.display_image(img, pixel_format=PixelFormat.BPP_4)  # Default
# Use partial updates
display.display_partial(img, x=100, y=100, width=200, height=200)
# Use progressive loading for very large images (v0.4.0+)
display.display_image_progressive(
    large_image,
    chunk_height=256,  # Process in 256-pixel chunks
    pixel_format=PixelFormat.BPP_4
)The progressive loading feature processes images in chunks to reduce memory usage:
- Ideal for images larger than 16MB
- Automatically handles alignment requirements
- Configurable chunk size for memory/performance tradeoff
- 
Use appropriate pixel format # 4bpp is 2x faster than 8bpp with minimal quality loss display.display_image(img) # Uses 4bpp by default 
- 
Choose the right display mode # Fast updates display.display_image(img, mode=DisplayMode.DU) # ~260ms display.display_image(img, mode=DisplayMode.A2) # ~120ms # Quality updates display.display_image(img, mode=DisplayMode.GC16) # ~450ms 
When developing on non-Raspberry Pi systems:
# The driver automatically uses MockSPI
display = EPaperDisplay(vcom=-2.0)  # Works on any platform
# To explicitly use mock mode
from IT8951_ePaper_Py.spi_interface import MockSPI
mock_spi = MockSPI()
display = EPaperDisplay(vcom=-2.0, spi_interface=mock_spi)- 
Enable debug logging import logging logging.basicConfig(level=logging.DEBUG) # Now you'll see timing information: # DEBUG: init completed in 523.45ms # DEBUG: display_image completed in 467.23ms 
- 
Check device info display = EPaperDisplay(vcom=-2.0) width, height = display.init() print(f"Display size: {width}x{height}") print(f"VCOM: {display.get_vcom()}V") 
- 
Verify register values # Dump important registers regs = display.dump_registers() for name, value in regs.items(): print(f"{name}: 0x{value:04X}") 
- Check the examples directory for working code
- Read the performance guide
- Search existing issues
- Create a new issue with:
- Python version (python --version)
- Raspberry Pi model (cat /proc/cpuinfo | grep Model)
- Display model and VCOM voltage
- Minimal code to reproduce
- Debug log output
 
- Python version (
- Based on Waveshare IT8951 C driver
- Inspired by other e-paper Python libraries
- Thanks to the Raspberry Pi and Python communities
MIT License - see LICENSE file for details