# printcontrol A keyboard-driven TUI for monitoring and controlling a 3D printer over USB serial. One static Go binary, zero runtime dependencies, theme-aware via your terminal's 16-colour palette — designed to feel at home on Omarchy alongside `wiremix`, `lazygit`, `lazydocker`, and friends. The model: **the printer is in charge during an SD print, we observe and adjust.** Start a print from the LCD, the SD card, or printcontrol's own SD picker — then live-monitor temps, progress, and ETA, and tweak feedrate, flow, fan, hotend / bed setpoints, or babystep Z without ever leaving the keyboard. Quit and come back later; the printer keeps printing and printcontrol rediscovers the in-progress job on reconnect. ``` ┌─ printcontrol · 3D printer TUI ────────────────────────────────────────┐ │ ● Status │ ● Temperatures │ │ Connection: /dev/ttyUSB1 @ 115200 │ Hotend: 201.3 / 210.0 °C │ │ · Marlin 2.1.2 │ Bed: 60.2 / 60.0 °C │ │ File: benchy.gcode SD PRINT │ │ │ Progress: [██████████··········] │ set hotend: [h] │ │ ETA: 1h 12m │ set bed: [b] │ ├─────────────────────────────────────┴────────────────────────────────────┤ │ ● Controls │ │ Feedrate: 110% [-/+] [0] Flow: 100% [ [ / ] ] Fan: 30% [f/F] │ │ Babystep Z: -0.050 [{ / }] [Z] │ │ press [g] to send raw G-code │ ├──────────────────────────────────────────────────────────────────────────┤ │ ● Log │ │ > M105 │ │ < ok T:201.3 /210.0 B:60.2 /60.0 @:127 B@:0 │ ├──────────────────────────────────────────────────────────────────────────┤ │ [c] connect [d] disconnect [s] sd start [p] pause [r] resume [x] cancel │ │ [h] hotend [b] bed [g] gcode [q] quit │ └──────────────────────────────────────────────────────────────────────────┘ ``` ## Install Build from source — needs Go 1.22+. The repo's canonical install target is `~/.local/bin` (matches Omarchy's PATH); adjust `GOBIN` if you want it elsewhere. ```bash cd go GOBIN=$HOME/.local/bin go install ./cmd/printcontrol ``` You need permission to open the serial port. On Arch / Omarchy that means membership in the `uucp` group: ```bash groups | grep -q uucp || sudo usermod -aG uucp "$USER" # then log out and back in ``` Verify the printer is visible: ```bash ls /dev/ttyACM* /dev/ttyUSB* 2>/dev/null ``` ## Run ```bash printcontrol # auto-detect port AND baud printcontrol -port /dev/ttyUSB1 # lock the port, still auto-baud printcontrol -port /dev/ttyUSB1 -baud 250000 printcontrol -no-connect # launch without touching serial ``` ### Flags | Flag | Default | Description | |---------------|----------|----------------------------------------------------------| | `-port` | *(auto)* | Serial device path. Scans `/dev/ttyACM*` + `/dev/ttyUSB*` if empty. | | `-baud` | `0` | Baud rate. `0` = auto-detect from `[250000, 115200, 230400, 500000, 57600]`. | | `-no-connect` | `false` | Skip auto-connect on launch. | ### Auto-detect With no flags, printcontrol scans every plausible serial device and probes each candidate baud until something replies with an unmistakably printer-shaped line (`Marlin`, `FIRMWARE_NAME`, `RepRapFirmware`, `Klipper`, or a `T:.../...` temperature report). Two-letter tokens like `"ok"` are *not* accepted as a match because they appear too easily in misaligned-baud garbage. Worst-case probe time is ~15 s; first match wins and the rest are skipped. The connect flash at the bottom of the screen reports the chosen combination — e.g. `connected: /dev/ttyUSB1 @ 115200`. ### Keys (top-level) | Key | Action | |-----------|-----------------------------------------| | `c` | Connect / reconnect | | `d` | Disconnect | | `s` | List SD card and pick a file to print | | `p` | Pause SD print (`M25`) | | `r` | Resume (`M24`) | | `x` | Cancel SD print (`M524`) | | `h` | Focus hotend setpoint input | | `b` | Focus bed setpoint input | | `g` | Focus raw G-code input | | `+` / `-` | Feedrate ±10% | | `0` | Feedrate back to 100% | | `[` / `]` | Flow ±5% (`M221`) | | `f` / `F` | Fan ±10% | | `{` / `}` | Babystep Z ∓0.05 mm (`M290`, Marlin) | | `Z` | Reset babystep to zero | | `q` | Quit | When a text input is focused: type the value, `Enter` to commit, `Esc` to cancel. ### SD start workflow (key: `s`) 1. Press `s`. printcontrol sends `M21` (mount SD) and `M20` (list files). 2. A picker appears in place of the Log panel. 3. `↑`/`↓` (or `k`/`j`) to highlight, `Enter` to start the print, `Esc` to dismiss without printing. 4. Selecting a file sends `M23 ` then `M24`. The print runs from the SD card, autonomously of printcontrol. 5. You can `q` immediately after — the printer doesn't care. Reconnect any time; the `M27` poll discovers the in-progress print within ~5 seconds and Status / Progress / ETA repopulate. Notes: - The picker shows the long filename if the firmware reports one, but always passes the **short 8.3 name** to `M23` (that's what Marlin requires). - Nested folders are listed but can't currently be entered. File a request if you need it. ## Register with the Omarchy launcher Once you're happy with it, drop a desktop entry so SUPER+SPACE finds it: ```bash omarchy-tui-install printcontrol printcontrol float ``` `float` makes Hyprland treat it as a floating window — usually the right call for a single-pane TUI. Switch to `tile` if you'd rather have it in your tiling layout. ## What works · what doesn't **Works today** - Auto-detect serial port and baud rate - Connect / disconnect over USB serial (multi-port scan) - Live temperatures (hotend, bed, chamber if present) every 2 s - SD-card print detection, progress %, ETA - SD card file listing (`M20`) and start (`M23` + `M24`) from the TUI - Pause / resume / cancel (`M25` / `M24` / `M524`) - Temperature setpoints (`M104` / `M140`) - Fan, feedrate, flow controls (`M106` / `M220` / `M221`) - Babystep Z (`M290` — Marlin) - Raw G-code entry - Firmware identification (`M115`) - Adaptive log panel (footer stays visible on short terminals) **Not yet** - Host-side print streaming (sending a `.gcode` file from the computer). Needs the line-numbered protocol with checksum + resend handling. - Klipper-specific babystep (`SET_GCODE_OFFSET`). Sends `M290` today, which Klipper rejects. - SD folder navigation (`M20 `). - Octoprint / Moonraker backends. Architecture supports it; see `ARCHITECTURE.md`. - Persisted profiles (per-printer last setpoints, preferred port). ## Troubleshooting **"no responsive printer on \[...]"** — Auto-detect probed every `ttyACM`/`ttyUSB` device at every common baud and didn't find a printer signature. Most often this means another program (OctoPrint, Klipper's `klippy`, `pronsole`) already owns the port. Quit it first, or pass `-port` explicitly. If you have an unusual baud (e.g. `1000000`), pass `-baud` to override the probe. **"could not auto-detect baud rate"** — printcontrol *opened* the port but saw no recognisable reply at any candidate baud. Confirm the device is actually a printer (`screen /dev/ttyUSB1 115200`, then send `M115`); also common: the printer is mid-firmware-flash, or the USB cable is power-only. **Temps show but progress stays at 0%** — `M27` only reports SD progress. If you started the print from a host (OctoPrint, etc.) progress won't appear here. **Babystep does nothing** — You're likely on Klipper, which doesn't implement `M290`. Klipper support is on the roadmap. **Mangled lines like `TT::57.4957.49`** — This was the symptom of Marlin's auto-temperature report colliding with our explicit `M105` poll. Fixed upstream: the prime block now sends `M155 S0` / `M27 S0` to disable auto-reports. ## Layout ``` printcontrol/ ├── go/ │ ├── cmd/printcontrol/main.go # entry point, flag parsing │ ├── internal/printer/ # serial driver + G-code state machine │ │ ├── state.go # State / PrintState / TempPair / SDFile │ │ ├── parse.go # line parsing (M105, M27, M115, M20, ...) │ │ ├── printer.go # Connect/Disconnect, owning goroutine, probe │ │ └── parse_test.go │ └── internal/tui/ # Bubble Tea model + Lipgloss styling │ ├── model.go # Model, key handling, SD picker, layout │ └── style.go ├── README.md └── ARCHITECTURE.md # protocol + design notes ``` See `ARCHITECTURE.md` for the wire protocol, the state model, the auto-detect probe strategy, and why things are wired the way they are. ## License GPL-3.0-or-later (matches Printrun, which inspired the protocol layer).