Files
2026-09-20 18:55:16 +02:00

155 lines
5.4 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# Open the TranspOtter serial console.
#
# ./console.sh minicom, port auto-detected
# ./console.sh /dev/ttyACM1 force a port
# ./console.sh -m use `pio device monitor` instead
# ./console.sh -r raw stty/cat fallback, no terminal program
# ./console.sh -l list candidate ports and exit
#
# Exit minicom with Ctrl-A X.
set -euo pipefail
PROFILE_NAME=transpotter
PROFILE="$HOME/.minirc.$PROFILE_NAME"
BAUD=115200
MODE=minicom
PORT="${TRANSPOTTER_PORT:-}"
# Print the comment block at the top of this file, stopping at the first line
# of actual code, so the help text cannot drift out of sync with it.
usage() { awk 'NR>2 && /^#/ { sub(/^# ?/, ""); print; next } NR>2 { exit }' "$0"; }
while [ $# -gt 0 ]; do
case "$1" in
-m|--monitor) MODE=monitor ;;
-r|--raw) MODE=raw ;;
-l|--list) MODE=list ;;
-h|--help) usage; exit 0 ;;
-*) echo "unknown option: $1" >&2; usage >&2; exit 2 ;;
*) PORT="$1" ;;
esac
shift
done
# --- find the board ----------------------------------------------------------
# by-id first: the name is stable across replugs and unambiguous when several
# USB serial devices are attached. Falls back to matching Espressif's USB vendor
# id (0x303a) in sysfs, then to the usual device node.
list_ports() {
local found=0
for l in /dev/serial/by-id/*Espressif*; do
[ -e "$l" ] && { echo "$l"; found=1; }
done
for d in /sys/class/tty/ttyACM* /sys/class/tty/ttyUSB*; do
[ -e "$d" ] || continue
[ "$(cat "$d/device/../idVendor" 2>/dev/null)" = "303a" ] || continue
echo "/dev/$(basename "$d")"; found=1
done
[ "$found" -eq 0 ] && [ -e /dev/ttyACM0 ] && echo /dev/ttyACM0
return 0
}
if [ "$MODE" = list ]; then
echo "candidate ports:"; list_ports | sort -u | sed 's/^/ /'
exit 0
fi
if [ -z "$PORT" ]; then
PORT="$(list_ports | head -1 || true)"
fi
if [ -z "$PORT" ] || [ ! -e "$PORT" ]; then
echo "error: no serial port found." >&2
echo " Plug the ESP32 in, or pass one explicitly: $0 /dev/ttyACM0" >&2
echo " '$0 -l' lists candidates." >&2
exit 1
fi
if [ ! -w "$PORT" ]; then
echo "error: $PORT is not writable. You likely need to be in the 'plugdev'" >&2
echo " or 'dialout' group: sudo usermod -aG plugdev \$USER (then re-login)." >&2
exit 1
fi
# A second reader steals half the bytes and makes the console look broken — and
# blocks `pio run -t upload` outright. fuser/lsof are often not installed, so
# walk /proc instead: our own processes' fds are always readable.
port_holders() {
local target pid
target="$(readlink -f "$1" 2>/dev/null)" || return 0
for l in /proc/[0-9]*/fd/*; do
[ -e "$l" ] || continue
[ "$(readlink -f "$l" 2>/dev/null)" = "$target" ] || continue
pid="${l#/proc/}"; pid="${pid%%/*}"
[ "$pid" = "$$" ] && continue
echo "$pid $(tr '\0' ' ' < "/proc/$pid/cmdline" 2>/dev/null)"
done | sort -u
}
HOLDERS="$(port_holders "$PORT")"
if [ -n "$HOLDERS" ]; then
echo "warning: something else already has $PORT open:" >&2
printf '%s\n' "$HOLDERS" | sed 's/^/ /' >&2
echo " close it first — two readers split the bytes between them," >&2
echo " and an upload cannot open the port at all." >&2
fi
# --- alternate modes ---------------------------------------------------------
case "$MODE" in
monitor)
exec pio device monitor -p "$PORT" -b "$BAUD"
;;
raw)
echo "raw mode on $PORT — type a command and press Enter, Ctrl-C to quit."
stty -F "$PORT" "$BAUD" raw -echo -crtscts
cat "$PORT" &
CATPID=$!
trap 'kill $CATPID 2>/dev/null || true' EXIT
while IFS= read -r line; do printf '%s\n' "$line" > "$PORT"; done
exit 0
;;
esac
# --- minicom -----------------------------------------------------------------
command -v minicom >/dev/null 2>&1 || {
echo "error: minicom is not installed. Try '$0 -m' (pio device monitor)" >&2
echo " or '$0 -r' (raw), or: sudo apt install minicom" >&2
exit 1
}
# Three minicom defaults make this board awkward, so the profile is written
# rather than assumed:
# rtscts No - the C3's USB CDC never asserts CTS, and minicom defaults
# hardware flow control ON, silently blocking every keystroke.
# localecho - the firmware does not echo, so without this nothing appears
# as you type and the terminal seems unresponsive.
# addcarreturn - turns a bare LF into CR+LF on the way in. Firmware that
# prints "\n" without "\r" otherwise staircases off the right
# of the screen. Harmless when the firmware already sends
# CR+LF, so it is on regardless.
WANT=$(cat <<PROFILE_EOF
# Written by $(basename "$0") — see that script for why these matter.
pu port $PORT
pu baudrate $BAUD
pu bits 8
pu parity N
pu stopbits 1
pu rtscts No
pu xonxoff No
pu localecho Yes
pu addcarreturn Yes
PROFILE_EOF
)
# Rewrite whenever it differs, so a stale profile cannot quietly reintroduce
# any of the problems above.
if [ ! -f "$PROFILE" ] || [ "$(cat "$PROFILE")" != "$WANT" ]; then
printf '%s\n' "$WANT" > "$PROFILE"
echo "wrote $PROFILE"
fi
echo "connecting to $PORT at $BAUD (Ctrl-A X to quit, Ctrl-A Z for help)"
echo "note: opening the port resets the ESP32 — expect the boot banner."
exec minicom "$PROFILE_NAME" -D "$PORT"