#!/usr/bin/env bash # # monitor.sh -- Open the serial monitor # # Reads baud rate from .anvil.toml. No Anvil binary required. # # Usage: # ./monitor.sh Auto-detect port # ./monitor.sh -p /dev/ttyUSB0 Specify port # ./monitor.sh -b 9600 Override baud rate # ./monitor.sh --board mega Use baud from a named board # ./monitor.sh --watch Reconnect after reset/replug # # Prerequisites: arduino-cli in PATH set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" CONFIG="$SCRIPT_DIR/.anvil.toml" # -- Colors ---------------------------------------------------------------- if [[ -t 1 ]]; then RED=$'\033[0;31m'; GRN=$'\033[0;32m'; YLW=$'\033[0;33m' CYN=$'\033[0;36m'; BLD=$'\033[1m'; RST=$'\033[0m' else RED=''; GRN=''; YLW=''; CYN=''; BLD=''; RST='' fi ok() { echo "${GRN}ok${RST} $*"; } warn() { echo "${YLW}warn${RST} $*"; } die() { echo "${RED}FAIL${RST} $*" >&2; exit 1; } # -- Parse .anvil.toml ----------------------------------------------------- [[ -f "$CONFIG" ]] || die "No .anvil.toml found in $SCRIPT_DIR" toml_get() { (grep "^$1 " "$CONFIG" 2>/dev/null || true) | head -1 | sed 's/.*= *"\{0,1\}\([^"]*\)"\{0,1\}/\1/' | tr -d ' ' } toml_section_get() { local section="$1" key="$2" awk -v section="[$section]" -v key="$key" ' $0 == section { found=1; next } /^\[/ { found=0 } found && $1 == key && /=/ { sub(/^[^=]*= *"?/, ""); sub(/"? *$/, ""); print; exit } ' "$CONFIG" } DEFAULT_BOARD="$(toml_get 'default')" BAUD="$(toml_get 'baud')" BAUD="${BAUD:-115200}" LOCAL_CONFIG="$SCRIPT_DIR/.anvil.local" LOCAL_PORT="" LOCAL_VID_PID="" if [[ -f "$LOCAL_CONFIG" ]]; then LOCAL_PORT="$(grep '^port ' "$LOCAL_CONFIG" 2>/dev/null | head -1 | sed 's/.*= *"\{0,1\}\([^"]*\)"\{0,1\}/\1/' | tr -d ' ')" LOCAL_VID_PID="$(grep '^vid_pid ' "$LOCAL_CONFIG" 2>/dev/null | head -1 | sed 's/.*= *"\{0,1\}\([^"]*\)"\{0,1\}/\1/' | tr -d ' ')" fi # -- Parse arguments ------------------------------------------------------- PORT="" DO_WATCH=0 BOARD_NAME="" while [[ $# -gt 0 ]]; do case "$1" in -p|--port) PORT="$2"; shift 2 ;; -b|--baud) BAUD="$2"; shift 2 ;; --board) BOARD_NAME="$2"; shift 2 ;; --watch) DO_WATCH=1; shift ;; -h|--help) echo "Usage: ./monitor.sh [-p PORT] [-b BAUD] [--board NAME] [--watch]" echo " Opens serial monitor. Baud rate from .anvil.toml." echo " --board NAME selects a board from [boards.NAME]." exit 0 ;; *) die "Unknown option: $1" ;; esac done # -- Resolve board --------------------------------------------------------- ACTIVE_BOARD="${BOARD_NAME:-$DEFAULT_BOARD}" if [[ -z "$ACTIVE_BOARD" ]]; then echo "${RED}FAIL${RST} No default board set in .anvil.toml." >&2 echo "" >&2 echo " Add a default to the [build] section of .anvil.toml:" >&2 echo " default = \"uno\"" >&2 echo "" >&2 echo " And make sure a matching [boards.uno] section exists:" >&2 echo " [boards.uno]" >&2 echo " fqbn = \"arduino:avr:uno\"" >&2 echo "" >&2 echo " Or with Anvil: anvil board --default uno" >&2 echo " List boards: anvil board --listall" >&2 echo " arduino-cli board listall" >&2 exit 1 fi BOARD_BAUD="$(toml_section_get "boards.$ACTIVE_BOARD" "baud")" if [[ -n "$BOARD_BAUD" ]]; then BAUD="$BOARD_BAUD" fi if [[ -n "$BOARD_NAME" ]]; then ok "Using board: $BOARD_NAME (baud: $BAUD)" fi # -- Preflight ------------------------------------------------------------- command -v arduino-cli &>/dev/null \ || die "arduino-cli not found in PATH." # -- Auto-detect port ------------------------------------------------------ auto_detect() { local port port=$(arduino-cli board list 2>/dev/null \ | grep -i "serial" \ | awk '{print $1}' \ | grep -E 'ttyUSB|ttyACM|COM' \ | head -1) if [[ -z "$port" ]]; then port=$(arduino-cli board list 2>/dev/null \ | grep -i "serial" \ | head -1 \ | awk '{print $1}') fi echo "$port" } resolve_vid_pid() { local target_vid target_pid json target_vid="$(echo "$1" | cut -d: -f1 | tr '[:upper:]' '[:lower:]')" target_pid="$(echo "$1" | cut -d: -f2 | tr '[:upper:]' '[:lower:]')" json="$(arduino-cli board list --format json 2>/dev/null)" || return echo "$json" | python3 -c " import sys, json try: data = json.load(sys.stdin) for dp in data.get('detected_ports', []): port = dp.get('port', {}) if port.get('protocol') != 'serial': continue props = port.get('properties', {}) vid = props.get('vid', '').lower().replace('0x', '') pid = props.get('pid', '').lower().replace('0x', '') if vid == '$target_vid' and pid == '$target_pid': print(port.get('address', '')) break except: pass " 2>/dev/null } if [[ -z "$PORT" ]]; then if [[ -n "$LOCAL_VID_PID" ]]; then PORT="$(resolve_vid_pid "$LOCAL_VID_PID")" if [[ -n "$PORT" ]]; then if [[ "$PORT" != "$LOCAL_PORT" ]] && [[ -n "$LOCAL_PORT" ]]; then warn "Device $LOCAL_VID_PID found on $PORT (moved from $LOCAL_PORT)" else warn "Using port $PORT (from .anvil.local)" fi fi fi if [[ -z "$PORT" ]] && [[ -n "$LOCAL_PORT" ]]; then PORT="$LOCAL_PORT" warn "Using port $PORT (from .anvil.local)" fi if [[ -z "$PORT" ]]; then PORT="$(auto_detect)" if [[ -z "$PORT" ]]; then die "No serial port detected. Is the board plugged in?\n Specify manually: ./monitor.sh -p /dev/ttyUSB0\n Or save a default: anvil devices --set" fi warn "Auto-detected port: $PORT (use -p to override, or: anvil devices --set)" fi fi # -- Watch mode ------------------------------------------------------------ if [[ $DO_WATCH -eq 1 ]]; then echo "${CYN}${BLD}Persistent monitor on ${PORT} at ${BAUD} baud${RST}" echo "Reconnects after upload / reset / replug." echo "Press Ctrl+C to exit." echo "" trap "echo ''; echo 'Monitor stopped.'; exit 0" INT while true; do if [[ -e "$PORT" ]]; then arduino-cli monitor -p "$PORT" -c "baudrate=$BAUD" 2>/dev/null || true echo "${YLW}--- ${PORT} disconnected ---${RST}" else echo "${CYN}--- Waiting for ${PORT} ...${RST}" while [[ ! -e "$PORT" ]]; do sleep 0.5 done sleep 1 echo "${GRN}--- ${PORT} connected ---${RST}" fi sleep 0.5 done else echo "Opening serial monitor on $PORT at $BAUD baud..." echo "Press Ctrl+C to exit." echo "" arduino-cli monitor -p "$PORT" -c "baudrate=$BAUD" fi