Skip to content

Repository files navigation

Python 3.10+ Flask 3.0+ Docker ESP32-S3 ST7796S MIT

ESP32 TFT Designer

Visual TFT Screen Designer · C/C++ Code Generator for ESP32-S3

🇫🇷 Lire en français


Table of Contents

  1. What is TFT Designer?
  2. Feature List
  3. Requirements
  4. Quick Start — Local (Python)
  5. Quick Start — Docker
  6. esp32-tft-container.sh — Complete Script Reference
  7. User Interface Walkthrough
  8. Drawing Tools Reference
  9. Layers Panel & Element Properties
  10. Grid & Magnetic Snap
  11. Keyboard Shortcuts
  12. Scene Format (JSON)
  13. REST API Reference
  14. Integrating Generated Code
  15. Project Structure
  16. Hardware Wiring
  17. Troubleshooting

1. What is TFT Designer?

ESP32 TFT Designer is a browser-based visual editor that lets you design graphical layouts for TFT displays and instantly export ready-to-compile C or C++ source code for your embedded project.

You draw on a virtual canvas that represents the exact resolution of your target screen. Every element — rectangles, circles, text, icons — is instantly converted to function calls for one of the two supported libraries.

┌─────────────────────────────────────────────────────────┐
│  Browser (TFT Designer)                                 │
│  ┌──────────┐  ┌──────────────────┐  ┌──────────────┐  │
│  │  Tools   │  │   Canvas 480×320 │  │    Layers    │  │
│  │  Colors  │  │   (live preview) │  │    Code C    │  │
│  │  Grid    │  │                  │  │    Export    │  │
│  └──────────┘  └──────────────────┘  └──────────────┘  │
└────────────────────────┬────────────────────────────────┘
                         │ HTTP POST /api/generate
                         ▼
            ┌────────────────────────┐
            │  Flask + codegen.py    │
            │  JSON scene → C code   │
            └────────────────────────┘
                         │
          ┌──────────────┴───────────────┐
          │                              │
   tft_screen.c                  tft_screen.cpp
   (st7796s / ESP-IDF)           (LovyanGFX / Arduino)

Target hardware:

Component Specification
MCU ESP32-S3 DevKit — 16 MB Flash + OPI PSRAM
Display MSP4020 — ST7796S controller
Resolution 480 × 320 pixels
Color format RGB565 (16 bits/pixel)
Interface SPI — SPI2_HOST — 40 MHz

Supported output libraries:

Library Language Build system Notes
st7796s C ESP-IDF v6.0 Pure driver, no external dependency
LovyanGFX C++ Arduino / PlatformIO / ESP-IDF Full-featured, native arc support

2. Feature List

🎨 Drawing Tools (9 tools)

Tool Description Parameters
Pixel Place a single pixel anywhere on the canvas Color
Line Draw a straight line between two points Color, Thickness (1–20 px)
Rect □ Draw an outlined rectangle Color, Thickness (1–20 px)
Rect ■ Draw a filled rectangle Color
Circle ○ Draw an outlined circle Color, Thickness (1–20 px)
Circle ● Draw a filled circle Color
Checkerboard Draw an alternating-color checkerboard pattern Color, Cell size (via Thickness slider, 2–20 px)
WiFi icon Draw a WiFi signal icon (3 concentric arcs + dot) Color, Thickness (1–10 px)
Text Place text using a bitmap font Text content, Font, Scale (×1–×4), Color

✋ Element Manipulation

Feature Description
Drag & drop Select an element with the Select tool and drag it to a new position
Resize handles When an element is selected, handles appear at corners and edge midpoints; drag to resize
Keyboard nudge Arrow keys move the selected element by 1 px; Shift+Arrow moves by 10 px
Copy / Paste Ctrl+C / Ctrl+V — each paste offsets the clone by +8 px
Cut Ctrl+X — removes the element and places it in the clipboard
Duplicate Ctrl+D — instant clone offset by +12 px, clipboard unchanged
Delete Delete key — removes the selected element permanently

📐 Grid & Alignment

Feature Description
Grid overlay Blue reference grid drawn on a separate canvas layer, always sharp regardless of zoom
Independent H/V steps Horizontal and vertical grid spacing set independently (2–128 px each)
Sub-grid When zoom × step > 48 px, a finer sub-grid appears automatically
Magnetic snap All element creation and drag positions snap to the nearest grid intersection
Snap indicator A ⊕ symbol appears in the coordinate display when snap is active

🔍 View Controls

Feature Description
Zoom Range 10%–800%; use +/− buttons, Ctrl+scroll, or type a value directly in the input field
Fit to screen "Fit" button scales the canvas to fill the available area
Pixelated rendering image-rendering: pixelated — each native pixel becomes a sharp block, matching the real TFT output
Smooth rendering Toggle off pixelated mode for anti-aliased browser rendering (easier to read text at low zoom)
Resizable panels Drag the vertical dividers to make the left or right panel wider or narrower

📋 Layers & History

Feature Description
Layer list Every element appears as a named entry in the right panel, in drawing order
Inline property editor Click an element in the layer list to expand a property grid (x, y, w, h, r, color, etc.)
Layer reordering ▲ / ▼ buttons change z-order (later elements draw on top)
Undo / Redo 80-state history — Ctrl+Z undoes, Ctrl+Y redoes
Selection indicator Selected element highlighted with a dashed blue bounding box + white handles

💾 Save, Load & Export

Feature Description
Save scene Downloads the current design as a JSON file (scene.json)
Load scene Imports a previously saved JSON file and restores the full design
Generate code Converts the scene to C or C++ via the API and displays it with syntax highlighting
Copy code Copies the displayed code to the clipboard
Export ZIP Downloads a single ZIP archive containing .c, .cpp, scene.json, and README.txt

3. Requirements

Local (Python)

Dependency Minimum version Purpose
Python 3.10 Runtime
pip Any recent Package installer
Flask 3.0.0 Web framework
Gunicorn 21.2.0 Production WSGI server (optional for local dev)
Web browser Chrome 90+, Firefox 88+, Edge 90+, Safari 15+ Canvas/ES2020 support required

Docker

Dependency Notes
Docker Engine 24.0+ recommended
Docker Compose V2 (docker compose, not docker-compose)

4. Quick Start — Local (Python)

Step 1 — Clone or extract the project

unzip esp32-tft-designer.zip
cd esp32-tft-designer

Step 2 — Create a virtual environment

# Linux / macOS
python3 -m venv venv
source venv/bin/activate

# Windows (Command Prompt)
python -m venv venv
venv\Scripts\activate.bat

# Windows (PowerShell)
python -m venv venv
venv\Scripts\Activate.ps1

Step 3 — Install dependencies

pip install -r requirements.txt

Step 4 — Start the development server

python app.py

Expected output:

╔══════════════════════════════════════════╗
║  TFT Designer — ST7796S / LovyanGFX     ║
║  http://127.0.0.1:5100                  ║
╚══════════════════════════════════════════╝

Step 5 — Open in your browser

Navigate to http://127.0.0.1:5100

Complete end-to-end example

# Extract, install, run, design, export
unzip esp32-tft-designer.zip && cd esp32-tft-designer
python3 -m venv venv && source venv/bin/activate
pip install -r requirements.txt
python app.py
# → Browser opens http://127.0.0.1:5100
# → Draw a rectangle, add "Hello!" text, click "Generate C", click "Export ZIP"
# → tft_screen_YYYYMMDD_HHMMSS.zip downloaded — ready for ESP-IDF

5. Quick Start — Docker

One-command startup

docker compose up -d --build
# or with Podman:
RUNTIME=podman ./esp32-tft-container.sh build && ./esp32-tft-container.sh start
# → http://localhost:5100

Using the management script (recommended)

# Make the script executable (first time only)
chmod +x esp32-tft-container.sh

# Full workflow
./esp32-tft-container.sh build          # Build the Docker image
./esp32-tft-container.sh start          # Start the container in the background
./esp32-tft-container.sh status         # Check that it is running
# → Open http://localhost:5100 in your browser
./esp32-tft-container.sh logs           # Watch live logs (Ctrl+C to exit)
./esp32-tft-container.sh stop           # Stop and remove the container

Custom port example

PORT=8080 ./esp32-tft-container.sh start
# → http://localhost:8080

6. esp32-tft-container.sh — Complete Script Reference

esp32-tft-container.sh is a Bash helper that wraps common Docker / Docker Compose commands. It automatically detects whether docker-compose.yml is present and uses Compose when available; otherwise it falls back to plain docker commands.

Syntax

./esp32-tft-container.sh <command> [environment variables]

Environment variables

Variable Default Description
PORT 5100 Host port mapped to the container's internal port 5100. Override with PORT=xxxx ./esp32-tft-container.sh start.

Commands

build — Build the Docker image

./esp32-tft-container.sh build

What it does:

  • Runs docker compose build --no-cache (or docker build --no-cache without Compose).
  • Forces a complete rebuild from scratch — no layer cache is used.
  • Should be run after any modification to app.py, codegen.py, requirements.txt, or the Dockerfile.

When to use:

  • First time setup.
  • After changing Python source files or dependencies.
  • After updating the Dockerfile.

Example output:

[TFT] Building image / Construction de l'image : tft-designer…
[+] Building 45.2s (12/12) FINISHED
[OK ] Image built successfully / Image construite avec succès : tft-designer

start — Start the container in background

./esp32-tft-container.sh start
# or with a custom host port:
PORT=8080 ./esp32-tft-container.sh start

What it does:

  • Runs docker compose up -d (detached mode).
  • The container starts in the background and restarts automatically on crash or system reboot (restart: unless-stopped).
  • Displays the container status and the access URL after startup.

When to use:

  • Normal production startup after build.
  • Restarting after a stop.

Example output:

[TFT] Starting container / Démarrage du container : tft-designer…
[OK ] Container started / Container démarré → http://localhost:5100
  Status / État : Up 3 seconds  |  Ports : 0.0.0.0:5100->5100/tcp

stop — Stop and remove the container

./esp32-tft-container.sh stop

What it does:

  • Runs docker compose down.
  • Stops the running container and removes it (and its network).
  • The Docker image is kept — you can start again without rebuilding.

When to use:

  • Planned shutdown.
  • Before rebuilding with new code.

restart — Restart the running container

./esp32-tft-container.sh restart

What it does:

  • Runs docker compose restart (or docker restart).
  • Stops and restarts the container without rebuilding the image.
  • Faster than stop + start; useful to pick up environment variable changes.

When to use:

  • Applying a configuration change in docker-compose.yml.
  • Recovering from an unresponsive but not crashed container.

⚠️ If you modified Python source files, use build + start instead.


logs — Follow live container logs

./esp32-tft-container.sh logs

What it does:

  • Runs docker compose logs -f --tail=100.
  • Streams the last 100 log lines then follows new output in real time.
  • Press Ctrl+C to stop following.

Log content:

  • Gunicorn startup and worker messages
  • HTTP access log: GET / 200 / POST /api/generate 200
  • Flask application logs (print() and logging calls)
  • Health check results

Example output:

[TFT] Logs from / Logs du container tft-designer (Ctrl+C to quit / pour quitter) :

tft-designer  | [INFO] Starting gunicorn 21.2.0
tft-designer  | [INFO] Listening at: http://0.0.0.0:5100
tft-designer  | [INFO] Worker booted (pid: 8)
tft-designer  | 172.17.0.1 - - [05/May/2026] "GET / HTTP/1.1" 200 18432
tft-designer  | 172.17.0.1 - - [05/May/2026] "POST /api/generate HTTP/1.1" 200 4821

status — Show container status and info

./esp32-tft-container.sh status

What it does:

  • Inspects the container and prints its state, image, start time, port mapping, CPU and memory limits.
  • Distinguishes three states:
    • RUNNING — container is healthy and serving requests
    • STOPPED — container exists but is not running
    • NOT FOUND — container has never been created or was cleaned

Example output (running):

═══ TFT Designer — Container Status / Statut container ═══
[OK ] Container RUNNING / EN COURS D'EXÉCUTION
  Image   : tft-designer
  Started : 2026-05-05T22:00:00Z
  Port    : 5100 → 5100
  CPU     : 1000000000 nano-CPU
  Memory  : 268435456 bytes

  → Open / Ouvrir : http://localhost:5100

shell — Open an interactive shell in the container

./esp32-tft-container.sh shell

What it does:

  • Runs docker exec -it tft-designer /bin/bash (falls back to /bin/sh).
  • Opens an interactive terminal inside the running container.
  • You are logged in as appuser (non-root, uid 1001).

Useful for:

  • Inspecting the application files inside the container.
  • Running Python commands for debugging: python -c "from codegen import *; ...".
  • Checking environment variables: env | grep FLASK.

Example session:

./esp32-tft-container.sh shell
appuser@abc123:/app$ ls
app.py  codegen.py  static/  templates/
appuser@abc123:/app$ python -c "from codegen import hex_to_rgb; print(hex_to_rgb('#FF8000'))"
(255, 128, 0)
appuser@abc123:/app$ exit

clean — Remove the container AND image

./esp32-tft-container.sh clean

What it does:

  • Asks for explicit confirmation (y / o).
  • Runs docker compose down --rmi all --volumes.
  • Removes: the running container, the Docker network, and the built image.
  • After clean, a full build is required before the next start.

When to use:

  • Freeing disk space.
  • Starting completely fresh after major changes.
  • Switching to a different version of the project.

⚠️ This action is irreversible. The image must be rebuilt from scratch afterward.

Example:

[WRN] This will PERMANENTLY remove the container AND image : tft-designer
  Confirm / Confirmer ? [y/o/N] y
[OK ] Cleanup complete / Nettoyage terminé.

dev — Development mode with hot-reload

./esp32-tft-container.sh dev
# or on a custom port:
PORT=8080 ./esp32-tft-container.sh dev

What it does:

  • Starts the container with --rm -it (removed on exit, interactive).
  • Mounts local source files as read-only volumes inside the container:
    • ./app.py/app/app.py
    • ./codegen.py/app/codegen.py
    • ./templates//app/templates/
    • ./static//app/static/
  • Launches python app.py with FLASK_DEBUG=1 (Werkzeug auto-reloader).
  • Any change to a local source file is automatically picked up without rebuilding the image.

When to use:

  • Developing and testing changes to app.py, codegen.py, templates, or CSS/JS.
  • The image must have been built at least once with ./esp32-tft-container.sh build.

⚠️ Never use in production. FLASK_DEBUG=1 exposes an interactive debugger in the browser.

Exit: Press Ctrl+C to stop the dev server and remove the container.


help — Display usage help

./esp32-tft-container.sh help
# or
./esp32-tft-container.sh --help
./esp32-tft-container.sh -h
./esp32-tft-container.sh          # (no argument also shows help)

Command summary table

Command Rebuilds image Container state after Use case
build ✅ Yes Unchanged After code changes
start ❌ No Running (detached) Normal startup
stop ❌ No Stopped & removed Planned shutdown
restart ❌ No Running (detached) Config change
logs ❌ No Unchanged Monitoring
status ❌ No Unchanged Check health
shell ❌ No Unchanged Debug inside container
clean Removes image Removed Full reset
dev ❌ No Running (interactive, auto-removed) Development
help ❌ No Unchanged Display help

7. User Interface Walkthrough

╔═══════════════════════════════════════════════════════════════════════════╗
║  [←] TFT Designer  ST7796S · LovyanGFX   [Undo][Redo][Clear][Scene] [Generate C][Export ZIP]  ║
╠═══════════╦═══╦══════════════════════════════════════╦═══╦════════════════╣
║           ║ ◄ ║                                      ║ ► ║                ║
║  LEFT     ║   ║        CANVAS AREA                   ║   ║  RIGHT PANEL   ║
║  PANEL    ║   ║  [Zoom − 100% + Fit] [Pixel] x:— y:—║   ║  [Layers|Code] ║
║           ║   ║  ┌───────────────────────────────┐   ║   ║                ║
║ Resolution║   ║  │                               │   ║   ║  Layer list    ║
║ Tools     ║   ║  │   480 × 320 canvas            │   ║   ║  + properties  ║
║ Color     ║   ║  │                               │   ║   ║                ║
║ Thickness ║   ║  └───────────────────────────────┘   ║   ║  Code viewer   ║
║ Grid      ║   ║                                      ║   ║  + copy btn    ║
║           ║ ◄ ║                                      ║ ► ║                ║
╚═══════════╩═══╩══════════════════════════════════════╩═══╩════════════════╝
             ↑ dividers — drag to resize panels

Left panel sections

Section Controls
Resolution Width, Height (canvas dimensions in px), Background color, Apply button
Tools 10 tool buttons in a 2-column grid (see §8)
Color Color picker + 25-color quick palette
Thickness / Cell Slider 1–20 px — controls stroke thickness and checkerboard cell size
Grid Grid / Snap toggle buttons + H step / V step inputs

Right panel tabs

Tab Content
Layers Toolbar (▲▼ Copy Cut Paste Duplicate Delete) + scrollable element list
Code C Library selector + Copy button + code preview with syntax highlighting

8. Drawing Tools Reference

How to draw

  1. Select a tool in the left panel.
  2. For most tools: click and drag on the canvas — release to place the element.
  3. For Pixel: a single click places a pixel.
  4. For Text: a single click opens the text modal.

Tool details

Pixel

  • Click anywhere on the canvas to place a 1×1 pixel.
  • Color: set in the Color section.
  • No thickness applies.

Line

  • Click the start point, drag to the end point, release.
  • Thickness 1–20 px (slider).
  • Horizontal/vertical lines generate optimized tft_draw_hline / tft_draw_vline calls.
  • Diagonal lines generate a Bresenham loop using tft_fill_rect.
  • Resize: drag either endpoint handle.

Rect □ (outline)

  • Click and drag to define the bounding box.
  • Thickness 1–20 px — generates concentric rectangle calls for thick borders.
  • Resize: 8 handles (4 corners + 4 edge midpoints).

Rect ■ (filled)

  • Same as Rect □ but generates a single tft_fill_rect call.
  • Resize: 8 handles.

Circle ○ (outline)

  • Click the center, drag outward — release when the radius is correct.
  • Thickness: concentric circles with decreasing radius.
  • Resize: 4 handles (N, E, S, W) — all adjust the radius.

Circle ● (filled)

  • Same as Circle ○ but generates tft_fill_circle.
  • Resize: 4 handles.

Checkerboard

  • Click and drag to define the pattern area.
  • Cell size is controlled by the Thickness slider (2–20 px).
  • The cell size can be fine-tuned in the Layer properties panel after placement.
  • Generates a nested for-loop in the output code.
  • Resize: 8 handles.

WiFi icon

  • Click the signal origin point (base of the icon), drag outward — release to set the outer arc radius.
  • Three concentric arcs at 100%, 65%, 33% of the radius, opening upward.
  • Center dot radius = radius / 10.
  • Resize: 4 handles adjust the radius.

Text

  • Click anywhere on the canvas — a modal dialog opens.
  • Fill in: text content (max 58 chars), font, scale (×1–×4), color.
  • Click Place — the text appears at the click position.
  • Move/resize by editing X, Y, Scale in the layer properties.

Available fonts:

Font name Description Approximate size at scale ×1
8×16 (default) Standard monospace bitmap 8 px wide × 16 px tall
6×8 Small monospace bitmap 6 px wide × 8 px tall
12×16 Medium proportional 12 px wide × 16 px tall
bold Bold proportional ~12 px wide × 24 px tall
large Large proportional ~14 px wide × 32 px tall
7seg 7-segment LCD style Variable

Resize handles

When an element is selected with the Select / Move tool, white square handles appear:

Rect and Checkerboard (8 handles):

  ●───●───●
  │       │
  ●       ●
  │       │
  ●───●───●
  • Corner handles (NW, NE, SW, SE): resize both dimensions simultaneously.
  • Edge handles (N, S, E, W): resize a single dimension.

Circle and WiFi (4 handles):

      ●  ← N
  ●  (c)  ●
  W       E
      ●  ← S
  • All 4 handles adjust the radius only. The center does not move.

Line (2 handles):

  • One handle per endpoint. Drag either to move that endpoint independently.

9. Layers Panel & Element Properties

Layer list

Each element appears as a row:

  [icon] [color swatch] [label]
  • Click a row to select the element (highlighted on canvas).
  • Selected row expands an inline property editor.

Inline property editor

Fields shown depend on element type:

Element Editable properties
Pixel x, y, color
Line x1, y1, x2, y2, thickness, color
Rect x, y, w, h, thickness (if outline), color
Checkerboard x, y, w, h, cell size, color
Circle cx, cy, r, thickness (if outline), color
WiFi cx, cy, r, thickness, color
Text x, y, scale, text content, color

All numeric fields update the canvas in real time as you type.

Layer toolbar buttons

Button Shortcut Action
Move element one step up in draw order
Move element one step down in draw order
⎘ Copy Ctrl+C Copy to internal clipboard
✂ Cut Ctrl+X Cut to internal clipboard
⎗ Paste Ctrl+V Paste from clipboard (+8 px offset per paste)
⧉ Duplicate Ctrl+D Instant clone (+12 px offset, clipboard unchanged)
🗑 Delete Delete Remove selected element

10. Grid & Magnetic Snap

Enabling the grid

  • Click the Grid button in the left panel, or press G.
  • The grid appears as a blue overlay on a dedicated canvas layer — it is always pixel-sharp at any zoom level.
  • Set Pas H (horizontal step) and Pas V (vertical step) independently (2–128 px).

Sub-grid

When zoom × step > 48 px, a finer semi-transparent sub-grid appears between the main lines automatically.

Magnetic snap

  • Requires the grid to be visible first.
  • Click Aimantation or press S.
  • When active, all positions snap to the nearest grid intersection:
    • Element creation start and end points
    • Drag positions (anchor point snaps)
    • Resize handle positions
  • The coordinate display shows x:nn y:nn ⊕ when snap is active.

Disabling

  • Pressing G again hides the grid and automatically disables snap.
  • Pressing S toggles snap independently (only when grid is visible).

11. Keyboard Shortcuts

Global

Shortcut Action
Ctrl+Z Undo (up to 80 states)
Ctrl+Y or Ctrl+Shift+Z Redo
G Toggle grid overlay
S Toggle magnetic snap (requires grid)
P Toggle pixelated / smooth rendering
Escape Deselect current element

Element operations (requires selection)

Shortcut Action
Ctrl+C Copy selected element to clipboard
Ctrl+X Cut selected element (copy + delete)
Ctrl+V Paste from clipboard (+8 px offset per paste)
Ctrl+D Duplicate selected element (+12 px offset)
Delete Delete selected element
↑ ↓ ← → Move element by 1 px
Shift + ↑ ↓ ← → Move element by 10 px

Canvas navigation

Shortcut Action
Ctrl + scroll wheel Zoom in / out
Type in zoom field + Enter Set zoom to exact value (e.g. 150% or 150)

Note: Keyboard shortcuts are disabled when focus is in a text input or textarea to avoid conflicts with typing.


12. Scene Format (JSON)

The scene file (scene.json) is the project file format. It can be saved, shared, version-controlled, and reloaded.

Top-level structure

{
  "width": 480,
  "height": 320,
  "bg_color": "#000000",
  "elements": [ ... ]
}
Field Type Description
width integer Canvas width in pixels (matches target display width)
height integer Canvas height in pixels
bg_color string Background color in #RRGGBB hex format
elements array Ordered list of graphical elements (first = drawn first = bottom layer)

Element formats

Pixel

{ "type": "pixel", "x": 10, "y": 20, "color": "#ff0000" }

Line

{
  "type": "line",
  "x1": 0, "y1": 0, "x2": 200, "y2": 100,
  "color": "#00ff00",
  "thickness": 2
}

Rectangle (outline)

{
  "type": "rect",
  "x": 20, "y": 20, "w": 160, "h": 80,
  "color": "#0000ff",
  "filled": false,
  "thickness": 1
}

Rectangle (filled)

{
  "type": "rect",
  "x": 20, "y": 20, "w": 160, "h": 80,
  "color": "#0000ff",
  "filled": true,
  "thickness": 1
}

Circle (outline)

{
  "type": "circle",
  "cx": 240, "cy": 160, "r": 60,
  "color": "#ffff00",
  "filled": false,
  "thickness": 2
}

Circle (filled)

{
  "type": "circle",
  "cx": 240, "cy": 160, "r": 60,
  "color": "#ffff00",
  "filled": true,
  "thickness": 1
}

Checkerboard

{
  "type": "checkerboard",
  "x": 0, "y": 0, "w": 120, "h": 120,
  "cell": 10,
  "color": "#ffffff"
}

WiFi icon

{
  "type": "wifi",
  "cx": 400, "cy": 160, "r": 40,
  "color": "#00ffff",
  "thickness": 2
}

Text

{
  "type": "text",
  "x": 10, "y": 10,
  "text": "Hello World!",
  "color": "#ffffff",
  "font": "8x16",
  "scale": 2,
  "bg_color": "#000000"
}

Minimal working example

{
  "width": 480,
  "height": 320,
  "bg_color": "#001020",
  "elements": [
    {
      "type": "rect",
      "x": 10, "y": 10, "w": 460, "h": 300,
      "color": "#58a6ff",
      "filled": false,
      "thickness": 2
    },
    {
      "type": "text",
      "x": 30, "y": 140,
      "text": "Hello ESP32!",
      "color": "#ffffff",
      "font": "8x16",
      "scale": 3,
      "bg_color": "#001020"
    },
    {
      "type": "wifi",
      "cx": 420, "cy": 280, "r": 30,
      "color": "#3fb950",
      "thickness": 2
    }
  ]
}

13. REST API Reference

The Flask server exposes a minimal REST API used by the browser editor. You can also call it directly for automation or integration purposes.

GET /

Returns the HTML editor page.

curl http://localhost:5100/
# → 200 OK, Content-Type: text/html

POST /api/generate

Converts a JSON scene to C and/or C++ source code.

Request body:

{
  "scene": { ... },
  "library": "st7796s"
}
Field Type Required Values
scene object Yes Full scene JSON (see §12)
library string No (default: "st7796s") "st7796s" · "lovyangfx" · "both"

Response:

{
  "st7796s": "/* tft_screen_st7796s.c */\n..."
}

When library = "both":

{
  "st7796s": "...",
  "lovyangfx": "..."
}

curl example:

curl -s -X POST http://localhost:5100/api/generate \
  -H "Content-Type: application/json" \
  -d '{
    "library": "both",
    "scene": {
      "width": 480, "height": 320, "bg_color": "#000000",
      "elements": [
        {"type": "text", "x": 100, "y": 140,
         "text": "Hello!", "color": "#ffffff",
         "font": "8x16", "scale": 3, "bg_color": "#000000"}
      ]
    }
  }' | python3 -m json.tool

Error response:

{ "error": "description of the error" }

HTTP status 400 (bad request) or 500 (generation error).


POST /api/export_zip

Generates and returns a ZIP archive containing both source files, the scene JSON, and a README.

Request body:

{ "scene": { ... } }

Response: application/zip file download, filename tft_screen_YYYYMMDD_HHMMSS.zip.

Archive contents:

File Description
tft_screen_st7796s_TIMESTAMP.c C source for st7796s (ESP-IDF)
tft_screen_lovyangfx_TIMESTAMP.cpp C++ source for LovyanGFX
scene.json The source scene (reloadable in the editor)
README.txt Integration instructions

curl example:

curl -X POST http://localhost:5100/api/export_zip \
  -H "Content-Type: application/json" \
  -d '{"scene": {"width":480,"height":320,"bg_color":"#000000","elements":[]}}' \
  -o output.zip

POST /api/load_scene

Validates a scene JSON object and re-returns it. Used by the editor when importing a file.

Request body:

{ "scene": { ... } }

Response:

{ "scene": { ... } }

14. Integrating Generated Code

st7796s — ESP-IDF v6.0

Step 1: Copy the generated .c file into main/ of your ESP-IDF project.

Step 2: Create the header file main/tft_screen_st7796s.h:

#pragma once
#include "st7796s.h"
void tft_draw_screen(tft_handle_t *tft);

Step 3: Add to main/CMakeLists.txt:

idf_component_register(
    SRCS "main.c" "tft_screen_st7796s.c"
    INCLUDE_DIRS "."
    REQUIRES st7796s
)

Step 4: Call from main.c:

#include "st7796s.h"
#include "tft_screen_st7796s.h"

void app_main(void) {
    tft_handle_t tft;
    ESP_ERROR_CHECK(tft_init(&tft));  // Initialize SPI + display
    tft_draw_screen(&tft);            // Render the designed layout
    // ... rest of your application
}

Build:

idf.py set-target esp32s3
idf.py build
idf.py -p /dev/ttyUSB0 flash monitor

LovyanGFX — Arduino / PlatformIO

Step 1: Copy the generated .cpp file into your sketch folder.

Step 2: Create the header file tft_screen_lovyangfx.h:

#pragma once
#define LGFX_USE_V1
#include <LovyanGFX.hpp>
void draw_screen(LGFX_Device& lcd);

Step 3: Use in your sketch:

#include <LovyanGFX.hpp>
#include "tft_screen_lovyangfx.h"

// The LGFX_ST7796 class is defined inside the generated .cpp file
LGFX_ST7796 lcd;

void setup() {
    lcd.begin();
    lcd.setRotation(0);     // Landscape: 480×320
    draw_screen(lcd);       // Render the designed layout
}

void loop() {
    // your application code
}

PlatformIO platformio.ini:

[env:esp32s3]
platform = espressif32
board = esp32-s3-devkitc-1
framework = arduino
lib_deps = lovyan03/LovyanGFX @ ^1.1.0

15. Project Structure

esp32-tft-designer/
│
├── app.py                 ← Flask web server
│                            Routes: GET /, POST /api/generate,
│                                    POST /api/export_zip, POST /api/load_scene
│
├── codegen.py             ← C/C++ code generator
│                            Functions: generate_st7796s(scene), generate_lovyangfx(scene)
│                            Utilities: hex_to_rgb(), rgb_to_565(), sanitize_text()
│
├── requirements.txt       ← Python dependencies
│                            flask>=3.0.0, gunicorn>=21.2.0
│
├── Dockerfile             ← Multi-stage production image
│                            Stage 1: pip install → /install
│                            Stage 2: copy sources + binaries, run as appuser (uid 1001)
│
├── docker-compose.yml     ← Compose service definition
│                            Port: 5100:5100, restart: unless-stopped
│                            Limits: 1 CPU, 256 MB RAM
│
├── esp32-tft-container.sh          ← Docker management script
│                            Commands: build start stop restart logs status shell clean dev help
│
├── README.md              ← This file (English)
├── README.fr.md           ← French version
│
├── templates/
│   └── index.html         ← Single-page HTML/CSS/JS editor
│                            3-panel layout: tools | canvas | layers+code
│                            Inline JS is loaded from static/js/app.js
│
└── static/
    ├── css/
    │   └── app.css        ← Dark theme (CSS custom properties, grid layout)
    └── js/
        └── app.js         ← Full editor implementation (~1300 lines)
                             Sections: state, canvas events, draw tools,
                                       resize handles, grid, snap, undo/redo,
                                       clipboard, code generation, panel resizers

16. Hardware Wiring

ESP32-S3 DevKit ↔ MSP4020 (ST7796S)

MSP4020 Pin Signal ESP32-S3 GPIO Notes
VCC Power 3V3 3.3V only — never 5V
GND Ground GND Common ground
CS SPI Chip Select GPIO 10 Active LOW
RESET Hardware reset GPIO 9 Active LOW pulse on init
DC / RS Data / Command GPIO 8 HIGH = data, LOW = command
SDI / MOSI SPI data out GPIO 11 SPI2_HOST MOSI
SCK / CLK SPI clock GPIO 12 SPI2_HOST CLK
LED / BL Backlight GPIO 46 PWM via LEDC timer 0
SDO / MISO SPI data in GPIO 13 Optional — read-back only

SPI configuration

Parameter Value
SPI host SPI2_HOST
SPI mode 0 (CPOL=0, CPHA=0)
Write frequency 40 MHz
Read frequency 10 MHz
DMA channel Auto
DMA buffer 16 rows × 480 px = 15 360 bytes

Display orientation

The ST7796S controller RAM is 320 columns × 480 rows. The MADCTL register configures how the RAM maps to the physical panel:

Rotation MADCTL value Logical size Use case
MV | BGR 480 × 320 Landscape native (default)
90° MX | BGR 320 × 480 Portrait
180° MV | MX | MY | BGR 480 × 320 Landscape mirrored
270° MY | BGR 320 × 480 Portrait mirrored

These MADCTL values have been validated on the physical MSP4020 panel. Other ST7796S breakout boards may require different bit combinations.


17. Troubleshooting

The server does not start

Symptom: python app.py shows an error immediately.

Cause Solution
Python version < 3.10 Upgrade Python: python3 --version
Dependencies not installed Run pip install -r requirements.txt
Port 5100 already in use Kill the other process or change the port: app.run(port=5101)
Virtual environment not active Run source venv/bin/activate

Docker container does not start

./esp32-tft-container.sh logs    # Check error messages
./esp32-tft-container.sh status  # Check state
Cause Solution
Port 5100 already in use PORT=5101 ./esp32-tft-container.sh start
Image not built ./esp32-tft-container.sh build
Docker daemon not running sudo systemctl start docker
Out of disk space docker system prune then rebuild

The canvas does not respond to clicks

  • Check that you have selected a tool (pixel, line, rect…) in the left panel.
  • The Select/Move tool does not draw — it moves existing elements.
  • Make sure the click is inside the canvas border.

Generated code does not compile

Symptom Cause Solution
tft_draw_screen undefined Missing header Create tft_screen_st7796s.h (see §14)
RGB565 macro not found Missing include Add #include "st7796s.h"
lgfx::color565 not found Missing LovyanGFX Add #include <LovyanGFX.hpp>
tft_draw_wifi not found Function at bottom of file Move it above tft_draw_screen or add a forward declaration

Display shows wrong colors (red/blue swapped)

The MADCTL BGR flag may need to be inverted for your specific panel. In st7796s.c, change:

// In tft_set_rotation(), remove or add MADCTL_BGR:
madctl = MADCTL_MV | MADCTL_BGR;   // Current (correct for MSP4020)
madctl = MADCTL_MV;                // Try this if colors are swapped

Text appears as ? characters

Characters outside ASCII 32–126 are not supported by the built-in 8×16 bitmap font. Use only standard ASCII characters in the text tool.


TFT Designer — MIT License — ESP32-S3 / MSP4020 / ST7796S

About

Container used to create a visual editor for the TFT st7796s Display, based on the 'esp32-tft-st7996s-library'.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages