diff --git a/autoflash_bothsides.sh b/autoflash_bothsides.sh new file mode 100644 index 00000000..dbedfb1f --- /dev/null +++ b/autoflash_bothsides.sh @@ -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 diff --git a/autoflash_bothsides_corrected.sh b/autoflash_bothsides_corrected.sh new file mode 100644 index 00000000..44d41a13 --- /dev/null +++ b/autoflash_bothsides_corrected.sh @@ -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 diff --git a/autoflash_bothsides_optimized.sh b/autoflash_bothsides_optimized.sh new file mode 100644 index 00000000..8d89b583 --- /dev/null +++ b/autoflash_bothsides_optimized.sh @@ -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 diff --git a/qmk_flash_tools/MAPPING_LOGIC.md b/qmk_flash_tools/MAPPING_LOGIC.md new file mode 100644 index 00000000..aada665f --- /dev/null +++ b/qmk_flash_tools/MAPPING_LOGIC.md @@ -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 diff --git a/qmk_flash_tools/QUICK_REFERENCE.md b/qmk_flash_tools/QUICK_REFERENCE.md new file mode 100644 index 00000000..0e559087 --- /dev/null +++ b/qmk_flash_tools/QUICK_REFERENCE.md @@ -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 diff --git a/qmk_flash_tools/README.md b/qmk_flash_tools/README.md new file mode 100644 index 00000000..3fc166b8 --- /dev/null +++ b/qmk_flash_tools/README.md @@ -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. diff --git a/qmk_flash_tools/SOLUTION_SUMMARY.md b/qmk_flash_tools/SOLUTION_SUMMARY.md new file mode 100644 index 00000000..5bb33ec0 --- /dev/null +++ b/qmk_flash_tools/SOLUTION_SUMMARY.md @@ -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! 🎉 diff --git a/qmk_flash_tools/UX_IMPROVEMENT.md b/qmk_flash_tools/UX_IMPROVEMENT.md new file mode 100644 index 00000000..5f81c7ca --- /dev/null +++ b/qmk_flash_tools/UX_IMPROVEMENT.md @@ -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! 🚀 diff --git a/qmk_flash_tools/WORKFLOW.md b/qmk_flash_tools/WORKFLOW.md new file mode 100644 index 00000000..a67dbc05 --- /dev/null +++ b/qmk_flash_tools/WORKFLOW.md @@ -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 diff --git a/qmk_flash_tools/autoflash_modular.sh b/qmk_flash_tools/autoflash_modular.sh new file mode 100644 index 00000000..e1aa6d6d --- /dev/null +++ b/qmk_flash_tools/autoflash_modular.sh @@ -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 "� 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 "$@" diff --git a/qmk_flash_tools/lib/device_detection.sh b/qmk_flash_tools/lib/device_detection.sh new file mode 100644 index 00000000..aa56b511 --- /dev/null +++ b/qmk_flash_tools/lib/device_detection.sh @@ -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" +} diff --git a/qmk_flash_tools/lib/qmk_helpers.sh b/qmk_flash_tools/lib/qmk_helpers.sh new file mode 100644 index 00000000..465ab7cd --- /dev/null +++ b/qmk_flash_tools/lib/qmk_helpers.sh @@ -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 +} diff --git a/qmk_flash_tools/lib/side_mapping.sh b/qmk_flash_tools/lib/side_mapping.sh new file mode 100644 index 00000000..0ad77e1c --- /dev/null +++ b/qmk_flash_tools/lib/side_mapping.sh @@ -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 +} diff --git a/qmk_flash_tools/test/run_all_tests.sh b/qmk_flash_tools/test/run_all_tests.sh new file mode 100644 index 00000000..b92b30b0 --- /dev/null +++ b/qmk_flash_tools/test/run_all_tests.sh @@ -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 "╚═══════════════════════════════════════════════════════════╝" diff --git a/qmk_flash_tools/test/test_device_detection.sh b/qmk_flash_tools/test/test_device_detection.sh new file mode 100644 index 00000000..94e0ea20 --- /dev/null +++ b/qmk_flash_tools/test/test_device_detection.sh @@ -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 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" diff --git a/qmk_flash_tools/test/test_qmk_helpers.sh b/qmk_flash_tools/test/test_qmk_helpers.sh new file mode 100644 index 00000000..f7495066 --- /dev/null +++ b/qmk_flash_tools/test/test_qmk_helpers.sh @@ -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." diff --git a/qmk_flash_tools/test/test_side_mapping.sh b/qmk_flash_tools/test/test_side_mapping.sh new file mode 100644 index 00000000..317fa136 --- /dev/null +++ b/qmk_flash_tools/test/test_side_mapping.sh @@ -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" diff --git a/users/smathev/config.h b/users/smathev/config.h index 8e732574..a0f82935 100644 --- a/users/smathev/config.h +++ b/users/smathev/config.h @@ -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