Add files via upload

This commit is contained in:
smathev 2025-10-09 14:45:23 +02:00 committed by GitHub
commit 0bd86793ac
Failed to generate hash of commit
18 changed files with 3628 additions and 1 deletions

197
autoflash_bothsides.sh Normal file
View file

@ -0,0 +1,197 @@
#!/usr/bin/env bash
# =============================================================================
# QMK Flashing Script for Fingerpunch/Sweeeeep with Liatris (RP2040)
# =============================================================================
# Features:
# - Single prebuilt firmware compilation
# - Automated handedness-aware flashing using uf2-split-left/right targets
# - Robust USB detection across Linux distributions
# - Auto-detection of which side is plugged in based on RP2040 USB serial/Board ID
# - Persistent mapping of USB devices to left/right sides (~/.qmk_rp2040_sides.json)
# - Optional prompting for unknown devices
# - Waits for device mount before flashing
# =============================================================================
set -euo pipefail
# ----------------------
# User-configurable variables
# ----------------------
KEYBOARD="fingerpunch/sweeeeep"
KEYMAP="smathev"
OUTPUT_DIR="$HOME/git_dev/keyboards/latest_firmware"
USB_MOUNT_PATHS=("/media/$USER" "/run/media/$USER" "/mnt")
RP2040_PATTERN="*RP2040*"
USB_WAIT_INTERVAL=0.5
SIDE_MAPPING_FILE="$HOME/.qmk_rp2040_sides.json"
# Ensure mapping file exists
if [[ ! -f "$SIDE_MAPPING_FILE" ]]; then
echo "{}" > "$SIDE_MAPPING_FILE"
fi
# ----------------------
# Function: build_firmware
# Build the firmware once for reuse during flashing
# ----------------------
build_firmware() {
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "🛠 Building firmware once for $KEYBOARD"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
qmk compile -kb "$KEYBOARD" -km "$KEYMAP"
}
# ----------------------
# Function: wait_for_rp2040
# Wait until an RP2040 UF2 device is mounted on any of the configured paths
# ----------------------
wait_for_rp2040() {
echo "⏳ Waiting for RP2040 UF2 device..."
local device=""
while true; do
for path in "${USB_MOUNT_PATHS[@]}"; do
device=$(find "$path" -maxdepth 2 -type d -name "$RP2040_PATTERN" 2>/dev/null | head -n1)
if [[ -n "$device" ]]; then
echo "✅ Found RP2040 device at $device"
echo "$device"
return
fi
done
sleep "$USB_WAIT_INTERVAL"
done
}
# ----------------------
# Function: get_rp2040_usb_serial
# Attempt to get the USB serial number of the RP2040 device
# Returns empty string if unavailable
# ----------------------
get_rp2040_usb_serial() {
local mount_point="$1"
local dev
dev=$(findmnt -n -o SOURCE --target "$mount_point" 2>/dev/null)
if [[ -n "$dev" ]]; then
local sys_path
sys_path=$(readlink -f "/sys/class/block/$(basename "$dev")/device")
if [[ -f "$sys_path/serial" ]]; then
cat "$sys_path/serial"
return
fi
fi
echo ""
}
# ----------------------
# Function: get_rp2040_id
# Extract a unique identifier from the mounted RP2040
# Prefers USB serial, falls back to info_uf2.txt Board ID, then mount path
# ----------------------
get_rp2040_id() {
local mount_point="$1"
local usb_serial
usb_serial=$(get_rp2040_usb_serial "$mount_point")
if [[ -n "$usb_serial" ]]; then
echo "$usb_serial"
elif [[ -f "$mount_point/info_uf2.txt" ]]; then
grep "^Board ID" "$mount_point/info_uf2.txt" | awk -F': ' '{print $2}'
else
basename "$mount_point"
fi
}
# ----------------------
# Function: detect_side
# Determine the left/right side of the plugged-in board
# If unknown, prompt the user and update mapping
# ----------------------
detect_side() {
local mount_point="$1"
local rp_id
rp_id=$(get_rp2040_id "$mount_point")
local side
side=$(jq -r --arg id "$rp_id" '.[$id]' "$SIDE_MAPPING_FILE")
if [[ "$side" == "null" ]]; then
read -rp "Unknown device detected. Which side is this half? [left/right]: " side
side=${side,,}
if [[ "$side" != "left" && "$side" != "right" ]]; then
echo "Invalid input. Defaulting to left."
side="left"
fi
# Save mapping
tmpfile=$(mktemp)
jq --arg id "$rp_id" --arg side "$side" '. + {($id): $side}' "$SIDE_MAPPING_FILE" > "$tmpfile"
mv "$tmpfile" "$SIDE_MAPPING_FILE"
fi
echo "$side"
}
# ----------------------
# Function: flash_side
# Flash the prebuilt firmware to the given side (left/right)
# Waits for device and applies UF2 split target
# ----------------------
flash_side() {
local side="$1"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "🔌 Flashing $side side..."
# Wait for device
local mount_point
mount_point=$(wait_for_rp2040)
# Auto-detect side if unknown
local detected_side
detected_side=$(detect_side "$mount_point")
if [[ "$detected_side" != "$side" ]]; then
echo "⚠️ Detected side '$detected_side' does not match expected side '$side'. Using detected side."
side="$detected_side"
fi
# Flash using prebuilt UF2 split target
qmk flash -kb "$KEYBOARD" -km "$KEYMAP:uf2-split-$side" -f
echo "$side side flashed successfully."
}
# ----------------------
# Function: main
# Main workflow: build firmware and flash both sides
# ----------------------
main() {
build_firmware
# Ask which side to flash first
read -rp "Which side to flash first? [left/right]: " SIDE1
SIDE1=${SIDE1,,}
if [[ "$SIDE1" != "left" && "$SIDE1" != "right" ]]; then
echo "Invalid input. Must be 'left' or 'right'."
exit 1
fi
# Determine second side
SIDE2=$([[ "$SIDE1" == "left" ]] && echo "right" || echo "left")
read -rp "Will you flash the other side afterward? [y/n]: " DO_SECOND
DO_SECOND=${DO_SECOND,,}
# Flash first side
flash_side "$SIDE1"
# Flash second side if requested
if [[ "$DO_SECOND" == "y" ]]; then
echo "Please reset the $SIDE2 half now, then press Enter to continue..."
read -r
flash_side "$SIDE2"
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "🎉 All requested flashing complete!"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
}
# Execute main
main

View file

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

View file

@ -0,0 +1,236 @@
#!/usr/bin/env bash
# =============================================================================
# QMK Auto-Flashing Script for Fingerpunch/Sweeeeep with Liatris (RP2040)
# =============================================================================
# OPTIMIZED VERSION - Features:
# - Single firmware compilation
# - TRUE auto-detection: plug in any side, script detects which it is
# - Automated handedness-aware flashing using uf2-split-left/right bootloader targets
# - Robust USB detection across Linux distributions
# - Persistent mapping of USB devices to left/right sides (~/.qmk_rp2040_sides.json)
# - No need to specify which side first - script figures it out!
# - Waits for device mount before flashing
# =============================================================================
set -euo pipefail
# ----------------------
# User-configurable variables
# ----------------------
KEYBOARD="fingerpunch/sweeeeep"
KEYMAP="smathev"
USB_MOUNT_PATHS=("/media/$USER" "/run/media/$USER" "/mnt")
RP2040_PATTERN="*RP2040*"
USB_WAIT_INTERVAL=0.5
SIDE_MAPPING_FILE="$HOME/.qmk_rp2040_sides.json"
# Ensure mapping file exists
if [[ ! -f "$SIDE_MAPPING_FILE" ]]; then
echo "{}" > "$SIDE_MAPPING_FILE"
fi
# Ensure jq is installed
if ! command -v jq &> /dev/null; then
echo "❌ Error: 'jq' is required but not installed."
echo " Install it with: sudo apt-get install jq"
exit 1
fi
# ----------------------
# Function: build_firmware
# Build the firmware once for reuse during flashing
# ----------------------
build_firmware() {
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "🛠 Building firmware for $KEYBOARD"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
qmk compile -kb "$KEYBOARD" -km "$KEYMAP"
echo "✅ Firmware compiled successfully"
echo ""
}
# ----------------------
# Function: wait_for_rp2040
# Wait until an RP2040 UF2 device is mounted on any of the configured paths
# ----------------------
wait_for_rp2040() {
echo "⏳ Waiting for RP2040 UF2 device..."
local device=""
while true; do
for path in "${USB_MOUNT_PATHS[@]}"; do
device=$(find "$path" -maxdepth 2 -type d -name "$RP2040_PATTERN" 2>/dev/null | head -n1)
if [[ -n "$device" ]]; then
echo "✅ Found RP2040 device at $device"
echo "$device"
return
fi
done
sleep "$USB_WAIT_INTERVAL"
done
}
# ----------------------
# Function: get_rp2040_usb_serial
# Attempt to get the USB serial number of the RP2040 device
# Returns empty string if unavailable
# ----------------------
get_rp2040_usb_serial() {
local mount_point="$1"
local dev
dev=$(findmnt -n -o SOURCE --target "$mount_point" 2>/dev/null || echo "")
if [[ -n "$dev" ]]; then
local sys_path
sys_path=$(readlink -f "/sys/class/block/$(basename "$dev")/device" 2>/dev/null || echo "")
if [[ -f "$sys_path/serial" ]]; then
cat "$sys_path/serial"
return
fi
fi
echo ""
}
# ----------------------
# Function: get_rp2040_id
# Extract a unique identifier from the mounted RP2040
# Prefers USB serial, falls back to info_uf2.txt Board ID, then mount path
# ----------------------
get_rp2040_id() {
local mount_point="$1"
local usb_serial
usb_serial=$(get_rp2040_usb_serial "$mount_point")
if [[ -n "$usb_serial" ]]; then
echo "$usb_serial"
elif [[ -f "$mount_point/INFO_UF2.TXT" ]]; then
grep -i "^Board-ID" "$mount_point/INFO_UF2.TXT" | awk -F': ' '{print $2}' | tr -d '\r\n '
elif [[ -f "$mount_point/info_uf2.txt" ]]; then
grep -i "^Board-ID" "$mount_point/info_uf2.txt" | awk -F': ' '{print $2}' | tr -d '\r\n '
else
basename "$mount_point"
fi
}
# ----------------------
# Function: detect_side
# Determine the left/right side of the plugged-in board
# If unknown, prompt the user and update mapping
# ----------------------
detect_side() {
local mount_point="$1"
local rp_id
rp_id=$(get_rp2040_id "$mount_point")
echo " Device ID: $rp_id"
local side
side=$(jq -r --arg id "$rp_id" '.[$id] // "null"' "$SIDE_MAPPING_FILE")
if [[ "$side" == "null" || -z "$side" ]]; then
echo ""
echo "⚠️ Unknown device detected!"
read -rp " Which side is this keyboard half? [left/right]: " side
side=${side,,}
if [[ "$side" != "left" && "$side" != "right" ]]; then
echo " Invalid input. Defaulting to left."
side="left"
fi
# Save mapping
tmpfile=$(mktemp)
jq --arg id "$rp_id" --arg side "$side" '. + {($id): $side}' "$SIDE_MAPPING_FILE" > "$tmpfile"
mv "$tmpfile" "$SIDE_MAPPING_FILE"
echo " ✅ Saved mapping: $rp_id$side"
fi
echo "$side"
}
# ----------------------
# Function: flash_side_auto
# Automatically detect and flash whichever keyboard half is plugged in
# ----------------------
flash_side_auto() {
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "🔌 Waiting for keyboard half in bootloader mode..."
echo " (Double-tap RESET button on Liatris controller)"
echo ""
# Wait for device
local mount_point
mount_point=$(wait_for_rp2040)
# Auto-detect which side
local detected_side
detected_side=$(detect_side "$mount_point")
echo ""
echo "🎯 Detected: $detected_side side"
echo "📤 Flashing as $detected_side..."
echo ""
# Flash using the detected side's bootloader target
qmk flash -kb "$KEYBOARD" -km "$KEYMAP" -bl "uf2-split-$detected_side"
echo ""
echo "$detected_side side flashed successfully!"
echo ""
}
# ----------------------
# Function: main
# Main workflow: build firmware and flash both sides automatically
# ----------------------
main() {
echo ""
echo "╔═══════════════════════════════════════════════════════════╗"
echo "║ QMK Auto-Flash: Fingerpunch Sweeeeep + Liatris (RP2040) ║"
echo "╚═══════════════════════════════════════════════════════════╝"
echo ""
# Build firmware once
build_firmware
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "🚀 Ready to flash!"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "Instructions:"
echo " 1. Enter bootloader on FIRST keyboard half (either side)"
echo " 2. Script will auto-detect which side it is"
echo " 3. After first side completes, do the same for the OTHER half"
echo ""
read -rp "Press Enter when ready to start..."
echo ""
# Flash first side (whichever is plugged in)
flash_side_auto
# Ask if user wants to flash the second side
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
read -rp "Flash the other keyboard half now? [y/n]: " DO_SECOND
DO_SECOND=${DO_SECOND,,}
echo ""
if [[ "$DO_SECOND" == "y" ]]; then
echo "Please:"
echo " 1. Unplug the keyboard half you just flashed"
echo " 2. Plug in the OTHER half"
echo " 3. Enter bootloader mode (double-tap RESET)"
echo ""
read -rp "Press Enter when ready..."
echo ""
# Flash second side (auto-detected)
flash_side_auto
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "🎉 Flashing complete!"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "✓ Handedness has been set in EEPROM"
echo "✓ Future firmware updates can be flashed to both sides"
echo "✓ Handedness will persist across updates"
echo ""
}
# Execute main
main

View file

@ -0,0 +1,200 @@
# Device Mapping Logic - Simple Explanation
## The Three States
```
State 1: EMPTY → Learn both devices
State 2: PARTIAL → Complete the mapping
State 3: COMPLETE → Only allow known devices
```
## State Transitions
```
┌─────────────┐
│ EMPTY │ No devices mapped
│ (0/2) │
└──────┬──────┘
│ Flash first device
┌─────────────┐
│ PARTIAL │ One device mapped
│ (1/2) │
└──────┬──────┘
│ Flash second device
┌─────────────┐
│ COMPLETE │ Both devices mapped
│ (2/2) │ ← Stays here forever
└─────────────┘
```
## Behavior by State
### State 1: EMPTY (0/2 devices)
```
Action: LEARN
Rule: Save devices as user specifies
Result: Build initial mapping
```
### State 2: PARTIAL (1/2 devices)
```
Action: COMPLETE
Rule: Known device must match, unknown must be the missing side
Result: Complete mapping OR error if ambiguous
```
### State 3: COMPLETE (2/2 devices)
```
Action: VERIFY
Rule: Only known devices allowed, must match expected side
Result: Continue OR error immediately
```
## Decision Tree
```
Device Detected
┌────────────────┐
│ Mapping State? │
└───┬────┬───┬───┘
│ │ │
▼ ▼ ▼
Empty Part Comp
│ │ │
│ │ ▼
│ │ ┌──────────────┐
│ │ │ Device Known?│
│ │ └──┬───────┬───┘
│ │ │ │
│ │ NO YES
│ │ │ │
│ │ ▼ ▼
│ │ REJECT Match?
│ │ │
│ │ ┌─────┴─────┐
│ │ YES NO
│ │ │ │
│ │ ▼ ▼
│ │ CONTINUE MISMATCH
│ │ OPTIONS
│ │
│ ▼
│ ┌──────────────┐
│ │ Device Known?│
│ └──┬───────┬───┘
│ │ │
│ NO YES
│ │ │
│ ▼ ▼
│ Expected Match?
│ unmapped │
│ side? ┌───┴────┐
│ │ YES NO
│ ▼ │ │
│ ┌───┐ ▼ ▼
│ │YES│CONTINUE ERROR
│ └─┬─┘
│ │NO
│ ▼
│ ERROR
SAVE AS
EXPECTED
```
## Examples
### Example 1: First Time (Empty → Partial → Complete)
```
$ ./autoflash_modular.sh
State: EMPTY (0/2)
You: "left"
Device: ABC123
Action: Save ABC123 → left
New State: PARTIAL (1/2)
You: "Flash right? yes"
Device: XYZ789
Action: Save XYZ789 → right
New State: COMPLETE (2/2)
Mapping: {"ABC123": "left", "XYZ789": "right"}
```
### Example 2: Normal Use (Complete)
```
$ ./autoflash_modular.sh
State: COMPLETE (2/2)
You: "left"
Device: ABC123
Check: ABC123 is mapped to "left" ✅
Action: Continue
You: "right"
Device: XYZ789
Check: XYZ789 is mapped to "right" ✅
Action: Continue
```
### Example 3: Unknown Device (Complete → Rejected)
```
$ ./autoflash_modular.sh
State: COMPLETE (2/2)
You: "left"
Device: UNKNOWN
Check: Not in mapping ❌
Action: REJECT and EXIT
Error: "Unknown device! Expected ABC123 or XYZ789"
```
### Example 4: Partial Mapping - Good
```
State: PARTIAL (1/2) - Only "left" mapped
Scenario A: Known device
You: "left"
Device: ABC123 (known as left)
Action: Continue ✅
Scenario B: Unknown device for unmapped side
You: "right" (unmapped)
Device: UNKNOWN
Action: Save as right ✅
New State: COMPLETE
```
### Example 5: Partial Mapping - Bad
```
State: PARTIAL (1/2) - Only "left" mapped
You: "left" (already mapped)
Device: UNKNOWN
Question: Is this a replacement for left? Or the unmapped right?
Action: ERROR - ambiguous ❌
Message: "Cannot determine! Clear mappings."
```
## Key Principle
**Once both sides are mapped (COMPLETE state), the script becomes protective:**
- ✅ Only the two known devices can be flashed
- ❌ Any unknown device is rejected immediately
- 🛡️ This prevents accidentally flashing the wrong keyboard
**To reset:** `cd qmk_flash_tools && rm device_mappings.json`
## Why This Works
1. **Learning Phase** (Empty/Partial) - Flexible, builds mapping
2. **Protection Phase** (Complete) - Strict, prevents mistakes
3. **Clear Errors** - Always know why something failed
4. **Easy Recovery** - Delete mapping file to restart

View file

@ -0,0 +1,223 @@
# QMK Flash Tools - Quick Reference
## 🚀 Common Commands
### Flash Both Keyboard Sides
```bash
cd qmk_flash_tools
./autoflash_modular.sh
```
### Test Individual Components
```bash
# Test side mapping (no hardware needed)
./test/test_side_mapping.sh
# Test device detection (needs keyboard in bootloader)
./test/test_device_detection.sh
# Test QMK functions
./test/test_qmk_helpers.sh
# Run all tests
./test/run_all_tests.sh
```
### View Saved Mappings
```bash
cd qmk_flash_tools
cat device_mappings.json
# or
jq '.' device_mappings.json
```
### Reset Mappings
```bash
# Reset all
cd qmk_flash_tools
rm device_mappings.json
# Or use the library
source lib/side_mapping.sh
clear_all_mappings
```
## 🔍 Debug Commands
### Check Device Info
```bash
source lib/device_detection.sh
mount_point="/media/$USER/RPI-RP2" # Adjust path
print_device_info "$mount_point"
```
### Manually Test Detection
```bash
source lib/device_detection.sh
source lib/side_mapping.sh
# Wait for device
mount_point=$(wait_for_rp2040)
# Get identifier
device_id=$(get_device_identifier "$mount_point")
echo "Device ID: $device_id"
# Detect side
side=$(detect_side "$device_id")
echo "Side: $side"
```
### Test QMK Commands
```bash
# Check QMK version
qmk --version
# List keymaps
qmk list-keymaps -kb fingerpunch/sweeeeep
# Compile only (no flash)
qmk compile -kb fingerpunch/sweeeeep -km smathev
# Flash with specific bootloader
qmk flash -kb fingerpunch/sweeeeep -km smathev -bl uf2-split-left
```
## 🛠️ Manual Side Mapping
### Add Mapping Manually
```bash
source lib/side_mapping.sh
save_side_mapping "serial:ABC123XYZ" "left"
save_side_mapping "serial:DEF456RST" "right"
```
### Check Specific Device
```bash
source lib/side_mapping.sh
side=$(get_saved_side "serial:ABC123XYZ")
echo "Device is: $side"
```
### List All Mappings
```bash
source lib/side_mapping.sh
list_all_mappings
```
## 📝 Environment Variables
```bash
# Keyboard configuration
export KEYBOARD="fingerpunch/sweeeeep"
export KEYMAP="smathev"
# Device detection
export USB_MOUNT_PATHS=("/media/$USER" "/run/media/$USER" "/mnt")
export RP2040_PATTERN="*RP2040*"
export USB_WAIT_INTERVAL=0.5
# Side mapping file (relative to qmk_flash_tools/)
export SIDE_MAPPING_FILE="./device_mappings.json"
```
## 🐛 Troubleshooting One-Liners
```bash
# Check if RP2040 is mounted
ls /media/$USER/ | grep -i rp2040
# Find all USB devices
lsusb
# Check USB device info
udevadm info --query=property /dev/sdb1 # Adjust device
# Monitor USB events (run in separate terminal)
udevadm monitor
# Check QMK firmware location
qmk config user.qmk_home
# Force clean and rebuild
qmk clean && qmk compile -kb fingerpunch/sweeeeep -km smathev
# Check if jq is installed
jq --version
```
## 📂 File Locations
```
Main script: qmk_flash_tools/autoflash_modular.sh
Libraries: qmk_flash_tools/lib/*.sh
Tests: qmk_flash_tools/test/*.sh
Mapping file: qmk_flash_tools/device_mappings.json
QMK firmware: ~/qmk_firmware/ (or user.qmk_home)
Build output: ~/qmk_firmware/.build/
```
## 🔧 Common Fixes
### Device not appearing
```bash
# Check dmesg for USB events
dmesg | tail -n 20
# Try different USB port
# Try different USB cable (must be data cable, not charge-only)
```
### Wrong side detected
```bash
# Clear and re-learn
source lib/side_mapping.sh
clear_mapping "serial:YOUR_DEVICE_ID"
# Then run autoflash again
```
### Build fails
```bash
# Update QMK
python3 -m pip install --upgrade qmk
# Pull latest QMK firmware
cd ~/qmk_firmware
git pull
# Clean and try again
qmk clean
```
### Permission denied
```bash
# Make scripts executable
chmod +x qmk_flash_tools/*.sh
chmod +x qmk_flash_tools/lib/*.sh
chmod +x qmk_flash_tools/test/*.sh
```
## 📚 Function Quick Reference
### device_detection.sh
- `wait_for_rp2040()` - Wait for device
- `get_usb_serial_from_host(mount)` - Get serial
- `get_usb_device_path(mount)` - Get USB path
- `get_device_identifier(mount)` - Get ID
- `print_device_info(mount)` - Debug info
### side_mapping.sh
- `init_mapping_file()` - Create file
- `save_side_mapping(id, side)` - Save
- `get_saved_side(id)` - Retrieve
- `detect_side(id)` - Auto-detect/prompt
- `list_all_mappings()` - Show all
- `clear_mapping(id)` - Remove one
- `clear_all_mappings()` - Reset
### qmk_helpers.sh
- `build_firmware(kb, km)` - Compile
- `flash_side(kb, km, side)` - Flash with handedness
- `check_qmk_installed()` - Verify QMK
- `verify_keyboard_exists(kb)` - Check KB
- `clean_build()` - Clean

338
qmk_flash_tools/README.md Normal file
View file

@ -0,0 +1,338 @@
# QMK Flash Tools - Modular Edition
Automated flashing tools for QMK split keyboards with RP2040 controllers (like Liatris).
## 📁 Structure
```
qmk_flash_tools/
├── autoflash_modular.sh # Main flashing script
├── lib/ # Reusable library modules
│ ├── device_detection.sh # USB device detection functions
│ ├── side_mapping.sh # Device-to-side mapping storage
│ └── qmk_helpers.sh # QMK build/flash wrappers
├── test/ # Standalone test scripts
│ ├── test_device_detection.sh
│ ├── test_side_mapping.sh
│ └── test_qmk_helpers.sh
└── README.md # This file
```
## 🚀 Quick Start
### 1. Flash Both Sides
```bash
cd qmk_flash_tools
chmod +x autoflash_modular.sh
./autoflash_modular.sh
```
The script will:
1. Build firmware once
2. **First time only:** Ask which side you'll flash (left/right)
3. **Subsequent runs:** Auto-detect which side is plugged in
4. Wait for you to enter bootloader
5. Verify and flash with correct handedness
6. Repeat for the other side
### 2. Test Individual Components
```bash
# Test device detection
cd test
chmod +x test_device_detection.sh
./test_device_detection.sh
# Test side mapping
chmod +x test_side_mapping.sh
./test_side_mapping.sh
# Test QMK helpers
chmod +x test_qmk_helpers.sh
./test_qmk_helpers.sh
```
## 🔧 Configuration
Edit the top of `autoflash_modular.sh`:
```bash
KEYBOARD="fingerpunch/sweeeeep" # Your keyboard
KEYMAP="smathev" # Your keymap
USB_MOUNT_PATHS=(...) # Where USB drives mount
SIDE_MAPPING_FILE="..." # Device mappings (defaults to ./device_mappings.json)
```
## 📚 Library Documentation
### device_detection.sh
Functions for detecting RP2040 devices via host USB information.
**Key Functions:**
- `wait_for_rp2040()` - Wait for device to enter bootloader
- `get_usb_serial_from_host(mount_point)` - Get USB serial from host
- `get_usb_device_path(mount_point)` - Get USB port location
- `get_device_identifier(mount_point)` - Get best available ID
- `print_device_info(mount_point)` - Debug info
**Example:**
```bash
source lib/device_detection.sh
mount_point=$(wait_for_rp2040)
device_id=$(get_device_identifier "$mount_point")
echo "Device: $device_id"
```
### side_mapping.sh
Functions for storing and retrieving which device is left/right.
**Key Functions:**
- `init_mapping_file()` - Create mapping file if needed
- `save_side_mapping(device_id, side)` - Save mapping
- `get_saved_side(device_id)` - Retrieve saved side
- `detect_side(device_id)` - Get side (prompts if unknown)
- `list_all_mappings()` - Show all saved mappings
- `clear_mapping(device_id)` - Remove a mapping
- `clear_all_mappings()` - Reset all mappings
**Example:**
```bash
source lib/side_mapping.sh
export SIDE_MAPPING_FILE="./device_mappings.json"
save_side_mapping "serial:ABC123" "left"
side=$(get_saved_side "serial:ABC123")
echo "Side: $side"
```
### qmk_helpers.sh
Wrapper functions for QMK CLI commands.
**Key Functions:**
- `check_qmk_installed()` - Verify QMK is available
- `build_firmware(keyboard, keymap)` - Compile firmware
- `flash_side(keyboard, keymap, side)` - Flash with handedness
- `flash_with_bootloader(keyboard, keymap, bootloader)` - Flash with specific bootloader
- `verify_keyboard_exists(keyboard)` - Check keyboard definition
- `clean_build()` - Clean build artifacts
**Example:**
```bash
source lib/qmk_helpers.sh
check_qmk_installed || exit 1
build_firmware "fingerpunch/sweeeeep" "smathev"
flash_side "fingerpunch/sweeeeep" "smathev" "left"
```
## 🧪 Testing Workflow
### Test Device Detection
1. **Without device:**
```bash
./test/test_device_detection.sh
# Shows "no device found", good for baseline
```
2. **With device:**
```bash
# Enter bootloader mode on keyboard
./test/test_device_detection.sh
# Shows USB serial, path, and identifier
```
### Test Side Mapping
```bash
./test/test_side_mapping.sh
# Runs comprehensive tests:
# - Create mapping file
# - Save/retrieve mappings
# - Clear mappings
# - Interactive prompt (optional)
```
### Test QMK Helpers
```bash
./test/test_qmk_helpers.sh
# Tests:
# - QMK installation check
# - Keyboard verification
# - Build (optional)
# - Function signatures
```
## 🐛 Troubleshooting
### Device not detected
```bash
# Check if device appears
ls /media/$USER/
# Should see RPI-RP2 or similar
# Run device detection test
./test/test_device_detection.sh
```
### Can't identify device
The script uses these methods in order:
1. USB serial number (most reliable)
2. USB physical port path
3. Mount point name (fallback)
Check which method worked:
```bash
source lib/device_detection.sh
mount_point="/media/$USER/RPI-RP2"
print_device_info "$mount_point"
```
### Side mismatch detected
If you see a mismatch warning:
**Option 1: Exit and plug in correct side (safest)**
- Choose `[e]` to exit
- Unplug the keyboard
- Plug in the correct side
- Run the script again
**Option 2: Update the mapping**
- Choose `[c]` to clear old mapping and save new one
- Use this if you know the old mapping was wrong
**Option 3: Force flash (dangerous!)**
- Choose `[f]` to flash anyway
- Only use if you're absolutely certain
- May result in swapped left/right behavior
Or manually reset mappings:
```bash
source lib/side_mapping.sh
clear_mapping "serial:ABC123" # Use your device ID
```
Or reset all mappings:
```bash
cd qmk_flash_tools
rm device_mappings.json
```
### Build fails
```bash
# Test QMK directly
qmk compile -kb fingerpunch/sweeeeep -km smathev
# Check keyboard exists
qmk list-keymaps -kb fingerpunch/sweeeeep
```
## 💡 Advanced Usage
### Use in other scripts
```bash
#!/usr/bin/env bash
source /path/to/qmk_flash_tools/lib/device_detection.sh
source /path/to/qmk_flash_tools/lib/side_mapping.sh
# Your custom logic here
mount_point=$(wait_for_rp2040)
device_id=$(get_device_identifier "$mount_point")
side=$(detect_side "$device_id")
echo "Detected $side side"
```
### Custom keyboard configuration
```bash
# Set environment variables before running
export KEYBOARD="your/keyboard"
export KEYMAP="your_keymap"
./autoflash_modular.sh
```
### Different mapping file
```bash
export SIDE_MAPPING_FILE="/tmp/my_test_mappings.json"
./autoflash_modular.sh
```
## 📋 Requirements
- **bash** - Shell interpreter
- **qmk** - QMK CLI (`python3 -m pip install qmk`)
- **jq** - JSON processor (`sudo apt-get install jq`)
- **findmnt** - Usually included with util-linux
- **udevadm** - Usually included with systemd
## 🔒 File Permissions
Make scripts executable:
```bash
chmod +x autoflash_modular.sh
chmod +x test/*.sh
chmod +x lib/*.sh
```
## 📝 How Device Mapping Works
The script intelligently handles three states:
### 🟢 Empty Mapping (First Time)
- **No devices mapped yet**
- **Asks:** "Which side will you flash first?"
- Script learns both sides as you flash them
- First device = saved as what you specify (left/right)
- Second device = saved as the other side
- Result: Complete mapping of both sides
### 🟡 Partial Mapping (One Side Known)
- **One device mapped, one unknown**
- **Auto-detects:** No asking needed!
- If you plug in the known device → "Detected: left side"
- If you plug in unknown device → "Detected: right side (inferred)"
- Result: Completes mapping automatically
### 🔴 Complete Mapping (Both Sides Known)
- **Both devices are mapped**
- **Auto-detects:** Fully automatic!
- Plug in any side → "Detected: left side" or "Detected: right side"
- Only the two known devices are allowed
- Unknown device → **Rejected immediately** (safety feature)
- Result: Fast, automatic flashing with full protection
### Why This Matters
- **First run**: Asks which side (one-time setup) ✅
- **Normal use**: Fully automatic - just plug and flash! ✅
- **Protection**: Unknown devices rejected - can't flash wrong keyboard ✅
- **Smart**: Knows when to ask vs. when to auto-detect 🧠
## 📝 Additional Notes
- **Input timing**: You're asked which side BEFORE entering bootloader (so you can still type!)
- **EEPROM wipe**: Liatris overwrites EEPROM on flash, so we use HOST USB info
- **Board-ID**: INFO_UF2.TXT is NOT unique per device, don't rely on it
- **USB serial**: Burned into RP2040 chip, persists even when EEPROM wiped
## 🆘 Support
Issues? Check:
1. Run test scripts to isolate the problem
2. Check device detection with `print_device_info()`
3. Verify mappings with `list_all_mappings()`
4. Test QMK commands directly: `qmk compile -kb ... -km ...`
## 📄 License
Same as your QMK userspace configuration.

View file

@ -0,0 +1,164 @@
# Solution Summary: Ask BEFORE Bootloader
## The Problem You Identified 🎯
> "Once I enter the bootloader I can't use the device - so I need to input the device-side beforehand."
**Absolutely correct!** This is a critical UX issue that the original design missed.
## The Solution ✅
### New Workflow Order:
1. **ASK which side** (while keyboard still works!)
2. **WAIT for bootloader** (user enters bootloader mode)
3. **DETECT device** (get USB serial from host)
4. **VERIFY match** (does device match expected side?)
5. **FLASH** (with correct handedness)
### Code Changes Made:
#### 1. New Function: `detect_side_with_expected()`
**Location:** `lib/side_mapping.sh`
```bash
detect_side_with_expected(device_id, expected_side)
```
This function:
- Takes the **expected** side (what user said)
- Checks if device is in mappings
- **First time:** Saves device as expected side ✅
- **Matches:** Continues automatically ✅
- **Mismatch:** Gives user options ⚠️
#### 2. Mismatch Handling
When device doesn't match expected:
```
⚠️ WARNING: SIDE MISMATCH DETECTED
Expected: left side
Saved mapping says: right side
What would you like to do?
[e] Exit safely (recommended)
[c] Clear this mapping and save as left
[f] Force flash as left anyway (DANGEROUS)
```
#### 3. Updated Main Script
**Location:** `autoflash_modular.sh`
New function:
```bash
flash_side_with_verification(expected_side)
```
Changes to workflow:
```bash
# OLD: Asked AFTER device detected
flash_side_auto() # User can't type in bootloader!
# NEW: Ask BEFORE device connected
read -p "Which side will you flash first? [left/right]:"
flash_side_with_verification("left") # ✅ Can type!
```
## Complete Flow Example 📋
### First Run:
```bash
$ ./autoflash_modular.sh
Which side will you flash first? [left/right]: left ← Can type!
Please enter bootloader on the left half:
Press Enter when ready...
[User enters bootloader, presses Enter]
Waiting for device...
Found device: serial:ABC123
New device detected (first time setup)
✅ Saving as left side
Flashing: left side
✅ left side flashed successfully!
Flash the right side now? [y/n]: y
Please enter bootloader on the right half:
Press Enter when ready...
[User enters bootloader, presses Enter]
Waiting for device...
Found device: serial:XYZ789
New device detected (first time setup)
✅ Saving as right side
Flashing: right side
✅ right side flashed successfully!
🎉 Flashing complete!
```
### Subsequent Runs (Auto-verified):
```bash
$ ./autoflash_modular.sh
Which side will you flash first? [left/right]: left
Please enter bootloader on the left half:
[User enters bootloader]
Found device: serial:ABC123
✅ Confirmed: This is the left side (matches saved mapping)
Flashing: left side
✅ Done!
```
### Mismatch Scenario:
```bash
Which side will you flash first? [left/right]: left
[User accidentally plugs in RIGHT keyboard]
Found device: serial:XYZ789
⚠️ WARNING: SIDE MISMATCH DETECTED
Expected: left side
Saved mapping says: right side
Your choice [e/c/f]: e
❌ Exiting safely. Please plug in the correct keyboard half.
```
## Why This Works 🎯
**User can type** - Asked before bootloader
**Safety first** - Verifies device matches
**Clear errors** - Obvious when wrong side plugged in
**Flexible** - Can update mappings if needed
**Persistent** - Remembers devices forever
## Files Modified
- `lib/side_mapping.sh` - Added `detect_side_with_expected()`
- `autoflash_modular.sh` - Changed to ask first, then verify
- `README.md` - Updated workflow documentation
- `WORKFLOW.md` - New visual flow diagrams
## Testing
Test the new flow:
```bash
# Test mismatch handling
./test/test_side_mapping.sh
# Test full workflow (needs hardware)
./autoflash_modular.sh
```
Your insight was **absolutely correct** - asking before bootloader is the only way to make this work! 🎉

View file

@ -0,0 +1,190 @@
# User Experience Comparison
## Old Behavior (Always Ask)
```
Run 1 (First time):
───────────────────
Script: "Which side first? [left/right]:"
You: "left"
[Flash left]
Script: "Flash right? [y/n]:"
You: "y"
[Flash right]
Run 2 (Second time):
────────────────────
Script: "Which side first? [left/right]:" ← Asked again!
You: "left"
[Flash left]
Script: "Flash right? [y/n]:"
You: "y"
[Flash right]
Run 3, 4, 5...
──────────────
Same thing every time - always asking! 😕
```
## New Behavior (Smart Auto-Detect)
```
Run 1 (First time - Empty mapping):
────────────────────────────────────
Script: "Which side first? [left/right]:" ← Only asked first time
You: "left"
[Flash left]
Script: "Flash other side? [y/n]:"
You: "y"
[Flash right]
Run 2 (Subsequent - Complete mapping):
───────────────────────────────────────
Script: "Plug in a keyboard half and enter bootloader..."
You: [Plugs in left half]
Script: "🎯 Auto-detected: left side" ← No asking!
[Flash left]
Script: "Flash other side? [y/n]:"
You: "y"
You: [Plugs in right half]
Script: "🎯 Auto-detected: right side" ← No asking!
[Flash right]
Run 3, 4, 5...
──────────────
Always auto-detects - never asks again! 🎉
```
## Workflow Comparison
### First Time Setup (Empty → Complete)
**Old:**
```
Ask → Flash left → Ask → Flash right
```
**New:**
```
Ask once → Flash left → Flash right (inferred)
```
### Normal Use (Complete mapping)
**Old:**
```
Ask every time → Flash
```
**New:**
```
Auto-detect → Flash ← No asking!
```
## Decision Logic
```
┌─────────────────────┐
│ Check mapping state │
└──────────┬──────────┘
┌─────┴──────┐
│ │
▼ ▼
Empty Partial/Complete
│ │
▼ ▼
📝 ASK 🎯 AUTO-DETECT
```
## User Benefits
### Empty Mapping (First Time)
**Asks** - Needs to learn your devices
**Simple** - Just answer "left" or "right"
**One-time** - Only happens once
### Partial Mapping (Learning Second Device)
**Smart** - Knows which device is which
**Infers** - Unknown must be the unmapped side
**Automatic** - No asking needed
### Complete Mapping (Normal Use)
**Instant** - Recognizes device immediately
**No questions** - Fully automatic
**Protected** - Rejects unknown devices
## Key Insight
**User knows their hardware!**
- If they replaced a controller → They know to clear mappings
- If mapping exists → Trust it and auto-detect
- Only ask when necessary (empty mapping)
## Example Sessions
### Session 1: Brand New Setup
```
$ ./autoflash_modular.sh
📝 First time setup - learning your keyboard halves
Which side will you flash first? [left/right]: left
Plug in left half and enter bootloader...
✅ Saved as left side
✅ left side flashed!
Flash the right side now? [y/n]: y
Plug in right half and enter bootloader...
✅ Saved as right side (completes mapping)
✅ right side flashed!
🎉 Complete! Mapping saved.
```
### Session 2: Normal Reflashing
```
$ ./autoflash_modular.sh
✅ Device mapping exists - auto-detection enabled
Plug in a keyboard half and enter bootloader...
🎯 Auto-detected: left side (known device)
✅ left side flashed!
Flash the other keyboard half now? [y/n]: y
Plug in other half and enter bootloader...
🎯 Auto-detected: right side (known device)
✅ right side flashed!
🎉 Done!
```
### Session 3: One Side Only
```
$ ./autoflash_modular.sh
✅ Device mapping exists - auto-detection enabled
Plug in a keyboard half and enter bootloader...
🎯 Auto-detected: right side (known device)
✅ right side flashed!
Flash the other keyboard half now? [y/n]: n
✅ Done!
```
## Summary
| Scenario | Old Behavior | New Behavior |
|----------|-------------|--------------|
| First time | Ask | Ask (once) |
| Second time | Ask | Auto-detect |
| Every time after | Ask | Auto-detect |
| User input needed | Always | Only first run |
| Speed | Slow | Fast |
| Convenience | Low | High |
**Result:** Ask once, auto-detect forever! 🚀

200
qmk_flash_tools/WORKFLOW.md Normal file
View file

@ -0,0 +1,200 @@
# Workflow Diagrams
## Standard Flashing Flow
```
┌────────────────────────────────────────────────────────────┐
│ START: Run ./autoflash_modular.sh │
└───────────────────┬────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────┐
│ Build firmware once │
└───────────────────┬────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────┐
│ PROMPT: "Which side will you flash first? [left/right]:" │
│ USER INPUT: "left" ◄── Can type! Not in bootloader yet │
└───────────────────┬────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────┐
│ Script: "Enter bootloader mode on the LEFT half..." │
│ Script: "Press Enter when ready..." │
│ USER: Double-taps RESET on LEFT keyboard │
│ USER: Presses Enter │
└───────────────────┬────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────┐
│ Script: Waiting for RP2040 device... │
│ Script: Found device at /media/user/RPI-RP2 │
└───────────────────┬────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────┐
│ Script: Get device identifier (e.g., serial:ABC123) │
└───────────────────┬────────────────────────────────────────┘
┌──────────┴───────────┐
│ │
▼ ▼
┌─────────┐ ┌──────────────┐
│ First │ │ Subsequent │
│ Time │ │ Run │
└────┬────┘ └──────┬───────┘
│ │
│ ▼
│ ┌──────────────────────────────┐
│ │ Device in mappings? │
│ └────┬─────────────────┬───────┘
│ │ │
│ ▼ ▼
│ ┌────────────┐ ┌────────────┐
│ │ Matches? │ │ Mismatch! │
│ │ ✅ Yes │ │ ⚠️ No │
│ └─────┬──────┘ └─────┬──────┘
│ │ │
▼ │ │
┌────────────────────┐ │ │
│ Save as LEFT │ │ │
└────────┬───────────┘ │ │
│ │ │
│ ▼ ▼
│ ┌────────────┐ ┌─────────────────────┐
│ │ Continue │ │ PROMPT USER: │
│ │ flashing │ │ [e] Exit │
│ └─────┬──────┘ │ [c] Clear & update │
│ │ │ [f] Force flash │
│ │ └──────┬──────────────┘
│ │ │
└─────────────────┴──────────────────┘
┌────────────────────────────────────────────────────────────┐
│ Flash LEFT side with uf2-split-left │
└───────────────────┬────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────┐
│ ✅ LEFT side flashed successfully! │
└───────────────────┬────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────┐
│ PROMPT: "Flash the RIGHT side now? [y/n]:" │
└───────────────────┬────────────────────────────────────────┘
┌──────────┴───────────┐
│ │
▼ ▼
┌─────────┐ ┌──────────┐
│ Yes │ │ No │
└────┬────┘ └────┬─────┘
│ │
│ ▼
│ ┌────────────┐
│ │ DONE │
│ └────────────┘
(Repeat process for RIGHT side)
```
## Mismatch Handling Detail
```
┌────────────────────────────────────────────────────────────┐
│ USER said: "left" │
│ Device detected: serial:ABC123 │
│ Mapping says: "right" │
└───────────────────┬────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────┐
│ ⚠️ WARNING: SIDE MISMATCH DETECTED │
│ │
│ Expected: left side │
│ Saved mapping says: right side │
│ │
│ This means either: │
│ 1. You plugged in the WRONG keyboard half │
│ 2. The saved mapping is incorrect │
│ │
│ What would you like to do? │
│ [e] Exit safely (recommended) │
│ [c] Clear this mapping and save as left │
│ [f] Force flash as left anyway (DANGEROUS) │
└───────────────────┬────────────────────────────────────────┘
┌──────────┼──────────┐
│ │ │
▼ ▼ ▼
┌────────┐ ┌────────┐ ┌──────────────┐
│ [e] │ │ [c] │ │ [f] │
│ Exit │ │ Clear │ │ Force │
└───┬────┘ └───┬────┘ └──────┬───────┘
│ │ │
│ │ ▼
│ │ ┌──────────────────┐
│ │ │ "Are you sure?" │
│ │ │ [yes/no] │
│ │ └────┬─────────────┘
│ │ │
│ │ ┌──────┴──────┐
│ │ │ │
│ │ ▼ ▼
│ │ ┌────┐ ┌────────┐
│ │ │yes │ │no │
│ │ └─┬──┘ └───┬────┘
│ │ │ │
│ ▼ ▼ │
│ ┌───────────┐ │
│ │ Update │ │
│ │ mapping & │ │
│ │ continue │ │
│ └─────┬─────┘ │
│ │ │
└────────────┴────────────────┘
┌────────────┐
│ Exit 1 │
└────────────┘
```
## First Time Setup
```
Run 1: First Device
───────────────────
User: "left"
Device: serial:ABC123
Mapping: {} (empty)
Result: ✅ Save serial:ABC123 → left
Run 1: Second Device
────────────────────
User: "right"
Device: serial:XYZ789
Mapping: {"serial:ABC123": "left"}
Result: ✅ Save serial:XYZ789 → right
Run 2+: Known Devices
─────────────────────
User: "left"
Device: serial:ABC123
Mapping: {"serial:ABC123": "left", "serial:XYZ789": "right"}
Result: ✅ Match! Continue automatically
```
## Why This Flow Works
1. **User can type** - Asked BEFORE entering bootloader
2. **Safety first** - Verifies device matches expectation
3. **Clear on mismatch** - User knows exactly what's wrong
4. **Flexible** - Can update mappings or exit safely
5. **Persistent** - Only asks once, remembers forever

View file

@ -0,0 +1,319 @@
#!/usr/bin/env bash
# =============================================================================
# QMK Auto-Flashing Script (Modular Version)
# =============================================================================
# For: Fingerpunch/Sweeeeep with Liatris (RP2040)
#
# Features:
# - Intelligent device detection with three-state verification
# - Learns devices on first run, verifies on subsequent runs
# - Rejects unknown devices once mapping is complete (safety)
# - Uses HOST USB information (reliable even when EEPROM is wiped)
# - Modular design with separate testable libraries
# =============================================================================
set -euo pipefail
# Get the directory where this script is located
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LIB_DIR="$SCRIPT_DIR/lib"
# Configuration
KEYBOARD="fingerpunch/sweeeeep"
KEYMAP="smathev"
export USB_MOUNT_PATHS=("/media/$USER" "/run/media/$USER" "/mnt")
export RP2040_PATTERN="*RP2040*"
export USB_WAIT_INTERVAL=0.5
export SIDE_MAPPING_FILE="$SCRIPT_DIR/device_mappings.json"
# Source library modules
source "$LIB_DIR/device_detection.sh"
source "$LIB_DIR/side_mapping.sh"
source "$LIB_DIR/qmk_helpers.sh"
# ----------------------
# Function: flash_side_with_verification
# Ask user which side they're flashing, then verify device matches
# ----------------------
flash_side_with_verification() {
local expected_side="$1"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "<EFBFBD> Preparing to flash: $expected_side side"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "Please enter bootloader mode on the $expected_side half:"
echo " • Double-tap RESET button on Liatris controller"
echo " • Wait for RPI-RP2 drive to appear"
echo ""
read -rp "Press Enter when you've entered bootloader mode..."
echo ""
echo "⏳ Waiting for device..."
# Wait for device to be mounted
local mount_point
mount_point=$(wait_for_rp2040)
echo ""
# Get unique device identifier from HOST
local device_id
device_id=$(get_device_identifier "$mount_point")
# Verify device matches expected side
local verified_side
verified_side=$(detect_side_with_expected "$device_id" "$expected_side")
echo ""
echo "🎯 Flashing: $verified_side side"
echo ""
# Flash using the verified side
if flash_side "$KEYBOARD" "$KEYMAP" "$verified_side"; then
echo ""
echo "$verified_side side flashed successfully!"
echo ""
return 0
else
echo ""
echo "❌ Failed to flash $verified_side side"
echo ""
return 1
fi
}
# ----------------------
# Function: flash_side_auto_detect
# Auto-detect which side is plugged in (no asking)
# Used when mapping exists (partial or complete)
# ----------------------
flash_side_auto_detect() {
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "🔌 Plug in a keyboard half and enter bootloader mode"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "Instructions:"
echo " • Plug in the keyboard half you want to flash"
echo " • Double-tap RESET button on Liatris controller"
echo " • Wait for RPI-RP2 drive to appear"
echo ""
read -rp "Press Enter when you've entered bootloader mode..."
echo ""
echo "⏳ Waiting for device..."
# Wait for device to be mounted
local mount_point
mount_point=$(wait_for_rp2040)
echo ""
# Get unique device identifier from HOST
local device_id
device_id=$(get_device_identifier "$mount_point")
# Get saved side (if exists)
local saved_side
saved_side=$(get_saved_side "$device_id" 2>/dev/null)
local detected_side
if [[ -n "$saved_side" ]]; then
# Device is known - use saved mapping
echo " 🎯 Auto-detected: $saved_side side (known device)" >&2
detected_side="$saved_side"
else
# Device is unknown - infer from mapping state
local mapping_state
mapping_state=$(get_mapping_state)
if [[ "$mapping_state" == "partial" ]]; then
# Partial mapping - this must be the unmapped side
local unmapped_side
unmapped_side=$(get_unmapped_side)
echo " 🎯 Auto-detected: $unmapped_side side (inferred from partial mapping)" >&2
detected_side="$unmapped_side"
# Save this device
echo " 📝 Saving device mapping..." >&2
save_side_mapping "$device_id" "$detected_side"
else
# Complete mapping but unknown device - this should error
echo "" >&2
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
echo "❌ ERROR: UNKNOWN DEVICE (COMPLETE MAPPING)" >&2
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
echo "" >&2
echo "Detected device: $device_id" >&2
echo "Expected one of the known devices:" >&2
get_mapped_devices | tr ' ' '\n' | while read dev; do
local side=$(get_saved_side "$dev")
echo " - $dev ($side side)" >&2
done
echo "" >&2
echo "Both sides are already mapped!" >&2
echo "If you replaced a controller, clear mappings:" >&2
echo " rm $SIDE_MAPPING_FILE" >&2
return 1
fi
fi
echo ""
echo "🎯 Flashing: $detected_side side"
echo ""
# Flash using the detected side
if flash_side "$KEYBOARD" "$KEYMAP" "$detected_side"; then
echo ""
echo "$detected_side side flashed successfully!"
echo ""
return 0
else
echo ""
echo "❌ Failed to flash $detected_side side"
echo ""
return 1
fi
}
# ----------------------
# Function: main
# Main workflow
# ----------------------
main() {
echo ""
echo "╔═══════════════════════════════════════════════════════════╗"
echo "║ QMK Auto-Flash: Fingerpunch Sweeeeep + Liatris (RP2040) ║"
echo "╚═══════════════════════════════════════════════════════════╝"
echo ""
echo "This script will:"
echo " • Build firmware once"
echo " • Auto-detect which keyboard half you plug in"
echo " • Flash the correct handedness (left/right)"
echo " • Remember your devices for future flashing"
echo ""
echo "Note: On first run, you'll be asked to identify each side."
echo " After that, detection is fully automatic!"
echo ""
# Check dependencies
if ! check_qmk_installed; then
exit 1
fi
if ! check_jq_installed; then
exit 1
fi
# Initialize mapping file
init_mapping_file
# Build firmware once
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
if ! build_firmware "$KEYBOARD" "$KEYMAP"; then
echo "❌ Build failed. Exiting."
exit 1
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "🚀 Ready to flash!"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
# Check mapping state to determine if we need to ask
local mapping_state
mapping_state=$(get_mapping_state)
# -----------------------------------------------------------------
# EMPTY MAPPING: Must ask user which side (learning mode)
# -----------------------------------------------------------------
if [[ "$mapping_state" == "empty" ]]; then
echo "📝 First time setup - learning your keyboard halves"
echo ""
# Ask which side to flash first (BEFORE entering bootloader)
local first_side
while true; do
read -rp "Which side will you flash first? [left/right]: " first_side
first_side=${first_side,,}
if [[ "$first_side" == "left" || "$first_side" == "right" ]]; then
break
else
echo "⚠️ Please enter 'left' or 'right'"
fi
done
echo ""
# Flash first side with verification
if ! flash_side_with_verification "$first_side"; then
echo "❌ Flashing failed. Exiting."
exit 1
fi
# Determine the other side
local second_side
if [[ "$first_side" == "left" ]]; then
second_side="right"
else
second_side="left"
fi
# Ask if user wants to flash the second side
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
read -rp "Flash the $second_side side now? [y/n]: " DO_SECOND
DO_SECOND=${DO_SECOND,,}
echo ""
if [[ "$DO_SECOND" == "y" ]]; then
# Flash second side with verification
if ! flash_side_with_verification "$second_side"; then
echo "❌ Flashing failed. Exiting."
exit 1
fi
fi
# -----------------------------------------------------------------
# PARTIAL or COMPLETE MAPPING: Auto-detect (no asking needed)
# -----------------------------------------------------------------
else
echo "✅ Device mapping exists - auto-detection enabled"
echo ""
# Flash first side with auto-detection
if ! flash_side_auto_detect; then
echo "❌ Flashing failed. Exiting."
exit 1
fi
# Ask if user wants to flash the second side
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
read -rp "Flash the other keyboard half now? [y/n]: " DO_SECOND
DO_SECOND=${DO_SECOND,,}
echo ""
if [[ "$DO_SECOND" == "y" ]]; then
# Flash second side with auto-detection
if ! flash_side_auto_detect; then
echo "❌ Flashing failed. Exiting."
exit 1
fi
fi
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "🎉 Flashing complete!"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "✓ Handedness has been set in EEPROM"
echo "✓ Device mappings saved to: $SIDE_MAPPING_FILE"
echo "✓ Future runs will automatically detect sides"
echo "✓ Future firmware updates will preserve handedness"
echo ""
# Show the saved mappings
echo "Current device mappings:"
list_all_mappings
echo ""
}
# Execute main
main "$@"

View file

@ -0,0 +1,187 @@
#!/usr/bin/env bash
# =============================================================================
# Device Detection Library
# =============================================================================
# Functions for detecting and identifying RP2040 devices in bootloader mode
# Uses HOST-side USB information (serial, port location)
# =============================================================================
# ----------------------
# Function: wait_for_rp2040
# Wait until an RP2040 UF2 device is mounted on any of the configured paths
# Returns the mount point
# Usage: mount_point=$(wait_for_rp2040)
# ----------------------
wait_for_rp2040() {
local usb_mount_paths=("${USB_MOUNT_PATHS[@]:-/media/$USER /run/media/$USER /mnt}")
local rp2040_pattern="${RP2040_PATTERN:-*RP2040*}"
local wait_interval="${USB_WAIT_INTERVAL:-0.5}"
echo "⏳ Waiting for RP2040 UF2 device..." >&2
local device=""
while true; do
for path in "${usb_mount_paths[@]}"; do
device=$(find "$path" -maxdepth 2 -type d -name "$rp2040_pattern" 2>/dev/null | head -n1)
if [[ -n "$device" ]]; then
echo "✅ Found RP2040 device at $device" >&2
echo "$device"
return 0
fi
done
sleep "$wait_interval"
done
}
# ----------------------
# Function: get_usb_serial_from_host
# Get the USB serial number from the HOST system (not from the device itself)
# This is the ONLY reliable way to identify devices when EEPROM is wiped
# Usage: serial=$(get_usb_serial_from_host "/media/user/RPI-RP2")
# ----------------------
get_usb_serial_from_host() {
local mount_point="$1"
if [[ -z "$mount_point" ]]; then
echo "" >&2
return 1
fi
# Method 1: Get the block device, then trace to USB serial
local dev
dev=$(findmnt -n -o SOURCE --target "$mount_point" 2>/dev/null || echo "")
if [[ -n "$dev" ]]; then
local block_dev=$(basename "$dev")
# Try to find USB serial through sysfs
local sys_path="/sys/class/block/$block_dev"
# Walk up the device tree to find the USB device
local current_path=$(readlink -f "$sys_path/device" 2>/dev/null || echo "")
while [[ -n "$current_path" && "$current_path" != "/sys" ]]; do
# Check if this directory has a serial file
if [[ -f "$current_path/serial" ]]; then
cat "$current_path/serial"
return 0
fi
# Also check for idVendor/idProduct to confirm it's a USB device
if [[ -f "$current_path/idVendor" ]]; then
# Found USB device level, check for serial
if [[ -f "$current_path/serial" ]]; then
cat "$current_path/serial"
return 0
fi
fi
# Move up one level
current_path=$(dirname "$current_path")
done
fi
# Method 2: Use udevadm to get USB info
if [[ -n "$dev" ]]; then
local serial
serial=$(udevadm info --query=property --name="$dev" 2>/dev/null | grep "ID_SERIAL_SHORT=" | cut -d'=' -f2)
if [[ -n "$serial" ]]; then
echo "$serial"
return 0
fi
fi
# No serial found
echo ""
return 1
}
# ----------------------
# Function: get_usb_device_path
# Get a unique identifier based on USB physical port location
# This persists even when serial is not available
# Usage: usbpath=$(get_usb_device_path "/media/user/RPI-RP2")
# ----------------------
get_usb_device_path() {
local mount_point="$1"
if [[ -z "$mount_point" ]]; then
echo "" >&2
return 1
fi
local dev
dev=$(findmnt -n -o SOURCE --target "$mount_point" 2>/dev/null || echo "")
if [[ -n "$dev" ]]; then
# Get the physical USB path (bus and port numbers)
local devpath
devpath=$(udevadm info --query=property --name="$dev" 2>/dev/null | grep "DEVPATH=" | cut -d'=' -f2)
if [[ -n "$devpath" ]]; then
# Extract the USB bus and port info (e.g., /devices/pci0000:00/0000:00:14.0/usb1/1-3/1-3:1.0)
local usbpath=$(echo "$devpath" | grep -oP 'usb\d+/\d+-[\d.]+')
if [[ -n "$usbpath" ]]; then
echo "$usbpath"
return 0
fi
fi
fi
echo ""
return 1
}
# ----------------------
# Function: get_device_identifier
# Get the best available identifier for the USB device
# Prefers USB serial, falls back to USB port location, then mount path
# Usage: device_id=$(get_device_identifier "/media/user/RPI-RP2")
# Returns: "serial:ABC123" or "usbpath:usb1/1-3" or "mount:RPI-RP2"
# ----------------------
get_device_identifier() {
local mount_point="$1"
if [[ -z "$mount_point" ]]; then
echo "Error: mount_point required" >&2
return 1
fi
# Try to get USB serial from host (PREFERRED)
local usb_serial
usb_serial=$(get_usb_serial_from_host "$mount_point")
if [[ -n "$usb_serial" ]]; then
echo "serial:$usb_serial"
return 0
fi
# Try USB device path (FALLBACK 1)
local usb_path
usb_path=$(get_usb_device_path "$mount_point")
if [[ -n "$usb_path" ]]; then
echo "usbpath:$usb_path"
return 0
fi
# Final fallback: use mount point basename (FALLBACK 2)
echo "mount:$(basename "$mount_point")"
return 0
}
# ----------------------
# Function: print_device_info
# Print detailed information about a detected device (for debugging)
# Usage: print_device_info "/media/user/RPI-RP2"
# ----------------------
print_device_info() {
local mount_point="$1"
echo "Device Information:"
echo " Mount Point: $mount_point"
local serial=$(get_usb_serial_from_host "$mount_point")
echo " USB Serial: ${serial:-[not available]}"
local usbpath=$(get_usb_device_path "$mount_point")
echo " USB Path: ${usbpath:-[not available]}"
local device_id=$(get_device_identifier "$mount_point")
echo " Identifier: $device_id"
}

View file

@ -0,0 +1,143 @@
#!/usr/bin/env bash
# =============================================================================
# QMK Helper Functions Library
# =============================================================================
# Functions for building and flashing QMK firmware
# =============================================================================
# ----------------------
# Function: build_firmware
# Build QMK firmware for the specified keyboard and keymap
# Usage: build_firmware "fingerpunch/sweeeeep" "smathev"
# ----------------------
build_firmware() {
local keyboard="$1"
local keymap="$2"
if [[ -z "$keyboard" || -z "$keymap" ]]; then
echo "❌ Error: keyboard and keymap required" >&2
return 1
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
echo "🛠 Building firmware for $keyboard" >&2
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
qmk compile -kb "$keyboard" -km "$keymap"
if [[ $? -eq 0 ]]; then
echo "✅ Firmware compiled successfully" >&2
echo "" >&2
return 0
else
echo "❌ Firmware compilation failed" >&2
return 1
fi
}
# ----------------------
# Function: flash_with_bootloader
# Flash firmware using a specific bootloader target
# Usage: flash_with_bootloader "fingerpunch/sweeeeep" "smathev" "uf2-split-left"
# ----------------------
flash_with_bootloader() {
local keyboard="$1"
local keymap="$2"
local bootloader="$3"
if [[ -z "$keyboard" || -z "$keymap" || -z "$bootloader" ]]; then
echo "❌ Error: keyboard, keymap, and bootloader required" >&2
return 1
fi
echo "📤 Flashing firmware with bootloader: $bootloader" >&2
echo "" >&2
qmk flash -kb "$keyboard" -km "$keymap" -bl "$bootloader"
if [[ $? -eq 0 ]]; then
echo "" >&2
echo "✅ Firmware flashed successfully!" >&2
return 0
else
echo "" >&2
echo "❌ Firmware flashing failed" >&2
return 1
fi
}
# ----------------------
# Function: flash_side
# Flash firmware for a specific keyboard side (left/right)
# Usage: flash_side "fingerpunch/sweeeeep" "smathev" "left"
# ----------------------
flash_side() {
local keyboard="$1"
local keymap="$2"
local side="$3"
if [[ -z "$keyboard" || -z "$keymap" || -z "$side" ]]; then
echo "❌ Error: keyboard, keymap, and side required" >&2
return 1
fi
if [[ "$side" != "left" && "$side" != "right" ]]; then
echo "❌ Error: side must be 'left' or 'right'" >&2
return 1
fi
local bootloader="uf2-split-$side"
echo "🎯 Flashing $side side with handedness" >&2
flash_with_bootloader "$keyboard" "$keymap" "$bootloader"
}
# ----------------------
# Function: check_qmk_installed
# Verify QMK CLI is installed and available
# Usage: check_qmk_installed || exit 1
# ----------------------
check_qmk_installed() {
if ! command -v qmk &> /dev/null; then
echo "❌ Error: 'qmk' CLI is not installed or not in PATH" >&2
echo " Install it with: python3 -m pip install qmk" >&2
return 1
fi
return 0
}
# ----------------------
# Function: verify_keyboard_exists
# Check if a keyboard definition exists in QMK
# Usage: verify_keyboard_exists "fingerpunch/sweeeeep"
# ----------------------
verify_keyboard_exists() {
local keyboard="$1"
if [[ -z "$keyboard" ]]; then
echo "❌ Error: keyboard required" >&2
return 1
fi
# Try to list keymaps for this keyboard
qmk list-keymaps -kb "$keyboard" &>/dev/null
if [[ $? -eq 0 ]]; then
return 0
else
echo "❌ Error: Keyboard '$keyboard' not found in QMK" >&2
return 1
fi
}
# ----------------------
# Function: clean_build
# Clean previous build artifacts
# Usage: clean_build
# ----------------------
clean_build() {
echo "🧹 Cleaning previous build..." >&2
qmk clean &>/dev/null
echo "✅ Build directory cleaned" >&2
}

View file

@ -0,0 +1,481 @@
#!/usr/bin/env bash
# =============================================================================
# Side Mapping Library
# =============================================================================
# Functions for storing and retrieving keyboard side mappings
# Maps USB device identifiers to left/right sides
# =============================================================================
# Default mapping file location (will be set by calling script)
# Falls back to current directory if not set
SIDE_MAPPING_FILE="${SIDE_MAPPING_FILE:-./device_mappings.json}"
# ----------------------
# Function: init_mapping_file
# Ensure the mapping file exists
# Usage: init_mapping_file
# ----------------------
init_mapping_file() {
if [[ ! -f "$SIDE_MAPPING_FILE" ]]; then
echo "{}" > "$SIDE_MAPPING_FILE"
echo "Created new mapping file: $SIDE_MAPPING_FILE" >&2
fi
}
# ----------------------
# Function: check_jq_installed
# Verify jq is available for JSON operations
# Usage: check_jq_installed || exit 1
# ----------------------
check_jq_installed() {
if ! command -v jq &> /dev/null; then
echo "❌ Error: 'jq' is required but not installed." >&2
echo " Install it with: sudo apt-get install jq" >&2
return 1
fi
return 0
}
# ----------------------
# Function: get_saved_side
# Retrieve the saved side mapping for a device identifier
# Usage: side=$(get_saved_side "serial:ABC123")
# Returns: "left", "right", or empty string if not found
# ----------------------
get_saved_side() {
local device_id="$1"
if [[ -z "$device_id" ]]; then
echo "" >&2
return 1
fi
check_jq_installed || return 1
init_mapping_file
local side
side=$(jq -r --arg id "$device_id" '.[$id] // ""' "$SIDE_MAPPING_FILE" 2>/dev/null)
# Handle null or empty
if [[ "$side" == "null" || -z "$side" ]]; then
echo ""
return 1
fi
echo "$side"
return 0
}
# ----------------------
# Function: save_side_mapping
# Save a device identifier to side mapping
# Usage: save_side_mapping "serial:ABC123" "left"
# ----------------------
save_side_mapping() {
local device_id="$1"
local side="$2"
if [[ -z "$device_id" || -z "$side" ]]; then
echo "Error: device_id and side required" >&2
return 1
fi
if [[ "$side" != "left" && "$side" != "right" ]]; then
echo "Error: side must be 'left' or 'right'" >&2
return 1
fi
check_jq_installed || return 1
init_mapping_file
# Save mapping
local tmpfile=$(mktemp)
jq --arg id "$device_id" --arg side "$side" '. + {($id): $side}' "$SIDE_MAPPING_FILE" > "$tmpfile"
if [[ $? -eq 0 ]]; then
mv "$tmpfile" "$SIDE_MAPPING_FILE"
echo "✅ Saved mapping: $device_id$side" >&2
return 0
else
rm -f "$tmpfile"
echo "❌ Failed to save mapping" >&2
return 1
fi
}
# ----------------------
# Function: prompt_for_side
# Ask user to identify which side a device is
# Usage: side=$(prompt_for_side "serial:ABC123")
# Returns: "left" or "right"
# ----------------------
prompt_for_side() {
local device_id="$1"
echo "" >&2
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
echo "⚠️ UNKNOWN DEVICE - First Time Setup" >&2
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
echo "" >&2
echo "The script has detected a keyboard half that it hasn't" >&2
echo "seen before. This is expected on first run." >&2
echo "" >&2
echo "Device ID: $device_id" >&2
echo "" >&2
echo "Please tell me which side this is so I can remember it" >&2
echo "for future flashing sessions." >&2
echo "" >&2
local side
while true; do
read -rp "Which side is currently plugged in? [left/right]: " side
side=${side,,}
if [[ "$side" == "left" || "$side" == "right" ]]; then
echo "$side"
return 0
else
echo "❌ Invalid input. Please type 'left' or 'right'." >&2
fi
done
}
# ----------------------
# Function: detect_side
# Determine the left/right side of a device
# Checks saved mapping first, prompts user if unknown, then saves
# Usage: side=$(detect_side "serial:ABC123")
# Returns: "left" or "right"
# ----------------------
detect_side() {
local device_id="$1"
if [[ -z "$device_id" ]]; then
echo "Error: device_id required" >&2
return 1
fi
echo " Device Identifier: $device_id" >&2
# Try to get saved side
local side
side=$(get_saved_side "$device_id" 2>/dev/null)
if [[ -n "$side" ]]; then
# Found in mapping
echo " ✅ Recognized: $side side (from saved mapping)" >&2
echo "$side"
return 0
fi
# Not found - prompt user
side=$(prompt_for_side "$device_id")
# Save the mapping
save_side_mapping "$device_id" "$side"
echo "" >&2
echo " Next time this device is detected, it will be" >&2
echo " automatically identified as the $side side." >&2
echo "" >&2
echo "$side"
return 0
}
# ----------------------
# Function: get_mapping_state
# Determine the current state of the mapping file
# Returns: "empty", "partial", or "complete"
# ----------------------
get_mapping_state() {
check_jq_installed || return 1
init_mapping_file
local count
count=$(jq 'length' "$SIDE_MAPPING_FILE")
case $count in
0) echo "empty" ;;
1) echo "partial" ;;
2) echo "complete" ;;
*) echo "unknown" ;;
esac
}
# ----------------------
# Function: get_mapped_devices
# Get list of all mapped device IDs
# Returns: space-separated list of device IDs
# ----------------------
get_mapped_devices() {
check_jq_installed || return 1
init_mapping_file
jq -r 'keys[]' "$SIDE_MAPPING_FILE" | tr '\n' ' '
}
# ----------------------
# Function: get_unmapped_side
# If mapping is partial, determine which side is NOT mapped yet
# Returns: "left", "right", or empty string if both/neither mapped
# ----------------------
get_unmapped_side() {
check_jq_installed || return 1
init_mapping_file
local has_left has_right
has_left=$(jq -r 'to_entries | map(select(.value == "left")) | length' "$SIDE_MAPPING_FILE")
has_right=$(jq -r 'to_entries | map(select(.value == "right")) | length' "$SIDE_MAPPING_FILE")
if [[ "$has_left" -eq 0 ]]; then
echo "left"
elif [[ "$has_right" -eq 0 ]]; then
echo "right"
else
echo ""
fi
}
# ----------------------
# Function: detect_side_with_expected
# Detect device side and verify it matches expected side
# Uses intelligent verification based on mapping state
# Usage: side=$(detect_side_with_expected "serial:ABC123" "left")
# Returns: "left" or "right", or exits on error
# ----------------------
detect_side_with_expected() {
local device_id="$1"
local expected_side="$2"
if [[ -z "$device_id" || -z "$expected_side" ]]; then
echo "Error: device_id and expected_side required" >&2
return 1
fi
echo " Device Identifier: $device_id" >&2
echo " Expected Side: $expected_side" >&2
# Get current mapping state
local mapping_state
mapping_state=$(get_mapping_state)
echo " Mapping State: $mapping_state" >&2
# Try to get saved side for this device
local saved_side
saved_side=$(get_saved_side "$device_id" 2>/dev/null)
# -----------------------------------------------------------------
# STATE 1: EMPTY MAPPING (Learning Mode)
# -----------------------------------------------------------------
if [[ "$mapping_state" == "empty" ]]; then
echo "" >&2
echo " 📝 Learning mode (first time setup)" >&2
echo " ✅ Saving device as $expected_side side" >&2
save_side_mapping "$device_id" "$expected_side"
echo "$expected_side"
return 0
fi
# -----------------------------------------------------------------
# STATE 2: PARTIAL MAPPING (One Side Known)
# -----------------------------------------------------------------
if [[ "$mapping_state" == "partial" ]]; then
local unmapped_side
unmapped_side=$(get_unmapped_side)
if [[ -n "$saved_side" ]]; then
# Device is known - verify it matches
if [[ "$saved_side" == "$expected_side" ]]; then
echo " ✅ Confirmed: $expected_side side (matches saved mapping)" >&2
echo "$expected_side"
return 0
else
# MISMATCH in partial state
echo "" >&2
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
echo "❌ ERROR: MISMATCH IN PARTIAL MAPPING" >&2
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
echo "" >&2
echo "Expected: $expected_side side" >&2
echo "Device is saved as: $saved_side side" >&2
echo "" >&2
echo "This device is known but on the wrong side!" >&2
echo "Clear mappings and re-learn: rm $SIDE_MAPPING_FILE" >&2
exit 1
fi
else
# Device is unknown - check if user expects the unmapped side
if [[ "$expected_side" == "$unmapped_side" ]]; then
echo "" >&2
echo " Unknown device, expecting unmapped side" >&2
echo " ✅ Saving as $expected_side side (completes mapping)" >&2
save_side_mapping "$device_id" "$expected_side"
echo "$expected_side"
return 0
else
# User expects mapped side but got unknown device
echo "" >&2
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
echo "❌ ERROR: AMBIGUOUS DEVICE IN PARTIAL MAPPING" >&2
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
echo "" >&2
echo "Expected: $expected_side side (which is already mapped)" >&2
echo "But got: Unknown device $device_id" >&2
echo "" >&2
echo "This could be:" >&2
echo " 1. A replacement controller for $expected_side side" >&2
echo " 2. The wrong side plugged in" >&2
echo "" >&2
echo "Cannot determine safely!" >&2
echo "Clear mappings and re-learn: rm $SIDE_MAPPING_FILE" >&2
exit 1
fi
fi
fi
# -----------------------------------------------------------------
# STATE 3: COMPLETE MAPPING (Both Sides Known)
# -----------------------------------------------------------------
if [[ "$mapping_state" == "complete" ]]; then
if [[ -z "$saved_side" ]]; then
# Unknown device with complete mapping = ERROR
echo "" >&2
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
echo "❌ ERROR: UNKNOWN DEVICE (COMPLETE MAPPING)" >&2
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
echo "" >&2
echo "Detected device: $device_id" >&2
echo "Expected one of the known devices:" >&2
get_mapped_devices | tr ' ' '\n' | while read dev; do
local side=$(get_saved_side "$dev")
echo " - $dev ($side side)" >&2
done
echo "" >&2
echo "Mapping is complete (both sides known)." >&2
echo "Unknown devices are not allowed!" >&2
echo "" >&2
echo "If you replaced a controller, clear mappings:" >&2
echo " rm $SIDE_MAPPING_FILE" >&2
exit 1
fi
# Device is known - verify it matches
if [[ "$saved_side" == "$expected_side" ]]; then
echo " ✅ Confirmed: $expected_side side (matches saved mapping)" >&2
echo "$expected_side"
return 0
fi
# MISMATCH! Device is known but wrong side
echo "" >&2
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
echo "⚠️ WARNING: SIDE MISMATCH DETECTED" >&2
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
echo "" >&2
echo "Expected: $expected_side side" >&2
echo "Saved mapping says: $saved_side side" >&2
echo "" >&2
echo "This means either:" >&2
echo " 1. You plugged in the WRONG keyboard half" >&2
echo " 2. The saved mapping is incorrect" >&2
echo "" >&2
echo "What would you like to do?" >&2
echo " [e] Exit safely (recommended)" >&2
echo " [c] Clear this mapping and save as $expected_side" >&2
echo " [f] Force flash as $expected_side anyway (DANGEROUS)" >&2
echo "" >&2
local choice
while true; do
read -rp "Your choice [e/c/f]: " choice
choice=${choice,,}
case "$choice" in
e)
echo "" >&2
echo "❌ Exiting safely. Please plug in the correct keyboard half." >&2
exit 1
;;
c)
echo "" >&2
echo "🔄 Clearing old mapping and saving as $expected_side side..." >&2
save_side_mapping "$device_id" "$expected_side"
echo "✅ Mapping updated" >&2
echo "$expected_side"
return 0
;;
f)
echo "" >&2
echo "⚠️ WARNING: Flashing device as $expected_side despite mismatch!" >&2
echo " This could result in incorrect keyboard behavior." >&2
read -rp " Are you absolutely sure? [yes/no]: " confirm
if [[ "$confirm" == "yes" ]]; then
# Don't update mapping, just return expected side
echo " Proceeding with $expected_side..." >&2
echo "$expected_side"
return 0
else
echo " Cancelled. Exiting." >&2
exit 1
fi
;;
*)
echo "Invalid choice. Please enter 'e', 'c', or 'f'." >&2
;;
esac
done
}
# ----------------------
# Function: list_all_mappings
# Display all saved device mappings
# Usage: list_all_mappings
# ----------------------
list_all_mappings() {
check_jq_installed || return 1
init_mapping_file
echo "Saved Device Mappings:"
jq '.' "$SIDE_MAPPING_FILE"
}
# ----------------------
# Function: clear_mapping
# Remove a specific device mapping
# Usage: clear_mapping "serial:ABC123"
# ----------------------
clear_mapping() {
local device_id="$1"
if [[ -z "$device_id" ]]; then
echo "Error: device_id required" >&2
return 1
fi
check_jq_installed || return 1
init_mapping_file
local tmpfile=$(mktemp)
jq --arg id "$device_id" 'del(.[$id])' "$SIDE_MAPPING_FILE" > "$tmpfile"
if [[ $? -eq 0 ]]; then
mv "$tmpfile" "$SIDE_MAPPING_FILE"
echo "✅ Removed mapping for: $device_id" >&2
return 0
else
rm -f "$tmpfile"
echo "❌ Failed to remove mapping" >&2
return 1
fi
}
# ----------------------
# Function: clear_all_mappings
# Remove all device mappings (reset to empty)
# Usage: clear_all_mappings
# ----------------------
clear_all_mappings() {
echo "{}" > "$SIDE_MAPPING_FILE"
echo "✅ Cleared all mappings" >&2
}

View file

@ -0,0 +1,41 @@
#!/usr/bin/env bash
# =============================================================================
# Quick Testing Script - Run all tests in sequence
# =============================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
echo "╔═══════════════════════════════════════════════════════════╗"
echo "║ QMK Flash Tools - Quick Test Suite ║"
echo "╚═══════════════════════════════════════════════════════════╝"
echo ""
# Test 1: Side Mapping (no hardware needed)
echo "Running side mapping tests..."
echo ""
bash "$SCRIPT_DIR/test_side_mapping.sh"
echo ""
# Test 2: QMK Helpers (partial, no hardware needed)
echo "Running QMK helper tests..."
echo ""
bash "$SCRIPT_DIR/test_qmk_helpers.sh"
echo ""
# Test 3: Device Detection (requires hardware)
echo "Running device detection tests..."
echo ""
read -rp "Do you have a keyboard in bootloader mode? [y/n]: " has_device
if [[ "$has_device" == "y" ]]; then
bash "$SCRIPT_DIR/test_device_detection.sh"
else
echo "⏭️ Skipping device detection test"
echo " Run manually when you have hardware ready:"
echo " ./test/test_device_detection.sh"
fi
echo ""
echo "╔═══════════════════════════════════════════════════════════╗"
echo "║ ✅ All tests complete! ║"
echo "╚═══════════════════════════════════════════════════════════╝"

View file

@ -0,0 +1,111 @@
#!/usr/bin/env bash
# =============================================================================
# Test Device Detection Functions
# =============================================================================
# Standalone test script for device_detection.sh library
# =============================================================================
# Get the directory where this script is located
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LIB_DIR="$SCRIPT_DIR/../lib"
# Source the device detection library
source "$LIB_DIR/device_detection.sh"
# Set configuration for testing
export USB_MOUNT_PATHS=("/media/$USER" "/run/media/$USER" "/mnt")
export RP2040_PATTERN="*RP2040*"
export USB_WAIT_INTERVAL=0.5
echo "╔═══════════════════════════════════════════════════════╗"
echo "║ Device Detection Library Test ║"
echo "╚═══════════════════════════════════════════════════════╝"
echo ""
# Test 1: Check if device is currently connected
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Test 1: Looking for currently connected RP2040..."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
# Quick check without waiting
device_found=""
for path in "${USB_MOUNT_PATHS[@]}"; do
device_found=$(find "$path" -maxdepth 2 -type d -name "$RP2040_PATTERN" 2>/dev/null | head -n1)
if [[ -n "$device_found" ]]; then
break
fi
done
if [[ -n "$device_found" ]]; then
echo "✅ RP2040 device found at: $device_found"
echo ""
# Test 2: Get USB serial
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Test 2: Getting USB serial from host..."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
usb_serial=$(get_usb_serial_from_host "$device_found")
if [[ -n "$usb_serial" ]]; then
echo "✅ USB Serial: $usb_serial"
else
echo "⚠️ USB Serial not available"
fi
echo ""
# Test 3: Get USB device path
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Test 3: Getting USB device path..."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
usb_path=$(get_usb_device_path "$device_found")
if [[ -n "$usb_path" ]]; then
echo "✅ USB Path: $usb_path"
else
echo "⚠️ USB Path not available"
fi
echo ""
# Test 4: Get device identifier
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Test 4: Getting device identifier..."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
device_id=$(get_device_identifier "$device_found")
echo "✅ Device Identifier: $device_id"
echo ""
# Test 5: Print full device info
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Test 5: Full device information..."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
print_device_info "$device_found"
echo ""
else
echo "⚠️ No RP2040 device currently connected"
echo ""
echo "To test device detection:"
echo " 1. Enter bootloader mode on your keyboard (double-tap RESET)"
echo " 2. Run this test script again"
echo ""
echo "Or test the wait function (will wait for device):"
read -rp "Wait for device? [y/n]: " wait_test
if [[ "$wait_test" == "y" ]]; then
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Test: Waiting for RP2040 device..."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Enter bootloader mode on your keyboard now..."
echo ""
mount_point=$(wait_for_rp2040)
echo ""
echo "✅ Device detected!"
echo ""
print_device_info "$mount_point"
fi
fi
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "✅ Device detection tests complete"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

View file

@ -0,0 +1,108 @@
#!/usr/bin/env bash
# =============================================================================
# Test QMK Helper Functions
# =============================================================================
# Standalone test script for qmk_helpers.sh library
# =============================================================================
# Get the directory where this script is located
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LIB_DIR="$SCRIPT_DIR/../lib"
# Source the QMK helpers library
source "$LIB_DIR/qmk_helpers.sh"
echo "╔═══════════════════════════════════════════════════════╗"
echo "║ QMK Helpers Library Test ║"
echo "╚═══════════════════════════════════════════════════════╝"
echo ""
# Test configuration
TEST_KEYBOARD="fingerpunch/sweeeeep"
TEST_KEYMAP="smathev"
# Test 1: Check QMK installation
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Test 1: Check QMK CLI installation..."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
if check_qmk_installed; then
echo "✅ QMK CLI is installed"
qmk --version
else
echo "❌ QMK CLI is not installed"
exit 1
fi
echo ""
# Test 2: Verify keyboard exists
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Test 2: Verify keyboard exists..."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Checking for: $TEST_KEYBOARD"
if verify_keyboard_exists "$TEST_KEYBOARD"; then
echo "✅ Keyboard exists in QMK"
else
echo "⚠️ Keyboard not found (may need to adjust TEST_KEYBOARD variable)"
fi
echo ""
# Test 3: Test build (optional)
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Test 3: Build firmware (optional)..."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "This will compile the firmware for $TEST_KEYBOARD:$TEST_KEYMAP"
read -rp "Proceed with build test? [y/n]: " do_build
if [[ "$do_build" == "y" ]]; then
echo ""
if build_firmware "$TEST_KEYBOARD" "$TEST_KEYMAP"; then
echo "✅ Build test passed"
else
echo "⚠️ Build test failed (check keyboard/keymap configuration)"
fi
else
echo "⏭️ Skipped build test"
fi
echo ""
# Test 4: Test clean build
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Test 4: Clean build directory..."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
read -rp "Proceed with clean test? [y/n]: " do_clean
if [[ "$do_clean" == "y" ]]; then
clean_build
echo "✅ Clean test passed"
else
echo "⏭️ Skipped clean test"
fi
echo ""
# Test 5: Function signature tests (no actual flashing)
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Test 5: Test function signatures (dry run)..."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Testing flash_side with missing arguments:"
flash_side "" "" "" 2>&1 | head -n1
echo ""
echo "Testing flash_side with invalid side:"
flash_side "$TEST_KEYBOARD" "$TEST_KEYMAP" "middle" 2>&1 | head -n1
echo ""
echo "Testing flash_with_bootloader with missing arguments:"
flash_with_bootloader "" "" "" 2>&1 | head -n1
echo ""
echo "✅ Function signature tests passed"
echo ""
# Summary
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "✅ QMK helpers tests complete"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "Note: Actual flashing tests require hardware and should be"
echo " done through the main autoflash script."

View file

@ -0,0 +1,148 @@
#!/usr/bin/env bash
# =============================================================================
# Test Side Mapping Functions
# =============================================================================
# Standalone test script for side_mapping.sh library
# =============================================================================
# Get the directory where this script is located
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LIB_DIR="$SCRIPT_DIR/../lib"
# Use a test mapping file
export SIDE_MAPPING_FILE="/tmp/qmk_test_mappings.json"
# Source the side mapping library
source "$LIB_DIR/side_mapping.sh"
echo "╔═══════════════════════════════════════════════════════╗"
echo "║ Side Mapping Library Test ║"
echo "╚═══════════════════════════════════════════════════════╝"
echo ""
echo "Test mapping file: $SIDE_MAPPING_FILE"
echo ""
# Clean up test file if it exists
rm -f "$SIDE_MAPPING_FILE"
# Test 1: Initialize mapping file
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Test 1: Initialize mapping file..."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
init_mapping_file
if [[ -f "$SIDE_MAPPING_FILE" ]]; then
echo "✅ Mapping file created"
cat "$SIDE_MAPPING_FILE"
else
echo "❌ Failed to create mapping file"
exit 1
fi
echo ""
# Test 2: Check jq installation
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Test 2: Check jq installation..."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
if check_jq_installed; then
echo "✅ jq is installed"
else
echo "❌ jq is not installed"
exit 1
fi
echo ""
# Test 3: Save mappings
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Test 3: Save test mappings..."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
save_side_mapping "serial:ABC123" "left"
save_side_mapping "serial:XYZ789" "right"
save_side_mapping "usbpath:usb1/1-3" "left"
echo ""
echo "Current mappings:"
cat "$SIDE_MAPPING_FILE"
echo ""
# Test 4: Retrieve saved sides
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Test 4: Retrieve saved sides..."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
side1=$(get_saved_side "serial:ABC123")
echo "Device serial:ABC123 → $side1"
if [[ "$side1" == "left" ]]; then
echo "✅ Correct"
else
echo "❌ Expected 'left', got '$side1'"
fi
side2=$(get_saved_side "serial:XYZ789")
echo "Device serial:XYZ789 → $side2"
if [[ "$side2" == "right" ]]; then
echo "✅ Correct"
else
echo "❌ Expected 'right', got '$side2'"
fi
side3=$(get_saved_side "serial:UNKNOWN")
echo "Device serial:UNKNOWN → ${side3:-[not found]}"
if [[ -z "$side3" ]]; then
echo "✅ Correct (not found)"
else
echo "❌ Expected empty, got '$side3'"
fi
echo ""
# Test 5: List all mappings
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Test 5: List all mappings..."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
list_all_mappings
echo ""
# Test 6: Clear specific mapping
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Test 6: Clear specific mapping..."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
clear_mapping "usbpath:usb1/1-3"
echo ""
echo "Mappings after clearing 'usbpath:usb1/1-3':"
cat "$SIDE_MAPPING_FILE"
echo ""
# Test 7: Interactive test (optional)
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Test 7: Interactive prompt test (optional)..."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
read -rp "Test the interactive prompt? [y/n]: " test_prompt
if [[ "$test_prompt" == "y" ]]; then
echo ""
test_device_id="serial:TEST_DEVICE"
detected_side=$(detect_side "$test_device_id")
echo ""
echo "You identified the device as: $detected_side"
echo ""
echo "Updated mappings:"
cat "$SIDE_MAPPING_FILE"
fi
echo ""
# Test 8: Clear all mappings
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Test 8: Clear all mappings..."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
clear_all_mappings
echo ""
echo "Mappings after clearing all:"
cat "$SIDE_MAPPING_FILE"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "✅ Side mapping tests complete"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "Cleaning up test file: $SIDE_MAPPING_FILE"
rm -f "$SIDE_MAPPING_FILE"

View file

@ -25,7 +25,7 @@
// Tapping and timing configuration
#define TAPPING_TERM 200
#define FLOW_TAP 130
//#define PERMISSIVE_HOLD // Activate mod immediately when another key pressed _REDUNDANT due to SpeculativeHold
#define PERMISSIVE_HOLD // Activate mod immediately when another key pressed
#define AUTO_SHIFT_TIMEOUT 140 // at what point are you holding the key to send a SHIFTED value
#define RETRO_SHIFT // Enable retroactive shift
#define RETRO_TAPPING // Enable retroactive tapping