A Windows desktop GUI for testing ESP32-based appliances over UART. It is a
graphical frontend over micro-cli (the Go tester, vendored as the iot-micro-cli submodule
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, as the
iot-micro-cligit submodule (clone with--recurse-submodules, or rungit submodule update --init). Build it in place:cd iot-micro-cli; go build -o micro-cli.exe ./cmd/micro-cli(Linux:-o micro-cli). That location and the submodule's schema files are the defaults. If the binary isn't found on Connect, the GUI asks you to locate it once and remembers the path inconfig.json(untracked by git). - 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).