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:

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 .pyhml files 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/fb0 access via mmap (Linux)
  • SPI/LCD — CircuitPython displayio compatible (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/fb0 via 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+
  • displayio protocol 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.