diff --git a/Runner/suites/Connectivity/Bluetooth/README.md b/Runner/suites/Connectivity/Bluetooth/BT_ON_FF/README.md similarity index 100% rename from Runner/suites/Connectivity/Bluetooth/README.md rename to Runner/suites/Connectivity/Bluetooth/BT_ON_FF/README.md diff --git a/Runner/suites/Connectivity/Bluetooth/run.sh b/Runner/suites/Connectivity/Bluetooth/BT_ON_FF/run.sh similarity index 88% rename from Runner/suites/Connectivity/Bluetooth/run.sh rename to Runner/suites/Connectivity/Bluetooth/BT_ON_FF/run.sh index 94699aa5..ebb0af79 100755 --- a/Runner/suites/Connectivity/Bluetooth/run.sh +++ b/Runner/suites/Connectivity/Bluetooth/BT_ON_FF/run.sh @@ -2,8 +2,8 @@ # Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. # SPDX-License-Identifier: BSD-3-Clause-Clear - -# Source init_env and functestlib.sh + +# Robustly find and source init_env SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" INIT_ENV="" SEARCH="$SCRIPT_DIR" @@ -14,33 +14,36 @@ while [ "$SEARCH" != "/" ]; do fi SEARCH=$(dirname "$SEARCH") done - + if [ -z "$INIT_ENV" ]; then echo "[ERROR] Could not find init_env (starting at $SCRIPT_DIR)" >&2 exit 1 fi - -# shellcheck disable=SC1090 -. "$INIT_ENV" - + +if [ -z "$__INIT_ENV_LOADED" ]; then + # shellcheck disable=SC1090 + . "$INIT_ENV" +fi # shellcheck disable=SC1090,SC1091 . "$TOOLS/functestlib.sh" - -TESTNAME="Bluetooth" + +TESTNAME="BT_ON_FF" test_path=$(find_test_case_by_name "$TESTNAME") || { log_fail "$TESTNAME : Test directory not found." echo "$TESTNAME FAIL" > "./$TESTNAME.res" exit 1 } - + cd "$test_path" || exit 1 res_file="./$TESTNAME.res" rm -f "$res_file" - -log_info "-----------------------------------------------------------------------------------------" -log_info "-------------------Starting $TESTNAME Testcase----------------------------" + +log_info "------------------------------------------------------------" +log_info "Starting $TESTNAME Testcase" log_info "Checking dependency: bluetoothctl" -check_dependencies bluetoothctl + +# verify that all necessary dependencies +check_dependencies bluetoothctl pgrep log_info "Checking if bluetoothd is running..." MAX_RETRIES=3 diff --git a/Runner/suites/Connectivity/Bluetooth/BT_SCAN_PAIR/BT_SCAN_PAID_README.md b/Runner/suites/Connectivity/Bluetooth/BT_SCAN_PAIR/BT_SCAN_PAID_README.md new file mode 100644 index 00000000..a5dd66ac --- /dev/null +++ b/Runner/suites/Connectivity/Bluetooth/BT_SCAN_PAIR/BT_SCAN_PAID_README.md @@ -0,0 +1,149 @@ + +# Bluetooth BT_SCAN_PAIR Test + +This test automates Bluetooth scanning and pairing for embedded Linux devices using BlueZ and bluetoothctl. It is designed for use in the [qcom-linux-testkit](https://github.com/qualcomm-linux/qcom-linux-testkit) test suite. + +## Features + +- Scans for Bluetooth devices +- Optionally pairs with a device by name or MAC address +- Retries pairing on failure, including handling for busy/temporarily unavailable devices +- Cleans up previous pairings for repeatable CI runs +- Accepts device name/MAC as argument, environment variable, or in `bt_device_list.txt` +- Generates summary and detailed logs (`scan.log`, `pair.log`, `found_devices.log`) + +## Usage + +```sh +./run.sh [DEVICE_NAME_OR_MAC] [WHITELIST] +``` +- `DEVICE_NAME_OR_MAC` – (optional) Device name or MAC address to pair. + - Can also be set as `BT_NAME_ENV` or in `bt_device_list.txt` +- `WHITELIST` – (optional) Comma-separated MACs/names allowed for pairing. + - Can also be set as `BT_WHITELIST_ENV` + +If no device name is given, only scanning is performed and the test passes if devices are found. + +## Examples + +```sh +./run.sh [BT_NAME] [WHITELIST] +``` + +- `BT_NAME` - Optional. Bluetooth name or MAC to search for. +- `WHITELIST` - Optional. Comma-separated names/MACs allowed for pairing. + +- Scan for any device (no pairing): + + ``` + ./run.sh + ``` + +- Scan and pair with a device named "MySpeaker": + + ``` + ./run.sh MySpeaker + ``` + +- Scan and pair only if device MAC is in whitelist: + + ``` + ./run.sh MySpeaker 00:11:22:33:44:55,AnotherSpeaker + ``` + +- Use environment variables: + + ``` + export BT_NAME_ENV="MySpeaker" + export BT_WHITELIST_ENV="00:11:22:33:44:55" + ./run.sh + ``` + +- Device list file (first line is used): + + ``` + echo "MySpeaker" > bt_device_list.txt + ./run.sh + ``` + +## Whitelist Usage + +To ensure only known devices are considered during scan: + +```sh +./run.sh JBL_Speaker "JBL_Speaker,12:34:56:78:9A:BC" +``` + +## Arguments & Variables + +- Argument 1: Device name or MAC address (takes precedence) +- `BT_NAME_ENV`: Device name or MAC from the environment +- `bt_device_list.txt`: Fallback if argument or env is not set + +## Example Summary Output + +``` +[INFO] 2025-06-23 10:00:00 - Starting BT_SCAN_PAIR Testcase +[INFO] 2025-06-23 10:00:02 - Unblocking and powering on Bluetooth +[INFO] 2025-06-23 10:00:05 - Devices found during scan: +Device 12:34:56:78:9A:BC SomeBTHeadset +[INFO] 2025-06-23 10:00:06 - Expected device 'SomeBTHeadset' found in scan +[PASS] 2025-06-23 10:00:08 - Pairing successful with 12:34:56:78:9A:BC +``` + +## Result + +- PASS: Device paired (or scan-only with no target) +- FAIL: Device not found, not in whitelist, or pairing failed +- All paired devices are removed after the test + +## Files Generated + +- `BT_SCAN_PAIR.res`: Test PASS/FAIL/SKIP result +- `scan.log`: Output of Bluetooth device scan +- `found_devices.log`: List of discovered device names/MACs +- `pair.log`: Detailed pairing output and errors + +## Troubleshooting + +- Ensure `bluetoothctl`, `rfkill`, `expect`, and `hciconfig` are available. +- For headless automation, the remote device must be in pairing/discoverable mode. +- The script retries pairing if "busy" or "temporarily unavailable" errors are seen. +- Check `pair.log` and `scan.log` for detailed debug info if a failure occurs. + +## Helper Functions (in functestlib.sh) + +- `bt_scan_devices` – Scans and logs found BT devices +- `bt_pair_with_mac` – Attempts pairing via expect with retries +- `bt_in_whitelist` – Checks if MAC/name is in whitelist +- `bt_cleanup_paired_device` – Removes paired device by MAC + +## Customization + +- **Whitelist**: You can restrict scan to a whitelist of MAC addresses or names using environment variables or script customization. +- **Retries/Timeouts**: Retry and timeout values can be set in the script for more robust pairing. + +## Integration with LAVA + +In your LAVA job: + +```yaml +deploy: + to: tftp + images: + bt_device_list.txt: + image: path/to/bt_device_list.txt + compression: none +``` + +Injects a per-DUT Bluetooth configuration. + +## Dependencies + +- `bluetoothctl`, `expect`, `rfkill`, `hciconfig` +- BlueZ stack running on embedded Linux + +## License + +SPDX-License-Identifier: BSD-3-Clause-Clear +Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. diff --git a/Runner/suites/Connectivity/Bluetooth/BT_SCAN_PAIR/bt_device_list.txt b/Runner/suites/Connectivity/Bluetooth/BT_SCAN_PAIR/bt_device_list.txt new file mode 100755 index 00000000..e69de29b diff --git a/Runner/suites/Connectivity/Bluetooth/BT_SCAN_PAIR/run.sh b/Runner/suites/Connectivity/Bluetooth/BT_SCAN_PAIR/run.sh new file mode 100755 index 00000000..51fe787f --- /dev/null +++ b/Runner/suites/Connectivity/Bluetooth/BT_SCAN_PAIR/run.sh @@ -0,0 +1,180 @@ +#!/bin/sh + +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause-Clear + +# Robustly find and source init_env +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +INIT_ENV="" +SEARCH="$SCRIPT_DIR" + +while [ "$SEARCH" != "/" ]; do + if [ -f "$SEARCH/init_env" ]; then + INIT_ENV="$SEARCH/init_env" + break + fi + SEARCH=$(dirname "$SEARCH") +done + +if [ -z "$INIT_ENV" ]; then + echo "[ERROR] Could not find init_env (starting at $SCRIPT_DIR)" >&2 + exit 1 +fi + +if [ -z "$__INIT_ENV_LOADED" ]; then + # shellcheck disable=SC1090 + . "$INIT_ENV" +fi + +# shellcheck disable=SC1090,SC1091 +. "$TOOLS/functestlib.sh" + +TESTNAME="BT_SCAN_PAIR" +test_path=$(find_test_case_by_name "$TESTNAME") || { + log_fail "$TESTNAME : Test directory not found." + echo "$TESTNAME FAIL" > "./$TESTNAME.res" + exit 1 +} +cd "$test_path" || exit 1 +RES_FILE="./$TESTNAME.res" +rm -f "$RES_FILE" + +log_info "------------------------------------------------------------" +log_info "Starting $TESTNAME Testcase" + +BT_NAME="" +BT_MAC="" +WHITELIST="" +PAIR_RETRIES="${PAIR_RETRIES:-3}" +SCAN_ATTEMPTS="${SCAN_ATTEMPTS:-2}" + +# Parse arguments +if [ -n "$1" ]; then + if echo "$1" | grep -Eq '^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$'; then + BT_MAC="$1" + else + BT_NAME="$1" + fi +fi + +if [ -n "$2" ]; then + WHITELIST="$2" + if [ -z "$BT_MAC" ] && echo "$2" | grep -Eq '^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$'; then + BT_MAC="$2" + fi +fi + +# Fallback to file +if [ -z "$BT_NAME" ] && [ -z "$BT_MAC" ] && [ -f "./bt_device_list.txt" ]; then + BT_NAME=$(awk '!/^#/ && NF {print $2}' ./bt_device_list.txt | head -n1) + BT_MAC=$(awk '!/^#/ && NF {print $1}' ./bt_device_list.txt | head -n1) +fi + +check_dependencies bluetoothctl rfkill expect hciconfig || { + echo "$TESTNAME SKIP" > "$RES_FILE" + exit 0 +} + +cleanup_bt_test() { + [ -n "$BT_MAC" ] && bt_cleanup_paired_device "$BT_MAC" + killall -q bluetoothctl 2>/dev/null +} +trap cleanup_bt_test EXIT + +rfkill unblock bluetooth +retry_command_bt "hciconfig hci0 up" "Bring up hci0" || { + log_fail "Failed to bring up hci0" + echo "$TESTNAME FAIL" > "$RES_FILE" + exit 1 +} + +bt_remove_all_paired_devices + +MATCH_FOUND=0 + +for scan_try in $(seq 1 "$SCAN_ATTEMPTS"); do + log_info "Bluetooth scan attempt $scan_try..." + bt_scan_devices + + LATEST_FOUND_LOG=$(find . -maxdepth 1 -name 'found_devices_*.log' -type f -print | sort -r | head -n1) + [ -z "$LATEST_FOUND_LOG" ] && continue + + log_info "Devices found during scan:" + cat "$LATEST_FOUND_LOG" + + if [ -z "$BT_NAME" ] && [ -z "$BT_MAC" ]; then + log_pass "No device specified. Scan-only mode." + echo "$TESTNAME PASS" > "$RES_FILE" + exit 0 + fi + + log_info "Matching against: BT_NAME='$BT_NAME', BT_MAC='$BT_MAC', WHITELIST='$WHITELIST'" + bt_remove_all_paired_devices + bluetoothctl --timeout 3 devices | awk '{print $2}' | while read -r addr; do + log_info "Forcing device removal: $addr" + bluetoothctl remove "$addr" >/dev/null 2>&1 + done + + # Clean and prepare whitelist +WHITELIST_CLEAN=$(echo "$WHITELIST" | tr -d '\r' | tr ',\n' ' ' | xargs) +MATCH_FOUND=0 + +while IFS= read -r line; do + mac=$(echo "$line" | awk '{print $1}') + name=$(echo "$line" | cut -d' ' -f2- | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + + log_info "Parsed: MAC='$mac' NAME='$name'" + log_info "Checking if MAC or NAME is in whitelist: '$WHITELIST_CLEAN'" + + if [ -n "$BT_MAC" ] && [ "$mac" = "$BT_MAC" ]; then + log_info "MAC matched: $mac" + if [ -z "$WHITELIST_CLEAN" ] || echo "$WHITELIST_CLEAN" | grep -wq "$mac" || echo "$WHITELIST_CLEAN" | grep -wq "$name"; then + log_info "MAC allowed by whitelist: $mac ($name)" + MATCH_FOUND=1 + break + else + log_info "MAC matched but not in whitelist: $name" + fi + elif [ -n "$BT_NAME" ] && [ "$name" = "$BT_NAME" ]; then + log_info "Name matched: $name" + if [ -z "$WHITELIST_CLEAN" ] || echo "$WHITELIST_CLEAN" | grep -wq "$mac" || echo "$WHITELIST_CLEAN" | grep -wq "$name"; then + log_info "Name allowed by whitelist: $name ($mac)" + BT_MAC="$mac" + MATCH_FOUND=1 + break + else + log_info "Name matched but not in whitelist: $name" + fi + fi +done < "$LATEST_FOUND_LOG" + + [ "$MATCH_FOUND" -eq 1 ] && break + sleep 2 +done + +if [ "$MATCH_FOUND" -ne 1 ]; then + log_fail "Expected device not found or not in whitelist" + echo "$TESTNAME FAIL" > "$RES_FILE" + exit 1 +fi + +log_info "Attempting to pair with $BT_NAME ($BT_MAC)" +if bt_pair_with_mac "$BT_MAC" "$PAIR_RETRIES"; then + log_info "Pairing successful. Attempting post-pair connection..." + if bt_post_pair_connect "$BT_MAC"; then + log_info "Post-pair connection successful, verifying with l2ping..." + if bt_l2ping_check "$BT_MAC" "$RES_FILE"; then + log_pass "Post-pair connection and l2ping verified" + echo "$TESTNAME PASS" > "$RES_FILE" + exit 0 + else + log_warn "Post-pair successful but l2ping failed" + echo "$TESTNAME FAIL" > "$RES_FILE" + exit 1 + fi + fi +else + log_fail "Pairing failed after $PAIR_RETRIES retries" + echo "$TESTNAME FAIL" > "$RES_FILE" + exit 1 +fi diff --git a/Runner/utils/functestlib.sh b/Runner/utils/functestlib.sh index a2ec407a..a0bcbb4a 100755 --- a/Runner/utils/functestlib.sh +++ b/Runner/utils/functestlib.sh @@ -3,9 +3,6 @@ # Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. # SPDX-License-Identifier: BSD-3-Clause-Clear -# Suppress 'Broken pipe' errors globally in this shell (put this at the very top once) -trap '' PIPE - # --- Logging helpers --- log() { level=$1 @@ -559,3 +556,459 @@ EOF rm -f "$safe_dhcp_script" return 0 } + +# Remove paired BT device by MAC +bt_cleanup_paired_device() { + mac="$1" + expect < /dev/null 2>&1 +log_user 0 +spawn bluetoothctl +expect "#" { send "remove $mac\r" } +expect "#" { send "quit\r" } +EOF +} + +# Retry a shell command N times with sleep +retry_command_bt() { + cmd="$1" + msg="$2" + max="${3:-3}" + count=1 + while [ "$count" -le "$max" ]; do + if eval "$cmd"; then + return 0 + fi + log_warn "Retry $count/$max failed: $msg" + count=$((count + 1)) + sleep 2 + done + return 1 +} + +# Check if a device (MAC or Name) is in the whitelist +bt_in_whitelist() { + device_name="$1" + device_mac="$2" + + whitelist_value="${WHITELIST:-}" + log_info "Checking if MAC='$device_mac' or NAME='$device_name' is in whitelist: '$whitelist_value'" + + echo "$whitelist_value" | tr -s ' ' '\n' | while IFS= read -r allowed; do + if [ "$allowed" = "$device_mac" ] || [ "$allowed" = "$device_name" ]; then + log_info "MAC or NAME matched and allowed: $allowed" + return 0 + fi + done + + log_info "MAC matched but neither MAC nor NAME in whitelist" + return 1 +} +# bt_parse_whitelist +# Reads a whitelist file where each line has: +bt_parse_whitelist() { + WHITELIST_ENTRIES="" + if [ -n "$1" ] && [ -f "$1" ]; then + while IFS= read -r line || [ -n "$line" ]; do + case "$line" in + ""|\#*) continue ;; # Skip blank lines and comments + *) WHITELIST_ENTRIES="${WHITELIST_ENTRIES}${line} +" ;; + esac + done < "$1" + fi +} + +# bt_in_whitelist +# Checks if a given device (MAC and optional name) exists +# Returns: +# 0 (success) if found +# 1 (failure) if not found +bt_in_whitelist() { + mac="$1" + name="$2" + + echo "$WHITELIST_ENTRIES" | while IFS= read -r entry || [ -n "$entry" ]; do + entry_mac=$(echo "$entry" | awk '{print $1}') + entry_name=$(echo "$entry" | cut -d' ' -f2-) + if [ "$mac" = "$entry_mac" ] && { [ -z "$entry_name" ] || [ "$name" = "$entry_name" ]; }; then + exit 0 + fi + done + + return 1 +} + +# bt_scan_devices +# Attempts to detect nearby Bluetooth devices using: +# 1. hcitool scan +# 2. fallback: bluetoothctl scan (via expect) +# Returns: +# 0 - if devices found +# 1 - if no devices found or error +bt_scan_devices() { + timestamp=$(date '+%Y%m%d_%H%M%S') + scan_log="scan_${timestamp}.log" + found_log="found_devices_${timestamp}.log" + : > "$scan_log" + : > "$found_log" + + log_info "Detecting Bluetooth adapter..." + hcidev=$(hciconfig | awk '/^hci/ { print $1 }' | head -n1) + if [ -z "$hcidev" ]; then + log_error "No Bluetooth adapter found" + return 1 + fi + + log_info "Using Bluetooth adapter: $hcidev" + hciconfig "$hcidev" up + + log_info "Running Bluetooth scan via hcitool..." + if hcitool -i "$hcidev" scan > hcitool_scan.tmp 2>/dev/null; then + tail -n +2 hcitool_scan.tmp | awk '{ printf "%s %s\n", $1, $2}' > "$found_log" + rm -f hcitool_scan.tmp + [ -s "$found_log" ] && return 0 + fi + + log_warn "hcitool scan returned nothing. Falling back to bluetoothctl scan..." + expect < "$scan_log" +log_user 0 +spawn bluetoothctl +expect "#" { send "power on\r" } +expect "#" { send "agent NoInputNoOutput\r" } +expect "#" { send "default-agent\r" } +expect "#" { send "scan on\r" } +sleep 10 +send "scan off\r" +expect "#" { send "quit\r" } +EOF + + grep -E "^\s*\[NEW\] Device" "$scan_log" | awk '{ print $4, substr($0, index($0, $5)) }' > "$found_log" + if [ ! -s "$found_log" ]; then + log_warn "Scan log is empty. Possible issue with bluetoothctl or adapter." + return 1 + fi + + return 0 +} + +# Pair with Bluetooth device using MAC (with retries and timestamped logs) +bt_pair_with_mac() { + bt_mac="$1" + max_retries=3 + retry=1 + + while [ "$retry" -le "$max_retries" ]; do + log_info "Attempt $retry: Pairing with $bt_mac using bluetoothctl" + + timestamp=$(date '+%Y%m%d_%H%M%S') + sanitized_mac=$(echo "$bt_mac" | sed 's/:/_/g') + logfile="pairing_attempt_${sanitized_mac}_${timestamp}.log" + + expect <]} { send "power on\r" } +} +expect { + -re {.*[#>]} { send "remove $bt_mac\r" } +} +expect { + -re {.*[#>]} { send "scan on\r" } +} +sleep 5 +expect { + -re {.*[#>]} { send "scan off\r" } +} +expect { + -re {.*[#>]} { send "agent KeyboardOnly\r" } +} +expect { + -re {.*[#>]} { send "default-agent\r" } +} +expect { + -re {.*[#>]} { send "pair $bt_mac\r" } +} +expect { + -re {.*Confirm passkey.*yes/no.*} { + send "yes\r" + exp_continue + } + -re {.*Paired: yes.*} { + send "quit\r" + exit 0 + } + -re {.*Pairing successful.*} { + send "quit\r" + exit 0 + } + -re {.*Failed to pair.*} { + send "quit\r" + exit 1 + } + -re {.*Device $bt_mac not available.*} { + send "quit\r" + exit 2 + } + timeout { + send "quit\r" + exit 3 + } +} +EOF + + result=$? + case "$result" in + 0) + log_pass "Pairing successful with $bt_mac" + return 0 + ;; + 1|3) + log_warn "Pairing attempt $retry failed for $bt_mac. Retrying after unpairing again..." + bt_cleanup_paired_device "$bt_mac" + ;; + 2) + log_warn "Device $bt_mac not available. Check proximity and power." + ;; + *) + log_warn "Unexpected pairing error code: $result" + ;; + esac + + retry=$((retry + 1)) + sleep 2 + done + + log_fail "Pairing failed after $max_retries attempts for $bt_mac" + return 1 +} + +# Post-pairing connection test with bluetoothctl and l2ping fallback +bt_post_pair_connect() { + target_mac="$1" + sanitized_mac=$(echo "$target_mac" | tr ':' '_') + timestamp=$(date '+%Y%m%d_%H%M%S') + base_logfile="bt_connect_${sanitized_mac}_${timestamp}" + max_attempts=3 + attempt=1 + + if bluetoothctl info "$target_mac" | grep -q "Connected: yes"; then + log_info "Device $target_mac is already connected, skipping explicit connect" + log_pass "Post-pair connection successful" + return 0 + fi + + while [ "$attempt" -le "$max_attempts" ]; do + log_info "Attempting to connect post-pair (try $attempt): $target_mac" + logfile="${base_logfile}_attempt${attempt}.log" + + expect <"$logfile" 2>&1 +log_user 0 +set timeout 10 +spawn bluetoothctl +expect { + -re ".*#.*" {} +} +send "connect $target_mac\r" +expect { + -re "Connection successful" { exit 0 } + -re "Failed to connect|Device not available" { exit 1 } + timeout { exit 1 } +} +EOF + result=$? + if [ "$result" -eq 0 ]; then + log_pass "Post-pair connection successful" + return 0 + fi + log_warn "Connect attempt $attempt failed (check $logfile)" + attempt=$((attempt + 1)) + sleep 2 + done + + log_info "Falling back to l2ping to trigger Bluetooth link with $target_mac" + if command -v l2ping >/dev/null 2>&1; then + if l2ping -c 3 -t 5 "$target_mac" >>"${base_logfile}_l2ping.log" 2>&1; then + if bluetoothctl info "$target_mac" | grep -q "Connected: yes"; then + log_pass "Fallback l2ping worked - device is now connected" + return 0 + fi + else + log_warn "l2ping failed or no response" + fi + else + log_warn "l2ping not available - skipping fallback" + fi + + log_fail "Post-pair connection failed for $target_mac" + return 1 +} +# Find MAC address from device name in scan log +bt_find_mac_by_name() { + target="$1" + log="$2" + grep -i "$target" "$log" | awk '{print $3}' | head -n1 +} + +# Scan and pair (dynamic adapter, timestamped logs) +bt_scan_and_pair() { + target_name="$1" + target_mac="$2" + scan_attempts=3 + found=0 + timestamp=$(date '+%Y%m%d_%H%M%S') + scan_log="scan_${timestamp}.log" + found_log="found_devices_${timestamp}.log" + : > "$scan_log" + : > "$found_log" + + log_info "Detecting Bluetooth adapter..." + hcidev=$(hciconfig | awk '/^hci/ { print $1 }' | head -n1) + if [ -z "$hcidev" ]; then + log_error "No Bluetooth adapter found" + return 1 + fi + log_info "Using Bluetooth adapter: $hcidev" + hciconfig "$hcidev" up + + log_info "Checking for connected devices..." + for mac in $(bluetoothctl paired-devices | awk '{print $2}'); do + if bluetoothctl info "$mac" | grep -q "Connected: yes"; then + log_info "Disconnecting $mac..." + bluetoothctl disconnect "$mac" >/dev/null 2>&1 + fi + done + + i=1 + while [ "$i" -le "$scan_attempts" ]; do + log_info "Scan attempt $i of $scan_attempts..." + expect < "$scan_log" +log_user 0 +spawn bluetoothctl +expect "#" { send "power on\r" } +expect "#" { send "agent NoInputNoOutput\r" } +expect "#" { send "default-agent\r" } +expect "#" { send "scan on\r" } +sleep 20 +send "scan off\r" +expect "#" { send "quit\r" } +EOF + + grep -iE 'Device ([0-9A-F]{2}:){5}[0-9A-F]{2}' "$scan_log" | awk '{print $2, $3}' > "$found_log" + + if grep -q "$target_mac" "$found_log"; then + found=1 + break + fi + i=$((i + 1)) + done + + if [ "$found" -ne 1 ]; then + log_warn "Device $target_mac ($target_name) not found after $scan_attempts attempts" + return 1 + fi + + log_info "Device $target_mac found. Proceeding to pair..." + + if bluetoothctl paired-devices | grep -q "$target_mac"; then + log_info "Device $target_mac is already paired. Unpairing..." + bluetoothctl remove "$target_mac" >/dev/null 2>&1 + sleep 1 + fi + + i=1 + while [ "$i" -le 3 ]; do + log_info "Pairing attempt $i..." + if expect </dev/null + done +} + +# Validate connectivity using l2ping +bt_l2ping_check() { + target_mac="$1" + logfile="$2" + + if ! command -v l2ping >/dev/null 2>&1; then + log_warn "l2ping command not available - skipping" + return 1 + fi + + log_info "Running l2ping test for $target_mac" + if l2ping -c 3 -t 5 "$target_mac" >>"$logfile" 2>&1; then + log_pass "l2ping to $target_mac succeeded" + return 0 + else + log_warn "l2ping to $target_mac failed" + return 1 + fi +} +