A Windows desktop GUI for testing ESP32-based appliances over UART. It is a
graphical frontend over micro-cli (the Go tester in the sibling cli
repo, which simulates the MCU side of the appliance UART protocol): every
command you click is executed by the real, tested CLI — the GUI reimplements
no protocol logic.
The
clirepo is a hard read-only dependency: this project never modifies it. See MIRRORING.md for exactly how the GUI maps onto the client, andPLAN.mdfor the development history and findings.
- Send any protocol command to a connected appliance by clicking it and filling a form — no memorizing shell syntax.
- Watch decoded TX/RX traffic live in a color-coded, timestamped log.
- Stay generic: commands, arguments and datapoints are generated from the same JSON schema files the CLI uses, so any appliance the CLI supports, the GUI supports.
Three layers, one file each, strictly stacked — each layer only knows the one below it:
┌────────────────────────────────────────────────────────────┐
│ gui.py — presentation (PySide6) │
│ MainWindow: top bar ⇔ CLI flags, command list + forms │
│ ⇔ shell commands, log pane ⇔ shell output. │
│ QTimer(50 ms) polls the queue below. No protocol logic. │
├────────────────────────────────────────────────────────────┤
│ microcli.py — bridge │
│ MicroCli (QObject): spawns micro-cli.exe in a ConPTY │
│ (pywinpty), writes shell command lines, and runs a │
│ QThread reader that turns terminal output into │
│ line(kind, text) signal emissions. classify() = the one │
│ place coupled to the CLI's output wording. │
├────────────────────────────────────────────────────────────┤
│ schema.py — data │
│ Read-only loader of the cli repo's schema JSON │
│ (shared_v2.0.0 + appliances/<name>), same merge the CLI │
│ itself performs. Drives the command list and arg forms. │
└────────────────────────────────────────────────────────────┘
│
▼
micro-cli.exe ──UART──► appliance (ESP32)
Threading: exactly one boundary, expressed in the Qt signal/slot paradigm.
MicroCli's QThread reader emits line(kind, text) / terminated
signals; the MainWindow slots live in the main thread, so Qt delivers
them as queued connections — no explicit queue, no polling, and no widget
is ever touched off the main thread.
Why a subprocess instead of importing the Go code: the CLI's packages live
under internal/ (sealed to external Go modules) and the repo must not be
modified — and wrapping the binary means a CLI upgrade is just swapping the
exe. Why a pseudo-terminal instead of pipes: the CLI's interactive shell
exits immediately on non-tty stdin (all findings documented in
spike_pipe.py). The pty backend is per-OS: ConPTY via pywinpty on
Windows, a Unix pty via ptyprocess on Linux — same API, selected at
import time in microcli.py.
| File | Role |
|---|---|
gui.py |
PySide6 window (--selftest for a windowless check) |
microcli.py |
subprocess wrapper (--demo for a self-check) |
schema.py |
schema loader (run directly for a self-check) |
spike_pipe.py |
ConPTY regression check + recorded Phase 0 findings |
run-gui.cmd |
Windows launcher (no console window) |
run-gui.sh |
Linux/macOS launcher |
PLAN.md |
development plan, phase status, findings |
MIRRORING.md |
detailed GUI ⇔ client mapping guide |
ARCHITECTURE.drawio |
visual architecture diagram (open on diagrams.net or with a draw.io IDE extension) |
- Windows 10+ (ConPTY) or Linux, Python 3.10+. The OS-dependent parts are
isolated to three spots: the pty backend (
pywinptyon Windows,ptyprocesson Linux — installed automatically by the platform markers inrequirements.txt), the CLI binary name, and the launcher script. Port listing is platform-independent (pyserial'slist_ports, used for enumeration only — the CLI opens the port, never the GUI). - The CLI binary — by default in this folder (
micro-cli.exe/micro-cli; build from the cli repo:go build -o ..\cli-gui\micro-cli.exe .\cmd\micro-cli, or on Linuxgo build -o ../cli-gui/micro-cli ./cmd/micro-cli). If it isn't found on Connect, the GUI asks you to locate it once and remembers the path inconfig.json(untracked by git). - The cli repo checked out as a sibling (
..\cli) for the schema files - For real hardware: on Windows, the USB-UART driver for your adapter
(CP210x or FTDI VCP — same requirement as the CLI); on Linux, membership
in the
dialoutgroup to open/dev/ttyUSB*.
# Windows
cd C:\Repos\FRESCO\cli-gui
python -m venv .venv
.venv\Scripts\python -m pip install -r requirements.txt# Linux
cd ~/FRESCO/cli-gui
python3 -m venv .venv
.venv/bin/python -m pip install -r requirements.txtWindows: double-click run-gui.cmd, or .venv\Scripts\python gui.py.
Linux: ./run-gui.sh (after chmod +x run-gui.sh), or .venv/bin/python gui.py.
First time on a new platform, run
spike_pipe.py— it is the go/no-go check that the pty-driving technique works there (already validated on Windows).
Pick port / baud / appliance in the top bar (check dry-run to try the UI without hardware), click Connect, select a command on the left, fill its arguments, Send. Traffic appears on the right: green = sent, blue = received, red = errors.
All runnable without hardware:
.venv\Scripts\python spike_pipe.py # ConPTY driving still works
.venv\Scripts\python microcli.py --demo # wrapper pipeline end-to-end
.venv\Scripts\python schema.py # schema loading/merge
.venv\Scripts\python gui.py --selftest # full click path, windowless- Dry-run shows TX only. The CLI's
-dry-runecho device has a bug (whole packet returned to a 1-byte SLIP read, remainder dropped), soReceived:lines can never appear without real hardware. Details inspike_pipe.py. - RX display is validated against the known output format but not yet against a live appliance.
- The log classifier is coupled to micro-cli's output wording; if a CLI
release rewords its prints, update
microcli.classify(). - Raw messages longer than ~400 hex chars would wrap in the pseudo-terminal
and corrupt line parsing (noted in
microcli.py).