Skip to content

kfeuerschvenger/lcdstats

Repository files navigation

Raspberry Pi System Monitor LCD Panel

Python C++ Arduino Docker License

A multi-mode system monitor for Raspberry Pi with support for:

  • Direct LCD display (ILI9163 via SPI)
  • ESP32 WiFi display (remote wireless screen)
  • Windows simulation (Tkinter GUI for development)

STL files and photos for the 3D-printed enclosure are available on Thingiverse: https://www.thingiverse.com/thing:7218125


Table of Contents


Motivation

I recently got a Raspberry Pi 5 to set up my own home server. I'm hosting my website on it and have some exciting future projects lined up. I wanted a way to monitor system stats from my desk, so I developed this project to display important information like public IP, local IP, CPU temperature, memory usage, disk usage, and other metrics.

The app supports three display modes:

  • Native LCD: Direct connection to ILI9163 display on Raspberry Pi
  • ESP32 WiFi: Wireless streaming to an ESP32-C3 with LCD display
  • Simulation: Windows development mode with Tkinter window

3D-Printed Enclosure (available as STL on Thingiverse)

The project includes a custom 3D-printed enclosure designed by me to make the hardware compact, mountable and tidy. The enclosure package on Thingiverse contains all STL files and photos and is linked above. The enclosure consists of the following parts:

  • Front frame that holds the 128×128 SPI display securely.
  • Two interchangeable back covers: one sized for builds with an ESP32-C3 (with a USB-C cutout) and another designed for direct wiring to a Raspberry Pi (with a central hole to route cables to the Pi).
  • Internal ESP32 insert: a small internal piece that isolates the ESP32 from the display, organizes wiring, and secures the board inside the housing when using the ESP32 option.
  • Clamp system with matching threaded screw to attach the assembly to a monitor frame.
  • Two arms and a support piece that snap into the back cover; the arms use a smaller GoPro-style mount (custom-sized) so the screen angle is adjustable and compact.

All STL files, print orientation suggestions and recommended print settings are published on Thingiverse. Use the Thingiverse page to download the ZIP of STL files and the included photos.

Features

Multi-Screen System

The application cycles through 4 screens with button navigation:

  1. System Metrics - Public/local IPs, CPU usage/temperature, memory, disk space, uptime, current time
  2. Weather Information - Current conditions, temperature, humidity, wind speed, cloud coverage (auto-refresh every 15 minutes)
  3. Docker Containers - Running container status, uptime, service overview (real-time updates every 5 seconds)
  4. Animated Display - Custom GIF animation with smooth frame transitions

Display Modes

  • Native SPI LCD (ILI9163) - Direct connection, lowest latency
  • ESP32 WiFi Display - Wireless streaming to remote ESP32-C3 with LCD
  • Desktop Simulation (Windows/Tkinter) - Development mode with GUI window

Architecture Features

  • Modular screen system with base class and helper methods
  • Hardware abstraction layer for multiple display types
  • Centralized configuration with structured dataclasses
  • Advanced logging system with colored console output and module-specific prefixes
  • Resource management with singleton pattern for fonts and icons
  • Input handling framework supporting both GPIO and keyboard input

Gallery

Photos and the full STL set are hosted on Thingiverse (link above).

Mounted on monitor just chilling

Me holding it without support bracket

Disconnected message, now it's in english but I'm too lazy to take another photo

Project Structure

lcdstats/
├── config/
│   └── config.py                 # Structured configuration with dataclasses
├── devices/
│   ├── device.py                 # Abstract interfaces for display devices
│   ├── fake_display.py           # Tkinter-based simulator for development
│   ├── ILI9163.py                # Native SPI LCD driver (Raspberry Pi)
│   └── esp32_wifi_display.py     # WiFi streaming client for ESP32
├── esp32_display_server/         # ESP32 firmware (Arduino IDE)
│   ├── config.h                  # WiFi & hardware configuration
│   ├── display.h/cpp             # ILI9163 driver for ESP32
│   ├── input.h/cpp               # Button input handling
│   ├── network.h/cpp             # WiFi communication protocol
│   ├── state_manager.h/cpp       # Device state machine
│   ├── progress.h/cpp            # Progress indicator UI component
│   └── esp32_display_server.ino  # Main firmware entry point
├── fonts/                        # Custom TrueType fonts
├── resources/                    # Graphics assets (icons, GIFs)
├── tests/                        # Test suite and validation
├── utils/                        # Utility classes and helpers
│   └── progress_indicator.py     # Circular progress widget
├── views/                        # UI Screen implementations
│   ├── screen.py                 # Base class with common utilities
│   ├── main_screen.py            # System metrics (Screen 1)
│   ├── secondary_screen.py       # Weather information (Screen 2)
│   ├── third_screen.py           # Docker containers (Screen 3)
│   └── fourth_screen.py          # Animated GIF display (Screen 4)
├── logger.py                     # Advanced logging system with colors
├── resource_manager.py           # Singleton for shared resources
├── weather_data.py               # Weather API data mappings
├── data_gatherer.py              # System metrics collection with caching
├── input_handler.py              # Unified input (GPIO + keyboard)
├── screen_manager.py             # Screen state and navigation management
├── stats.py                      # Main application entry point
├── requirements.txt              # Python dependencies (Raspberry Pi)
├── requirements-windows.txt      # Python dependencies (Windows)
├── Dockerfile                    # Docker container configuration
└── docker-compose.yml            # Docker orchestration with environment variables

Architecture Highlights

Configuration System (config/config.py)

Structured configuration with dataclasses and environment variable support:

  • HardwareConfig: GPIO pins, SPI settings, screen dimensions
  • AppConfig: Frame rate, input handling, update intervals
  • APIConfig: Weather API with validation and environment variables
  • DockerConfig: Container monitoring settings
  • DisplayConfig: ESP32 WiFi and simulation parameters
  • VisualConfig: Colors, temperature thresholds, animations
  • FontConfig: Font paths and sizes

Logging Infrastructure (logger.py)

Professional logging system with colored console output, module-specific prefixes, and configurable levels (DEBUG, INFO, WARNING, ERROR, CRITICAL). Consistent format: [LEVEL] [Module] Message.

Resource Management (resource_manager.py)

Singleton pattern for efficient resource handling with font caching, centralized icon definitions, and memory-efficient loading.

Screen System (views/screen.py)

Object-oriented architecture with base Screen class providing font helpers, icon support, color gradients, drawing utilities, and consistent update()/draw() interface.

Input Handling (input_handler.py)

Unified input supporting GPIO buttons with debouncing, keyboard simulation (spacebar), short/long press detection, and cross-platform compatibility with graceful fallbacks.

Hardware Requirements

Native LCD Setup (Raspberry Pi)

Component Specification
Raspberry Pi Model 3B+/4/5 (Tested on Pi 5)
Display 1.44" 128x128 SPI TFT (ILI9163 driver)
Button Momentary push button (Normally Open)

ESP32 WiFi Display Setup

Component Specification
Raspberry Pi Model 3B+/4/5 (runs Python client)
ESP32 ESP32-C3 or similar
Display 1.44" 128x128 SPI TFT (ILI9163 driver)
Button Momentary push button (Normally Open)

Software Dependencies

Raspberry Pi (Native LCD Mode)

# Core dependencies
Pillow==10.3.0          # Image processing
numpy==1.26.4           # Array operations
python-periphery==2.4.1 # GPIO control
spidev==3.6             # SPI communication

# Optional dependencies
requests==2.31.0        # Weather API (Screen 2)
docker==7.0.0           # Docker monitoring (Screen 3)

Windows (Development/Simulation)

# Core dependencies
Pillow==10.3.0          # Image processing
numpy==1.26.4           # Array operations

# Optional dependencies
requests==2.31.0        # Weather API (Screen 2)
docker==7.0.0           # Docker monitoring (Screen 3)

Note: tkinter is included with Python on Windows, no separate installation needed.

Configuration

Environment Variables (Recommended)

The application supports environment variables for sensitive configuration:

# Weather API configuration
export WEATHER_API_KEY="your_api_key_here"
export WEATHER_LOCATION="37.239541,-115.812265"  # Your coordinates
export WEATHER_UPDATE_INTERVAL="900.0"  # 15 minutes

Weather API Setup (Optional - for Screen 2)

  1. Get a free API key from WeatherAPI.com
  2. Set environment variables above or edit config/config.py

Customization

Edit config/config.py to customize behavior:

# Application behavior
class AppConfig:
    FPS: int = 30                    # Frames per second
    LONG_PRESS_THRESHOLD: float = 3.0  # Long press duration
    STARTING_SCREEN_INDEX: int = 0    # Starting screen

# Temperature thresholds (for color coding)
class VisualConfig:
    MIN_TEMP: float = 40.0    # Blue (below this)
    IDLE_TEMP: float = 50.0   # Green (normal)  
    MAX_TEMP: float = 80.0    # Red (above this)

# Docker monitoring
class DockerConfig:
    CONTAINER_UPDATE_INTERVAL: float = 5.0  # Update frequency
    MAX_VISIBLE_CONTAINERS: int = 5

# Display settings
class DisplayConfig:
    SCALE_FACTOR: int = 1       # Simulation mode scaling
    ESP32_PORT: int = 8080      # WiFi communication port

Logging

Enable debug logging with: python stats.py --debug

Log Format: [LEVEL] [Module] Message
Levels: DEBUG (cyan), INFO (green), WARNING (yellow), ERROR (red), CRITICAL (magenta)

Wiring Diagrams

Native LCD Mode (Raspberry Pi → ILI9163)

Pin Physical Pin Raspberry Pi Function
VCC Pin 1 3.3V Power Power supply
GND Pin 6 Ground Common ground
SCL Pin 23 GPIO 11 (SCLK) SPI Clock
SDA Pin 19 GPIO 10 (MOSI) SPI Data
RES (Reset) Pin 22 GPIO 25 Display Reset
DC (Data/Command) Pin 18 GPIO 24 Data/Command Select
CS Pin 29 GPIO 5 Chip Select
LED Pin 17 3.3V Power Backlight Power
Button Signal Pin 12 GPIO 18 Button input
Button Ground Pin 9 Ground Button return path

ESP32 WiFi Mode (ESP32-C3 → ILI9163)

Pin ESP32-C3 GPIO Function
VCC 3.3V Power supply
GND GND Common ground
CS GPIO 7 Chip Select
RST GPIO 8 Display Reset
RS GPIO 10 Data/Command Select
SDI GPIO 6 SPI Data (MOSI)
CLK GPIO 4 SPI Clock
LED 3.3V Backlight Power
Button Signal GPIO 2 Button input
Button Ground GND Button return path

Note: ESP32 button uses internal pull-up, no external resistor needed.

Installation

Option 1: Native Installation (Raspberry Pi)

# 1. Install system dependencies
sudo apt update && sudo apt install -y \
  python3-dev \
  libgpiod-dev \
  libjpeg-dev \
  zlib1g-dev \
  python3-spidev

# 2. Create and activate virtual environment
python3 -m venv stats_env
source stats_env/bin/activate

# 3. Install Python packages
pip install -r requirements.txt

# 4. (Optional) Configure weather API in config.py
# Edit config.py and add your WeatherAPI.com key

Option 2: Docker Installation (Recommended for Raspberry Pi)

Important Note on Docker Mode:

When running in Docker, the app cannot access real system metrics (CPU temp, memory usage, etc.) because it's isolated in a container. Docker mode is primarily useful for:

  • ESP32 WiFi display: Stream to remote display (works perfectly)
  • Development/Testing: Simulated data for UI testing
  • Portability: Easy deployment without system dependencies

For native LCD with real metrics, use native installation (Option 1).

# Quick start with docker-compose
docker-compose up -d

# Or build and run manually
docker build -t lcdstats .
docker run -d \
  --name lcdstats \
  --network host \
  -e DISPLAY_MODE=esp32 \
  -e ESP32_HOST=192.168.0.199 \
  lcdstats

Docker environment variables:

  • DISPLAY_MODE: window (default), esp32, or raspberry
  • ESP32_HOST: IP address of ESP32 (required for esp32 mode)

Option 3: Windows Simulation Setup

# 1. Create virtual environment
python -m venv stats_env
stats_env\Scripts\activate

# 2. Install dependencies
pip install -r requirements-windows.txt

# 3. (Optional) Configure weather API in config.py
# Edit config.py and add your WeatherAPI.com key

ESP32 Firmware Installation

  1. Install Arduino IDE and ESP32 board support:

    • Download Arduino IDE
    • Go to Tools → Board → Board Manager
    • Search for "esp32" and install "esp32 by Espressif Systems"
  2. Install required libraries:

    • Go to Tools → Manage Libraries (or Sketch → Include Library → Manage Libraries)
    • Search and install: ArduinoJson (by Benoit Blanchon)
  3. Configure WiFi in esp32_display_server/config.h:

    #define WIFI_SSID "YourNetworkName"
    #define WIFI_PASSWORD "YourPassword"  // Plain text password (to be improved in future releases)
    
    // Optional: Set static IP (recommended)
    #define STATIC_IP_ENABLED true
    #define STATIC_IP_0 192
    #define STATIC_IP_1 168
    #define STATIC_IP_2 0
    #define STATIC_IP_3 199  // Last octet (change as needed)
  4. Upload firmware:

    • Connect ESP32 to your computer via USB
    • Open esp32_display_server/esp32_display_server.ino in Arduino IDE
    • Select board: Tools → Board → ESP32 Arduino → ESP32C3 Dev Module
    • Select port: Tools → Port → (your COM port or /dev/ttyUSB0)
    • Click Upload button (→)
    • Wait for "Done uploading" message
  5. Verify operation:

    • Open Tools → Serial Monitor (set to 115200 baud)
    • Press the RESET button on your ESP32
    • You should see:
      === ESP32 Display System ===
      Connecting to WiFi....
      WiFi Connected!
      IP Address: 192.168.0.199
      Server started on port 8080
      System ready - State: DISCONNECTED
      
    • The LCD should display "Desconectado" with the IP address
  6. Note the IP address shown on the ESP32 display - you'll need it for the Python client

Usage

Command Line Options

python stats.py [OPTIONS]

Options:
  --display {auto,raspberry,window,esp32}
                                  Display type to use (default: auto)
  --esp32-host TEXT              ESP32 host IP address for WiFi display
  --debug                        Enable debug logging with colored output
  --help                         Show this help message

Native LCD Mode (Direct SPI)

source stats_env/bin/activate
python stats.py --display raspberry

ESP32 WiFi Mode (Wireless)

# Make sure ESP32 is powered and showing its IP
source stats_env/bin/activate
python stats.py --display esp32 --esp32-host 192.168.0.199

Docker Mode (ESP32 WiFi)

# Edit docker-compose.yml with your ESP32 IP, then:
docker-compose up -d

# View logs with color coding
docker-compose logs -f

# Stop
docker-compose down

Windows Simulation Mode

stats_env\Scripts\activate
python stats.py --display window

Debug Mode

Enable detailed logging for troubleshooting:

python stats.py --debug

This will show:

  • Colored log output by level
  • Module-specific prefixes
  • Detailed error messages with stack traces
  • Performance metrics and timing information

Controls

  • Short press: Cycle through screens (System → Weather → Docker → Animation)
  • Long press (3 seconds): Toggle display on/off (shows progress indicator)
  • Spacebar (simulation mode): Emulates button press

Screen Navigation

The display cycles through 4 screens:

  1. System Info - CPU, memory, disk, network, uptime
  2. Weather - Current conditions, temperature, humidity, wind
  3. Docker - Running containers with status and uptime
  4. Animation - Custom GIF display (configurable in config.py)

Communication Protocol (ESP32 WiFi Mode)

The Python client and ESP32 communicate over TCP port 8080 using a JSON + binary protocol:

  1. Handshake: ESP32 sends capabilities (width, height, format)
  2. Display Command: Client sends JSON header + RGB565 binary payload
  3. Acknowledgment: ESP32 confirms receipt or requests retransmission
  4. Commands: ESP32 can request next screen or stop sending

Protocol features:

  • Automatic reconnection with exponential backoff
  • Fragmentation detection and retry logic
  • Error codes for debugging
  • Screen ID tracking for synchronization

Testing

python -m lcdstats.tests.test_suite           # Interactive menu
python -m lcdstats.tests.test_suite all       # Run all tests
python -m lcdstats.tests.test_suite image     # Run specific test

The project follows professional software engineering practices with centralized logging, type-safe configuration, graceful error handling, and modular architecture for easy extensibility.

Troubleshooting

ESP32 WiFi Display Issues

Problem: ESP32 shows "Disconnected" screen
Solution:

  • Check WiFi credentials in config.h (use plain text password, not PSK hash)
  • Verify ESP32 and Raspberry Pi are on same network
  • Open Serial Monitor (115200 baud) to check for WiFi errors
  • Try power cycling the ESP32 (unplug and plug back in)

Problem: Python client cannot connect ("Connection timeout" or "No handshake")
Solution:

  • Verify ESP32 is showing "Desconectado" with IP address on screen
  • Test connectivity from Raspberry Pi: ping <esp32-ip>
  • Test TCP port: nc -zv <esp32-ip> 8080 or telnet <esp32-ip> 8080
  • Check ESP32 Serial Monitor for errors or crashes
  • Power cycle the ESP32 - unplug and reconnect (common fix!)
  • Verify firewall isn't blocking port 8080
  • Try re-uploading the firmware with Arduino IDE

Problem: Display freezes or shows artifacts
Solution:

  • Check SPI connections (loose wires between ESP32 and LCD)
  • Reduce SPI frequency in config.h if needed (try 20MHz instead of 30MHz)
  • Verify power supply stability (use quality USB cable, 5V 1A minimum)
  • Check for loose connections on breadboard

Problem: ESP32 won't connect to WiFi
Solution:

  • Double-check SSID and password in config.h (case-sensitive!)
  • Ensure WiFi is 2.4GHz (ESP32-C3 doesn't support 5GHz)
  • Check if MAC filtering is enabled on your router
  • Try disabling static IP (STATIC_IP_ENABLED false) to use DHCP
  • Open Serial Monitor to see actual error messages

Problem: Weather screen or Docker screen shows "No Docker" or error
Solution:

  • Weather API key not configured in config.py
  • Docker daemon not running: sudo systemctl start docker
  • Docker SDK not installed: pip install docker
  • Check API key is valid at WeatherAPI.com

Problem: Docker container keeps restarting
Solution:

  • Check logs: docker logs lcdstats
  • Common issues:
    • ESP32 not reachable: verify with ping <esp32-ip>
    • Wrong ESP32_HOST in docker-compose.yml
    • ESP32 not powered on or firmware not uploaded
  • Try rebuilding: docker-compose down && docker-compose build --no-cache && docker-compose up

Docker Mode Issues

Problem: Cannot connect to ESP32 from Docker
Solution:

  • Verify network_mode: host is set in docker-compose.yml
  • Test connectivity from host: ping <esp32-ip> should work
  • Check ESP32_HOST environment variable matches actual ESP32 IP
  • Ensure ESP32 is on same network as Raspberry Pi

Problem: System metrics show weird/simulated data
Solution:

  • Verify IS_RASPBERRY: "true" in docker-compose.yml
  • Check that privileged mode is enabled
  • Verify volumes are mounted: /sys:/sys:ro and /proc:/proc:ro
  • Rebuild container: docker-compose build --no-cache

Native LCD Issues

Problem: "No module named 'periphery'"
Solution:

  • Install correct package: pip install python-periphery==2.4.1
  • Old package name was periphery, new one is python-periphery
  • Update requirements.txt if needed

Problem: Display doesn't turn on
Solution:

  • Check all wiring connections (see wiring diagram)
  • Verify SPI is enabled: sudo raspi-config → Interface Options → SPI → Enable
  • Test with: ls /dev/spidev* (should show /dev/spidev0.0)
  • Check power connections (VCC to 3.3V, not 5V!)

General Issues

Problem: "Permission denied" when accessing GPIO
Solution:

  • Add user to gpio group: sudo usermod -a -G gpio $USER
  • Logout and login again
  • Or run with sudo (not recommended for production)

Problem: High CPU usage
Solution:

  • This is normal during ESP32 streaming (encoding RGB565)
  • Reduce FPS in config.py: change AppConfig.FPS to lower value (e.g., 20)
  • Native LCD mode uses less CPU than WiFi streaming

Future Improvements

Recently Implemented

  • Environment variables for sensitive configuration
  • Advanced logging system with colors and module prefixes
  • Type-safe configuration with dataclasses
  • Modular architecture with clean separation of concerns

Planned Features

  • Backlight control and PWM dimming
  • Configurable timeouts and themes
  • ESP32 battery monitoring and low-power modes
  • mDNS discovery for automatic ESP32 detection
  • Multiple display support
  • Additional screens (system logs, network traffic)
  • Mobile app for remote control

Technical Debt

  • WiFi security improvements for ESP32
  • Expanded unit test coverage
  • API documentation
  • Performance optimizations
  • Enhanced error recovery

Credits & Acknowledgments

This project was inspired by mklements/OLED_Stats, a great starting point for displaying system info on tiny screens. Big thanks for the spark!

License

MIT License - see LICENSE file for details.

About

Stats viewer for a Raspberry PI 5 LCD screen

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors