341 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Bash
		
	
	
	
	
	
			
		
		
	
	
			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
 |