Architecture
Overview
PyHMI implements a single codebase, multi-runtime architecture. The same Python UI description, widget logic, and controller code runs on three distinct platforms:
- Desktop Linux — rendering directly to
/dev/fb0(Linux framebuffer) with no X11/Wayland dependency - Microcontrollers — rendering to SPI LCDs via CircuitPython
displayio-compatible drivers - Emulation — rendering in an SDL2 window on macOS, Linux, and Windows for design and testing
Layered Design
PyHMI is organized into six horizontal layers. Each layer depends only on the layer below it, enabling independent testing and platform-specific substitution.
Six-layer architecture: Application, Widget, Core Engine, Graphics Abstraction, Display Backend, Input Backend.
1. Application Layer
User-facing entry points. Contains the PyHML parser, QML importer, HTML converter, data model, Python controllers, and scene manager.
- PyHML Parser — Converts
.pyhmlfiles into widget trees at runtime - QML Importer — Loads Qt QML designs for reuse
- HTML Converter — Transforms HTML files into PyHML
- Data Model — Observable properties for reactive binding
- Scene Manager — Load, unload, and navigate between scenes with data segue
2. Widget Layer
Reusable UI components. Each widget is a Python class inheriting from WidgetBase. Widgets manage their own state, rendering, and event handling.
- 50+ built-in widgets — covering all elements from uGUI, LVGL, TouchGFX, emWin, and Qt/Quick
- Custom widgets — user-defined via class inheritance, nestable in PyHML
- Canvas — pixel-level drawing surface
- Video — MP4, MJPEG stream playback (desktop only)
- VirtualKeyboard — on-screen keyboard for small displays
3. Core Engine
The heart of PyHMI. Provides the infrastructure that powers all widgets and enables the declarative UI paradigm.
- Widget Tree — Hierarchical model with parent-child relationships
- Layout Engine — Flex (row/column) and Grid layouts
- Event Loop — Single-threaded, polls input and schedules redraws
- Animation Engine — Timeline-based with easing curves
- Style Templates — Cascading properties (widget → class → theme)
- Data Binding — Observable properties with dependency tracking
- Gesture Recognizer — Touch gesture detection (swipe, pinch, tap, long-press)
- Dialog Manager — Modal dialog system with segue support
4. Graphics Abstraction
Platform-agnostic drawing API. Shields the widget layer from platform-specific rendering details.
- Canvas — Double-buffered render target with pixel-level operations
- Painter — Drawing primitives (line, rect, circle, arc, text, image)
- Font Renderer — Bitmap fonts (built-in) + TrueType (desktop)
- Image Decoder — PNG, BMP, GIF (desktop); BMP, PNG (MCU)
- Video Decoder — MP4, MJPEG stream (desktop only)
5. Display Backend
Pluggable rendering target. Three implementations, selected at startup.
- Framebuffer — Direct
/dev/fb0access via mmap (Linux) - SPI/LCD — CircuitPython
displayiocompatible (MCU) - SDL2 — Cross-platform emulation window (macOS, Linux, Windows)
6. Input Backend
Pluggable input handling. Events are normalized and dispatched to the event loop.
- evdev — Linux input devices (touchscreen, keyboard, mouse)
- tinyusb — USB HID keyboard/mouse on CircuitPython (MCU)
- Touch Controller — SPI/I2C touch screen (MCU)
Data Flow
User input is captured by the Input Backend, normalized by the Event Loop, and dispatched to the appropriate widget. Widgets update their state, triggering layout recalculation and redraw. The Graphics Abstraction layer translates draw calls to the active Display Backend.
Input → Event Loop → Widget Tree → Layout → Graphics Abstraction → Display Backend.
Runtime Environments
Desktop Linux (Framebuffer)
Runs on Raspberry Pi, BeagleBone, or any Linux SBC with framebuffer support. No X11/Wayland required. Direct pixel access via mmap for maximum performance.
- Python 3.8+
/dev/fb0via mmap- evdev for input (touchscreen, keyboard, mouse)
- TrueType fonts, full image/video support
- Target: 30+ FPS on Raspberry Pi 3
Microcontroller (CircuitPython)
Runs on RP2040, SAMD51, or any CircuitPython-compatible board with SPI LCD. Memory-constrained but fully functional.
- CircuitPython 9+
displayioprotocol for SPI LCDs- tinyusb for USB HID input
- Bitmap fonts, BMP/PNG images only
- Target: 15+ FPS on Raspberry Pi Pico W with 240x240 display
Emulation (SDL2)
Runs on macOS, Linux, and Windows. Provides a windowed environment for design and testing. Renders identically to the framebuffer backend.
- Python 3.8+ with PySDL2
- SDL2 window with configurable resolution
- Standard keyboard/mouse input
- Simulated touch input for gesture testing
Design Principles
Single Codebase, Multiple Runtimes
The same Python code runs on all three platforms. Platform-specific code is isolated in backend modules. UI descriptions (PyHML) are portable across runtimes.
Declarative UI
PyHML uses a YAML-compatible, QML-inspired syntax. UI hierarchy is defined by indentation. Properties, bindings, and signals are declared inline. The parser constructs a widget tree at runtime.
Composability
Custom widgets are Python classes. They can be composed, nested, and reused. The widget tree is a standard tree data structure — no special frameworks required.
Memory Efficiency
Desktop runtime targets <5 MB RAM for a typical HMI with 15 widgets. MCU runtime targets <500 KB RAM including display buffer. Achieved through lazy loading, selective widget compilation, and minimal abstractions.
Progressive Enhancement
Features are available based on platform capability. TrueType fonts, video, and MJPEG are desktop-only. The same UI description degrades gracefully on MCU targets.