mirror of
				https://github.com/qmk/qmk_userspace.git
				synced 2025-11-03 18:30:07 -05:00 
			
		
		
		
	Add files via upload
This commit is contained in:
		
					parent
					
						
							
								ffdd049589
							
						
					
				
			
			
				commit
				
					
						0bd86793ac
					
				
			
		
					 18 changed files with 3628 additions and 1 deletions
				
			
		
							
								
								
									
										197
									
								
								autoflash_bothsides.sh
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										197
									
								
								autoflash_bothsides.sh
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,197 @@
 | 
			
		|||
#!/usr/bin/env bash
 | 
			
		||||
# =============================================================================
 | 
			
		||||
# QMK Flashing Script for Fingerpunch/Sweeeeep with Liatris (RP2040)
 | 
			
		||||
# =============================================================================
 | 
			
		||||
# Features:
 | 
			
		||||
#   - Single prebuilt firmware compilation
 | 
			
		||||
#   - Automated handedness-aware flashing using uf2-split-left/right targets
 | 
			
		||||
#   - Robust USB detection across Linux distributions
 | 
			
		||||
#   - Auto-detection of which side is plugged in based on RP2040 USB serial/Board ID
 | 
			
		||||
#   - Persistent mapping of USB devices to left/right sides (~/.qmk_rp2040_sides.json)
 | 
			
		||||
#   - Optional prompting for unknown devices
 | 
			
		||||
#   - Waits for device mount before flashing
 | 
			
		||||
# =============================================================================
 | 
			
		||||
 | 
			
		||||
set -euo pipefail
 | 
			
		||||
 | 
			
		||||
# ----------------------
 | 
			
		||||
# User-configurable variables
 | 
			
		||||
# ----------------------
 | 
			
		||||
KEYBOARD="fingerpunch/sweeeeep"
 | 
			
		||||
KEYMAP="smathev"
 | 
			
		||||
OUTPUT_DIR="$HOME/git_dev/keyboards/latest_firmware"
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
# ----------------------
 | 
			
		||||
# Function: build_firmware
 | 
			
		||||
# Build the firmware once for reuse during flashing
 | 
			
		||||
# ----------------------
 | 
			
		||||
build_firmware() {
 | 
			
		||||
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
    echo "🛠 Building firmware once for $KEYBOARD"
 | 
			
		||||
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
    qmk compile -kb "$KEYBOARD" -km "$KEYMAP"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# ----------------------
 | 
			
		||||
# Function: wait_for_rp2040
 | 
			
		||||
# Wait until an RP2040 UF2 device is mounted on any of the configured paths
 | 
			
		||||
# ----------------------
 | 
			
		||||
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_rp2040_usb_serial
 | 
			
		||||
# Attempt to get the USB serial number of the RP2040 device
 | 
			
		||||
# Returns empty string if unavailable
 | 
			
		||||
# ----------------------
 | 
			
		||||
get_rp2040_usb_serial() {
 | 
			
		||||
    local mount_point="$1"
 | 
			
		||||
    local dev
 | 
			
		||||
    dev=$(findmnt -n -o SOURCE --target "$mount_point" 2>/dev/null)
 | 
			
		||||
    if [[ -n "$dev" ]]; then
 | 
			
		||||
        local sys_path
 | 
			
		||||
        sys_path=$(readlink -f "/sys/class/block/$(basename "$dev")/device")
 | 
			
		||||
        if [[ -f "$sys_path/serial" ]]; then
 | 
			
		||||
            cat "$sys_path/serial"
 | 
			
		||||
            return
 | 
			
		||||
        fi
 | 
			
		||||
    fi
 | 
			
		||||
    echo ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# ----------------------
 | 
			
		||||
# Function: get_rp2040_id
 | 
			
		||||
# Extract a unique identifier from the mounted RP2040
 | 
			
		||||
# Prefers USB serial, falls back to info_uf2.txt Board ID, then mount path
 | 
			
		||||
# ----------------------
 | 
			
		||||
get_rp2040_id() {
 | 
			
		||||
    local mount_point="$1"
 | 
			
		||||
    local usb_serial
 | 
			
		||||
    usb_serial=$(get_rp2040_usb_serial "$mount_point")
 | 
			
		||||
    if [[ -n "$usb_serial" ]]; then
 | 
			
		||||
        echo "$usb_serial"
 | 
			
		||||
    elif [[ -f "$mount_point/info_uf2.txt" ]]; then
 | 
			
		||||
        grep "^Board ID" "$mount_point/info_uf2.txt" | awk -F': ' '{print $2}'
 | 
			
		||||
    else
 | 
			
		||||
        basename "$mount_point"
 | 
			
		||||
    fi
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# ----------------------
 | 
			
		||||
# Function: detect_side
 | 
			
		||||
# Determine the left/right side of the plugged-in board
 | 
			
		||||
# If unknown, prompt the user and update mapping
 | 
			
		||||
# ----------------------
 | 
			
		||||
detect_side() {
 | 
			
		||||
    local mount_point="$1"
 | 
			
		||||
    local rp_id
 | 
			
		||||
    rp_id=$(get_rp2040_id "$mount_point")
 | 
			
		||||
 | 
			
		||||
    local side
 | 
			
		||||
    side=$(jq -r --arg id "$rp_id" '.[$id]' "$SIDE_MAPPING_FILE")
 | 
			
		||||
 | 
			
		||||
    if [[ "$side" == "null" ]]; then
 | 
			
		||||
        read -rp "Unknown device detected. Which side is this half? [left/right]: " side
 | 
			
		||||
        side=${side,,}
 | 
			
		||||
        if [[ "$side" != "left" && "$side" != "right" ]]; then
 | 
			
		||||
            echo "Invalid input. Defaulting to left."
 | 
			
		||||
            side="left"
 | 
			
		||||
        fi
 | 
			
		||||
        # Save mapping
 | 
			
		||||
        tmpfile=$(mktemp)
 | 
			
		||||
        jq --arg id "$rp_id" --arg side "$side" '. + {($id): $side}' "$SIDE_MAPPING_FILE" > "$tmpfile"
 | 
			
		||||
        mv "$tmpfile" "$SIDE_MAPPING_FILE"
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    echo "$side"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# ----------------------
 | 
			
		||||
# Function: flash_side
 | 
			
		||||
# Flash the prebuilt firmware to the given side (left/right)
 | 
			
		||||
# Waits for device and applies UF2 split target
 | 
			
		||||
# ----------------------
 | 
			
		||||
flash_side() {
 | 
			
		||||
    local side="$1"
 | 
			
		||||
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
    echo "🔌 Flashing $side side..."
 | 
			
		||||
 | 
			
		||||
    # Wait for device
 | 
			
		||||
    local mount_point
 | 
			
		||||
    mount_point=$(wait_for_rp2040)
 | 
			
		||||
 | 
			
		||||
    # Auto-detect side if unknown
 | 
			
		||||
    local detected_side
 | 
			
		||||
    detected_side=$(detect_side "$mount_point")
 | 
			
		||||
 | 
			
		||||
    if [[ "$detected_side" != "$side" ]]; then
 | 
			
		||||
        echo "⚠️  Detected side '$detected_side' does not match expected side '$side'. Using detected side."
 | 
			
		||||
        side="$detected_side"
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    # Flash using prebuilt UF2 split target
 | 
			
		||||
    qmk flash -kb "$KEYBOARD" -km "$KEYMAP:uf2-split-$side" -f
 | 
			
		||||
 | 
			
		||||
    echo "✅ $side side flashed successfully."
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# ----------------------
 | 
			
		||||
# Function: main
 | 
			
		||||
# Main workflow: build firmware and flash both sides
 | 
			
		||||
# ----------------------
 | 
			
		||||
main() {
 | 
			
		||||
    build_firmware
 | 
			
		||||
 | 
			
		||||
    # Ask which side to flash first
 | 
			
		||||
    read -rp "Which side to flash first? [left/right]: " SIDE1
 | 
			
		||||
    SIDE1=${SIDE1,,}
 | 
			
		||||
    if [[ "$SIDE1" != "left" && "$SIDE1" != "right" ]]; then
 | 
			
		||||
        echo "Invalid input. Must be 'left' or 'right'."
 | 
			
		||||
        exit 1
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    # Determine second side
 | 
			
		||||
    SIDE2=$([[ "$SIDE1" == "left" ]] && echo "right" || echo "left")
 | 
			
		||||
 | 
			
		||||
    read -rp "Will you flash the other side afterward? [y/n]: " DO_SECOND
 | 
			
		||||
    DO_SECOND=${DO_SECOND,,}
 | 
			
		||||
 | 
			
		||||
    # Flash first side
 | 
			
		||||
    flash_side "$SIDE1"
 | 
			
		||||
 | 
			
		||||
    # Flash second side if requested
 | 
			
		||||
    if [[ "$DO_SECOND" == "y" ]]; then
 | 
			
		||||
        echo "Please reset the $SIDE2 half now, then press Enter to continue..."
 | 
			
		||||
        read -r
 | 
			
		||||
        flash_side "$SIDE2"
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
    echo "🎉 All requested flashing complete!"
 | 
			
		||||
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Execute main
 | 
			
		||||
main
 | 
			
		||||
							
								
								
									
										341
									
								
								autoflash_bothsides_corrected.sh
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										341
									
								
								autoflash_bothsides_corrected.sh
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,341 @@
 | 
			
		|||
#!/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
 | 
			
		||||
							
								
								
									
										236
									
								
								autoflash_bothsides_optimized.sh
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										236
									
								
								autoflash_bothsides_optimized.sh
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,236 @@
 | 
			
		|||
#!/usr/bin/env bash
 | 
			
		||||
# =============================================================================
 | 
			
		||||
# QMK Auto-Flashing Script for Fingerpunch/Sweeeeep with Liatris (RP2040)
 | 
			
		||||
# =============================================================================
 | 
			
		||||
# OPTIMIZED VERSION - Features:
 | 
			
		||||
#   - Single firmware compilation
 | 
			
		||||
#   - TRUE auto-detection: plug in any side, script detects which it is
 | 
			
		||||
#   - Automated handedness-aware flashing using uf2-split-left/right bootloader targets
 | 
			
		||||
#   - Robust USB detection across Linux distributions
 | 
			
		||||
#   - Persistent mapping of USB devices to left/right sides (~/.qmk_rp2040_sides.json)
 | 
			
		||||
#   - No need to specify which side first - script figures it out!
 | 
			
		||||
#   - Waits for device mount before flashing
 | 
			
		||||
# =============================================================================
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
# ----------------------
 | 
			
		||||
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_rp2040_usb_serial
 | 
			
		||||
# Attempt to get the USB serial number of the RP2040 device
 | 
			
		||||
# Returns empty string if unavailable
 | 
			
		||||
# ----------------------
 | 
			
		||||
get_rp2040_usb_serial() {
 | 
			
		||||
    local mount_point="$1"
 | 
			
		||||
    local dev
 | 
			
		||||
    dev=$(findmnt -n -o SOURCE --target "$mount_point" 2>/dev/null || echo "")
 | 
			
		||||
    if [[ -n "$dev" ]]; then
 | 
			
		||||
        local sys_path
 | 
			
		||||
        sys_path=$(readlink -f "/sys/class/block/$(basename "$dev")/device" 2>/dev/null || echo "")
 | 
			
		||||
        if [[ -f "$sys_path/serial" ]]; then
 | 
			
		||||
            cat "$sys_path/serial"
 | 
			
		||||
            return
 | 
			
		||||
        fi
 | 
			
		||||
    fi
 | 
			
		||||
    echo ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# ----------------------
 | 
			
		||||
# Function: get_rp2040_id
 | 
			
		||||
# Extract a unique identifier from the mounted RP2040
 | 
			
		||||
# Prefers USB serial, falls back to info_uf2.txt Board ID, then mount path
 | 
			
		||||
# ----------------------
 | 
			
		||||
get_rp2040_id() {
 | 
			
		||||
    local mount_point="$1"
 | 
			
		||||
    local usb_serial
 | 
			
		||||
    usb_serial=$(get_rp2040_usb_serial "$mount_point")
 | 
			
		||||
    if [[ -n "$usb_serial" ]]; then
 | 
			
		||||
        echo "$usb_serial"
 | 
			
		||||
    elif [[ -f "$mount_point/INFO_UF2.TXT" ]]; then
 | 
			
		||||
        grep -i "^Board-ID" "$mount_point/INFO_UF2.TXT" | awk -F': ' '{print $2}' | tr -d '\r\n '
 | 
			
		||||
    elif [[ -f "$mount_point/info_uf2.txt" ]]; then
 | 
			
		||||
        grep -i "^Board-ID" "$mount_point/info_uf2.txt" | awk -F': ' '{print $2}' | tr -d '\r\n '
 | 
			
		||||
    else
 | 
			
		||||
        basename "$mount_point"
 | 
			
		||||
    fi
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# ----------------------
 | 
			
		||||
# Function: detect_side
 | 
			
		||||
# Determine the left/right side of the plugged-in board
 | 
			
		||||
# If unknown, prompt the user and update mapping
 | 
			
		||||
# ----------------------
 | 
			
		||||
detect_side() {
 | 
			
		||||
    local mount_point="$1"
 | 
			
		||||
    local rp_id
 | 
			
		||||
    rp_id=$(get_rp2040_id "$mount_point")
 | 
			
		||||
 | 
			
		||||
    echo "   Device ID: $rp_id"
 | 
			
		||||
 | 
			
		||||
    local side
 | 
			
		||||
    side=$(jq -r --arg id "$rp_id" '.[$id] // "null"' "$SIDE_MAPPING_FILE")
 | 
			
		||||
 | 
			
		||||
    if [[ "$side" == "null" || -z "$side" ]]; then
 | 
			
		||||
        echo ""
 | 
			
		||||
        echo "⚠️  Unknown device detected!"
 | 
			
		||||
        read -rp "   Which side is this keyboard half? [left/right]: " side
 | 
			
		||||
        side=${side,,}
 | 
			
		||||
        if [[ "$side" != "left" && "$side" != "right" ]]; then
 | 
			
		||||
            echo "   Invalid input. Defaulting to left."
 | 
			
		||||
            side="left"
 | 
			
		||||
        fi
 | 
			
		||||
        # Save mapping
 | 
			
		||||
        tmpfile=$(mktemp)
 | 
			
		||||
        jq --arg id "$rp_id" --arg side "$side" '. + {($id): $side}' "$SIDE_MAPPING_FILE" > "$tmpfile"
 | 
			
		||||
        mv "$tmpfile" "$SIDE_MAPPING_FILE"
 | 
			
		||||
        echo "   ✅ Saved mapping: $rp_id → $side"
 | 
			
		||||
    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
 | 
			
		||||
    local detected_side
 | 
			
		||||
    detected_side=$(detect_side "$mount_point")
 | 
			
		||||
 | 
			
		||||
    echo ""
 | 
			
		||||
    echo "🎯 Detected: $detected_side side"
 | 
			
		||||
    echo "📤 Flashing as $detected_side..."
 | 
			
		||||
    echo ""
 | 
			
		||||
 | 
			
		||||
    # Flash using the detected side's bootloader target
 | 
			
		||||
    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 ""
 | 
			
		||||
 | 
			
		||||
    # Build firmware once
 | 
			
		||||
    build_firmware
 | 
			
		||||
 | 
			
		||||
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
    echo "🚀 Ready to flash!"
 | 
			
		||||
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
    echo ""
 | 
			
		||||
    echo "Instructions:"
 | 
			
		||||
    echo "  1. Enter bootloader on FIRST keyboard half (either side)"
 | 
			
		||||
    echo "  2. Script will auto-detect which side it is"
 | 
			
		||||
    echo "  3. After first side completes, do the same for the OTHER half"
 | 
			
		||||
    echo ""
 | 
			
		||||
    read -rp "Press Enter when ready to start..."
 | 
			
		||||
    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 keyboard half you just flashed"
 | 
			
		||||
        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 "✓ Future firmware updates can be flashed to both sides"
 | 
			
		||||
    echo "✓ Handedness will persist across updates"
 | 
			
		||||
    echo ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Execute main
 | 
			
		||||
main
 | 
			
		||||
							
								
								
									
										200
									
								
								qmk_flash_tools/MAPPING_LOGIC.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										200
									
								
								qmk_flash_tools/MAPPING_LOGIC.md
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,200 @@
 | 
			
		|||
# Device Mapping Logic - Simple Explanation
 | 
			
		||||
 | 
			
		||||
## The Three States
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
State 1: EMPTY      →  Learn both devices
 | 
			
		||||
State 2: PARTIAL    →  Complete the mapping  
 | 
			
		||||
State 3: COMPLETE   →  Only allow known devices
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## State Transitions
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
┌─────────────┐
 | 
			
		||||
│   EMPTY     │  No devices mapped
 | 
			
		||||
│   (0/2)     │
 | 
			
		||||
└──────┬──────┘
 | 
			
		||||
       │ Flash first device
 | 
			
		||||
       ▼
 | 
			
		||||
┌─────────────┐
 | 
			
		||||
│  PARTIAL    │  One device mapped
 | 
			
		||||
│   (1/2)     │
 | 
			
		||||
└──────┬──────┘
 | 
			
		||||
       │ Flash second device
 | 
			
		||||
       ▼
 | 
			
		||||
┌─────────────┐
 | 
			
		||||
│  COMPLETE   │  Both devices mapped
 | 
			
		||||
│   (2/2)     │  ← Stays here forever
 | 
			
		||||
└─────────────┘
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Behavior by State
 | 
			
		||||
 | 
			
		||||
### State 1: EMPTY (0/2 devices)
 | 
			
		||||
```
 | 
			
		||||
Action: LEARN
 | 
			
		||||
Rule:   Save devices as user specifies
 | 
			
		||||
Result: Build initial mapping
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### State 2: PARTIAL (1/2 devices)
 | 
			
		||||
```
 | 
			
		||||
Action: COMPLETE
 | 
			
		||||
Rule:   Known device must match, unknown must be the missing side
 | 
			
		||||
Result: Complete mapping OR error if ambiguous
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### State 3: COMPLETE (2/2 devices)
 | 
			
		||||
```
 | 
			
		||||
Action: VERIFY
 | 
			
		||||
Rule:   Only known devices allowed, must match expected side
 | 
			
		||||
Result: Continue OR error immediately
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Decision Tree
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
Device Detected
 | 
			
		||||
       │
 | 
			
		||||
       ▼
 | 
			
		||||
┌────────────────┐
 | 
			
		||||
│ Mapping State? │
 | 
			
		||||
└───┬────┬───┬───┘
 | 
			
		||||
    │    │   │
 | 
			
		||||
    ▼    ▼   ▼
 | 
			
		||||
  Empty Part Comp
 | 
			
		||||
    │    │    │
 | 
			
		||||
    │    │    ▼
 | 
			
		||||
    │    │  ┌──────────────┐
 | 
			
		||||
    │    │  │ Device Known?│
 | 
			
		||||
    │    │  └──┬───────┬───┘
 | 
			
		||||
    │    │     │       │
 | 
			
		||||
    │    │     NO      YES
 | 
			
		||||
    │    │     │       │
 | 
			
		||||
    │    │     ▼       ▼
 | 
			
		||||
    │    │   REJECT  Match?
 | 
			
		||||
    │    │            │
 | 
			
		||||
    │    │      ┌─────┴─────┐
 | 
			
		||||
    │    │      YES          NO
 | 
			
		||||
    │    │      │            │
 | 
			
		||||
    │    │      ▼            ▼
 | 
			
		||||
    │    │    CONTINUE    MISMATCH
 | 
			
		||||
    │    │                 OPTIONS
 | 
			
		||||
    │    │
 | 
			
		||||
    │    ▼
 | 
			
		||||
    │  ┌──────────────┐
 | 
			
		||||
    │  │ Device Known?│
 | 
			
		||||
    │  └──┬───────┬───┘
 | 
			
		||||
    │     │       │
 | 
			
		||||
    │     NO      YES
 | 
			
		||||
    │     │       │
 | 
			
		||||
    │     ▼       ▼
 | 
			
		||||
    │  Expected  Match?
 | 
			
		||||
    │  unmapped    │
 | 
			
		||||
    │   side?  ┌───┴────┐
 | 
			
		||||
    │     │    YES      NO
 | 
			
		||||
    │     ▼    │        │
 | 
			
		||||
    │  ┌───┐  ▼        ▼
 | 
			
		||||
    │  │YES│CONTINUE  ERROR
 | 
			
		||||
    │  └─┬─┘
 | 
			
		||||
    │    │NO
 | 
			
		||||
    │    ▼
 | 
			
		||||
    │  ERROR
 | 
			
		||||
    │
 | 
			
		||||
    ▼
 | 
			
		||||
  SAVE AS
 | 
			
		||||
  EXPECTED
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Examples
 | 
			
		||||
 | 
			
		||||
### Example 1: First Time (Empty → Partial → Complete)
 | 
			
		||||
```
 | 
			
		||||
$ ./autoflash_modular.sh
 | 
			
		||||
 | 
			
		||||
State: EMPTY (0/2)
 | 
			
		||||
You: "left"
 | 
			
		||||
Device: ABC123
 | 
			
		||||
Action: Save ABC123 → left
 | 
			
		||||
New State: PARTIAL (1/2)
 | 
			
		||||
 | 
			
		||||
You: "Flash right? yes"
 | 
			
		||||
Device: XYZ789
 | 
			
		||||
Action: Save XYZ789 → right
 | 
			
		||||
New State: COMPLETE (2/2)
 | 
			
		||||
 | 
			
		||||
Mapping: {"ABC123": "left", "XYZ789": "right"}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Example 2: Normal Use (Complete)
 | 
			
		||||
```
 | 
			
		||||
$ ./autoflash_modular.sh
 | 
			
		||||
 | 
			
		||||
State: COMPLETE (2/2)
 | 
			
		||||
You: "left"
 | 
			
		||||
Device: ABC123
 | 
			
		||||
Check: ABC123 is mapped to "left" ✅
 | 
			
		||||
Action: Continue
 | 
			
		||||
 | 
			
		||||
You: "right"
 | 
			
		||||
Device: XYZ789
 | 
			
		||||
Check: XYZ789 is mapped to "right" ✅
 | 
			
		||||
Action: Continue
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Example 3: Unknown Device (Complete → Rejected)
 | 
			
		||||
```
 | 
			
		||||
$ ./autoflash_modular.sh
 | 
			
		||||
 | 
			
		||||
State: COMPLETE (2/2)
 | 
			
		||||
You: "left"
 | 
			
		||||
Device: UNKNOWN
 | 
			
		||||
Check: Not in mapping ❌
 | 
			
		||||
Action: REJECT and EXIT
 | 
			
		||||
 | 
			
		||||
Error: "Unknown device! Expected ABC123 or XYZ789"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Example 4: Partial Mapping - Good
 | 
			
		||||
```
 | 
			
		||||
State: PARTIAL (1/2) - Only "left" mapped
 | 
			
		||||
 | 
			
		||||
Scenario A: Known device
 | 
			
		||||
You: "left"
 | 
			
		||||
Device: ABC123 (known as left)
 | 
			
		||||
Action: Continue ✅
 | 
			
		||||
 | 
			
		||||
Scenario B: Unknown device for unmapped side
 | 
			
		||||
You: "right" (unmapped)
 | 
			
		||||
Device: UNKNOWN
 | 
			
		||||
Action: Save as right ✅
 | 
			
		||||
New State: COMPLETE
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Example 5: Partial Mapping - Bad
 | 
			
		||||
```
 | 
			
		||||
State: PARTIAL (1/2) - Only "left" mapped
 | 
			
		||||
 | 
			
		||||
You: "left" (already mapped)
 | 
			
		||||
Device: UNKNOWN
 | 
			
		||||
Question: Is this a replacement for left? Or the unmapped right?
 | 
			
		||||
Action: ERROR - ambiguous ❌
 | 
			
		||||
Message: "Cannot determine! Clear mappings."
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Key Principle
 | 
			
		||||
 | 
			
		||||
**Once both sides are mapped (COMPLETE state), the script becomes protective:**
 | 
			
		||||
- ✅ Only the two known devices can be flashed
 | 
			
		||||
- ❌ Any unknown device is rejected immediately
 | 
			
		||||
- 🛡️ This prevents accidentally flashing the wrong keyboard
 | 
			
		||||
 | 
			
		||||
**To reset:** `cd qmk_flash_tools && rm device_mappings.json`
 | 
			
		||||
 | 
			
		||||
## Why This Works
 | 
			
		||||
 | 
			
		||||
1. **Learning Phase** (Empty/Partial) - Flexible, builds mapping
 | 
			
		||||
2. **Protection Phase** (Complete) - Strict, prevents mistakes
 | 
			
		||||
3. **Clear Errors** - Always know why something failed
 | 
			
		||||
4. **Easy Recovery** - Delete mapping file to restart
 | 
			
		||||
							
								
								
									
										223
									
								
								qmk_flash_tools/QUICK_REFERENCE.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										223
									
								
								qmk_flash_tools/QUICK_REFERENCE.md
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,223 @@
 | 
			
		|||
# QMK Flash Tools - Quick Reference
 | 
			
		||||
 | 
			
		||||
## 🚀 Common Commands
 | 
			
		||||
 | 
			
		||||
### Flash Both Keyboard Sides
 | 
			
		||||
```bash
 | 
			
		||||
cd qmk_flash_tools
 | 
			
		||||
./autoflash_modular.sh
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Test Individual Components
 | 
			
		||||
```bash
 | 
			
		||||
# Test side mapping (no hardware needed)
 | 
			
		||||
./test/test_side_mapping.sh
 | 
			
		||||
 | 
			
		||||
# Test device detection (needs keyboard in bootloader)
 | 
			
		||||
./test/test_device_detection.sh
 | 
			
		||||
 | 
			
		||||
# Test QMK functions
 | 
			
		||||
./test/test_qmk_helpers.sh
 | 
			
		||||
 | 
			
		||||
# Run all tests
 | 
			
		||||
./test/run_all_tests.sh
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### View Saved Mappings
 | 
			
		||||
```bash
 | 
			
		||||
cd qmk_flash_tools
 | 
			
		||||
cat device_mappings.json
 | 
			
		||||
# or
 | 
			
		||||
jq '.' device_mappings.json
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Reset Mappings
 | 
			
		||||
```bash
 | 
			
		||||
# Reset all
 | 
			
		||||
cd qmk_flash_tools
 | 
			
		||||
rm device_mappings.json
 | 
			
		||||
 | 
			
		||||
# Or use the library
 | 
			
		||||
source lib/side_mapping.sh
 | 
			
		||||
clear_all_mappings
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 🔍 Debug Commands
 | 
			
		||||
 | 
			
		||||
### Check Device Info
 | 
			
		||||
```bash
 | 
			
		||||
source lib/device_detection.sh
 | 
			
		||||
mount_point="/media/$USER/RPI-RP2"  # Adjust path
 | 
			
		||||
print_device_info "$mount_point"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Manually Test Detection
 | 
			
		||||
```bash
 | 
			
		||||
source lib/device_detection.sh
 | 
			
		||||
source lib/side_mapping.sh
 | 
			
		||||
 | 
			
		||||
# Wait for device
 | 
			
		||||
mount_point=$(wait_for_rp2040)
 | 
			
		||||
 | 
			
		||||
# Get identifier
 | 
			
		||||
device_id=$(get_device_identifier "$mount_point")
 | 
			
		||||
echo "Device ID: $device_id"
 | 
			
		||||
 | 
			
		||||
# Detect side
 | 
			
		||||
side=$(detect_side "$device_id")
 | 
			
		||||
echo "Side: $side"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Test QMK Commands
 | 
			
		||||
```bash
 | 
			
		||||
# Check QMK version
 | 
			
		||||
qmk --version
 | 
			
		||||
 | 
			
		||||
# List keymaps
 | 
			
		||||
qmk list-keymaps -kb fingerpunch/sweeeeep
 | 
			
		||||
 | 
			
		||||
# Compile only (no flash)
 | 
			
		||||
qmk compile -kb fingerpunch/sweeeeep -km smathev
 | 
			
		||||
 | 
			
		||||
# Flash with specific bootloader
 | 
			
		||||
qmk flash -kb fingerpunch/sweeeeep -km smathev -bl uf2-split-left
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 🛠️ Manual Side Mapping
 | 
			
		||||
 | 
			
		||||
### Add Mapping Manually
 | 
			
		||||
```bash
 | 
			
		||||
source lib/side_mapping.sh
 | 
			
		||||
save_side_mapping "serial:ABC123XYZ" "left"
 | 
			
		||||
save_side_mapping "serial:DEF456RST" "right"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Check Specific Device
 | 
			
		||||
```bash
 | 
			
		||||
source lib/side_mapping.sh
 | 
			
		||||
side=$(get_saved_side "serial:ABC123XYZ")
 | 
			
		||||
echo "Device is: $side"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### List All Mappings
 | 
			
		||||
```bash
 | 
			
		||||
source lib/side_mapping.sh
 | 
			
		||||
list_all_mappings
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 📝 Environment Variables
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
# Keyboard configuration
 | 
			
		||||
export KEYBOARD="fingerpunch/sweeeeep"
 | 
			
		||||
export KEYMAP="smathev"
 | 
			
		||||
 | 
			
		||||
# Device detection
 | 
			
		||||
export USB_MOUNT_PATHS=("/media/$USER" "/run/media/$USER" "/mnt")
 | 
			
		||||
export RP2040_PATTERN="*RP2040*"
 | 
			
		||||
export USB_WAIT_INTERVAL=0.5
 | 
			
		||||
 | 
			
		||||
# Side mapping file (relative to qmk_flash_tools/)
 | 
			
		||||
export SIDE_MAPPING_FILE="./device_mappings.json"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 🐛 Troubleshooting One-Liners
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
# Check if RP2040 is mounted
 | 
			
		||||
ls /media/$USER/ | grep -i rp2040
 | 
			
		||||
 | 
			
		||||
# Find all USB devices
 | 
			
		||||
lsusb
 | 
			
		||||
 | 
			
		||||
# Check USB device info
 | 
			
		||||
udevadm info --query=property /dev/sdb1  # Adjust device
 | 
			
		||||
 | 
			
		||||
# Monitor USB events (run in separate terminal)
 | 
			
		||||
udevadm monitor
 | 
			
		||||
 | 
			
		||||
# Check QMK firmware location
 | 
			
		||||
qmk config user.qmk_home
 | 
			
		||||
 | 
			
		||||
# Force clean and rebuild
 | 
			
		||||
qmk clean && qmk compile -kb fingerpunch/sweeeeep -km smathev
 | 
			
		||||
 | 
			
		||||
# Check if jq is installed
 | 
			
		||||
jq --version
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 📂 File Locations
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
Main script:        qmk_flash_tools/autoflash_modular.sh
 | 
			
		||||
Libraries:          qmk_flash_tools/lib/*.sh
 | 
			
		||||
Tests:              qmk_flash_tools/test/*.sh
 | 
			
		||||
Mapping file:       qmk_flash_tools/device_mappings.json
 | 
			
		||||
QMK firmware:       ~/qmk_firmware/ (or user.qmk_home)
 | 
			
		||||
Build output:       ~/qmk_firmware/.build/
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 🔧 Common Fixes
 | 
			
		||||
 | 
			
		||||
### Device not appearing
 | 
			
		||||
```bash
 | 
			
		||||
# Check dmesg for USB events
 | 
			
		||||
dmesg | tail -n 20
 | 
			
		||||
 | 
			
		||||
# Try different USB port
 | 
			
		||||
# Try different USB cable (must be data cable, not charge-only)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Wrong side detected
 | 
			
		||||
```bash
 | 
			
		||||
# Clear and re-learn
 | 
			
		||||
source lib/side_mapping.sh
 | 
			
		||||
clear_mapping "serial:YOUR_DEVICE_ID"
 | 
			
		||||
# Then run autoflash again
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Build fails
 | 
			
		||||
```bash
 | 
			
		||||
# Update QMK
 | 
			
		||||
python3 -m pip install --upgrade qmk
 | 
			
		||||
 | 
			
		||||
# Pull latest QMK firmware
 | 
			
		||||
cd ~/qmk_firmware
 | 
			
		||||
git pull
 | 
			
		||||
 | 
			
		||||
# Clean and try again
 | 
			
		||||
qmk clean
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Permission denied
 | 
			
		||||
```bash
 | 
			
		||||
# Make scripts executable
 | 
			
		||||
chmod +x qmk_flash_tools/*.sh
 | 
			
		||||
chmod +x qmk_flash_tools/lib/*.sh
 | 
			
		||||
chmod +x qmk_flash_tools/test/*.sh
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 📚 Function Quick Reference
 | 
			
		||||
 | 
			
		||||
### device_detection.sh
 | 
			
		||||
- `wait_for_rp2040()` - Wait for device
 | 
			
		||||
- `get_usb_serial_from_host(mount)` - Get serial
 | 
			
		||||
- `get_usb_device_path(mount)` - Get USB path
 | 
			
		||||
- `get_device_identifier(mount)` - Get ID
 | 
			
		||||
- `print_device_info(mount)` - Debug info
 | 
			
		||||
 | 
			
		||||
### side_mapping.sh
 | 
			
		||||
- `init_mapping_file()` - Create file
 | 
			
		||||
- `save_side_mapping(id, side)` - Save
 | 
			
		||||
- `get_saved_side(id)` - Retrieve
 | 
			
		||||
- `detect_side(id)` - Auto-detect/prompt
 | 
			
		||||
- `list_all_mappings()` - Show all
 | 
			
		||||
- `clear_mapping(id)` - Remove one
 | 
			
		||||
- `clear_all_mappings()` - Reset
 | 
			
		||||
 | 
			
		||||
### qmk_helpers.sh
 | 
			
		||||
- `build_firmware(kb, km)` - Compile
 | 
			
		||||
- `flash_side(kb, km, side)` - Flash with handedness
 | 
			
		||||
- `check_qmk_installed()` - Verify QMK
 | 
			
		||||
- `verify_keyboard_exists(kb)` - Check KB
 | 
			
		||||
- `clean_build()` - Clean
 | 
			
		||||
							
								
								
									
										338
									
								
								qmk_flash_tools/README.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										338
									
								
								qmk_flash_tools/README.md
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,338 @@
 | 
			
		|||
# QMK Flash Tools - Modular Edition
 | 
			
		||||
 | 
			
		||||
Automated flashing tools for QMK split keyboards with RP2040 controllers (like Liatris).
 | 
			
		||||
 | 
			
		||||
## 📁 Structure
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
qmk_flash_tools/
 | 
			
		||||
├── autoflash_modular.sh       # Main flashing script
 | 
			
		||||
├── lib/                        # Reusable library modules
 | 
			
		||||
│   ├── device_detection.sh     # USB device detection functions
 | 
			
		||||
│   ├── side_mapping.sh         # Device-to-side mapping storage
 | 
			
		||||
│   └── qmk_helpers.sh          # QMK build/flash wrappers
 | 
			
		||||
├── test/                       # Standalone test scripts
 | 
			
		||||
│   ├── test_device_detection.sh
 | 
			
		||||
│   ├── test_side_mapping.sh
 | 
			
		||||
│   └── test_qmk_helpers.sh
 | 
			
		||||
└── README.md                   # This file
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 🚀 Quick Start
 | 
			
		||||
 | 
			
		||||
### 1. Flash Both Sides
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
cd qmk_flash_tools
 | 
			
		||||
chmod +x autoflash_modular.sh
 | 
			
		||||
./autoflash_modular.sh
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The script will:
 | 
			
		||||
1. Build firmware once
 | 
			
		||||
2. **First time only:** Ask which side you'll flash (left/right)
 | 
			
		||||
3. **Subsequent runs:** Auto-detect which side is plugged in
 | 
			
		||||
4. Wait for you to enter bootloader
 | 
			
		||||
5. Verify and flash with correct handedness
 | 
			
		||||
6. Repeat for the other side
 | 
			
		||||
 | 
			
		||||
### 2. Test Individual Components
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
# Test device detection
 | 
			
		||||
cd test
 | 
			
		||||
chmod +x test_device_detection.sh
 | 
			
		||||
./test_device_detection.sh
 | 
			
		||||
 | 
			
		||||
# Test side mapping
 | 
			
		||||
chmod +x test_side_mapping.sh
 | 
			
		||||
./test_side_mapping.sh
 | 
			
		||||
 | 
			
		||||
# Test QMK helpers
 | 
			
		||||
chmod +x test_qmk_helpers.sh
 | 
			
		||||
./test_qmk_helpers.sh
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 🔧 Configuration
 | 
			
		||||
 | 
			
		||||
Edit the top of `autoflash_modular.sh`:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
KEYBOARD="fingerpunch/sweeeeep"  # Your keyboard
 | 
			
		||||
KEYMAP="smathev"                 # Your keymap
 | 
			
		||||
USB_MOUNT_PATHS=(...)            # Where USB drives mount
 | 
			
		||||
SIDE_MAPPING_FILE="..."          # Device mappings (defaults to ./device_mappings.json)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 📚 Library Documentation
 | 
			
		||||
 | 
			
		||||
### device_detection.sh
 | 
			
		||||
 | 
			
		||||
Functions for detecting RP2040 devices via host USB information.
 | 
			
		||||
 | 
			
		||||
**Key Functions:**
 | 
			
		||||
- `wait_for_rp2040()` - Wait for device to enter bootloader
 | 
			
		||||
- `get_usb_serial_from_host(mount_point)` - Get USB serial from host
 | 
			
		||||
- `get_usb_device_path(mount_point)` - Get USB port location
 | 
			
		||||
- `get_device_identifier(mount_point)` - Get best available ID
 | 
			
		||||
- `print_device_info(mount_point)` - Debug info
 | 
			
		||||
 | 
			
		||||
**Example:**
 | 
			
		||||
```bash
 | 
			
		||||
source lib/device_detection.sh
 | 
			
		||||
mount_point=$(wait_for_rp2040)
 | 
			
		||||
device_id=$(get_device_identifier "$mount_point")
 | 
			
		||||
echo "Device: $device_id"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### side_mapping.sh
 | 
			
		||||
 | 
			
		||||
Functions for storing and retrieving which device is left/right.
 | 
			
		||||
 | 
			
		||||
**Key Functions:**
 | 
			
		||||
- `init_mapping_file()` - Create mapping file if needed
 | 
			
		||||
- `save_side_mapping(device_id, side)` - Save mapping
 | 
			
		||||
- `get_saved_side(device_id)` - Retrieve saved side
 | 
			
		||||
- `detect_side(device_id)` - Get side (prompts if unknown)
 | 
			
		||||
- `list_all_mappings()` - Show all saved mappings
 | 
			
		||||
- `clear_mapping(device_id)` - Remove a mapping
 | 
			
		||||
- `clear_all_mappings()` - Reset all mappings
 | 
			
		||||
 | 
			
		||||
**Example:**
 | 
			
		||||
```bash
 | 
			
		||||
source lib/side_mapping.sh
 | 
			
		||||
export SIDE_MAPPING_FILE="./device_mappings.json"
 | 
			
		||||
 | 
			
		||||
save_side_mapping "serial:ABC123" "left"
 | 
			
		||||
side=$(get_saved_side "serial:ABC123")
 | 
			
		||||
echo "Side: $side"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### qmk_helpers.sh
 | 
			
		||||
 | 
			
		||||
Wrapper functions for QMK CLI commands.
 | 
			
		||||
 | 
			
		||||
**Key Functions:**
 | 
			
		||||
- `check_qmk_installed()` - Verify QMK is available
 | 
			
		||||
- `build_firmware(keyboard, keymap)` - Compile firmware
 | 
			
		||||
- `flash_side(keyboard, keymap, side)` - Flash with handedness
 | 
			
		||||
- `flash_with_bootloader(keyboard, keymap, bootloader)` - Flash with specific bootloader
 | 
			
		||||
- `verify_keyboard_exists(keyboard)` - Check keyboard definition
 | 
			
		||||
- `clean_build()` - Clean build artifacts
 | 
			
		||||
 | 
			
		||||
**Example:**
 | 
			
		||||
```bash
 | 
			
		||||
source lib/qmk_helpers.sh
 | 
			
		||||
 | 
			
		||||
check_qmk_installed || exit 1
 | 
			
		||||
build_firmware "fingerpunch/sweeeeep" "smathev"
 | 
			
		||||
flash_side "fingerpunch/sweeeeep" "smathev" "left"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 🧪 Testing Workflow
 | 
			
		||||
 | 
			
		||||
### Test Device Detection
 | 
			
		||||
 | 
			
		||||
1. **Without device:**
 | 
			
		||||
```bash
 | 
			
		||||
./test/test_device_detection.sh
 | 
			
		||||
# Shows "no device found", good for baseline
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
2. **With device:**
 | 
			
		||||
```bash
 | 
			
		||||
# Enter bootloader mode on keyboard
 | 
			
		||||
./test/test_device_detection.sh
 | 
			
		||||
# Shows USB serial, path, and identifier
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Test Side Mapping
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
./test/test_side_mapping.sh
 | 
			
		||||
# Runs comprehensive tests:
 | 
			
		||||
# - Create mapping file
 | 
			
		||||
# - Save/retrieve mappings
 | 
			
		||||
# - Clear mappings
 | 
			
		||||
# - Interactive prompt (optional)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Test QMK Helpers
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
./test/test_qmk_helpers.sh
 | 
			
		||||
# Tests:
 | 
			
		||||
# - QMK installation check
 | 
			
		||||
# - Keyboard verification
 | 
			
		||||
# - Build (optional)
 | 
			
		||||
# - Function signatures
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 🐛 Troubleshooting
 | 
			
		||||
 | 
			
		||||
### Device not detected
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
# Check if device appears
 | 
			
		||||
ls /media/$USER/
 | 
			
		||||
# Should see RPI-RP2 or similar
 | 
			
		||||
 | 
			
		||||
# Run device detection test
 | 
			
		||||
./test/test_device_detection.sh
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Can't identify device
 | 
			
		||||
 | 
			
		||||
The script uses these methods in order:
 | 
			
		||||
1. USB serial number (most reliable)
 | 
			
		||||
2. USB physical port path
 | 
			
		||||
3. Mount point name (fallback)
 | 
			
		||||
 | 
			
		||||
Check which method worked:
 | 
			
		||||
```bash
 | 
			
		||||
source lib/device_detection.sh
 | 
			
		||||
mount_point="/media/$USER/RPI-RP2"
 | 
			
		||||
print_device_info "$mount_point"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Side mismatch detected
 | 
			
		||||
 | 
			
		||||
If you see a mismatch warning:
 | 
			
		||||
 | 
			
		||||
**Option 1: Exit and plug in correct side (safest)**
 | 
			
		||||
- Choose `[e]` to exit
 | 
			
		||||
- Unplug the keyboard
 | 
			
		||||
- Plug in the correct side
 | 
			
		||||
- Run the script again
 | 
			
		||||
 | 
			
		||||
**Option 2: Update the mapping**
 | 
			
		||||
- Choose `[c]` to clear old mapping and save new one
 | 
			
		||||
- Use this if you know the old mapping was wrong
 | 
			
		||||
 | 
			
		||||
**Option 3: Force flash (dangerous!)**
 | 
			
		||||
- Choose `[f]` to flash anyway
 | 
			
		||||
- Only use if you're absolutely certain
 | 
			
		||||
- May result in swapped left/right behavior
 | 
			
		||||
 | 
			
		||||
Or manually reset mappings:
 | 
			
		||||
```bash
 | 
			
		||||
source lib/side_mapping.sh
 | 
			
		||||
clear_mapping "serial:ABC123"  # Use your device ID
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Or reset all mappings:
 | 
			
		||||
```bash
 | 
			
		||||
cd qmk_flash_tools
 | 
			
		||||
rm device_mappings.json
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Build fails
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
# Test QMK directly
 | 
			
		||||
qmk compile -kb fingerpunch/sweeeeep -km smathev
 | 
			
		||||
 | 
			
		||||
# Check keyboard exists
 | 
			
		||||
qmk list-keymaps -kb fingerpunch/sweeeeep
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 💡 Advanced Usage
 | 
			
		||||
 | 
			
		||||
### Use in other scripts
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
source /path/to/qmk_flash_tools/lib/device_detection.sh
 | 
			
		||||
source /path/to/qmk_flash_tools/lib/side_mapping.sh
 | 
			
		||||
 | 
			
		||||
# Your custom logic here
 | 
			
		||||
mount_point=$(wait_for_rp2040)
 | 
			
		||||
device_id=$(get_device_identifier "$mount_point")
 | 
			
		||||
side=$(detect_side "$device_id")
 | 
			
		||||
echo "Detected $side side"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Custom keyboard configuration
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
# Set environment variables before running
 | 
			
		||||
export KEYBOARD="your/keyboard"
 | 
			
		||||
export KEYMAP="your_keymap"
 | 
			
		||||
./autoflash_modular.sh
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Different mapping file
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
export SIDE_MAPPING_FILE="/tmp/my_test_mappings.json"
 | 
			
		||||
./autoflash_modular.sh
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 📋 Requirements
 | 
			
		||||
 | 
			
		||||
- **bash** - Shell interpreter
 | 
			
		||||
- **qmk** - QMK CLI (`python3 -m pip install qmk`)
 | 
			
		||||
- **jq** - JSON processor (`sudo apt-get install jq`)
 | 
			
		||||
- **findmnt** - Usually included with util-linux
 | 
			
		||||
- **udevadm** - Usually included with systemd
 | 
			
		||||
 | 
			
		||||
## 🔒 File Permissions
 | 
			
		||||
 | 
			
		||||
Make scripts executable:
 | 
			
		||||
```bash
 | 
			
		||||
chmod +x autoflash_modular.sh
 | 
			
		||||
chmod +x test/*.sh
 | 
			
		||||
chmod +x lib/*.sh
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 📝 How Device Mapping Works
 | 
			
		||||
 | 
			
		||||
The script intelligently handles three states:
 | 
			
		||||
 | 
			
		||||
### 🟢 Empty Mapping (First Time)
 | 
			
		||||
- **No devices mapped yet**
 | 
			
		||||
- **Asks:** "Which side will you flash first?"
 | 
			
		||||
- Script learns both sides as you flash them
 | 
			
		||||
- First device = saved as what you specify (left/right)
 | 
			
		||||
- Second device = saved as the other side
 | 
			
		||||
- Result: Complete mapping of both sides
 | 
			
		||||
 | 
			
		||||
### 🟡 Partial Mapping (One Side Known)
 | 
			
		||||
- **One device mapped, one unknown**
 | 
			
		||||
- **Auto-detects:** No asking needed!
 | 
			
		||||
- If you plug in the known device → "Detected: left side"
 | 
			
		||||
- If you plug in unknown device → "Detected: right side (inferred)"
 | 
			
		||||
- Result: Completes mapping automatically
 | 
			
		||||
 | 
			
		||||
### 🔴 Complete Mapping (Both Sides Known)
 | 
			
		||||
- **Both devices are mapped**
 | 
			
		||||
- **Auto-detects:** Fully automatic!
 | 
			
		||||
- Plug in any side → "Detected: left side" or "Detected: right side"
 | 
			
		||||
- Only the two known devices are allowed
 | 
			
		||||
- Unknown device → **Rejected immediately** (safety feature)
 | 
			
		||||
- Result: Fast, automatic flashing with full protection
 | 
			
		||||
 | 
			
		||||
### Why This Matters
 | 
			
		||||
- **First run**: Asks which side (one-time setup) ✅
 | 
			
		||||
- **Normal use**: Fully automatic - just plug and flash! ✅
 | 
			
		||||
- **Protection**: Unknown devices rejected - can't flash wrong keyboard ✅
 | 
			
		||||
- **Smart**: Knows when to ask vs. when to auto-detect 🧠
 | 
			
		||||
 | 
			
		||||
## 📝 Additional Notes
 | 
			
		||||
 | 
			
		||||
- **Input timing**: You're asked which side BEFORE entering bootloader (so you can still type!)
 | 
			
		||||
- **EEPROM wipe**: Liatris overwrites EEPROM on flash, so we use HOST USB info
 | 
			
		||||
- **Board-ID**: INFO_UF2.TXT is NOT unique per device, don't rely on it
 | 
			
		||||
- **USB serial**: Burned into RP2040 chip, persists even when EEPROM wiped
 | 
			
		||||
 | 
			
		||||
## 🆘 Support
 | 
			
		||||
 | 
			
		||||
Issues? Check:
 | 
			
		||||
1. Run test scripts to isolate the problem
 | 
			
		||||
2. Check device detection with `print_device_info()`
 | 
			
		||||
3. Verify mappings with `list_all_mappings()`
 | 
			
		||||
4. Test QMK commands directly: `qmk compile -kb ... -km ...`
 | 
			
		||||
 | 
			
		||||
## 📄 License
 | 
			
		||||
 | 
			
		||||
Same as your QMK userspace configuration.
 | 
			
		||||
							
								
								
									
										164
									
								
								qmk_flash_tools/SOLUTION_SUMMARY.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										164
									
								
								qmk_flash_tools/SOLUTION_SUMMARY.md
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,164 @@
 | 
			
		|||
# Solution Summary: Ask BEFORE Bootloader
 | 
			
		||||
 | 
			
		||||
## The Problem You Identified 🎯
 | 
			
		||||
 | 
			
		||||
> "Once I enter the bootloader I can't use the device - so I need to input the device-side beforehand."
 | 
			
		||||
 | 
			
		||||
**Absolutely correct!** This is a critical UX issue that the original design missed.
 | 
			
		||||
 | 
			
		||||
## The Solution ✅
 | 
			
		||||
 | 
			
		||||
### New Workflow Order:
 | 
			
		||||
 | 
			
		||||
1. **ASK which side** (while keyboard still works!)
 | 
			
		||||
2. **WAIT for bootloader** (user enters bootloader mode)
 | 
			
		||||
3. **DETECT device** (get USB serial from host)
 | 
			
		||||
4. **VERIFY match** (does device match expected side?)
 | 
			
		||||
5. **FLASH** (with correct handedness)
 | 
			
		||||
 | 
			
		||||
### Code Changes Made:
 | 
			
		||||
 | 
			
		||||
#### 1. New Function: `detect_side_with_expected()`
 | 
			
		||||
**Location:** `lib/side_mapping.sh`
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
detect_side_with_expected(device_id, expected_side)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
This function:
 | 
			
		||||
- Takes the **expected** side (what user said)
 | 
			
		||||
- Checks if device is in mappings
 | 
			
		||||
- **First time:** Saves device as expected side ✅
 | 
			
		||||
- **Matches:** Continues automatically ✅
 | 
			
		||||
- **Mismatch:** Gives user options ⚠️
 | 
			
		||||
 | 
			
		||||
#### 2. Mismatch Handling
 | 
			
		||||
 | 
			
		||||
When device doesn't match expected:
 | 
			
		||||
```
 | 
			
		||||
⚠️  WARNING: SIDE MISMATCH DETECTED
 | 
			
		||||
 | 
			
		||||
Expected: left side
 | 
			
		||||
Saved mapping says: right side
 | 
			
		||||
 | 
			
		||||
What would you like to do?
 | 
			
		||||
  [e] Exit safely (recommended)
 | 
			
		||||
  [c] Clear this mapping and save as left
 | 
			
		||||
  [f] Force flash as left anyway (DANGEROUS)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 3. Updated Main Script
 | 
			
		||||
**Location:** `autoflash_modular.sh`
 | 
			
		||||
 | 
			
		||||
New function:
 | 
			
		||||
```bash
 | 
			
		||||
flash_side_with_verification(expected_side)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Changes to workflow:
 | 
			
		||||
```bash
 | 
			
		||||
# OLD: Asked AFTER device detected
 | 
			
		||||
flash_side_auto()  # User can't type in bootloader!
 | 
			
		||||
 | 
			
		||||
# NEW: Ask BEFORE device connected
 | 
			
		||||
read -p "Which side will you flash first? [left/right]:"
 | 
			
		||||
flash_side_with_verification("left")  # ✅ Can type!
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Complete Flow Example 📋
 | 
			
		||||
 | 
			
		||||
### First Run:
 | 
			
		||||
```bash
 | 
			
		||||
$ ./autoflash_modular.sh
 | 
			
		||||
 | 
			
		||||
Which side will you flash first? [left/right]: left  ← Can type!
 | 
			
		||||
 | 
			
		||||
Please enter bootloader on the left half:
 | 
			
		||||
Press Enter when ready...
 | 
			
		||||
[User enters bootloader, presses Enter]
 | 
			
		||||
 | 
			
		||||
Waiting for device...
 | 
			
		||||
Found device: serial:ABC123
 | 
			
		||||
New device detected (first time setup)
 | 
			
		||||
✅ Saving as left side
 | 
			
		||||
 | 
			
		||||
Flashing: left side
 | 
			
		||||
✅ left side flashed successfully!
 | 
			
		||||
 | 
			
		||||
Flash the right side now? [y/n]: y
 | 
			
		||||
 | 
			
		||||
Please enter bootloader on the right half:
 | 
			
		||||
Press Enter when ready...
 | 
			
		||||
[User enters bootloader, presses Enter]
 | 
			
		||||
 | 
			
		||||
Waiting for device...
 | 
			
		||||
Found device: serial:XYZ789
 | 
			
		||||
New device detected (first time setup)
 | 
			
		||||
✅ Saving as right side
 | 
			
		||||
 | 
			
		||||
Flashing: right side
 | 
			
		||||
✅ right side flashed successfully!
 | 
			
		||||
 | 
			
		||||
🎉 Flashing complete!
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Subsequent Runs (Auto-verified):
 | 
			
		||||
```bash
 | 
			
		||||
$ ./autoflash_modular.sh
 | 
			
		||||
 | 
			
		||||
Which side will you flash first? [left/right]: left
 | 
			
		||||
 | 
			
		||||
Please enter bootloader on the left half:
 | 
			
		||||
[User enters bootloader]
 | 
			
		||||
 | 
			
		||||
Found device: serial:ABC123
 | 
			
		||||
✅ Confirmed: This is the left side (matches saved mapping)
 | 
			
		||||
 | 
			
		||||
Flashing: left side
 | 
			
		||||
✅ Done!
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Mismatch Scenario:
 | 
			
		||||
```bash
 | 
			
		||||
Which side will you flash first? [left/right]: left
 | 
			
		||||
 | 
			
		||||
[User accidentally plugs in RIGHT keyboard]
 | 
			
		||||
 | 
			
		||||
Found device: serial:XYZ789
 | 
			
		||||
⚠️  WARNING: SIDE MISMATCH DETECTED
 | 
			
		||||
 | 
			
		||||
Expected: left side
 | 
			
		||||
Saved mapping says: right side
 | 
			
		||||
 | 
			
		||||
Your choice [e/c/f]: e
 | 
			
		||||
 | 
			
		||||
❌ Exiting safely. Please plug in the correct keyboard half.
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Why This Works 🎯
 | 
			
		||||
 | 
			
		||||
✅ **User can type** - Asked before bootloader  
 | 
			
		||||
✅ **Safety first** - Verifies device matches  
 | 
			
		||||
✅ **Clear errors** - Obvious when wrong side plugged in  
 | 
			
		||||
✅ **Flexible** - Can update mappings if needed  
 | 
			
		||||
✅ **Persistent** - Remembers devices forever  
 | 
			
		||||
 | 
			
		||||
## Files Modified
 | 
			
		||||
 | 
			
		||||
- `lib/side_mapping.sh` - Added `detect_side_with_expected()`
 | 
			
		||||
- `autoflash_modular.sh` - Changed to ask first, then verify
 | 
			
		||||
- `README.md` - Updated workflow documentation
 | 
			
		||||
- `WORKFLOW.md` - New visual flow diagrams
 | 
			
		||||
 | 
			
		||||
## Testing
 | 
			
		||||
 | 
			
		||||
Test the new flow:
 | 
			
		||||
```bash
 | 
			
		||||
# Test mismatch handling
 | 
			
		||||
./test/test_side_mapping.sh
 | 
			
		||||
 | 
			
		||||
# Test full workflow (needs hardware)
 | 
			
		||||
./autoflash_modular.sh
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Your insight was **absolutely correct** - asking before bootloader is the only way to make this work! 🎉
 | 
			
		||||
							
								
								
									
										190
									
								
								qmk_flash_tools/UX_IMPROVEMENT.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								qmk_flash_tools/UX_IMPROVEMENT.md
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,190 @@
 | 
			
		|||
# User Experience Comparison
 | 
			
		||||
 | 
			
		||||
## Old Behavior (Always Ask)
 | 
			
		||||
```
 | 
			
		||||
Run 1 (First time):
 | 
			
		||||
───────────────────
 | 
			
		||||
Script: "Which side first? [left/right]:"
 | 
			
		||||
You: "left"
 | 
			
		||||
[Flash left]
 | 
			
		||||
Script: "Flash right? [y/n]:"
 | 
			
		||||
You: "y"
 | 
			
		||||
[Flash right]
 | 
			
		||||
 | 
			
		||||
Run 2 (Second time):
 | 
			
		||||
────────────────────
 | 
			
		||||
Script: "Which side first? [left/right]:"  ← Asked again!
 | 
			
		||||
You: "left"
 | 
			
		||||
[Flash left]
 | 
			
		||||
Script: "Flash right? [y/n]:"
 | 
			
		||||
You: "y"
 | 
			
		||||
[Flash right]
 | 
			
		||||
 | 
			
		||||
Run 3, 4, 5...
 | 
			
		||||
──────────────
 | 
			
		||||
Same thing every time - always asking! 😕
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## New Behavior (Smart Auto-Detect)
 | 
			
		||||
```
 | 
			
		||||
Run 1 (First time - Empty mapping):
 | 
			
		||||
────────────────────────────────────
 | 
			
		||||
Script: "Which side first? [left/right]:"  ← Only asked first time
 | 
			
		||||
You: "left"
 | 
			
		||||
[Flash left]
 | 
			
		||||
Script: "Flash other side? [y/n]:"
 | 
			
		||||
You: "y"
 | 
			
		||||
[Flash right]
 | 
			
		||||
 | 
			
		||||
Run 2 (Subsequent - Complete mapping):
 | 
			
		||||
───────────────────────────────────────
 | 
			
		||||
Script: "Plug in a keyboard half and enter bootloader..."
 | 
			
		||||
You: [Plugs in left half]
 | 
			
		||||
Script: "🎯 Auto-detected: left side"  ← No asking!
 | 
			
		||||
[Flash left]
 | 
			
		||||
Script: "Flash other side? [y/n]:"
 | 
			
		||||
You: "y"
 | 
			
		||||
You: [Plugs in right half]
 | 
			
		||||
Script: "🎯 Auto-detected: right side"  ← No asking!
 | 
			
		||||
[Flash right]
 | 
			
		||||
 | 
			
		||||
Run 3, 4, 5...
 | 
			
		||||
──────────────
 | 
			
		||||
Always auto-detects - never asks again! 🎉
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Workflow Comparison
 | 
			
		||||
 | 
			
		||||
### First Time Setup (Empty → Complete)
 | 
			
		||||
 | 
			
		||||
**Old:**
 | 
			
		||||
```
 | 
			
		||||
Ask → Flash left → Ask → Flash right
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
**New:**
 | 
			
		||||
```
 | 
			
		||||
Ask once → Flash left → Flash right (inferred)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Normal Use (Complete mapping)
 | 
			
		||||
 | 
			
		||||
**Old:**
 | 
			
		||||
```
 | 
			
		||||
Ask every time → Flash
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
**New:**
 | 
			
		||||
```
 | 
			
		||||
Auto-detect → Flash  ← No asking!
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Decision Logic
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
┌─────────────────────┐
 | 
			
		||||
│ Check mapping state │
 | 
			
		||||
└──────────┬──────────┘
 | 
			
		||||
           │
 | 
			
		||||
     ┌─────┴──────┐
 | 
			
		||||
     │            │
 | 
			
		||||
     ▼            ▼
 | 
			
		||||
   Empty      Partial/Complete
 | 
			
		||||
     │            │
 | 
			
		||||
     ▼            ▼
 | 
			
		||||
   📝 ASK      🎯 AUTO-DETECT
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## User Benefits
 | 
			
		||||
 | 
			
		||||
### Empty Mapping (First Time)
 | 
			
		||||
✅ **Asks** - Needs to learn your devices  
 | 
			
		||||
✅ **Simple** - Just answer "left" or "right"  
 | 
			
		||||
✅ **One-time** - Only happens once
 | 
			
		||||
 | 
			
		||||
### Partial Mapping (Learning Second Device)
 | 
			
		||||
✅ **Smart** - Knows which device is which  
 | 
			
		||||
✅ **Infers** - Unknown must be the unmapped side  
 | 
			
		||||
✅ **Automatic** - No asking needed
 | 
			
		||||
 | 
			
		||||
### Complete Mapping (Normal Use)
 | 
			
		||||
✅ **Instant** - Recognizes device immediately  
 | 
			
		||||
✅ **No questions** - Fully automatic  
 | 
			
		||||
✅ **Protected** - Rejects unknown devices
 | 
			
		||||
 | 
			
		||||
## Key Insight
 | 
			
		||||
 | 
			
		||||
**User knows their hardware!**
 | 
			
		||||
- If they replaced a controller → They know to clear mappings
 | 
			
		||||
- If mapping exists → Trust it and auto-detect
 | 
			
		||||
- Only ask when necessary (empty mapping)
 | 
			
		||||
 | 
			
		||||
## Example Sessions
 | 
			
		||||
 | 
			
		||||
### Session 1: Brand New Setup
 | 
			
		||||
```
 | 
			
		||||
$ ./autoflash_modular.sh
 | 
			
		||||
 | 
			
		||||
📝 First time setup - learning your keyboard halves
 | 
			
		||||
 | 
			
		||||
Which side will you flash first? [left/right]: left
 | 
			
		||||
 | 
			
		||||
Plug in left half and enter bootloader...
 | 
			
		||||
✅ Saved as left side
 | 
			
		||||
✅ left side flashed!
 | 
			
		||||
 | 
			
		||||
Flash the right side now? [y/n]: y
 | 
			
		||||
 | 
			
		||||
Plug in right half and enter bootloader...
 | 
			
		||||
✅ Saved as right side (completes mapping)
 | 
			
		||||
✅ right side flashed!
 | 
			
		||||
 | 
			
		||||
🎉 Complete! Mapping saved.
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Session 2: Normal Reflashing
 | 
			
		||||
```
 | 
			
		||||
$ ./autoflash_modular.sh
 | 
			
		||||
 | 
			
		||||
✅ Device mapping exists - auto-detection enabled
 | 
			
		||||
 | 
			
		||||
Plug in a keyboard half and enter bootloader...
 | 
			
		||||
🎯 Auto-detected: left side (known device)
 | 
			
		||||
✅ left side flashed!
 | 
			
		||||
 | 
			
		||||
Flash the other keyboard half now? [y/n]: y
 | 
			
		||||
 | 
			
		||||
Plug in other half and enter bootloader...
 | 
			
		||||
🎯 Auto-detected: right side (known device)
 | 
			
		||||
✅ right side flashed!
 | 
			
		||||
 | 
			
		||||
🎉 Done!
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Session 3: One Side Only
 | 
			
		||||
```
 | 
			
		||||
$ ./autoflash_modular.sh
 | 
			
		||||
 | 
			
		||||
✅ Device mapping exists - auto-detection enabled
 | 
			
		||||
 | 
			
		||||
Plug in a keyboard half and enter bootloader...
 | 
			
		||||
🎯 Auto-detected: right side (known device)
 | 
			
		||||
✅ right side flashed!
 | 
			
		||||
 | 
			
		||||
Flash the other keyboard half now? [y/n]: n
 | 
			
		||||
 | 
			
		||||
✅ Done!
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Summary
 | 
			
		||||
 | 
			
		||||
| Scenario | Old Behavior | New Behavior |
 | 
			
		||||
|----------|-------------|--------------|
 | 
			
		||||
| First time | Ask | Ask (once) |
 | 
			
		||||
| Second time | Ask | Auto-detect |
 | 
			
		||||
| Every time after | Ask | Auto-detect |
 | 
			
		||||
| User input needed | Always | Only first run |
 | 
			
		||||
| Speed | Slow | Fast |
 | 
			
		||||
| Convenience | Low | High |
 | 
			
		||||
 | 
			
		||||
**Result:** Ask once, auto-detect forever! 🚀
 | 
			
		||||
							
								
								
									
										200
									
								
								qmk_flash_tools/WORKFLOW.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										200
									
								
								qmk_flash_tools/WORKFLOW.md
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,200 @@
 | 
			
		|||
# Workflow Diagrams
 | 
			
		||||
 | 
			
		||||
## Standard Flashing Flow
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
┌────────────────────────────────────────────────────────────┐
 | 
			
		||||
│ START: Run ./autoflash_modular.sh                         │
 | 
			
		||||
└───────────────────┬────────────────────────────────────────┘
 | 
			
		||||
                    │
 | 
			
		||||
                    ▼
 | 
			
		||||
┌────────────────────────────────────────────────────────────┐
 | 
			
		||||
│ Build firmware once                                        │
 | 
			
		||||
└───────────────────┬────────────────────────────────────────┘
 | 
			
		||||
                    │
 | 
			
		||||
                    ▼
 | 
			
		||||
┌────────────────────────────────────────────────────────────┐
 | 
			
		||||
│ PROMPT: "Which side will you flash first? [left/right]:"  │
 | 
			
		||||
│ USER INPUT: "left" ◄── Can type! Not in bootloader yet    │
 | 
			
		||||
└───────────────────┬────────────────────────────────────────┘
 | 
			
		||||
                    │
 | 
			
		||||
                    ▼
 | 
			
		||||
┌────────────────────────────────────────────────────────────┐
 | 
			
		||||
│ Script: "Enter bootloader mode on the LEFT half..."       │
 | 
			
		||||
│ Script: "Press Enter when ready..."                       │
 | 
			
		||||
│ USER: Double-taps RESET on LEFT keyboard                  │
 | 
			
		||||
│ USER: Presses Enter                                        │
 | 
			
		||||
└───────────────────┬────────────────────────────────────────┘
 | 
			
		||||
                    │
 | 
			
		||||
                    ▼
 | 
			
		||||
┌────────────────────────────────────────────────────────────┐
 | 
			
		||||
│ Script: Waiting for RP2040 device...                      │
 | 
			
		||||
│ Script: Found device at /media/user/RPI-RP2               │
 | 
			
		||||
└───────────────────┬────────────────────────────────────────┘
 | 
			
		||||
                    │
 | 
			
		||||
                    ▼
 | 
			
		||||
┌────────────────────────────────────────────────────────────┐
 | 
			
		||||
│ Script: Get device identifier (e.g., serial:ABC123)       │
 | 
			
		||||
└───────────────────┬────────────────────────────────────────┘
 | 
			
		||||
                    │
 | 
			
		||||
                    ▼
 | 
			
		||||
         ┌──────────┴───────────┐
 | 
			
		||||
         │                      │
 | 
			
		||||
         ▼                      ▼
 | 
			
		||||
    ┌─────────┐         ┌──────────────┐
 | 
			
		||||
    │ First   │         │ Subsequent   │
 | 
			
		||||
    │ Time    │         │ Run          │
 | 
			
		||||
    └────┬────┘         └──────┬───────┘
 | 
			
		||||
         │                     │
 | 
			
		||||
         │                     ▼
 | 
			
		||||
         │              ┌──────────────────────────────┐
 | 
			
		||||
         │              │ Device in mappings?          │
 | 
			
		||||
         │              └────┬─────────────────┬───────┘
 | 
			
		||||
         │                   │                 │
 | 
			
		||||
         │                   ▼                 ▼
 | 
			
		||||
         │            ┌────────────┐    ┌────────────┐
 | 
			
		||||
         │            │ Matches?   │    │ Mismatch!  │
 | 
			
		||||
         │            │ ✅ Yes     │    │ ⚠️  No     │
 | 
			
		||||
         │            └─────┬──────┘    └─────┬──────┘
 | 
			
		||||
         │                  │                  │
 | 
			
		||||
         ▼                  │                  │
 | 
			
		||||
┌────────────────────┐      │                  │
 | 
			
		||||
│ Save as LEFT       │      │                  │
 | 
			
		||||
└────────┬───────────┘      │                  │
 | 
			
		||||
         │                  │                  │
 | 
			
		||||
         │                  ▼                  ▼
 | 
			
		||||
         │           ┌────────────┐    ┌─────────────────────┐
 | 
			
		||||
         │           │ Continue   │    │ PROMPT USER:        │
 | 
			
		||||
         │           │ flashing   │    │ [e] Exit            │
 | 
			
		||||
         │           └─────┬──────┘    │ [c] Clear & update  │
 | 
			
		||||
         │                 │           │ [f] Force flash     │
 | 
			
		||||
         │                 │           └──────┬──────────────┘
 | 
			
		||||
         │                 │                  │
 | 
			
		||||
         └─────────────────┴──────────────────┘
 | 
			
		||||
                           │
 | 
			
		||||
                           ▼
 | 
			
		||||
┌────────────────────────────────────────────────────────────┐
 | 
			
		||||
│ Flash LEFT side with uf2-split-left                        │
 | 
			
		||||
└───────────────────┬────────────────────────────────────────┘
 | 
			
		||||
                    │
 | 
			
		||||
                    ▼
 | 
			
		||||
┌────────────────────────────────────────────────────────────┐
 | 
			
		||||
│ ✅ LEFT side flashed successfully!                         │
 | 
			
		||||
└───────────────────┬────────────────────────────────────────┘
 | 
			
		||||
                    │
 | 
			
		||||
                    ▼
 | 
			
		||||
┌────────────────────────────────────────────────────────────┐
 | 
			
		||||
│ PROMPT: "Flash the RIGHT side now? [y/n]:"                │
 | 
			
		||||
└───────────────────┬────────────────────────────────────────┘
 | 
			
		||||
                    │
 | 
			
		||||
                    ▼
 | 
			
		||||
         ┌──────────┴───────────┐
 | 
			
		||||
         │                      │
 | 
			
		||||
         ▼                      ▼
 | 
			
		||||
    ┌─────────┐           ┌──────────┐
 | 
			
		||||
    │ Yes     │           │ No       │
 | 
			
		||||
    └────┬────┘           └────┬─────┘
 | 
			
		||||
         │                     │
 | 
			
		||||
         │                     ▼
 | 
			
		||||
         │              ┌────────────┐
 | 
			
		||||
         │              │ DONE       │
 | 
			
		||||
         │              └────────────┘
 | 
			
		||||
         │
 | 
			
		||||
         ▼
 | 
			
		||||
    (Repeat process for RIGHT side)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Mismatch Handling Detail
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
┌────────────────────────────────────────────────────────────┐
 | 
			
		||||
│ USER said: "left"                                          │
 | 
			
		||||
│ Device detected: serial:ABC123                             │
 | 
			
		||||
│ Mapping says: "right"                                      │
 | 
			
		||||
└───────────────────┬────────────────────────────────────────┘
 | 
			
		||||
                    │
 | 
			
		||||
                    ▼
 | 
			
		||||
┌────────────────────────────────────────────────────────────┐
 | 
			
		||||
│ ⚠️  WARNING: SIDE MISMATCH DETECTED                        │
 | 
			
		||||
│                                                            │
 | 
			
		||||
│ Expected: left side                                        │
 | 
			
		||||
│ Saved mapping says: right side                             │
 | 
			
		||||
│                                                            │
 | 
			
		||||
│ This means either:                                         │
 | 
			
		||||
│   1. You plugged in the WRONG keyboard half                │
 | 
			
		||||
│   2. The saved mapping is incorrect                        │
 | 
			
		||||
│                                                            │
 | 
			
		||||
│ What would you like to do?                                │
 | 
			
		||||
│   [e] Exit safely (recommended)                            │
 | 
			
		||||
│   [c] Clear this mapping and save as left                  │
 | 
			
		||||
│   [f] Force flash as left anyway (DANGEROUS)               │
 | 
			
		||||
└───────────────────┬────────────────────────────────────────┘
 | 
			
		||||
                    │
 | 
			
		||||
         ┌──────────┼──────────┐
 | 
			
		||||
         │          │          │
 | 
			
		||||
         ▼          ▼          ▼
 | 
			
		||||
    ┌────────┐ ┌────────┐ ┌──────────────┐
 | 
			
		||||
    │ [e]    │ │ [c]    │ │ [f]          │
 | 
			
		||||
    │ Exit   │ │ Clear  │ │ Force        │
 | 
			
		||||
    └───┬────┘ └───┬────┘ └──────┬───────┘
 | 
			
		||||
        │          │              │
 | 
			
		||||
        │          │              ▼
 | 
			
		||||
        │          │       ┌──────────────────┐
 | 
			
		||||
        │          │       │ "Are you sure?"  │
 | 
			
		||||
        │          │       │ [yes/no]         │
 | 
			
		||||
        │          │       └────┬─────────────┘
 | 
			
		||||
        │          │            │
 | 
			
		||||
        │          │     ┌──────┴──────┐
 | 
			
		||||
        │          │     │             │
 | 
			
		||||
        │          │     ▼             ▼
 | 
			
		||||
        │          │  ┌────┐      ┌────────┐
 | 
			
		||||
        │          │  │yes │      │no      │
 | 
			
		||||
        │          │  └─┬──┘      └───┬────┘
 | 
			
		||||
        │          │    │             │
 | 
			
		||||
        │          ▼    ▼             │
 | 
			
		||||
        │      ┌───────────┐          │
 | 
			
		||||
        │      │ Update    │          │
 | 
			
		||||
        │      │ mapping & │          │
 | 
			
		||||
        │      │ continue  │          │
 | 
			
		||||
        │      └─────┬─────┘          │
 | 
			
		||||
        │            │                │
 | 
			
		||||
        └────────────┴────────────────┘
 | 
			
		||||
                     │
 | 
			
		||||
                     ▼
 | 
			
		||||
              ┌────────────┐
 | 
			
		||||
              │ Exit 1     │
 | 
			
		||||
              └────────────┘
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## First Time Setup
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
Run 1: First Device
 | 
			
		||||
───────────────────
 | 
			
		||||
User: "left"
 | 
			
		||||
Device: serial:ABC123
 | 
			
		||||
Mapping: {} (empty)
 | 
			
		||||
Result: ✅ Save serial:ABC123 → left
 | 
			
		||||
 | 
			
		||||
Run 1: Second Device
 | 
			
		||||
────────────────────
 | 
			
		||||
User: "right"
 | 
			
		||||
Device: serial:XYZ789
 | 
			
		||||
Mapping: {"serial:ABC123": "left"}
 | 
			
		||||
Result: ✅ Save serial:XYZ789 → right
 | 
			
		||||
 | 
			
		||||
Run 2+: Known Devices
 | 
			
		||||
─────────────────────
 | 
			
		||||
User: "left"
 | 
			
		||||
Device: serial:ABC123
 | 
			
		||||
Mapping: {"serial:ABC123": "left", "serial:XYZ789": "right"}
 | 
			
		||||
Result: ✅ Match! Continue automatically
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Why This Flow Works
 | 
			
		||||
 | 
			
		||||
1. **User can type** - Asked BEFORE entering bootloader
 | 
			
		||||
2. **Safety first** - Verifies device matches expectation
 | 
			
		||||
3. **Clear on mismatch** - User knows exactly what's wrong
 | 
			
		||||
4. **Flexible** - Can update mappings or exit safely
 | 
			
		||||
5. **Persistent** - Only asks once, remembers forever
 | 
			
		||||
							
								
								
									
										319
									
								
								qmk_flash_tools/autoflash_modular.sh
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										319
									
								
								qmk_flash_tools/autoflash_modular.sh
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,319 @@
 | 
			
		|||
#!/usr/bin/env bash
 | 
			
		||||
# =============================================================================
 | 
			
		||||
# QMK Auto-Flashing Script (Modular Version)
 | 
			
		||||
# =============================================================================
 | 
			
		||||
# For: Fingerpunch/Sweeeeep with Liatris (RP2040)
 | 
			
		||||
#
 | 
			
		||||
# Features:
 | 
			
		||||
#   - Intelligent device detection with three-state verification
 | 
			
		||||
#   - Learns devices on first run, verifies on subsequent runs
 | 
			
		||||
#   - Rejects unknown devices once mapping is complete (safety)
 | 
			
		||||
#   - Uses HOST USB information (reliable even when EEPROM is wiped)
 | 
			
		||||
#   - Modular design with separate testable libraries
 | 
			
		||||
# =============================================================================
 | 
			
		||||
 | 
			
		||||
set -euo pipefail
 | 
			
		||||
 | 
			
		||||
# Get the directory where this script is located
 | 
			
		||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
 | 
			
		||||
LIB_DIR="$SCRIPT_DIR/lib"
 | 
			
		||||
 | 
			
		||||
# Configuration
 | 
			
		||||
KEYBOARD="fingerpunch/sweeeeep"
 | 
			
		||||
KEYMAP="smathev"
 | 
			
		||||
export USB_MOUNT_PATHS=("/media/$USER" "/run/media/$USER" "/mnt")
 | 
			
		||||
export RP2040_PATTERN="*RP2040*"
 | 
			
		||||
export USB_WAIT_INTERVAL=0.5
 | 
			
		||||
export SIDE_MAPPING_FILE="$SCRIPT_DIR/device_mappings.json"
 | 
			
		||||
 | 
			
		||||
# Source library modules
 | 
			
		||||
source "$LIB_DIR/device_detection.sh"
 | 
			
		||||
source "$LIB_DIR/side_mapping.sh"
 | 
			
		||||
source "$LIB_DIR/qmk_helpers.sh"
 | 
			
		||||
 | 
			
		||||
# ----------------------
 | 
			
		||||
# Function: flash_side_with_verification
 | 
			
		||||
# Ask user which side they're flashing, then verify device matches
 | 
			
		||||
# ----------------------
 | 
			
		||||
flash_side_with_verification() {
 | 
			
		||||
    local expected_side="$1"
 | 
			
		||||
 | 
			
		||||
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
    echo "<EFBFBD> Preparing to flash: $expected_side side"
 | 
			
		||||
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
    echo ""
 | 
			
		||||
    echo "Please enter bootloader mode on the $expected_side half:"
 | 
			
		||||
    echo "  • Double-tap RESET button on Liatris controller"
 | 
			
		||||
    echo "  • Wait for RPI-RP2 drive to appear"
 | 
			
		||||
    echo ""
 | 
			
		||||
    read -rp "Press Enter when you've entered bootloader mode..."
 | 
			
		||||
    echo ""
 | 
			
		||||
    echo "⏳ Waiting for device..."
 | 
			
		||||
 | 
			
		||||
    # Wait for device to be mounted
 | 
			
		||||
    local mount_point
 | 
			
		||||
    mount_point=$(wait_for_rp2040)
 | 
			
		||||
 | 
			
		||||
    echo ""
 | 
			
		||||
 | 
			
		||||
    # Get unique device identifier from HOST
 | 
			
		||||
    local device_id
 | 
			
		||||
    device_id=$(get_device_identifier "$mount_point")
 | 
			
		||||
 | 
			
		||||
    # Verify device matches expected side
 | 
			
		||||
    local verified_side
 | 
			
		||||
    verified_side=$(detect_side_with_expected "$device_id" "$expected_side")
 | 
			
		||||
 | 
			
		||||
    echo ""
 | 
			
		||||
    echo "🎯 Flashing: $verified_side side"
 | 
			
		||||
    echo ""
 | 
			
		||||
 | 
			
		||||
    # Flash using the verified side
 | 
			
		||||
    if flash_side "$KEYBOARD" "$KEYMAP" "$verified_side"; then
 | 
			
		||||
        echo ""
 | 
			
		||||
        echo "✅ $verified_side side flashed successfully!"
 | 
			
		||||
        echo ""
 | 
			
		||||
        return 0
 | 
			
		||||
    else
 | 
			
		||||
        echo ""
 | 
			
		||||
        echo "❌ Failed to flash $verified_side side"
 | 
			
		||||
        echo ""
 | 
			
		||||
        return 1
 | 
			
		||||
    fi
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# ----------------------
 | 
			
		||||
# Function: flash_side_auto_detect
 | 
			
		||||
# Auto-detect which side is plugged in (no asking)
 | 
			
		||||
# Used when mapping exists (partial or complete)
 | 
			
		||||
# ----------------------
 | 
			
		||||
flash_side_auto_detect() {
 | 
			
		||||
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
    echo "🔌 Plug in a keyboard half and enter bootloader mode"
 | 
			
		||||
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
    echo ""
 | 
			
		||||
    echo "Instructions:"
 | 
			
		||||
    echo "  • Plug in the keyboard half you want to flash"
 | 
			
		||||
    echo "  • Double-tap RESET button on Liatris controller"
 | 
			
		||||
    echo "  • Wait for RPI-RP2 drive to appear"
 | 
			
		||||
    echo ""
 | 
			
		||||
    read -rp "Press Enter when you've entered bootloader mode..."
 | 
			
		||||
    echo ""
 | 
			
		||||
    echo "⏳ Waiting for device..."
 | 
			
		||||
 | 
			
		||||
    # Wait for device to be mounted
 | 
			
		||||
    local mount_point
 | 
			
		||||
    mount_point=$(wait_for_rp2040)
 | 
			
		||||
 | 
			
		||||
    echo ""
 | 
			
		||||
 | 
			
		||||
    # Get unique device identifier from HOST
 | 
			
		||||
    local device_id
 | 
			
		||||
    device_id=$(get_device_identifier "$mount_point")
 | 
			
		||||
 | 
			
		||||
    # Get saved side (if exists)
 | 
			
		||||
    local saved_side
 | 
			
		||||
    saved_side=$(get_saved_side "$device_id" 2>/dev/null)
 | 
			
		||||
 | 
			
		||||
    local detected_side
 | 
			
		||||
 | 
			
		||||
    if [[ -n "$saved_side" ]]; then
 | 
			
		||||
        # Device is known - use saved mapping
 | 
			
		||||
        echo "   🎯 Auto-detected: $saved_side side (known device)" >&2
 | 
			
		||||
        detected_side="$saved_side"
 | 
			
		||||
    else
 | 
			
		||||
        # Device is unknown - infer from mapping state
 | 
			
		||||
        local mapping_state
 | 
			
		||||
        mapping_state=$(get_mapping_state)
 | 
			
		||||
 | 
			
		||||
        if [[ "$mapping_state" == "partial" ]]; then
 | 
			
		||||
            # Partial mapping - this must be the unmapped side
 | 
			
		||||
            local unmapped_side
 | 
			
		||||
            unmapped_side=$(get_unmapped_side)
 | 
			
		||||
            echo "   🎯 Auto-detected: $unmapped_side side (inferred from partial mapping)" >&2
 | 
			
		||||
            detected_side="$unmapped_side"
 | 
			
		||||
 | 
			
		||||
            # Save this device
 | 
			
		||||
            echo "   📝 Saving device mapping..." >&2
 | 
			
		||||
            save_side_mapping "$device_id" "$detected_side"
 | 
			
		||||
        else
 | 
			
		||||
            # Complete mapping but unknown device - this should error
 | 
			
		||||
            echo "" >&2
 | 
			
		||||
            echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
 | 
			
		||||
            echo "❌ ERROR: UNKNOWN DEVICE (COMPLETE MAPPING)" >&2
 | 
			
		||||
            echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
 | 
			
		||||
            echo "" >&2
 | 
			
		||||
            echo "Detected device: $device_id" >&2
 | 
			
		||||
            echo "Expected one of the known devices:" >&2
 | 
			
		||||
            get_mapped_devices | tr ' ' '\n' | while read dev; do
 | 
			
		||||
                local side=$(get_saved_side "$dev")
 | 
			
		||||
                echo "  - $dev ($side side)" >&2
 | 
			
		||||
            done
 | 
			
		||||
            echo "" >&2
 | 
			
		||||
            echo "Both sides are already mapped!" >&2
 | 
			
		||||
            echo "If you replaced a controller, clear mappings:" >&2
 | 
			
		||||
            echo "  rm $SIDE_MAPPING_FILE" >&2
 | 
			
		||||
            return 1
 | 
			
		||||
        fi
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    echo ""
 | 
			
		||||
    echo "🎯 Flashing: $detected_side side"
 | 
			
		||||
    echo ""
 | 
			
		||||
 | 
			
		||||
    # Flash using the detected side
 | 
			
		||||
    if flash_side "$KEYBOARD" "$KEYMAP" "$detected_side"; then
 | 
			
		||||
        echo ""
 | 
			
		||||
        echo "✅ $detected_side side flashed successfully!"
 | 
			
		||||
        echo ""
 | 
			
		||||
        return 0
 | 
			
		||||
    else
 | 
			
		||||
        echo ""
 | 
			
		||||
        echo "❌ Failed to flash $detected_side side"
 | 
			
		||||
        echo ""
 | 
			
		||||
        return 1
 | 
			
		||||
    fi
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# ----------------------
 | 
			
		||||
# Function: main
 | 
			
		||||
# Main workflow
 | 
			
		||||
# ----------------------
 | 
			
		||||
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 ""
 | 
			
		||||
 | 
			
		||||
    # Check dependencies
 | 
			
		||||
    if ! check_qmk_installed; then
 | 
			
		||||
        exit 1
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    if ! check_jq_installed; then
 | 
			
		||||
        exit 1
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    # Initialize mapping file
 | 
			
		||||
    init_mapping_file
 | 
			
		||||
 | 
			
		||||
    # Build firmware once
 | 
			
		||||
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
    if ! build_firmware "$KEYBOARD" "$KEYMAP"; then
 | 
			
		||||
        echo "❌ Build failed. Exiting."
 | 
			
		||||
        exit 1
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
    echo "🚀 Ready to flash!"
 | 
			
		||||
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
    echo ""
 | 
			
		||||
 | 
			
		||||
    # Check mapping state to determine if we need to ask
 | 
			
		||||
    local mapping_state
 | 
			
		||||
    mapping_state=$(get_mapping_state)
 | 
			
		||||
 | 
			
		||||
    # -----------------------------------------------------------------
 | 
			
		||||
    # EMPTY MAPPING: Must ask user which side (learning mode)
 | 
			
		||||
    # -----------------------------------------------------------------
 | 
			
		||||
    if [[ "$mapping_state" == "empty" ]]; then
 | 
			
		||||
        echo "📝 First time setup - learning your keyboard halves"
 | 
			
		||||
        echo ""
 | 
			
		||||
 | 
			
		||||
        # Ask which side to flash first (BEFORE entering bootloader)
 | 
			
		||||
        local first_side
 | 
			
		||||
        while true; do
 | 
			
		||||
            read -rp "Which side will you flash first? [left/right]: " first_side
 | 
			
		||||
            first_side=${first_side,,}
 | 
			
		||||
            if [[ "$first_side" == "left" || "$first_side" == "right" ]]; then
 | 
			
		||||
                break
 | 
			
		||||
            else
 | 
			
		||||
                echo "⚠️  Please enter 'left' or 'right'"
 | 
			
		||||
            fi
 | 
			
		||||
        done
 | 
			
		||||
        echo ""
 | 
			
		||||
 | 
			
		||||
        # Flash first side with verification
 | 
			
		||||
        if ! flash_side_with_verification "$first_side"; then
 | 
			
		||||
            echo "❌ Flashing failed. Exiting."
 | 
			
		||||
            exit 1
 | 
			
		||||
        fi
 | 
			
		||||
 | 
			
		||||
        # Determine the other side
 | 
			
		||||
        local second_side
 | 
			
		||||
        if [[ "$first_side" == "left" ]]; then
 | 
			
		||||
            second_side="right"
 | 
			
		||||
        else
 | 
			
		||||
            second_side="left"
 | 
			
		||||
        fi
 | 
			
		||||
 | 
			
		||||
        # Ask if user wants to flash the second side
 | 
			
		||||
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
        read -rp "Flash the $second_side side now? [y/n]: " DO_SECOND
 | 
			
		||||
        DO_SECOND=${DO_SECOND,,}
 | 
			
		||||
        echo ""
 | 
			
		||||
 | 
			
		||||
        if [[ "$DO_SECOND" == "y" ]]; then
 | 
			
		||||
            # Flash second side with verification
 | 
			
		||||
            if ! flash_side_with_verification "$second_side"; then
 | 
			
		||||
                echo "❌ Flashing failed. Exiting."
 | 
			
		||||
                exit 1
 | 
			
		||||
            fi
 | 
			
		||||
        fi
 | 
			
		||||
 | 
			
		||||
    # -----------------------------------------------------------------
 | 
			
		||||
    # PARTIAL or COMPLETE MAPPING: Auto-detect (no asking needed)
 | 
			
		||||
    # -----------------------------------------------------------------
 | 
			
		||||
    else
 | 
			
		||||
        echo "✅ Device mapping exists - auto-detection enabled"
 | 
			
		||||
        echo ""
 | 
			
		||||
 | 
			
		||||
        # Flash first side with auto-detection
 | 
			
		||||
        if ! flash_side_auto_detect; then
 | 
			
		||||
            echo "❌ Flashing failed. Exiting."
 | 
			
		||||
            exit 1
 | 
			
		||||
        fi
 | 
			
		||||
 | 
			
		||||
        # 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
 | 
			
		||||
            # Flash second side with auto-detection
 | 
			
		||||
            if ! flash_side_auto_detect; then
 | 
			
		||||
                echo "❌ Flashing failed. Exiting."
 | 
			
		||||
                exit 1
 | 
			
		||||
            fi
 | 
			
		||||
        fi
 | 
			
		||||
    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 "Current device mappings:"
 | 
			
		||||
    list_all_mappings
 | 
			
		||||
    echo ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Execute main
 | 
			
		||||
main "$@"
 | 
			
		||||
							
								
								
									
										187
									
								
								qmk_flash_tools/lib/device_detection.sh
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								qmk_flash_tools/lib/device_detection.sh
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,187 @@
 | 
			
		|||
#!/usr/bin/env bash
 | 
			
		||||
# =============================================================================
 | 
			
		||||
# Device Detection Library
 | 
			
		||||
# =============================================================================
 | 
			
		||||
# Functions for detecting and identifying RP2040 devices in bootloader mode
 | 
			
		||||
# Uses HOST-side USB information (serial, port location)
 | 
			
		||||
# =============================================================================
 | 
			
		||||
 | 
			
		||||
# ----------------------
 | 
			
		||||
# Function: wait_for_rp2040
 | 
			
		||||
# Wait until an RP2040 UF2 device is mounted on any of the configured paths
 | 
			
		||||
# Returns the mount point
 | 
			
		||||
# Usage: mount_point=$(wait_for_rp2040)
 | 
			
		||||
# ----------------------
 | 
			
		||||
wait_for_rp2040() {
 | 
			
		||||
    local usb_mount_paths=("${USB_MOUNT_PATHS[@]:-/media/$USER /run/media/$USER /mnt}")
 | 
			
		||||
    local rp2040_pattern="${RP2040_PATTERN:-*RP2040*}"
 | 
			
		||||
    local wait_interval="${USB_WAIT_INTERVAL:-0.5}"
 | 
			
		||||
 | 
			
		||||
    echo "⏳ Waiting for RP2040 UF2 device..." >&2
 | 
			
		||||
    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" >&2
 | 
			
		||||
                echo "$device"
 | 
			
		||||
                return 0
 | 
			
		||||
            fi
 | 
			
		||||
        done
 | 
			
		||||
        sleep "$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
 | 
			
		||||
# Usage: serial=$(get_usb_serial_from_host "/media/user/RPI-RP2")
 | 
			
		||||
# ----------------------
 | 
			
		||||
get_usb_serial_from_host() {
 | 
			
		||||
    local mount_point="$1"
 | 
			
		||||
 | 
			
		||||
    if [[ -z "$mount_point" ]]; then
 | 
			
		||||
        echo "" >&2
 | 
			
		||||
        return 1
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    # 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 0
 | 
			
		||||
            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 0
 | 
			
		||||
                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 0
 | 
			
		||||
        fi
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    # No serial found
 | 
			
		||||
    echo ""
 | 
			
		||||
    return 1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# ----------------------
 | 
			
		||||
# Function: get_usb_device_path
 | 
			
		||||
# Get a unique identifier based on USB physical port location
 | 
			
		||||
# This persists even when serial is not available
 | 
			
		||||
# Usage: usbpath=$(get_usb_device_path "/media/user/RPI-RP2")
 | 
			
		||||
# ----------------------
 | 
			
		||||
get_usb_device_path() {
 | 
			
		||||
    local mount_point="$1"
 | 
			
		||||
 | 
			
		||||
    if [[ -z "$mount_point" ]]; then
 | 
			
		||||
        echo "" >&2
 | 
			
		||||
        return 1
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    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)
 | 
			
		||||
            local usbpath=$(echo "$devpath" | grep -oP 'usb\d+/\d+-[\d.]+')
 | 
			
		||||
            if [[ -n "$usbpath" ]]; then
 | 
			
		||||
                echo "$usbpath"
 | 
			
		||||
                return 0
 | 
			
		||||
            fi
 | 
			
		||||
        fi
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    echo ""
 | 
			
		||||
    return 1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# ----------------------
 | 
			
		||||
# Function: get_device_identifier
 | 
			
		||||
# Get the best available identifier for the USB device
 | 
			
		||||
# Prefers USB serial, falls back to USB port location, then mount path
 | 
			
		||||
# Usage: device_id=$(get_device_identifier "/media/user/RPI-RP2")
 | 
			
		||||
# Returns: "serial:ABC123" or "usbpath:usb1/1-3" or "mount:RPI-RP2"
 | 
			
		||||
# ----------------------
 | 
			
		||||
get_device_identifier() {
 | 
			
		||||
    local mount_point="$1"
 | 
			
		||||
 | 
			
		||||
    if [[ -z "$mount_point" ]]; then
 | 
			
		||||
        echo "Error: mount_point required" >&2
 | 
			
		||||
        return 1
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    # Try to get USB serial from host (PREFERRED)
 | 
			
		||||
    local usb_serial
 | 
			
		||||
    usb_serial=$(get_usb_serial_from_host "$mount_point")
 | 
			
		||||
 | 
			
		||||
    if [[ -n "$usb_serial" ]]; then
 | 
			
		||||
        echo "serial:$usb_serial"
 | 
			
		||||
        return 0
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    # Try USB device path (FALLBACK 1)
 | 
			
		||||
    local usb_path
 | 
			
		||||
    usb_path=$(get_usb_device_path "$mount_point")
 | 
			
		||||
    if [[ -n "$usb_path" ]]; then
 | 
			
		||||
        echo "usbpath:$usb_path"
 | 
			
		||||
        return 0
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    # Final fallback: use mount point basename (FALLBACK 2)
 | 
			
		||||
    echo "mount:$(basename "$mount_point")"
 | 
			
		||||
    return 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# ----------------------
 | 
			
		||||
# Function: print_device_info
 | 
			
		||||
# Print detailed information about a detected device (for debugging)
 | 
			
		||||
# Usage: print_device_info "/media/user/RPI-RP2"
 | 
			
		||||
# ----------------------
 | 
			
		||||
print_device_info() {
 | 
			
		||||
    local mount_point="$1"
 | 
			
		||||
 | 
			
		||||
    echo "Device Information:"
 | 
			
		||||
    echo "  Mount Point: $mount_point"
 | 
			
		||||
 | 
			
		||||
    local serial=$(get_usb_serial_from_host "$mount_point")
 | 
			
		||||
    echo "  USB Serial: ${serial:-[not available]}"
 | 
			
		||||
 | 
			
		||||
    local usbpath=$(get_usb_device_path "$mount_point")
 | 
			
		||||
    echo "  USB Path: ${usbpath:-[not available]}"
 | 
			
		||||
 | 
			
		||||
    local device_id=$(get_device_identifier "$mount_point")
 | 
			
		||||
    echo "  Identifier: $device_id"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										143
									
								
								qmk_flash_tools/lib/qmk_helpers.sh
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								qmk_flash_tools/lib/qmk_helpers.sh
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,143 @@
 | 
			
		|||
#!/usr/bin/env bash
 | 
			
		||||
# =============================================================================
 | 
			
		||||
# QMK Helper Functions Library
 | 
			
		||||
# =============================================================================
 | 
			
		||||
# Functions for building and flashing QMK firmware
 | 
			
		||||
# =============================================================================
 | 
			
		||||
 | 
			
		||||
# ----------------------
 | 
			
		||||
# Function: build_firmware
 | 
			
		||||
# Build QMK firmware for the specified keyboard and keymap
 | 
			
		||||
# Usage: build_firmware "fingerpunch/sweeeeep" "smathev"
 | 
			
		||||
# ----------------------
 | 
			
		||||
build_firmware() {
 | 
			
		||||
    local keyboard="$1"
 | 
			
		||||
    local keymap="$2"
 | 
			
		||||
 | 
			
		||||
    if [[ -z "$keyboard" || -z "$keymap" ]]; then
 | 
			
		||||
        echo "❌ Error: keyboard and keymap required" >&2
 | 
			
		||||
        return 1
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
 | 
			
		||||
    echo "🛠  Building firmware for $keyboard" >&2
 | 
			
		||||
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
 | 
			
		||||
 | 
			
		||||
    qmk compile -kb "$keyboard" -km "$keymap"
 | 
			
		||||
 | 
			
		||||
    if [[ $? -eq 0 ]]; then
 | 
			
		||||
        echo "✅ Firmware compiled successfully" >&2
 | 
			
		||||
        echo "" >&2
 | 
			
		||||
        return 0
 | 
			
		||||
    else
 | 
			
		||||
        echo "❌ Firmware compilation failed" >&2
 | 
			
		||||
        return 1
 | 
			
		||||
    fi
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# ----------------------
 | 
			
		||||
# Function: flash_with_bootloader
 | 
			
		||||
# Flash firmware using a specific bootloader target
 | 
			
		||||
# Usage: flash_with_bootloader "fingerpunch/sweeeeep" "smathev" "uf2-split-left"
 | 
			
		||||
# ----------------------
 | 
			
		||||
flash_with_bootloader() {
 | 
			
		||||
    local keyboard="$1"
 | 
			
		||||
    local keymap="$2"
 | 
			
		||||
    local bootloader="$3"
 | 
			
		||||
 | 
			
		||||
    if [[ -z "$keyboard" || -z "$keymap" || -z "$bootloader" ]]; then
 | 
			
		||||
        echo "❌ Error: keyboard, keymap, and bootloader required" >&2
 | 
			
		||||
        return 1
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    echo "📤 Flashing firmware with bootloader: $bootloader" >&2
 | 
			
		||||
    echo "" >&2
 | 
			
		||||
 | 
			
		||||
    qmk flash -kb "$keyboard" -km "$keymap" -bl "$bootloader"
 | 
			
		||||
 | 
			
		||||
    if [[ $? -eq 0 ]]; then
 | 
			
		||||
        echo "" >&2
 | 
			
		||||
        echo "✅ Firmware flashed successfully!" >&2
 | 
			
		||||
        return 0
 | 
			
		||||
    else
 | 
			
		||||
        echo "" >&2
 | 
			
		||||
        echo "❌ Firmware flashing failed" >&2
 | 
			
		||||
        return 1
 | 
			
		||||
    fi
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# ----------------------
 | 
			
		||||
# Function: flash_side
 | 
			
		||||
# Flash firmware for a specific keyboard side (left/right)
 | 
			
		||||
# Usage: flash_side "fingerpunch/sweeeeep" "smathev" "left"
 | 
			
		||||
# ----------------------
 | 
			
		||||
flash_side() {
 | 
			
		||||
    local keyboard="$1"
 | 
			
		||||
    local keymap="$2"
 | 
			
		||||
    local side="$3"
 | 
			
		||||
 | 
			
		||||
    if [[ -z "$keyboard" || -z "$keymap" || -z "$side" ]]; then
 | 
			
		||||
        echo "❌ Error: keyboard, keymap, and side required" >&2
 | 
			
		||||
        return 1
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    if [[ "$side" != "left" && "$side" != "right" ]]; then
 | 
			
		||||
        echo "❌ Error: side must be 'left' or 'right'" >&2
 | 
			
		||||
        return 1
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    local bootloader="uf2-split-$side"
 | 
			
		||||
 | 
			
		||||
    echo "🎯 Flashing $side side with handedness" >&2
 | 
			
		||||
 | 
			
		||||
    flash_with_bootloader "$keyboard" "$keymap" "$bootloader"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# ----------------------
 | 
			
		||||
# Function: check_qmk_installed
 | 
			
		||||
# Verify QMK CLI is installed and available
 | 
			
		||||
# Usage: check_qmk_installed || exit 1
 | 
			
		||||
# ----------------------
 | 
			
		||||
check_qmk_installed() {
 | 
			
		||||
    if ! command -v qmk &> /dev/null; then
 | 
			
		||||
        echo "❌ Error: 'qmk' CLI is not installed or not in PATH" >&2
 | 
			
		||||
        echo "   Install it with: python3 -m pip install qmk" >&2
 | 
			
		||||
        return 1
 | 
			
		||||
    fi
 | 
			
		||||
    return 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# ----------------------
 | 
			
		||||
# Function: verify_keyboard_exists
 | 
			
		||||
# Check if a keyboard definition exists in QMK
 | 
			
		||||
# Usage: verify_keyboard_exists "fingerpunch/sweeeeep"
 | 
			
		||||
# ----------------------
 | 
			
		||||
verify_keyboard_exists() {
 | 
			
		||||
    local keyboard="$1"
 | 
			
		||||
 | 
			
		||||
    if [[ -z "$keyboard" ]]; then
 | 
			
		||||
        echo "❌ Error: keyboard required" >&2
 | 
			
		||||
        return 1
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    # Try to list keymaps for this keyboard
 | 
			
		||||
    qmk list-keymaps -kb "$keyboard" &>/dev/null
 | 
			
		||||
 | 
			
		||||
    if [[ $? -eq 0 ]]; then
 | 
			
		||||
        return 0
 | 
			
		||||
    else
 | 
			
		||||
        echo "❌ Error: Keyboard '$keyboard' not found in QMK" >&2
 | 
			
		||||
        return 1
 | 
			
		||||
    fi
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# ----------------------
 | 
			
		||||
# Function: clean_build
 | 
			
		||||
# Clean previous build artifacts
 | 
			
		||||
# Usage: clean_build
 | 
			
		||||
# ----------------------
 | 
			
		||||
clean_build() {
 | 
			
		||||
    echo "🧹 Cleaning previous build..." >&2
 | 
			
		||||
    qmk clean &>/dev/null
 | 
			
		||||
    echo "✅ Build directory cleaned" >&2
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										481
									
								
								qmk_flash_tools/lib/side_mapping.sh
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										481
									
								
								qmk_flash_tools/lib/side_mapping.sh
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,481 @@
 | 
			
		|||
#!/usr/bin/env bash
 | 
			
		||||
# =============================================================================
 | 
			
		||||
# Side Mapping Library
 | 
			
		||||
# =============================================================================
 | 
			
		||||
# Functions for storing and retrieving keyboard side mappings
 | 
			
		||||
# Maps USB device identifiers to left/right sides
 | 
			
		||||
# =============================================================================
 | 
			
		||||
 | 
			
		||||
# Default mapping file location (will be set by calling script)
 | 
			
		||||
# Falls back to current directory if not set
 | 
			
		||||
SIDE_MAPPING_FILE="${SIDE_MAPPING_FILE:-./device_mappings.json}"
 | 
			
		||||
 | 
			
		||||
# ----------------------
 | 
			
		||||
# Function: init_mapping_file
 | 
			
		||||
# Ensure the mapping file exists
 | 
			
		||||
# Usage: init_mapping_file
 | 
			
		||||
# ----------------------
 | 
			
		||||
init_mapping_file() {
 | 
			
		||||
    if [[ ! -f "$SIDE_MAPPING_FILE" ]]; then
 | 
			
		||||
        echo "{}" > "$SIDE_MAPPING_FILE"
 | 
			
		||||
        echo "Created new mapping file: $SIDE_MAPPING_FILE" >&2
 | 
			
		||||
    fi
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# ----------------------
 | 
			
		||||
# Function: check_jq_installed
 | 
			
		||||
# Verify jq is available for JSON operations
 | 
			
		||||
# Usage: check_jq_installed || exit 1
 | 
			
		||||
# ----------------------
 | 
			
		||||
check_jq_installed() {
 | 
			
		||||
    if ! command -v jq &> /dev/null; then
 | 
			
		||||
        echo "❌ Error: 'jq' is required but not installed." >&2
 | 
			
		||||
        echo "   Install it with: sudo apt-get install jq" >&2
 | 
			
		||||
        return 1
 | 
			
		||||
    fi
 | 
			
		||||
    return 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# ----------------------
 | 
			
		||||
# Function: get_saved_side
 | 
			
		||||
# Retrieve the saved side mapping for a device identifier
 | 
			
		||||
# Usage: side=$(get_saved_side "serial:ABC123")
 | 
			
		||||
# Returns: "left", "right", or empty string if not found
 | 
			
		||||
# ----------------------
 | 
			
		||||
get_saved_side() {
 | 
			
		||||
    local device_id="$1"
 | 
			
		||||
 | 
			
		||||
    if [[ -z "$device_id" ]]; then
 | 
			
		||||
        echo "" >&2
 | 
			
		||||
        return 1
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    check_jq_installed || return 1
 | 
			
		||||
    init_mapping_file
 | 
			
		||||
 | 
			
		||||
    local side
 | 
			
		||||
    side=$(jq -r --arg id "$device_id" '.[$id] // ""' "$SIDE_MAPPING_FILE" 2>/dev/null)
 | 
			
		||||
 | 
			
		||||
    # Handle null or empty
 | 
			
		||||
    if [[ "$side" == "null" || -z "$side" ]]; then
 | 
			
		||||
        echo ""
 | 
			
		||||
        return 1
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    echo "$side"
 | 
			
		||||
    return 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# ----------------------
 | 
			
		||||
# Function: save_side_mapping
 | 
			
		||||
# Save a device identifier to side mapping
 | 
			
		||||
# Usage: save_side_mapping "serial:ABC123" "left"
 | 
			
		||||
# ----------------------
 | 
			
		||||
save_side_mapping() {
 | 
			
		||||
    local device_id="$1"
 | 
			
		||||
    local side="$2"
 | 
			
		||||
 | 
			
		||||
    if [[ -z "$device_id" || -z "$side" ]]; then
 | 
			
		||||
        echo "Error: device_id and side required" >&2
 | 
			
		||||
        return 1
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    if [[ "$side" != "left" && "$side" != "right" ]]; then
 | 
			
		||||
        echo "Error: side must be 'left' or 'right'" >&2
 | 
			
		||||
        return 1
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    check_jq_installed || return 1
 | 
			
		||||
    init_mapping_file
 | 
			
		||||
 | 
			
		||||
    # Save mapping
 | 
			
		||||
    local tmpfile=$(mktemp)
 | 
			
		||||
    jq --arg id "$device_id" --arg side "$side" '. + {($id): $side}' "$SIDE_MAPPING_FILE" > "$tmpfile"
 | 
			
		||||
 | 
			
		||||
    if [[ $? -eq 0 ]]; then
 | 
			
		||||
        mv "$tmpfile" "$SIDE_MAPPING_FILE"
 | 
			
		||||
        echo "✅ Saved mapping: $device_id → $side" >&2
 | 
			
		||||
        return 0
 | 
			
		||||
    else
 | 
			
		||||
        rm -f "$tmpfile"
 | 
			
		||||
        echo "❌ Failed to save mapping" >&2
 | 
			
		||||
        return 1
 | 
			
		||||
    fi
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# ----------------------
 | 
			
		||||
# Function: prompt_for_side
 | 
			
		||||
# Ask user to identify which side a device is
 | 
			
		||||
# Usage: side=$(prompt_for_side "serial:ABC123")
 | 
			
		||||
# Returns: "left" or "right"
 | 
			
		||||
# ----------------------
 | 
			
		||||
prompt_for_side() {
 | 
			
		||||
    local device_id="$1"
 | 
			
		||||
 | 
			
		||||
    echo "" >&2
 | 
			
		||||
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
 | 
			
		||||
    echo "⚠️  UNKNOWN DEVICE - First Time Setup" >&2
 | 
			
		||||
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
 | 
			
		||||
    echo "" >&2
 | 
			
		||||
    echo "The script has detected a keyboard half that it hasn't" >&2
 | 
			
		||||
    echo "seen before. This is expected on first run." >&2
 | 
			
		||||
    echo "" >&2
 | 
			
		||||
    echo "Device ID: $device_id" >&2
 | 
			
		||||
    echo "" >&2
 | 
			
		||||
    echo "Please tell me which side this is so I can remember it" >&2
 | 
			
		||||
    echo "for future flashing sessions." >&2
 | 
			
		||||
    echo "" >&2
 | 
			
		||||
 | 
			
		||||
    local side
 | 
			
		||||
    while true; do
 | 
			
		||||
        read -rp "Which side is currently plugged in? [left/right]: " side
 | 
			
		||||
        side=${side,,}
 | 
			
		||||
 | 
			
		||||
        if [[ "$side" == "left" || "$side" == "right" ]]; then
 | 
			
		||||
            echo "$side"
 | 
			
		||||
            return 0
 | 
			
		||||
        else
 | 
			
		||||
            echo "❌ Invalid input. Please type 'left' or 'right'." >&2
 | 
			
		||||
        fi
 | 
			
		||||
    done
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# ----------------------
 | 
			
		||||
# Function: detect_side
 | 
			
		||||
# Determine the left/right side of a device
 | 
			
		||||
# Checks saved mapping first, prompts user if unknown, then saves
 | 
			
		||||
# Usage: side=$(detect_side "serial:ABC123")
 | 
			
		||||
# Returns: "left" or "right"
 | 
			
		||||
# ----------------------
 | 
			
		||||
detect_side() {
 | 
			
		||||
    local device_id="$1"
 | 
			
		||||
 | 
			
		||||
    if [[ -z "$device_id" ]]; then
 | 
			
		||||
        echo "Error: device_id required" >&2
 | 
			
		||||
        return 1
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    echo "   Device Identifier: $device_id" >&2
 | 
			
		||||
 | 
			
		||||
    # Try to get saved side
 | 
			
		||||
    local side
 | 
			
		||||
    side=$(get_saved_side "$device_id" 2>/dev/null)
 | 
			
		||||
 | 
			
		||||
    if [[ -n "$side" ]]; then
 | 
			
		||||
        # Found in mapping
 | 
			
		||||
        echo "   ✅ Recognized: $side side (from saved mapping)" >&2
 | 
			
		||||
        echo "$side"
 | 
			
		||||
        return 0
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    # Not found - prompt user
 | 
			
		||||
    side=$(prompt_for_side "$device_id")
 | 
			
		||||
 | 
			
		||||
    # Save the mapping
 | 
			
		||||
    save_side_mapping "$device_id" "$side"
 | 
			
		||||
 | 
			
		||||
    echo "" >&2
 | 
			
		||||
    echo "   Next time this device is detected, it will be" >&2
 | 
			
		||||
    echo "   automatically identified as the $side side." >&2
 | 
			
		||||
    echo "" >&2
 | 
			
		||||
 | 
			
		||||
    echo "$side"
 | 
			
		||||
    return 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# ----------------------
 | 
			
		||||
# Function: get_mapping_state
 | 
			
		||||
# Determine the current state of the mapping file
 | 
			
		||||
# Returns: "empty", "partial", or "complete"
 | 
			
		||||
# ----------------------
 | 
			
		||||
get_mapping_state() {
 | 
			
		||||
    check_jq_installed || return 1
 | 
			
		||||
    init_mapping_file
 | 
			
		||||
 | 
			
		||||
    local count
 | 
			
		||||
    count=$(jq 'length' "$SIDE_MAPPING_FILE")
 | 
			
		||||
 | 
			
		||||
    case $count in
 | 
			
		||||
        0) echo "empty" ;;
 | 
			
		||||
        1) echo "partial" ;;
 | 
			
		||||
        2) echo "complete" ;;
 | 
			
		||||
        *) echo "unknown" ;;
 | 
			
		||||
    esac
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# ----------------------
 | 
			
		||||
# Function: get_mapped_devices
 | 
			
		||||
# Get list of all mapped device IDs
 | 
			
		||||
# Returns: space-separated list of device IDs
 | 
			
		||||
# ----------------------
 | 
			
		||||
get_mapped_devices() {
 | 
			
		||||
    check_jq_installed || return 1
 | 
			
		||||
    init_mapping_file
 | 
			
		||||
 | 
			
		||||
    jq -r 'keys[]' "$SIDE_MAPPING_FILE" | tr '\n' ' '
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# ----------------------
 | 
			
		||||
# Function: get_unmapped_side
 | 
			
		||||
# If mapping is partial, determine which side is NOT mapped yet
 | 
			
		||||
# Returns: "left", "right", or empty string if both/neither mapped
 | 
			
		||||
# ----------------------
 | 
			
		||||
get_unmapped_side() {
 | 
			
		||||
    check_jq_installed || return 1
 | 
			
		||||
    init_mapping_file
 | 
			
		||||
 | 
			
		||||
    local has_left has_right
 | 
			
		||||
    has_left=$(jq -r 'to_entries | map(select(.value == "left")) | length' "$SIDE_MAPPING_FILE")
 | 
			
		||||
    has_right=$(jq -r 'to_entries | map(select(.value == "right")) | length' "$SIDE_MAPPING_FILE")
 | 
			
		||||
 | 
			
		||||
    if [[ "$has_left" -eq 0 ]]; then
 | 
			
		||||
        echo "left"
 | 
			
		||||
    elif [[ "$has_right" -eq 0 ]]; then
 | 
			
		||||
        echo "right"
 | 
			
		||||
    else
 | 
			
		||||
        echo ""
 | 
			
		||||
    fi
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# ----------------------
 | 
			
		||||
# Function: detect_side_with_expected
 | 
			
		||||
# Detect device side and verify it matches expected side
 | 
			
		||||
# Uses intelligent verification based on mapping state
 | 
			
		||||
# Usage: side=$(detect_side_with_expected "serial:ABC123" "left")
 | 
			
		||||
# Returns: "left" or "right", or exits on error
 | 
			
		||||
# ----------------------
 | 
			
		||||
detect_side_with_expected() {
 | 
			
		||||
    local device_id="$1"
 | 
			
		||||
    local expected_side="$2"
 | 
			
		||||
 | 
			
		||||
    if [[ -z "$device_id" || -z "$expected_side" ]]; then
 | 
			
		||||
        echo "Error: device_id and expected_side required" >&2
 | 
			
		||||
        return 1
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    echo "   Device Identifier: $device_id" >&2
 | 
			
		||||
    echo "   Expected Side: $expected_side" >&2
 | 
			
		||||
 | 
			
		||||
    # Get current mapping state
 | 
			
		||||
    local mapping_state
 | 
			
		||||
    mapping_state=$(get_mapping_state)
 | 
			
		||||
    echo "   Mapping State: $mapping_state" >&2
 | 
			
		||||
 | 
			
		||||
    # Try to get saved side for this device
 | 
			
		||||
    local saved_side
 | 
			
		||||
    saved_side=$(get_saved_side "$device_id" 2>/dev/null)
 | 
			
		||||
 | 
			
		||||
    # -----------------------------------------------------------------
 | 
			
		||||
    # STATE 1: EMPTY MAPPING (Learning Mode)
 | 
			
		||||
    # -----------------------------------------------------------------
 | 
			
		||||
    if [[ "$mapping_state" == "empty" ]]; then
 | 
			
		||||
        echo "" >&2
 | 
			
		||||
        echo "   📝 Learning mode (first time setup)" >&2
 | 
			
		||||
        echo "   ✅ Saving device as $expected_side side" >&2
 | 
			
		||||
        save_side_mapping "$device_id" "$expected_side"
 | 
			
		||||
        echo "$expected_side"
 | 
			
		||||
        return 0
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    # -----------------------------------------------------------------
 | 
			
		||||
    # STATE 2: PARTIAL MAPPING (One Side Known)
 | 
			
		||||
    # -----------------------------------------------------------------
 | 
			
		||||
    if [[ "$mapping_state" == "partial" ]]; then
 | 
			
		||||
        local unmapped_side
 | 
			
		||||
        unmapped_side=$(get_unmapped_side)
 | 
			
		||||
 | 
			
		||||
        if [[ -n "$saved_side" ]]; then
 | 
			
		||||
            # Device is known - verify it matches
 | 
			
		||||
            if [[ "$saved_side" == "$expected_side" ]]; then
 | 
			
		||||
                echo "   ✅ Confirmed: $expected_side side (matches saved mapping)" >&2
 | 
			
		||||
                echo "$expected_side"
 | 
			
		||||
                return 0
 | 
			
		||||
            else
 | 
			
		||||
                # MISMATCH in partial state
 | 
			
		||||
                echo "" >&2
 | 
			
		||||
                echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
 | 
			
		||||
                echo "❌ ERROR: MISMATCH IN PARTIAL MAPPING" >&2
 | 
			
		||||
                echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
 | 
			
		||||
                echo "" >&2
 | 
			
		||||
                echo "Expected: $expected_side side" >&2
 | 
			
		||||
                echo "Device is saved as: $saved_side side" >&2
 | 
			
		||||
                echo "" >&2
 | 
			
		||||
                echo "This device is known but on the wrong side!" >&2
 | 
			
		||||
                echo "Clear mappings and re-learn: rm $SIDE_MAPPING_FILE" >&2
 | 
			
		||||
                exit 1
 | 
			
		||||
            fi
 | 
			
		||||
        else
 | 
			
		||||
            # Device is unknown - check if user expects the unmapped side
 | 
			
		||||
            if [[ "$expected_side" == "$unmapped_side" ]]; then
 | 
			
		||||
                echo "" >&2
 | 
			
		||||
                echo "   ℹ️  Unknown device, expecting unmapped side" >&2
 | 
			
		||||
                echo "   ✅ Saving as $expected_side side (completes mapping)" >&2
 | 
			
		||||
                save_side_mapping "$device_id" "$expected_side"
 | 
			
		||||
                echo "$expected_side"
 | 
			
		||||
                return 0
 | 
			
		||||
            else
 | 
			
		||||
                # User expects mapped side but got unknown device
 | 
			
		||||
                echo "" >&2
 | 
			
		||||
                echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
 | 
			
		||||
                echo "❌ ERROR: AMBIGUOUS DEVICE IN PARTIAL MAPPING" >&2
 | 
			
		||||
                echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
 | 
			
		||||
                echo "" >&2
 | 
			
		||||
                echo "Expected: $expected_side side (which is already mapped)" >&2
 | 
			
		||||
                echo "But got: Unknown device $device_id" >&2
 | 
			
		||||
                echo "" >&2
 | 
			
		||||
                echo "This could be:" >&2
 | 
			
		||||
                echo "  1. A replacement controller for $expected_side side" >&2
 | 
			
		||||
                echo "  2. The wrong side plugged in" >&2
 | 
			
		||||
                echo "" >&2
 | 
			
		||||
                echo "Cannot determine safely!" >&2
 | 
			
		||||
                echo "Clear mappings and re-learn: rm $SIDE_MAPPING_FILE" >&2
 | 
			
		||||
                exit 1
 | 
			
		||||
            fi
 | 
			
		||||
        fi
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    # -----------------------------------------------------------------
 | 
			
		||||
    # STATE 3: COMPLETE MAPPING (Both Sides Known)
 | 
			
		||||
    # -----------------------------------------------------------------
 | 
			
		||||
    if [[ "$mapping_state" == "complete" ]]; then
 | 
			
		||||
        if [[ -z "$saved_side" ]]; then
 | 
			
		||||
            # Unknown device with complete mapping = ERROR
 | 
			
		||||
            echo "" >&2
 | 
			
		||||
            echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
 | 
			
		||||
            echo "❌ ERROR: UNKNOWN DEVICE (COMPLETE MAPPING)" >&2
 | 
			
		||||
            echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
 | 
			
		||||
            echo "" >&2
 | 
			
		||||
            echo "Detected device: $device_id" >&2
 | 
			
		||||
            echo "Expected one of the known devices:" >&2
 | 
			
		||||
            get_mapped_devices | tr ' ' '\n' | while read dev; do
 | 
			
		||||
                local side=$(get_saved_side "$dev")
 | 
			
		||||
                echo "  - $dev ($side side)" >&2
 | 
			
		||||
            done
 | 
			
		||||
            echo "" >&2
 | 
			
		||||
            echo "Mapping is complete (both sides known)." >&2
 | 
			
		||||
            echo "Unknown devices are not allowed!" >&2
 | 
			
		||||
            echo "" >&2
 | 
			
		||||
            echo "If you replaced a controller, clear mappings:" >&2
 | 
			
		||||
            echo "  rm $SIDE_MAPPING_FILE" >&2
 | 
			
		||||
            exit 1
 | 
			
		||||
        fi
 | 
			
		||||
 | 
			
		||||
        # Device is known - verify it matches
 | 
			
		||||
        if [[ "$saved_side" == "$expected_side" ]]; then
 | 
			
		||||
            echo "   ✅ Confirmed: $expected_side side (matches saved mapping)" >&2
 | 
			
		||||
            echo "$expected_side"
 | 
			
		||||
            return 0
 | 
			
		||||
        fi
 | 
			
		||||
 | 
			
		||||
    # MISMATCH! Device is known but wrong side
 | 
			
		||||
    echo "" >&2
 | 
			
		||||
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
 | 
			
		||||
    echo "⚠️  WARNING: SIDE MISMATCH DETECTED" >&2
 | 
			
		||||
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
 | 
			
		||||
    echo "" >&2
 | 
			
		||||
    echo "Expected: $expected_side side" >&2
 | 
			
		||||
    echo "Saved mapping says: $saved_side side" >&2
 | 
			
		||||
    echo "" >&2
 | 
			
		||||
    echo "This means either:" >&2
 | 
			
		||||
    echo "  1. You plugged in the WRONG keyboard half" >&2
 | 
			
		||||
    echo "  2. The saved mapping is incorrect" >&2
 | 
			
		||||
    echo "" >&2
 | 
			
		||||
    echo "What would you like to do?" >&2
 | 
			
		||||
    echo "  [e] Exit safely (recommended)" >&2
 | 
			
		||||
    echo "  [c] Clear this mapping and save as $expected_side" >&2
 | 
			
		||||
    echo "  [f] Force flash as $expected_side anyway (DANGEROUS)" >&2
 | 
			
		||||
    echo "" >&2
 | 
			
		||||
 | 
			
		||||
    local choice
 | 
			
		||||
    while true; do
 | 
			
		||||
        read -rp "Your choice [e/c/f]: " choice
 | 
			
		||||
        choice=${choice,,}
 | 
			
		||||
 | 
			
		||||
        case "$choice" in
 | 
			
		||||
            e)
 | 
			
		||||
                echo "" >&2
 | 
			
		||||
                echo "❌ Exiting safely. Please plug in the correct keyboard half." >&2
 | 
			
		||||
                exit 1
 | 
			
		||||
                ;;
 | 
			
		||||
            c)
 | 
			
		||||
                echo "" >&2
 | 
			
		||||
                echo "🔄 Clearing old mapping and saving as $expected_side side..." >&2
 | 
			
		||||
                save_side_mapping "$device_id" "$expected_side"
 | 
			
		||||
                echo "✅ Mapping updated" >&2
 | 
			
		||||
                echo "$expected_side"
 | 
			
		||||
                return 0
 | 
			
		||||
                ;;
 | 
			
		||||
            f)
 | 
			
		||||
                echo "" >&2
 | 
			
		||||
                echo "⚠️  WARNING: Flashing device as $expected_side despite mismatch!" >&2
 | 
			
		||||
                echo "   This could result in incorrect keyboard behavior." >&2
 | 
			
		||||
                read -rp "   Are you absolutely sure? [yes/no]: " confirm
 | 
			
		||||
                if [[ "$confirm" == "yes" ]]; then
 | 
			
		||||
                    # Don't update mapping, just return expected side
 | 
			
		||||
                    echo "   Proceeding with $expected_side..." >&2
 | 
			
		||||
                    echo "$expected_side"
 | 
			
		||||
                    return 0
 | 
			
		||||
                else
 | 
			
		||||
                    echo "   Cancelled. Exiting." >&2
 | 
			
		||||
                    exit 1
 | 
			
		||||
                fi
 | 
			
		||||
                ;;
 | 
			
		||||
            *)
 | 
			
		||||
                echo "Invalid choice. Please enter 'e', 'c', or 'f'." >&2
 | 
			
		||||
                ;;
 | 
			
		||||
        esac
 | 
			
		||||
    done
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# ----------------------
 | 
			
		||||
# Function: list_all_mappings
 | 
			
		||||
# Display all saved device mappings
 | 
			
		||||
# Usage: list_all_mappings
 | 
			
		||||
# ----------------------
 | 
			
		||||
list_all_mappings() {
 | 
			
		||||
    check_jq_installed || return 1
 | 
			
		||||
    init_mapping_file
 | 
			
		||||
 | 
			
		||||
    echo "Saved Device Mappings:"
 | 
			
		||||
    jq '.' "$SIDE_MAPPING_FILE"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# ----------------------
 | 
			
		||||
# Function: clear_mapping
 | 
			
		||||
# Remove a specific device mapping
 | 
			
		||||
# Usage: clear_mapping "serial:ABC123"
 | 
			
		||||
# ----------------------
 | 
			
		||||
clear_mapping() {
 | 
			
		||||
    local device_id="$1"
 | 
			
		||||
 | 
			
		||||
    if [[ -z "$device_id" ]]; then
 | 
			
		||||
        echo "Error: device_id required" >&2
 | 
			
		||||
        return 1
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    check_jq_installed || return 1
 | 
			
		||||
    init_mapping_file
 | 
			
		||||
 | 
			
		||||
    local tmpfile=$(mktemp)
 | 
			
		||||
    jq --arg id "$device_id" 'del(.[$id])' "$SIDE_MAPPING_FILE" > "$tmpfile"
 | 
			
		||||
 | 
			
		||||
    if [[ $? -eq 0 ]]; then
 | 
			
		||||
        mv "$tmpfile" "$SIDE_MAPPING_FILE"
 | 
			
		||||
        echo "✅ Removed mapping for: $device_id" >&2
 | 
			
		||||
        return 0
 | 
			
		||||
    else
 | 
			
		||||
        rm -f "$tmpfile"
 | 
			
		||||
        echo "❌ Failed to remove mapping" >&2
 | 
			
		||||
        return 1
 | 
			
		||||
    fi
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# ----------------------
 | 
			
		||||
# Function: clear_all_mappings
 | 
			
		||||
# Remove all device mappings (reset to empty)
 | 
			
		||||
# Usage: clear_all_mappings
 | 
			
		||||
# ----------------------
 | 
			
		||||
clear_all_mappings() {
 | 
			
		||||
    echo "{}" > "$SIDE_MAPPING_FILE"
 | 
			
		||||
    echo "✅ Cleared all mappings" >&2
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										41
									
								
								qmk_flash_tools/test/run_all_tests.sh
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								qmk_flash_tools/test/run_all_tests.sh
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,41 @@
 | 
			
		|||
#!/usr/bin/env bash
 | 
			
		||||
# =============================================================================
 | 
			
		||||
# Quick Testing Script - Run all tests in sequence
 | 
			
		||||
# =============================================================================
 | 
			
		||||
 | 
			
		||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
 | 
			
		||||
 | 
			
		||||
echo "╔═══════════════════════════════════════════════════════════╗"
 | 
			
		||||
echo "║  QMK Flash Tools - Quick Test Suite                      ║"
 | 
			
		||||
echo "╚═══════════════════════════════════════════════════════════╝"
 | 
			
		||||
echo ""
 | 
			
		||||
 | 
			
		||||
# Test 1: Side Mapping (no hardware needed)
 | 
			
		||||
echo "Running side mapping tests..."
 | 
			
		||||
echo ""
 | 
			
		||||
bash "$SCRIPT_DIR/test_side_mapping.sh"
 | 
			
		||||
echo ""
 | 
			
		||||
 | 
			
		||||
# Test 2: QMK Helpers (partial, no hardware needed)
 | 
			
		||||
echo "Running QMK helper tests..."
 | 
			
		||||
echo ""
 | 
			
		||||
bash "$SCRIPT_DIR/test_qmk_helpers.sh"
 | 
			
		||||
echo ""
 | 
			
		||||
 | 
			
		||||
# Test 3: Device Detection (requires hardware)
 | 
			
		||||
echo "Running device detection tests..."
 | 
			
		||||
echo ""
 | 
			
		||||
read -rp "Do you have a keyboard in bootloader mode? [y/n]: " has_device
 | 
			
		||||
 | 
			
		||||
if [[ "$has_device" == "y" ]]; then
 | 
			
		||||
    bash "$SCRIPT_DIR/test_device_detection.sh"
 | 
			
		||||
else
 | 
			
		||||
    echo "⏭️  Skipping device detection test"
 | 
			
		||||
    echo "   Run manually when you have hardware ready:"
 | 
			
		||||
    echo "   ./test/test_device_detection.sh"
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
echo ""
 | 
			
		||||
echo "╔═══════════════════════════════════════════════════════════╗"
 | 
			
		||||
echo "║  ✅ All tests complete!                                   ║"
 | 
			
		||||
echo "╚═══════════════════════════════════════════════════════════╝"
 | 
			
		||||
							
								
								
									
										111
									
								
								qmk_flash_tools/test/test_device_detection.sh
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								qmk_flash_tools/test/test_device_detection.sh
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,111 @@
 | 
			
		|||
#!/usr/bin/env bash
 | 
			
		||||
# =============================================================================
 | 
			
		||||
# Test Device Detection Functions
 | 
			
		||||
# =============================================================================
 | 
			
		||||
# Standalone test script for device_detection.sh library
 | 
			
		||||
# =============================================================================
 | 
			
		||||
 | 
			
		||||
# Get the directory where this script is located
 | 
			
		||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
 | 
			
		||||
LIB_DIR="$SCRIPT_DIR/../lib"
 | 
			
		||||
 | 
			
		||||
# Source the device detection library
 | 
			
		||||
source "$LIB_DIR/device_detection.sh"
 | 
			
		||||
 | 
			
		||||
# Set configuration for testing
 | 
			
		||||
export USB_MOUNT_PATHS=("/media/$USER" "/run/media/$USER" "/mnt")
 | 
			
		||||
export RP2040_PATTERN="*RP2040*"
 | 
			
		||||
export USB_WAIT_INTERVAL=0.5
 | 
			
		||||
 | 
			
		||||
echo "╔═══════════════════════════════════════════════════════╗"
 | 
			
		||||
echo "║  Device Detection Library Test                        ║"
 | 
			
		||||
echo "╚═══════════════════════════════════════════════════════╝"
 | 
			
		||||
echo ""
 | 
			
		||||
 | 
			
		||||
# Test 1: Check if device is currently connected
 | 
			
		||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
echo "Test 1: Looking for currently connected RP2040..."
 | 
			
		||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
 | 
			
		||||
# Quick check without waiting
 | 
			
		||||
device_found=""
 | 
			
		||||
for path in "${USB_MOUNT_PATHS[@]}"; do
 | 
			
		||||
    device_found=$(find "$path" -maxdepth 2 -type d -name "$RP2040_PATTERN" 2>/dev/null | head -n1)
 | 
			
		||||
    if [[ -n "$device_found" ]]; then
 | 
			
		||||
        break
 | 
			
		||||
    fi
 | 
			
		||||
done
 | 
			
		||||
 | 
			
		||||
if [[ -n "$device_found" ]]; then
 | 
			
		||||
    echo "✅ RP2040 device found at: $device_found"
 | 
			
		||||
    echo ""
 | 
			
		||||
 | 
			
		||||
    # Test 2: Get USB serial
 | 
			
		||||
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
    echo "Test 2: Getting USB serial from host..."
 | 
			
		||||
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
    usb_serial=$(get_usb_serial_from_host "$device_found")
 | 
			
		||||
    if [[ -n "$usb_serial" ]]; then
 | 
			
		||||
        echo "✅ USB Serial: $usb_serial"
 | 
			
		||||
    else
 | 
			
		||||
        echo "⚠️  USB Serial not available"
 | 
			
		||||
    fi
 | 
			
		||||
    echo ""
 | 
			
		||||
 | 
			
		||||
    # Test 3: Get USB device path
 | 
			
		||||
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
    echo "Test 3: Getting USB device path..."
 | 
			
		||||
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
    usb_path=$(get_usb_device_path "$device_found")
 | 
			
		||||
    if [[ -n "$usb_path" ]]; then
 | 
			
		||||
        echo "✅ USB Path: $usb_path"
 | 
			
		||||
    else
 | 
			
		||||
        echo "⚠️  USB Path not available"
 | 
			
		||||
    fi
 | 
			
		||||
    echo ""
 | 
			
		||||
 | 
			
		||||
    # Test 4: Get device identifier
 | 
			
		||||
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
    echo "Test 4: Getting device identifier..."
 | 
			
		||||
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
    device_id=$(get_device_identifier "$device_found")
 | 
			
		||||
    echo "✅ Device Identifier: $device_id"
 | 
			
		||||
    echo ""
 | 
			
		||||
 | 
			
		||||
    # Test 5: Print full device info
 | 
			
		||||
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
    echo "Test 5: Full device information..."
 | 
			
		||||
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
    print_device_info "$device_found"
 | 
			
		||||
    echo ""
 | 
			
		||||
 | 
			
		||||
else
 | 
			
		||||
    echo "⚠️  No RP2040 device currently connected"
 | 
			
		||||
    echo ""
 | 
			
		||||
    echo "To test device detection:"
 | 
			
		||||
    echo "  1. Enter bootloader mode on your keyboard (double-tap RESET)"
 | 
			
		||||
    echo "  2. Run this test script again"
 | 
			
		||||
    echo ""
 | 
			
		||||
    echo "Or test the wait function (will wait for device):"
 | 
			
		||||
    read -rp "Wait for device? [y/n]: " wait_test
 | 
			
		||||
 | 
			
		||||
    if [[ "$wait_test" == "y" ]]; then
 | 
			
		||||
        echo ""
 | 
			
		||||
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
        echo "Test: Waiting for RP2040 device..."
 | 
			
		||||
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
        echo "Enter bootloader mode on your keyboard now..."
 | 
			
		||||
        echo ""
 | 
			
		||||
 | 
			
		||||
        mount_point=$(wait_for_rp2040)
 | 
			
		||||
        echo ""
 | 
			
		||||
        echo "✅ Device detected!"
 | 
			
		||||
        echo ""
 | 
			
		||||
        print_device_info "$mount_point"
 | 
			
		||||
    fi
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
echo ""
 | 
			
		||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
echo "✅ Device detection tests complete"
 | 
			
		||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
							
								
								
									
										108
									
								
								qmk_flash_tools/test/test_qmk_helpers.sh
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								qmk_flash_tools/test/test_qmk_helpers.sh
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,108 @@
 | 
			
		|||
#!/usr/bin/env bash
 | 
			
		||||
# =============================================================================
 | 
			
		||||
# Test QMK Helper Functions
 | 
			
		||||
# =============================================================================
 | 
			
		||||
# Standalone test script for qmk_helpers.sh library
 | 
			
		||||
# =============================================================================
 | 
			
		||||
 | 
			
		||||
# Get the directory where this script is located
 | 
			
		||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
 | 
			
		||||
LIB_DIR="$SCRIPT_DIR/../lib"
 | 
			
		||||
 | 
			
		||||
# Source the QMK helpers library
 | 
			
		||||
source "$LIB_DIR/qmk_helpers.sh"
 | 
			
		||||
 | 
			
		||||
echo "╔═══════════════════════════════════════════════════════╗"
 | 
			
		||||
echo "║  QMK Helpers Library Test                             ║"
 | 
			
		||||
echo "╚═══════════════════════════════════════════════════════╝"
 | 
			
		||||
echo ""
 | 
			
		||||
 | 
			
		||||
# Test configuration
 | 
			
		||||
TEST_KEYBOARD="fingerpunch/sweeeeep"
 | 
			
		||||
TEST_KEYMAP="smathev"
 | 
			
		||||
 | 
			
		||||
# Test 1: Check QMK installation
 | 
			
		||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
echo "Test 1: Check QMK CLI installation..."
 | 
			
		||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
if check_qmk_installed; then
 | 
			
		||||
    echo "✅ QMK CLI is installed"
 | 
			
		||||
    qmk --version
 | 
			
		||||
else
 | 
			
		||||
    echo "❌ QMK CLI is not installed"
 | 
			
		||||
    exit 1
 | 
			
		||||
fi
 | 
			
		||||
echo ""
 | 
			
		||||
 | 
			
		||||
# Test 2: Verify keyboard exists
 | 
			
		||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
echo "Test 2: Verify keyboard exists..."
 | 
			
		||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
echo "Checking for: $TEST_KEYBOARD"
 | 
			
		||||
if verify_keyboard_exists "$TEST_KEYBOARD"; then
 | 
			
		||||
    echo "✅ Keyboard exists in QMK"
 | 
			
		||||
else
 | 
			
		||||
    echo "⚠️  Keyboard not found (may need to adjust TEST_KEYBOARD variable)"
 | 
			
		||||
fi
 | 
			
		||||
echo ""
 | 
			
		||||
 | 
			
		||||
# Test 3: Test build (optional)
 | 
			
		||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
echo "Test 3: Build firmware (optional)..."
 | 
			
		||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
echo "This will compile the firmware for $TEST_KEYBOARD:$TEST_KEYMAP"
 | 
			
		||||
read -rp "Proceed with build test? [y/n]: " do_build
 | 
			
		||||
 | 
			
		||||
if [[ "$do_build" == "y" ]]; then
 | 
			
		||||
    echo ""
 | 
			
		||||
    if build_firmware "$TEST_KEYBOARD" "$TEST_KEYMAP"; then
 | 
			
		||||
        echo "✅ Build test passed"
 | 
			
		||||
    else
 | 
			
		||||
        echo "⚠️  Build test failed (check keyboard/keymap configuration)"
 | 
			
		||||
    fi
 | 
			
		||||
else
 | 
			
		||||
    echo "⏭️  Skipped build test"
 | 
			
		||||
fi
 | 
			
		||||
echo ""
 | 
			
		||||
 | 
			
		||||
# Test 4: Test clean build
 | 
			
		||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
echo "Test 4: Clean build directory..."
 | 
			
		||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
read -rp "Proceed with clean test? [y/n]: " do_clean
 | 
			
		||||
 | 
			
		||||
if [[ "$do_clean" == "y" ]]; then
 | 
			
		||||
    clean_build
 | 
			
		||||
    echo "✅ Clean test passed"
 | 
			
		||||
else
 | 
			
		||||
    echo "⏭️  Skipped clean test"
 | 
			
		||||
fi
 | 
			
		||||
echo ""
 | 
			
		||||
 | 
			
		||||
# Test 5: Function signature tests (no actual flashing)
 | 
			
		||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
echo "Test 5: Test function signatures (dry run)..."
 | 
			
		||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
 | 
			
		||||
echo "Testing flash_side with missing arguments:"
 | 
			
		||||
flash_side "" "" "" 2>&1 | head -n1
 | 
			
		||||
echo ""
 | 
			
		||||
 | 
			
		||||
echo "Testing flash_side with invalid side:"
 | 
			
		||||
flash_side "$TEST_KEYBOARD" "$TEST_KEYMAP" "middle" 2>&1 | head -n1
 | 
			
		||||
echo ""
 | 
			
		||||
 | 
			
		||||
echo "Testing flash_with_bootloader with missing arguments:"
 | 
			
		||||
flash_with_bootloader "" "" "" 2>&1 | head -n1
 | 
			
		||||
echo ""
 | 
			
		||||
 | 
			
		||||
echo "✅ Function signature tests passed"
 | 
			
		||||
echo ""
 | 
			
		||||
 | 
			
		||||
# Summary
 | 
			
		||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
echo "✅ QMK helpers tests complete"
 | 
			
		||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
echo ""
 | 
			
		||||
echo "Note: Actual flashing tests require hardware and should be"
 | 
			
		||||
echo "      done through the main autoflash script."
 | 
			
		||||
							
								
								
									
										148
									
								
								qmk_flash_tools/test/test_side_mapping.sh
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								qmk_flash_tools/test/test_side_mapping.sh
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,148 @@
 | 
			
		|||
#!/usr/bin/env bash
 | 
			
		||||
# =============================================================================
 | 
			
		||||
# Test Side Mapping Functions
 | 
			
		||||
# =============================================================================
 | 
			
		||||
# Standalone test script for side_mapping.sh library
 | 
			
		||||
# =============================================================================
 | 
			
		||||
 | 
			
		||||
# Get the directory where this script is located
 | 
			
		||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
 | 
			
		||||
LIB_DIR="$SCRIPT_DIR/../lib"
 | 
			
		||||
 | 
			
		||||
# Use a test mapping file
 | 
			
		||||
export SIDE_MAPPING_FILE="/tmp/qmk_test_mappings.json"
 | 
			
		||||
 | 
			
		||||
# Source the side mapping library
 | 
			
		||||
source "$LIB_DIR/side_mapping.sh"
 | 
			
		||||
 | 
			
		||||
echo "╔═══════════════════════════════════════════════════════╗"
 | 
			
		||||
echo "║  Side Mapping Library Test                            ║"
 | 
			
		||||
echo "╚═══════════════════════════════════════════════════════╝"
 | 
			
		||||
echo ""
 | 
			
		||||
echo "Test mapping file: $SIDE_MAPPING_FILE"
 | 
			
		||||
echo ""
 | 
			
		||||
 | 
			
		||||
# Clean up test file if it exists
 | 
			
		||||
rm -f "$SIDE_MAPPING_FILE"
 | 
			
		||||
 | 
			
		||||
# Test 1: Initialize mapping file
 | 
			
		||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
echo "Test 1: Initialize mapping file..."
 | 
			
		||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
init_mapping_file
 | 
			
		||||
if [[ -f "$SIDE_MAPPING_FILE" ]]; then
 | 
			
		||||
    echo "✅ Mapping file created"
 | 
			
		||||
    cat "$SIDE_MAPPING_FILE"
 | 
			
		||||
else
 | 
			
		||||
    echo "❌ Failed to create mapping file"
 | 
			
		||||
    exit 1
 | 
			
		||||
fi
 | 
			
		||||
echo ""
 | 
			
		||||
 | 
			
		||||
# Test 2: Check jq installation
 | 
			
		||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
echo "Test 2: Check jq installation..."
 | 
			
		||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
if check_jq_installed; then
 | 
			
		||||
    echo "✅ jq is installed"
 | 
			
		||||
else
 | 
			
		||||
    echo "❌ jq is not installed"
 | 
			
		||||
    exit 1
 | 
			
		||||
fi
 | 
			
		||||
echo ""
 | 
			
		||||
 | 
			
		||||
# Test 3: Save mappings
 | 
			
		||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
echo "Test 3: Save test mappings..."
 | 
			
		||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
 | 
			
		||||
save_side_mapping "serial:ABC123" "left"
 | 
			
		||||
save_side_mapping "serial:XYZ789" "right"
 | 
			
		||||
save_side_mapping "usbpath:usb1/1-3" "left"
 | 
			
		||||
 | 
			
		||||
echo ""
 | 
			
		||||
echo "Current mappings:"
 | 
			
		||||
cat "$SIDE_MAPPING_FILE"
 | 
			
		||||
echo ""
 | 
			
		||||
 | 
			
		||||
# Test 4: Retrieve saved sides
 | 
			
		||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
echo "Test 4: Retrieve saved sides..."
 | 
			
		||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
 | 
			
		||||
side1=$(get_saved_side "serial:ABC123")
 | 
			
		||||
echo "Device serial:ABC123 → $side1"
 | 
			
		||||
if [[ "$side1" == "left" ]]; then
 | 
			
		||||
    echo "✅ Correct"
 | 
			
		||||
else
 | 
			
		||||
    echo "❌ Expected 'left', got '$side1'"
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
side2=$(get_saved_side "serial:XYZ789")
 | 
			
		||||
echo "Device serial:XYZ789 → $side2"
 | 
			
		||||
if [[ "$side2" == "right" ]]; then
 | 
			
		||||
    echo "✅ Correct"
 | 
			
		||||
else
 | 
			
		||||
    echo "❌ Expected 'right', got '$side2'"
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
side3=$(get_saved_side "serial:UNKNOWN")
 | 
			
		||||
echo "Device serial:UNKNOWN → ${side3:-[not found]}"
 | 
			
		||||
if [[ -z "$side3" ]]; then
 | 
			
		||||
    echo "✅ Correct (not found)"
 | 
			
		||||
else
 | 
			
		||||
    echo "❌ Expected empty, got '$side3'"
 | 
			
		||||
fi
 | 
			
		||||
echo ""
 | 
			
		||||
 | 
			
		||||
# Test 5: List all mappings
 | 
			
		||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
echo "Test 5: List all mappings..."
 | 
			
		||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
list_all_mappings
 | 
			
		||||
echo ""
 | 
			
		||||
 | 
			
		||||
# Test 6: Clear specific mapping
 | 
			
		||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
echo "Test 6: Clear specific mapping..."
 | 
			
		||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
clear_mapping "usbpath:usb1/1-3"
 | 
			
		||||
echo ""
 | 
			
		||||
echo "Mappings after clearing 'usbpath:usb1/1-3':"
 | 
			
		||||
cat "$SIDE_MAPPING_FILE"
 | 
			
		||||
echo ""
 | 
			
		||||
 | 
			
		||||
# Test 7: Interactive test (optional)
 | 
			
		||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
echo "Test 7: Interactive prompt test (optional)..."
 | 
			
		||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
read -rp "Test the interactive prompt? [y/n]: " test_prompt
 | 
			
		||||
 | 
			
		||||
if [[ "$test_prompt" == "y" ]]; then
 | 
			
		||||
    echo ""
 | 
			
		||||
    test_device_id="serial:TEST_DEVICE"
 | 
			
		||||
    detected_side=$(detect_side "$test_device_id")
 | 
			
		||||
    echo ""
 | 
			
		||||
    echo "You identified the device as: $detected_side"
 | 
			
		||||
    echo ""
 | 
			
		||||
    echo "Updated mappings:"
 | 
			
		||||
    cat "$SIDE_MAPPING_FILE"
 | 
			
		||||
fi
 | 
			
		||||
echo ""
 | 
			
		||||
 | 
			
		||||
# Test 8: Clear all mappings
 | 
			
		||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
echo "Test 8: Clear all mappings..."
 | 
			
		||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
clear_all_mappings
 | 
			
		||||
echo ""
 | 
			
		||||
echo "Mappings after clearing all:"
 | 
			
		||||
cat "$SIDE_MAPPING_FILE"
 | 
			
		||||
echo ""
 | 
			
		||||
 | 
			
		||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
echo "✅ Side mapping tests complete"
 | 
			
		||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
 | 
			
		||||
echo ""
 | 
			
		||||
echo "Cleaning up test file: $SIDE_MAPPING_FILE"
 | 
			
		||||
rm -f "$SIDE_MAPPING_FILE"
 | 
			
		||||
| 
						 | 
				
			
			@ -25,7 +25,7 @@
 | 
			
		|||
// Tapping and timing configuration
 | 
			
		||||
#define TAPPING_TERM 200
 | 
			
		||||
#define FLOW_TAP 130
 | 
			
		||||
//#define PERMISSIVE_HOLD         // Activate mod immediately when another key pressed _REDUNDANT due to SpeculativeHold
 | 
			
		||||
#define PERMISSIVE_HOLD         // Activate mod immediately when another key pressed
 | 
			
		||||
#define AUTO_SHIFT_TIMEOUT 140  // at what point are you holding the key to send a SHIFTED value
 | 
			
		||||
#define RETRO_SHIFT             // Enable retroactive shift
 | 
			
		||||
#define RETRO_TAPPING           // Enable retroactive tapping
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue