diff --git a/Runner/suites/Connectivity/Bluetooth/BT_SCAN/BT_SCAN_README.md b/Runner/suites/Connectivity/Bluetooth/BT_SCAN/BT_SCAN_README.md new file mode 100644 index 00000000..6c2e0039 --- /dev/null +++ b/Runner/suites/Connectivity/Bluetooth/BT_SCAN/BT_SCAN_README.md @@ -0,0 +1,388 @@ +# BT_SCAN + +## Overview + +`BT_SCAN` validates Bluetooth scan functionality on a Linux target. + +The test verifies that: + +1. `bluetoothd` is running. +2. A Bluetooth HCI adapter is available. +3. The adapter is visible to `bluetoothctl`. +4. The adapter can be powered on. +5. Bluetooth scan can discover nearby devices. + +The test supports two modes: + +- Generic scan mode: pass if any valid Bluetooth device is discovered. +- Target MAC mode: pass only if the requested target MAC address is discovered. + +This test uses common Bluetooth helpers from: + +```text +Runner/utils/lib_bluetooth.sh +``` + +## Test location + +```text +Runner/suites/Connectivity/Bluetooth/BT_SCAN/ +``` + +## Files + +```text +BT_SCAN/ +├── BT_SCAN.yaml +├── README.md +└── run.sh +``` + +## Dependencies + +Required tools: + +```text +bluetoothctl +pgrep +``` + +Required service: + +```text +bluetoothd +``` + +Required runtime support: + +```text +/sys/class/bluetooth/hci* +``` + +The test automatically skips if no HCI adapter is found. + +## Basic usage + +Run from the test directory: + +```sh +./run.sh +``` + +Run from anywhere inside the testkit repository: + +```sh +Runner/suites/Connectivity/Bluetooth/BT_SCAN/run.sh +``` + +## Generic scan mode + +When no target MAC is provided, the test checks generic device visibility. + +```sh +./run.sh +``` + +Expected behavior: + +```text +PASS: at least one valid Bluetooth device MAC is discovered. +FAIL: no valid Bluetooth device is discovered after scan attempts. +SKIP: no adapter/controller is available. +``` + +This mode is useful for basic scan sanity validation, but it depends on at least one discoverable or advertising Bluetooth device being nearby. + +## Target MAC mode + +To validate discovery of a specific Bluetooth device: + +```sh +./run.sh --target-mac AA:BB:CC:DD:EE:FF +``` + +Or using environment variables: + +```sh +BT_SCAN_TARGET_MAC=AA:BB:CC:DD:EE:FF ./run.sh +``` + +Alternative supported variable: + +```sh +BT_TARGET_MAC=AA:BB:CC:DD:EE:FF ./run.sh +``` + +Priority: + +```text +BT_SCAN_TARGET_MAC > BT_TARGET_MAC +``` + +Expected behavior: + +```text +PASS: requested MAC is discovered. +FAIL: requested MAC is not discovered after scan attempts. +``` + +## Adapter selection + +By default, the test auto-detects the adapter using Bluetooth helper logic. + +To force a specific adapter: + +```sh +./run.sh --adapter hci0 +``` + +Or: + +```sh +BT_ADAPTER=hci0 ./run.sh +``` + +## Scan tuning + +The updated scan flow supports retries to reduce CI flakiness. + +Default values: + +```text +BT_SCAN_SECONDS=15 +BT_SCAN_RETRIES=3 +BT_SCAN_RETRY_DELAY=2 +``` + +These values mean: + +```text +Run up to 3 scan attempts. +Each scan attempt lasts 15 seconds. +Wait 2 seconds between attempts. +``` + +### Environment tuning + +```sh +BT_SCAN_SECONDS=20 BT_SCAN_RETRIES=5 BT_SCAN_RETRY_DELAY=3 ./run.sh +``` + +### CLI tuning + +```sh +./run.sh --scan-seconds 20 --scan-retries 5 --scan-retry-delay 3 +``` + +## Supported options + +```text +--adapter + Select Bluetooth adapter manually. + Example: --adapter hci0 + +--target-mac + Validate that a specific target MAC is discovered. + Example: --target-mac AA:BB:CC:DD:EE:FF + +--scan-seconds + Scan duration per attempt in seconds. + Default: 15 + +--scan-retries + Number of scan attempts before failing. + Default: 3 + +--scan-retry-delay + Delay between scan attempts in seconds. + Default: 2 +``` + +## Supported environment variables + +```text +BT_ADAPTER + Bluetooth adapter to use. + Example: hci0 + +BT_SCAN_TARGET_MAC + Target MAC for scan validation. + Highest priority target MAC variable. + +BT_TARGET_MAC + Alternative target MAC variable. + +BT_SCAN_SECONDS + Scan window per attempt. + Default: 15 + +BT_SCAN_RETRIES + Number of scan attempts. + Default: 3 + +BT_SCAN_RETRY_DELAY + Delay between scan attempts. + Default: 2 +``` + +## Result file + +The test writes: + +```text +BT_SCAN.res +``` + +Possible contents: + +```text +BT_SCAN PASS +BT_SCAN FAIL +BT_SCAN SKIP +``` + +LAVA consumes this result file through: + +```sh +$REPO_PATH/Runner/utils/send-to-lava.sh BT_SCAN.res +``` + +## PASS criteria + +### Generic scan mode + +The test passes if any valid Bluetooth MAC address is discovered during scan attempts. + +Examples of acceptable discovery: + +```text +AA:BB:CC:DD:EE:FF DeviceName +AA:BB:CC:DD:EE:FF +``` + +A MAC-only discovery is accepted because name resolution can be delayed or unavailable on minimal images. + +### Target MAC mode + +The test passes only if the requested target MAC is discovered. + +Matching is case-insensitive: + +```text +aa:bb:cc:dd:ee:ff +AA:BB:CC:DD:EE:FF +``` + +Both are treated as the same MAC. + +## FAIL criteria + +The test fails if: + +- `bluetoothd` is not running after retries. +- Adapter power-on fails. +- Target MAC is provided but not discovered. +- No Bluetooth device is discovered in generic mode after all scan attempts. + +## SKIP criteria + +The test skips if: + +- Required dependencies are missing. +- No HCI adapter is found. +- Adapter is not visible to `bluetoothctl`. + +## LAVA usage + +Current YAML runs the test with defaults: + +```yaml +run: + steps: + - REPO_PATH=$PWD + - cd Runner/suites/Connectivity/Bluetooth/BT_SCAN/ + - ./run.sh || true + - $REPO_PATH/Runner/utils/send-to-lava.sh BT_SCAN.res +``` + +No YAML change is required for the default retry behavior. + +## LAVA usage with target MAC + +If a board has a known nearby Bluetooth peer, YAML can optionally export a target MAC: + +```yaml +run: + steps: + - REPO_PATH=$PWD + - cd Runner/suites/Connectivity/Bluetooth/BT_SCAN/ + - BT_SCAN_TARGET_MAC=AA:BB:CC:DD:EE:FF ./run.sh || true + - $REPO_PATH/Runner/utils/send-to-lava.sh BT_SCAN.res +``` + +## LAVA usage with scan tuning + +Optional tuning example: + +```yaml +run: + steps: + - REPO_PATH=$PWD + - cd Runner/suites/Connectivity/Bluetooth/BT_SCAN/ + - BT_SCAN_SECONDS=20 BT_SCAN_RETRIES=5 BT_SCAN_RETRY_DELAY=3 ./run.sh || true + - $REPO_PATH/Runner/utils/send-to-lava.sh BT_SCAN.res +``` + +## Debugging + +### Check adapter + +```sh +ls /sys/class/bluetooth +``` + +Expected: + +```text +hci0 +``` + +### Check bluetoothd + +```sh +pgrep bluetoothd +``` + +### Check controller visibility + +```sh +bluetoothctl list +``` + +On some minimal images, non-interactive `bluetoothctl list` may return no controllers even when interactive `bluetoothctl` works. The test handles this using helper logic. + +### Manual scan + +```sh +bluetoothctl +power on +scan on +devices +scan off +quit +``` + +### Non-interactive scan + +```sh +bluetoothctl --timeout 15 scan on +bluetoothctl devices +bluetoothctl scan off +``` + +## Notes + +- Generic scan mode is useful for sanity testing, but it depends on nearby Bluetooth advertisements. +- Target MAC mode is more deterministic for CI. +- Some devices advertise without a resolved name. The test accepts MAC-only discovery. +- BlueZ device cache can be delayed. The updated scan helper checks both live scan output and cached device output across retries. diff --git a/Runner/suites/Connectivity/Bluetooth/BT_SCAN/run.sh b/Runner/suites/Connectivity/Bluetooth/BT_SCAN/run.sh index 45aaa0de..de5cdc55 100755 --- a/Runner/suites/Connectivity/Bluetooth/BT_SCAN/run.sh +++ b/Runner/suites/Connectivity/Bluetooth/BT_SCAN/run.sh @@ -39,6 +39,9 @@ fi BT_ADAPTER="${BT_ADAPTER-}" BT_SCAN_TARGET_MAC="${BT_SCAN_TARGET_MAC-}" BT_TARGET_MAC="${BT_TARGET_MAC-}" +BT_SCAN_SECONDS="${BT_SCAN_SECONDS:-15}" +BT_SCAN_RETRIES="${BT_SCAN_RETRIES:-3}" +BT_SCAN_RETRY_DELAY="${BT_SCAN_RETRY_DELAY:-2}" while [ "$#" -gt 0 ]; do case "$1" in @@ -50,6 +53,18 @@ while [ "$#" -gt 0 ]; do BT_TARGET_MAC="$2" shift 2 ;; + --scan-seconds) + BT_SCAN_SECONDS="$2" + shift 2 + ;; + --scan-retries) + BT_SCAN_RETRIES="$2" + shift 2 + ;; + --scan-retry-delay) + BT_SCAN_RETRY_DELAY="$2" + shift 2 + ;; *) log_warn "Unknown argument ignored: $1" shift 1 @@ -158,52 +173,46 @@ else fi # ----------------------------- -# 6. Scan ON via helper +# 6. Scan using common Bluetooth helper # ----------------------------- -log_info "Testing scan ON..." -if ! bt_set_scan on "$ADAPTER"; then - log_warn "bt_set_scan(on) returned non-zero will still inspect devices list." -fi +log_info "Testing Bluetooth scan..." +log_info "Scan config: BT_SCAN_SECONDS=${BT_SCAN_SECONDS} BT_SCAN_RETRIES=${BT_SCAN_RETRIES} BT_SCAN_RETRY_DELAY=${BT_SCAN_RETRY_DELAY}" -# Optional: single Discovering snapshot after scan-on window -dstate_on="$(bt_get_discovering 2>/dev/null || true)" -[ -z "$dstate_on" ] && dstate_on="unknown" -log_info "Discovering state after scan ON window: $dstate_on" +SCAN_SECONDS="$BT_SCAN_SECONDS" +SCAN_ATTEMPTS="$BT_SCAN_RETRIES" +SCAN_RETRY_DELAY="$BT_SCAN_RETRY_DELAY" +MAC_ID="$TARGET_MAC" +BT_ADAPTER="$ADAPTER" -# ----------------------------- -# 7. Get devices list after scan ON -# - Try non-interactive bluetoothctl first -# - If empty/flaky, fallback to btctl_script "devices" "quit" -# ----------------------------- -devices_out="$( - bt_list_devices_raw 2>/dev/null \ - | grep '^Device ' || true -)" +export SCAN_SECONDS +export SCAN_ATTEMPTS +export SCAN_RETRY_DELAY +export MAC_ID +export BT_ADAPTER -if [ -n "$TARGET_MAC" ]; then - mac_up=$(printf '%s\n' "$TARGET_MAC" | tr '[:lower:]' '[:upper:]') - if printf '%s\n' "$devices_out" \ - | awk '/^Device /{print toupper($2)}' \ - | grep -q "$mac_up" - then +if bt_scan_devices "$TARGET_MAC"; then + dstate_on="$(bt_get_discovering 2>/dev/null || true)" + [ -z "$dstate_on" ] && dstate_on="unknown" + log_info "Discovering state after scan attempts: $dstate_on" + + if [ -n "$TARGET_MAC" ]; then log_pass "Target MAC $TARGET_MAC detected." else - log_fail "Target MAC $TARGET_MAC missing after scan ON window." - echo "$TESTNAME FAIL" > "$res_file" - exit 0 + log_pass "At least one Bluetooth device discovered." fi else - if [ -n "$devices_out" ]; then - log_info "Devices seen by bluetoothctl after scan ON:" - printf '%s\n' "$devices_out" | while IFS= read -r line; do - [ -n "$line" ] && log_info " $line" - done - log_pass "At least one device discovered." + dstate_on="$(bt_get_discovering 2>/dev/null || true)" + [ -z "$dstate_on" ] && dstate_on="unknown" + log_info "Discovering state after failed scan attempts: $dstate_on" + + if [ -n "$TARGET_MAC" ]; then + log_fail "Target MAC $TARGET_MAC missing after scan attempts." else - log_fail "No devices discovered in bluetoothctl devices after scan ON." - echo "$TESTNAME FAIL" > "$res_file" - exit 0 + log_fail "No Bluetooth devices discovered after scan attempts." fi + + echo "$TESTNAME FAIL" > "$res_file" + exit 0 fi # ----------------------------- @@ -226,3 +235,4 @@ fi echo "$TESTNAME PASS" > "$res_file" exit 0 + diff --git a/Runner/utils/lib_bluetooth.sh b/Runner/utils/lib_bluetooth.sh index 4bee53ef..76b3c267 100755 --- a/Runner/utils/lib_bluetooth.sh +++ b/Runner/utils/lib_bluetooth.sh @@ -241,31 +241,85 @@ expect { timeout {} eof {} } return 0 } -# Scan for nearby BT devices using bluetoothctl (no expect). +# Scan for nearby BT devices using bluetoothctl. +# # Env: -# BT_ADAPTER : adapter (e.g. hci0); auto-detected if unset -# SCAN_SECONDS : total scan window (default 10) -# MAC_ID : optional AA:BB:CC:DD:EE:FF; if set, succeed only if seen +# BT_ADAPTER : adapter, for example hci0; auto-detected if unset +# SCAN_SECONDS : scan window per attempt, default 10 +# SCAN_ATTEMPTS : scan attempts, default 3 +# SCAN_RETRY_DELAY : delay between attempts, default 2 +# MAC_ID : optional AA:BB:CC:DD:EE:FF; if set, succeed only if seen +# SCAN_RESULT : optional file to store parsed scan result lines +# # Usage: -# bt_scan_devices # list all devices (prints "MAC NAME" lines) -# bt_scan_devices AA:..:FF # success only if that MAC is seen +# bt_scan_devices # generic scan, succeeds if any valid MAC is seen +# bt_scan_devices AA:..:FF # target scan, succeeds only if target MAC is seen +# +# Output: +# MAC NAME +# # Return: -# 0 on success, 1 on failure (no devices / MAC not found) +# 0 on success +# 1 on failure +# +# Notes: +# - Reuses existing helper name; do not add a parallel scan helper. +# - Parses live bluetoothctl scan output and cached bluetoothctl devices output. +# - Accepts MAC-only discovery because name resolution can be delayed. +# - Keeps positional MAC support for existing callers. # shellcheck disable=SC2120 bt_scan_devices() { adapter="${BT_ADAPTER:-}" scan_window="${SCAN_SECONDS:-10}" + scan_attempts="${SCAN_ATTEMPTS:-3}" + scan_retry_delay="${SCAN_RETRY_DELAY:-2}" mac_id="${MAC_ID:-}" - # Allow positional MAC as first arg if MAC_ID not set if [ -z "$mac_id" ] && [ -n "${1:-}" ]; then mac_id="$1" fi - # Detect adapter (hci0, hci1, ...) + case "$scan_window" in + ""|*[!0-9]*) + scan_window=10 + ;; + esac + + case "$scan_attempts" in + ""|*[!0-9]*) + scan_attempts=3 + ;; + esac + + case "$scan_retry_delay" in + ""|*[!0-9]*) + scan_retry_delay=2 + ;; + esac + + if [ "$scan_window" -le 0 ] 2>/dev/null; then + scan_window=10 + fi + + if [ "$scan_attempts" -le 0 ] 2>/dev/null; then + scan_attempts=3 + fi + + if [ "$scan_retry_delay" -lt 0 ] 2>/dev/null; then + scan_retry_delay=2 + fi + + if [ -z "$adapter" ] && command -v findhcisysfs >/dev/null 2>&1; then + adapter="$(findhcisysfs 2>/dev/null || true)" + fi + if [ -z "$adapter" ] && command -v hciconfig >/dev/null 2>&1; then - adapter="$(hciconfig 2>/dev/null | awk '/^hci[0-9]+:/ {print $1}' | head -n1)" + adapter="$( + hciconfig 2>/dev/null \ + | awk '/^hci[0-9]+:/ { print $1; exit }' + )" fi + adapter="${adapter%:}" if [ -z "$adapter" ]; then @@ -273,112 +327,204 @@ bt_scan_devices() { return 1 fi - log_info "bt_scan_devices: using adapter $adapter (window=${scan_window}s)" + mac_id_up="" + if [ -n "$mac_id" ]; then + mac_id_up="$(printf '%s\n' "$mac_id" | tr '[:lower:]' '[:upper:]')" + fi + + log_info "bt_scan_devices: using adapter $adapter" + log_info "bt_scan_devices: scan_window=${scan_window}s attempts=${scan_attempts} retry_delay=${scan_retry_delay}s" - # Make sure adapter is powered on (best-effort) if command -v btpower >/dev/null 2>&1; then if ! btpower "$adapter" on; then - log_warn "bt_scan_devices: btpower($adapter on) did not report success; continuing anyway" + log_warn "bt_scan_devices: btpower($adapter on) did not report success; continuing" fi else - log_info "bt_scan_devices: btpower helper missing, assuming adapter is already powered." + bluetoothctl power on >/dev/null 2>&1 || true fi - # Start scan using CLI helper (non-expect) - if ! bt_set_scan on; then - log_warn "bt_scan_devices: bt_set_scan(on) reported failure; will still poll 'devices'." - fi - - start_ts=$(date +%s 2>/dev/null || printf '%s\n' 0) - [ "$scan_window" -le 0 ] 2>/dev/null && scan_window=10 + bluetoothctl select "$adapter" >/dev/null 2>&1 || true + attempt=1 found_lines="" - seen_target=0 - # Poll 'bluetoothctl devices' for up to scan_window seconds - while :; do - devices_out="$( - bluetoothctl devices 2>/dev/null | sanitize_bt_output || true + while [ "$attempt" -le "$scan_attempts" ]; do + log_info "bt_scan_devices: scan attempt ${attempt}/${scan_attempts}" + + live_out="$( + { + bluetoothctl select "$adapter" 2>/dev/null || true + bluetoothctl power on 2>/dev/null || true + bluetoothctl --timeout "$scan_window" scan on 2>&1 || true + } | sanitize_bt_output )" - if [ -n "$devices_out" ]; then - # Convert "Device MAC NAME..." → "MAC NAME" lines, unique - found_lines="$( - printf '%s\n' "$devices_out" | awk ' - function is_hex_pair(s, c1,c2) { - if (length(s)!=2) return 0 - c1=substr(s,1,1); c2=substr(s,2,1) - return index("0123456789ABCDEFabcdef", c1) && index("0123456789ABCDEFabcdef", c2) - } - function is_mac(m, a,n,i) { - n = split(m, a, ":"); if (n!=6) return 0 - for (i=1;i<=6;i++) if (!is_hex_pair(a[i])) return 0 - return 1 - } - /^Device[[:space:]]/ { - mac=$2 - if (!is_mac(mac)) next - name="" - for (i=3;i<=NF;i++) name = name (i==3?"":" ") $i - sub(/[[:space:]]+$/,"",name) - if (name=="") next - print mac, name - } - ' | sort -u + if command -v bt_set_scan >/dev/null 2>&1; then + if ! bt_set_scan off "$adapter"; then + log_warn "bt_scan_devices: bt_set_scan(off) reported failure after attempt $attempt" + fi + else + bluetoothctl scan off >/dev/null 2>&1 || true + fi + + if command -v bt_list_devices_raw >/dev/null 2>&1; then + cache_out="$( + bt_list_devices_raw 2>/dev/null \ + | sanitize_bt_output || true )" + else + cache_out="$( + bluetoothctl devices 2>/dev/null \ + | sanitize_bt_output || true + )" + fi + + found_lines="$( + { + printf '%s\n' "$live_out" + printf '%s\n' "$cache_out" + } | awk ' + function is_hex_pair(s) { + return s ~ /^[0-9A-Fa-f][0-9A-Fa-f]$/ + } + + function is_mac(m, a,n,i) { + n = split(m, a, ":") + if (n != 6) { + return 0 + } + + for (i = 1; i <= 6; i++) { + if (!is_hex_pair(a[i])) { + return 0 + } + } + + return 1 + } - if [ -n "$found_lines" ]; then - if [ -n "$mac_id" ]; then - if printf '%s\n' "$found_lines" \ - | awk -v t="$mac_id" 'BEGIN{IGNORECASE=1} $1==t {exit 0} END{exit 1}' - then - seen_target=1 - break - fi - else - # Any device is enough if no MAC_ID filter - seen_target=1 - break + function is_property_token(s) { + return s ~ /^(RSSI|UUIDs:|TxPower|ManufacturerData|ManufacturerData\.|Paired|Connected|Advertising|Flags:|ServiceData|Alias:|Class:|Icon:|Modalias:|Services)/ + } + + function emit_device(mac, name) { + mac = toupper(mac) + + if (name == "") { + name = "" + } + + if (!(mac in seen)) { + seen[mac] = name + order[++count] = mac + } else if (seen[mac] == "" && name != "") { + seen[mac] = name + } + } + + { + mac = "" + start = 0 + + for (i = 1; i <= NF; i++) { + if ($i == "Device" && (i + 1) <= NF && is_mac($(i + 1))) { + mac = $(i + 1) + start = i + 2 + break + } + } + + if (mac == "") { + next + } + + name = "" + for (i = start; i <= NF; i++) { + if (is_property_token($i)) { + break + } + + if ($i == "") { + continue + } + + name = name (name == "" ? "" : " ") $i + } + + sub(/[[:space:]]+$/, "", name) + + if (name ~ /^[0-9A-Fa-f][0-9A-Fa-f]-[0-9A-Fa-f][0-9A-Fa-f]-[0-9A-Fa-f][0-9A-Fa-f]-[0-9A-Fa-f][0-9A-Fa-f]-[0-9A-Fa-f][0-9A-Fa-f]-[0-9A-Fa-f][0-9A-Fa-f]$/) { + name = "" + } + + emit_device(mac, name) + } + + END { + for (i = 1; i <= count; i++) { + mac = order[i] + print mac, seen[mac] + } + } + ' + )" + + if [ -n "$found_lines" ]; then + log_info "bt_scan_devices: devices seen during attempt $attempt" + printf '%s\n' "$found_lines" | while IFS= read -r line; do + if [ -n "$line" ]; then + log_info " $line" fi + done + + if [ -n "${SCAN_RESULT:-}" ]; then + printf '%s\n' "$found_lines" > "$SCAN_RESULT" fi - fi - # Check timeout - now_ts=$(date +%s 2>/dev/null || printf '%s\n' "$scan_window") - elapsed=$((now_ts - start_ts)) - if [ "$elapsed" -ge "$scan_window" ]; then - break + if [ -n "$mac_id_up" ]; then + match_line="$( + printf '%s\n' "$found_lines" \ + | awk -v target="$mac_id_up" ' + toupper($1) == target { + print + found = 1 + exit + } + + END { + if (found != 1) { + exit 1 + } + } + ' + )" + + if [ -n "$match_line" ]; then + return 0 + fi + + log_warn "bt_scan_devices: target MAC not seen in attempt $attempt: $mac_id" + else + return 0 + fi + else + log_warn "bt_scan_devices: no devices parsed in attempt $attempt" fi - sleep 1 - done - # Stop scan (best-effort) - if ! bt_set_scan off; then - log_warn "bt_scan_devices: bt_set_scan(off) reported failure" - fi + attempt=$((attempt + 1)) - if [ -z "$found_lines" ]; then - log_warn "bt_scan_devices: no devices parsed from 'bluetoothctl devices'" - return 1 - fi + if [ "$attempt" -le "$scan_attempts" ]; then + sleep "$scan_retry_delay" + fi + done if [ -n "$mac_id" ]; then - # Print only the matching line if available - match_line="$( - printf '%s\n' "$found_lines" \ - | awk -v t="$mac_id" 'BEGIN{IGNORECASE=1} $1==t {print; exit}' - )" - if [ -n "$match_line" ]; then - printf '%s\n' "$match_line" - return 0 - fi - log_warn "bt_scan_devices: MAC_ID not found: $mac_id" - return 1 + log_warn "bt_scan_devices: MAC_ID not found after ${scan_attempts} attempts: $mac_id" + else + log_warn "bt_scan_devices: no devices found after ${scan_attempts} attempts" fi - # No filter: print all MAC/NAME pairs - printf '%s\n' "$found_lines" - return 0 + return 1 } # Pair with Bluetooth device using MAC (with retries and timestamped logs)