Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
8d9cc00
chore: sync Cargo.lock with 0.2.0 version bump
joelsa Sep 9, 2026
bcb34ef
fix: keep scroll-down and page-down anchored at bottom while following
joelsa Sep 9, 2026
9b5a8ae
fix: keep manual scroll offset anchored when output is trimmed
joelsa Sep 9, 2026
fad97b9
fix: release writer port handle on disconnect
joelsa Sep 9, 2026
8cef535
build: use ratatui's crossterm re-export instead of a second crossterm
joelsa Sep 9, 2026
fb5a46d
fix: restore terminal on panic and setup errors
joelsa Sep 9, 2026
ff3b1f1
perf: coalesce queued serial and input events into one frame
joelsa Sep 9, 2026
f707694
refactor: state invariant instead of bare unwrap when splitting long …
joelsa Sep 9, 2026
1b793fc
feat: strip ANSI escape sequences from text output
joelsa Sep 9, 2026
b49e73c
fix: report Ctrl+V keys that have no literal byte mapping
joelsa Sep 9, 2026
66e3a95
chore: drop unused chrono serde and tokio time features
joelsa Sep 9, 2026
0bb4e8f
fix: stop and join input thread when the UI loop fails
joelsa Sep 9, 2026
1631b6f
feat: add --raw flag to keep ANSI escape sequences
joelsa Sep 9, 2026
1baa245
fix: anchor manual scrolling below the partial line row
joelsa Sep 9, 2026
7977813
fix: retry reopen when the reconnected port cannot be cloned
joelsa Sep 9, 2026
a95884f
fix: show timestamps in local time instead of UTC
joelsa Sep 9, 2026
d36192c
feat: rename --log-ts to --timestamps and keep the old name as alias
joelsa Sep 9, 2026
3b5a97c
feat: accept lf as alias for the nl line ending
joelsa Sep 9, 2026
b47352d
fix: send pasted text line by line and accept Ctrl+J as Enter
joelsa Sep 9, 2026
168eea4
feat: readline-style editing keys in the input line
joelsa Sep 9, 2026
46d2c0a
refactor: tag output lines with their origin and style TX and notices
joelsa Sep 9, 2026
86a5b39
feat: add --echo to show transmitted lines in the output
joelsa Sep 9, 2026
efa3cd2
fix: keep typed input when sending while disconnected
joelsa Sep 9, 2026
fb86de3
fix: keep the status bar readable with long port paths
joelsa Sep 9, 2026
746f963
fix: scroll the output viewport and page by the visible height
joelsa Sep 9, 2026
7efd3f2
feat: wrap long output lines with --wrap or Ctrl+T
joelsa Sep 9, 2026
2d652b2
feat: add --mouse for wheel scrolling of the output
joelsa Sep 9, 2026
fee873d
feat: hide unknown-type ports unless --all-ports and sort ports natur…
joelsa Sep 9, 2026
4ab7526
feat: show printable characters next to hex rows
joelsa Sep 9, 2026
370597c
feat: hint at busy ports and missing permissions when opening fails
joelsa Sep 9, 2026
751fd5d
fix: neutral exit message after a clean quit
joelsa Sep 9, 2026
6457176
docs: replace stale screenshot with a rendered text capture
joelsa Sep 9, 2026
1764718
fix: exit quietly when the port list is piped into a pager that close…
joelsa Sep 9, 2026
138dbad
chore: add justfile with screenshot recipes
joelsa Sep 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 4 additions & 108 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 2 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,17 @@ authors = [
categories = ["command-line-utilities", "development-tools", "embedded"]
description = "A simple, clean serial monitor with a clean terminal UI for embedded development"
license = "MIT"
readme = "README.md"
repository = "https://github.com/systemscape/sermonizer"
keywords = ["serial", "monitor", "embedded", "terminal", "tui"]

[dependencies]
anyhow = "1.0"
clap = { version = "4.5", features = ["derive"] }
crossterm = "0.29"
ctrlc = "3.4"
ratatui = "0.29"
serialport = "4.3"
tokio = { version = "1.0", features = ["rt-multi-thread", "sync", "time", "macros"] }
chrono = { version = "0.4", features = ["serde"] }
tokio = { version = "1.0", features = ["rt-multi-thread", "sync", "macros"] }
chrono = "0.4"
unicode-width = "0.2"

# The profile that 'dist' will build with
Expand Down
45 changes: 39 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,39 @@ A simple, clean serial monitor with a clean terminal UI for embedded development

Most terminal-based serial monitors are annoying to use - they have clunky interfaces and no sane defaults. We wanted something that could be quickly spun up to interact with embedded devices during firmware development without any hassle.

![Screenshot](./docs/screenshot.png)
```text
┌Serial Monitor────────────────────────────────────────────────────────────────────────────────┐
│[2026-09-10 09:41:02.118] I (312) boot: ESP-IDF v5.2 │
│[2026-09-10 09:41:02.121] I (318) wifi: connecting to lab-iot │
│[2026-09-10 09:41:03.877] I (2074) wifi: got ip 192.168.4.23 │
│> [2026-09-10 09:41:07.402] AT+GMR │
│[2026-09-10 09:41:07.410] AT version:2.4.0.0 │
│[2026-09-10 09:41:07.411] OK │
│[sermonizer] device disconnected: Broken pipe - reconnecting (Ctrl+C to quit) │
│[sermonizer] device reconnected │
│[2026-09-10 09:41:12.006] I (309) boot: ESP-IDF v5.2 │
│[2026-09-10 09:41:12.009] I (315) main: sensor=23.4C hum=41% │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
┌Input─────────────────────────────────────────────────────────────────────────────────────────┐
│AT+CWJAP="lab-iot"," │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
ttyUSB0 115200 8N1 follow | LF | Enter send, Up/Down history, Shift+Up/Down PgUp/PgDn scroll,
```

*Received lines, a sent line (`>`, shown with `--echo`), sermonizer's own notices and the line still being received. Regenerate with `just update-screenshot`.*

## Features

- **Smart auto-scroll**: Follows new data, easy to switch to manual scrolling
- **Auto-reconnect**: Keeps watching the port and resumes when the device comes back
- **Clean TUI**: Split view with input at bottom, output on top, status bar with connection state
- **Auto-detect ports**: Just run `sermonizer` and it finds your device
- **Auto-detect ports**: Just run `sermonizer` and it finds your device; dead onboard UARTs stay out of the way unless you ask for `--all-ports`
- **Sane defaults**: 115200 baud, 8 data bits, no parity, 1 stop bit
- **Hex mode**: View binary data as hex bytes
- **Hex mode**: View binary data as hex bytes with a `hexdump -C` style ASCII column
- **Clean text**: ANSI colour codes from firmware logs are stripped (keep them with `--raw`)
- **Local echo**: `--echo` shows what you sent, for devices that do not echo
- **Logging**: Save RX/TX data with timestamps
- **Fast**: Built in Rust, handles high baud rates smoothly

Expand Down Expand Up @@ -57,26 +80,36 @@ sermonizer [OPTIONS]
Options:
-p, --port <PORT> Serial port path
-b, --baud <BAUD> Baud rate (default: 115200)
--line-ending <E> Line ending: none|nl|cr|crlf (default: nl)
--line-ending <E> Line ending: none|nl|lf|cr|crlf (default: nl)
--data-bits <N> Data bits: 5|6|7|8 (default: 8)
--parity <P> Parity: none|odd|even (default: none)
--stop-bits <N> Stop bits: 1|2 (default: 1)
--flow-control <F> Flow control: none|software|hardware (default: none)
--dtr <on|off> Set the DTR line after opening
--rts <on|off> Set the RTS line after opening
--hex Display data as hex
--raw Keep ANSI escape sequences instead of stripping them
-e, --echo Show sent lines in the output, prefixed with "> "
-w, --wrap Wrap long lines instead of clipping them
--mouse Scroll output with the mouse wheel
--log <FILE> Log received data
--tx-log <FILE> Log transmitted data
--log-ts Add timestamps to logs and display
-t, --timestamps Add timestamps to display and logs (alias: --log-ts)
--list List available ports
--all-ports Also list ports of unknown type (e.g. /dev/ttyS*), hidden by default
```

## Controls

- **Type and press Enter**: Send data to device
- **Paste**: Multi-line text is sent line by line; an unfinished last line stays in the input box
- **↑↓**: Browse send history
- **Home / End, Ctrl+A / Ctrl+E**: Jump to start / end of the input line
- **Ctrl+U / Ctrl+K / Ctrl+W**: Delete to start of line / to end of line / previous word
- **Shift+↑↓ / Page Up/Down**: Scroll through output
- **Home / End**: Jump to top / bottom (End resumes auto-scroll)
- **Shift+Home / Shift+End** (or Ctrl+Home / Ctrl+End): Jump to top / bottom of output (End resumes auto-scroll)
- **Mouse wheel**: Scrolls the output when started with `--mouse`. It is off by default because capturing the mouse makes most terminals require Shift+drag to select and copy text
- **Ctrl+T**: Toggle wrapping of long lines
- **Ctrl+L**: Clear output
- **Ctrl+V, then a key**: Send that key as a raw control byte (e.g. Ctrl+V Ctrl+C sends 0x03)
- **Esc**: Clear input line
Expand Down
Binary file removed docs/screenshot.png
Binary file not shown.
20 changes: 20 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Project tasks. Needs only cargo; update-screenshot also uses awk.

default:
@just --list

# Render the README screenshot from the real UI code and print it
screenshot:
@cargo test --quiet readme_screenshot -- --ignored --nocapture | sed -n '/^┌/,/^ ttyUSB0/p'

# Replace the screenshot block in README.md with a fresh render
update-screenshot:
#!/usr/bin/env bash
set -euo pipefail
SHOT="$(just screenshot)" awk '
/^```text$/ && !done { print; print ENVIRON["SHOT"]; skip = 1; next }
skip && /^```$/ { skip = 0; done = 1 }
!skip { print }
' README.md > README.md.tmp
mv README.md.tmp README.md
git --no-pager diff --stat -- README.md
Loading
Loading