qmk_userspace/autoflash_bothsides_corrected.sh
2025-10-09 14:45:23 +02:00

341 lines
12 KiB
Bash

#!/usr/bin/env bash
# =============================================================================
# QMK Auto-Flashing Script for Fingerpunch/Sweeeeep with Liatris (RP2040)
# =============================================================================
# CORRECTED VERSION - Uses HOST USB information for device identification
#
# Key Insight:
# - Liatris overwrites EEPROM on flash, so on-board info is unreliable
# - Board-ID in INFO_UF2.TXT is the SAME for all controllers of the same type
# - ONLY the host's USB serial/path is reliable for distinguishing sides
#
# Features:
# - Single firmware compilation
# - TRUE auto-detection using USB serial from host system
# - First-run learning: asks user to identify which side is which
# - Persistent mapping stored in ~/.qmk_rp2040_sides.json
# - Automated flashing using uf2-split-left/right bootloader targets
# - Robust USB detection across Linux distributions
# =============================================================================
set -euo pipefail
# ----------------------
# User-configurable variables
# ----------------------
KEYBOARD="fingerpunch/sweeeeep"
KEYMAP="smathev"
USB_MOUNT_PATHS=("/media/$USER" "/run/media/$USER" "/mnt")
RP2040_PATTERN="*RP2040*"
USB_WAIT_INTERVAL=0.5
SIDE_MAPPING_FILE="$HOME/.qmk_rp2040_sides.json"
# Ensure mapping file exists
if [[ ! -f "$SIDE_MAPPING_FILE" ]]; then
echo "{}" > "$SIDE_MAPPING_FILE"
fi
# Ensure jq is installed
if ! command -v jq &> /dev/null; then
echo "❌ Error: 'jq' is required but not installed."
echo " Install it with: sudo apt-get install jq"
exit 1
fi
# ----------------------
# Function: build_firmware
# Build the firmware once for reuse during flashing
# ----------------------
build_firmware() {
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "🛠 Building firmware for $KEYBOARD"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
qmk compile -kb "$KEYBOARD" -km "$KEYMAP"
echo "✅ Firmware compiled successfully"
echo ""
}
# ----------------------
# Function: wait_for_rp2040
# Wait until an RP2040 UF2 device is mounted on any of the configured paths
# Returns the mount point
# ----------------------
wait_for_rp2040() {
echo "⏳ Waiting for RP2040 UF2 device..."
local device=""
while true; do
for path in "${USB_MOUNT_PATHS[@]}"; do
device=$(find "$path" -maxdepth 2 -type d -name "$RP2040_PATTERN" 2>/dev/null | head -n1)
if [[ -n "$device" ]]; then
echo "✅ Found RP2040 device at $device"
echo "$device"
return
fi
done
sleep "$USB_WAIT_INTERVAL"
done
}
# ----------------------
# Function: get_usb_serial_from_host
# Get the USB serial number from the HOST system (not from the device itself)
# This is the ONLY reliable way to identify devices when EEPROM is wiped
# ----------------------
get_usb_serial_from_host() {
local mount_point="$1"
# Method 1: Get the block device, then trace to USB serial
local dev
dev=$(findmnt -n -o SOURCE --target "$mount_point" 2>/dev/null || echo "")
if [[ -n "$dev" ]]; then
local block_dev=$(basename "$dev")
# Try to find USB serial through sysfs
local sys_path="/sys/class/block/$block_dev"
# Walk up the device tree to find the USB device
local current_path=$(readlink -f "$sys_path/device" 2>/dev/null || echo "")
while [[ -n "$current_path" && "$current_path" != "/sys" ]]; do
# Check if this directory has a serial file
if [[ -f "$current_path/serial" ]]; then
cat "$current_path/serial"
return
fi
# Also check for idVendor/idProduct to confirm it's a USB device
if [[ -f "$current_path/idVendor" ]]; then
# Found USB device level, check for serial
if [[ -f "$current_path/serial" ]]; then
cat "$current_path/serial"
return
fi
fi
# Move up one level
current_path=$(dirname "$current_path")
done
fi
# Method 2: Use udevadm to get USB info
if [[ -n "$dev" ]]; then
local serial
serial=$(udevadm info --query=property --name="$dev" 2>/dev/null | grep "ID_SERIAL_SHORT=" | cut -d'=' -f2)
if [[ -n "$serial" ]]; then
echo "$serial"
return
fi
fi
# Method 3: Fallback - use the mount point path as identifier
# This is less reliable but better than nothing
echo "mount_path_$(basename "$mount_point")"
}
# ----------------------
# Function: get_usb_device_path
# Get a unique identifier based on USB physical port location
# This persists even when serial is not available
# ----------------------
get_usb_device_path() {
local mount_point="$1"
local dev
dev=$(findmnt -n -o SOURCE --target "$mount_point" 2>/dev/null || echo "")
if [[ -n "$dev" ]]; then
# Get the physical USB path (bus and port numbers)
local devpath
devpath=$(udevadm info --query=property --name="$dev" 2>/dev/null | grep "DEVPATH=" | cut -d'=' -f2)
if [[ -n "$devpath" ]]; then
# Extract the USB bus and port info (e.g., /devices/pci0000:00/0000:00:14.0/usb1/1-3/1-3:1.0)
echo "$devpath" | grep -oP 'usb\d+/\d+-[\d.]+'
return
fi
fi
echo ""
}
# ----------------------
# Function: get_device_identifier
# Get the best available identifier for the USB device
# Prefers USB serial, falls back to USB port location
# ----------------------
get_device_identifier() {
local mount_point="$1"
# Try to get USB serial from host
local usb_serial
usb_serial=$(get_usb_serial_from_host "$mount_point")
# If serial doesn't start with "mount_path_", it's a real serial
if [[ -n "$usb_serial" && "$usb_serial" != mount_path_* ]]; then
echo "serial:$usb_serial"
return
fi
# Try USB device path
local usb_path
usb_path=$(get_usb_device_path "$mount_point")
if [[ -n "$usb_path" ]]; then
echo "usbpath:$usb_path"
return
fi
# Final fallback: use mount point basename
echo "mount:$(basename "$mount_point")"
}
# ----------------------
# Function: detect_side
# Determine the left/right side of the plugged-in board
# On first encounter, ask user to identify the side
# ----------------------
detect_side() {
local mount_point="$1"
local device_id
device_id=$(get_device_identifier "$mount_point")
echo " Device Identifier: $device_id"
local side
side=$(jq -r --arg id "$device_id" '.[$id] // "null"' "$SIDE_MAPPING_FILE")
if [[ "$side" == "null" || -z "$side" ]]; then
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "⚠️ UNKNOWN DEVICE - First Time Setup"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "The script has detected a keyboard half that it hasn't"
echo "seen before. This is expected on first run."
echo ""
echo "Please tell me which side this is so I can remember it"
echo "for future flashing sessions."
echo ""
read -rp "Which side is currently plugged in? [left/right]: " side
side=${side,,}
if [[ "$side" != "left" && "$side" != "right" ]]; then
echo "❌ Invalid input. Must be 'left' or 'right'."
echo " Exiting to avoid incorrect flashing."
exit 1
fi
# Save mapping
tmpfile=$(mktemp)
jq --arg id "$device_id" --arg side "$side" '. + {($id): $side}' "$SIDE_MAPPING_FILE" > "$tmpfile"
mv "$tmpfile" "$SIDE_MAPPING_FILE"
echo ""
echo "✅ Saved mapping: $side side"
echo " Next time this device is detected, it will be"
echo " automatically identified as the $side side."
echo ""
fi
echo "$side"
}
# ----------------------
# Function: flash_side_auto
# Automatically detect and flash whichever keyboard half is plugged in
# ----------------------
flash_side_auto() {
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "🔌 Waiting for keyboard half in bootloader mode..."
echo " (Double-tap RESET button on Liatris controller)"
echo ""
# Wait for device
local mount_point
mount_point=$(wait_for_rp2040)
# Auto-detect which side based on HOST USB information
local detected_side
detected_side=$(detect_side "$mount_point")
echo ""
echo "🎯 Detected: $detected_side side"
echo "📤 Flashing with handedness: $detected_side"
echo ""
# Flash using the detected side's bootloader target
# Using -bl (bootloader) parameter with uf2-split-left or uf2-split-right
qmk flash -kb "$KEYBOARD" -km "$KEYMAP" -bl "uf2-split-$detected_side"
echo ""
echo "$detected_side side flashed successfully!"
echo ""
}
# ----------------------
# Function: main
# Main workflow: build firmware and flash both sides automatically
# ----------------------
main() {
echo ""
echo "╔═══════════════════════════════════════════════════════════╗"
echo "║ QMK Auto-Flash: Fingerpunch Sweeeeep + Liatris (RP2040) ║"
echo "╚═══════════════════════════════════════════════════════════╝"
echo ""
echo "This script will:"
echo " • Build firmware once"
echo " • Auto-detect which keyboard half you plug in"
echo " • Flash the correct handedness (left/right)"
echo " • Remember your devices for future flashing"
echo ""
echo "Note: On first run, you'll be asked to identify each side."
echo " After that, detection is fully automatic!"
echo ""
# Build firmware once
build_firmware
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "🚀 Ready to flash!"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
read -rp "Press Enter to start flashing the first side..."
echo ""
# Flash first side (whichever is plugged in)
flash_side_auto
# Ask if user wants to flash the second side
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
read -rp "Flash the other keyboard half now? [y/n]: " DO_SECOND
DO_SECOND=${DO_SECOND,,}
echo ""
if [[ "$DO_SECOND" == "y" ]]; then
echo "Please:"
echo " 1. Unplug the first keyboard half"
echo " 2. Plug in the OTHER half"
echo " 3. Enter bootloader mode (double-tap RESET)"
echo ""
read -rp "Press Enter when ready..."
echo ""
# Flash second side (auto-detected)
flash_side_auto
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "🎉 Flashing complete!"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "✓ Handedness has been set in EEPROM"
echo "✓ Device mappings saved to: $SIDE_MAPPING_FILE"
echo "✓ Future runs will automatically detect sides"
echo "✓ Future firmware updates will preserve handedness"
echo ""
# Show the saved mappings
echo "Saved device mappings:"
jq '.' "$SIDE_MAPPING_FILE"
echo ""
}
# Execute main
main