#!/usr/bin/env bash # # upload.sh -- Compile and upload the sketch to the board # # Reads all settings from .anvil.toml. No Anvil binary required. # # Usage: # ./upload.sh Auto-detect port, compile + upload # ./upload.sh -p /dev/ttyUSB0 Specify port # ./upload.sh --monitor Open serial monitor after upload # ./upload.sh --clean Clean build cache first # ./upload.sh --verbose Full compiler + avrdude output # # Prerequisites: arduino-cli in PATH, arduino:avr core installed 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_array() { (grep "^$1 " "$CONFIG" 2>/dev/null || true) | head -1 \ | sed 's/.*\[//; s/\].*//; s/"//g; s/,/ /g' | tr -s ' ' } SKETCH_NAME="$(toml_get 'name')" FQBN="$(toml_get 'fqbn')" WARNINGS="$(toml_get 'warnings')" INCLUDE_DIRS="$(toml_array 'include_dirs')" EXTRA_FLAGS="$(toml_array 'extra_flags')" BAUD="$(toml_get 'baud')" [[ -n "$SKETCH_NAME" ]] || die "Could not read project name from .anvil.toml" [[ -n "$FQBN" ]] || die "Could not read fqbn from .anvil.toml" 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 SKETCH_DIR="$SCRIPT_DIR/$SKETCH_NAME" BUILD_DIR="$SCRIPT_DIR/.build" # -- Parse arguments ------------------------------------------------------- PORT="" DO_MONITOR=0 DO_CLEAN=0 VERBOSE="" while [[ $# -gt 0 ]]; do case "$1" in -p|--port) PORT="$2"; shift 2 ;; --monitor) DO_MONITOR=1; shift ;; --clean) DO_CLEAN=1; shift ;; --verbose) VERBOSE="--verbose"; shift ;; -h|--help) echo "Usage: ./upload.sh [-p PORT] [--monitor] [--clean] [--verbose]" echo " Compiles and uploads the sketch. Settings from .anvil.toml." exit 0 ;; *) die "Unknown option: $1" ;; esac done # -- Preflight ------------------------------------------------------------- command -v arduino-cli &>/dev/null \ || die "arduino-cli not found in PATH." [[ -d "$SKETCH_DIR" ]] \ || die "Sketch directory not found: $SKETCH_DIR" # -- Resolve port ---------------------------------------------------------- # Priority: -p flag > VID:PID resolve > saved port > auto-detect # resolve_vid_pid VID:PID -- search arduino-cli JSON for matching device 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 # Walk through JSON looking for matching vid/pid on serial ports 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 # Try VID:PID resolution first 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 # Fall back to saved port if [[ -z "$PORT" ]] && [[ -n "$LOCAL_PORT" ]]; then PORT="$LOCAL_PORT" warn "Using port $PORT (from .anvil.local)" fi # Fall back to auto-detect if [[ -z "$PORT" ]]; then 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 if [[ -z "$PORT" ]]; then die "No serial port detected. Is the board plugged in?\n Specify manually: ./upload.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 # -- Clean ----------------------------------------------------------------- if [[ $DO_CLEAN -eq 1 ]] && [[ -d "$BUILD_DIR" ]]; then echo "${YLW}Cleaning build cache...${RST}" rm -rf "$BUILD_DIR" fi # -- Build include flags --------------------------------------------------- BUILD_FLAGS="" for dir in $INCLUDE_DIRS; do abs="$SCRIPT_DIR/$dir" if [[ -d "$abs" ]]; then BUILD_FLAGS="$BUILD_FLAGS -I$abs" fi done for flag in $EXTRA_FLAGS; do BUILD_FLAGS="$BUILD_FLAGS $flag" done # -- Compile --------------------------------------------------------------- echo "${CYN}${BLD}Compiling ${SKETCH_NAME}...${RST}" mkdir -p "$BUILD_DIR" COMPILE_ARGS=( compile --fqbn "$FQBN" --build-path "$BUILD_DIR" --warnings "$WARNINGS" ) if [[ -n "$BUILD_FLAGS" ]]; then COMPILE_ARGS+=(--build-property "build.extra_flags=$BUILD_FLAGS") fi [[ -n "$VERBOSE" ]] && COMPILE_ARGS+=("$VERBOSE") COMPILE_ARGS+=("$SKETCH_DIR") arduino-cli "${COMPILE_ARGS[@]}" || die "Compilation failed." ok "Compile succeeded." # -- Upload ---------------------------------------------------------------- echo "" echo "${CYN}${BLD}Uploading to ${PORT}...${RST}" UPLOAD_ARGS=( upload --fqbn "$FQBN" --port "$PORT" --input-dir "$BUILD_DIR" ) [[ -n "$VERBOSE" ]] && UPLOAD_ARGS+=("$VERBOSE") arduino-cli "${UPLOAD_ARGS[@]}" || die "Upload failed." ok "Upload complete!" # -- Monitor --------------------------------------------------------------- if [[ $DO_MONITOR -eq 1 ]]; then echo "" 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