Back to ESP32 Desk Companion

Documentation

SCREENSHOTS

Capturing Screenshots

The firmware includes a screenshot capture mode that dumps the LVGL display buffer over USB serial as base64-encoded RGB565 data. A Python script on the host decodes it to PNG files.

How It Works

  • A SCREENSHOT_ENABLED flag in main/main.c controls the feature
  • When enabled, a FreeRTOS task auto-runs 10 seconds after boot
  • The task cycles through all screen states (face, night, date, pomodoro, plant)
  • Each screen is captured via lv_snapshot_take_to_draw_buf() into a PSRAM buffer
  • The buffer is base64-encoded and written directly to USB Serial JTAG with markers
  • A Python script reads the serial stream and saves each capture as a PNG
  • Enabling Screenshot Mode

    In main/main.c, set:

    c
    #define SCREENSHOT_ENABLED 1

    Also ensure these are in sdkconfig.defaults:

    shell
    CONFIG_LV_USE_SNAPSHOT=y
    CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y

    Then rebuild and flash:

    bash
    source ~/esp/esp-idf/export.sh
    rm sdkconfig          # force sdkconfig rebuild with new defaults
    idf.py build
    idf.py -p /dev/ttyACM0 flash

    Running the Capture Script

    Requirements: Python 3 with pyserial and Pillow.

    bash
    python3 tools/capture-screenshots.py /dev/ttyACM0 ./screenshots

    The script:

  • Resets the device via DTR toggle
  • Waits for the demo to auto-start (~10s after boot)
  • Captures 6 screens: face-day, face-night, date-screen, pomo-setting, pomo-running, plant-thirsty
  • Saves each as a 240x240 PNG
  • Disabling After Capture

    Set SCREENSHOT_ENABLED back to 0, rebuild, and flash to restore normal operation. The screenshot code compiles out completely when disabled.

    Serial Protocol

    Each screenshot is framed as:

    shell
    ===SCREENSHOT:name:width:height===
    <base64 data line>
    <base64 data line>
    ...
    ===END===

    The base64 data encodes raw RGB565 pixels (little-endian, 2 bytes per pixel, row-major). The Python script strips any ESP-IDF log prefixes and validates base64 content before decoding.

    Adding New Screens

    To capture additional screen states, add entries to the screenshot_demo() function in main/main.c. The pattern is:

  • Acquire LVGL lock
  • Set up the desired UI state (show/hide containers, set colours, etc.)
  • Release LVGL lock
  • Wait 500ms for rendering
  • Call screenshot_capture("name")
  • © 2026 Jonathan Leahy · v0.8.1-31-g196fa14