bluetooth-audit.sh 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. #!/usr/bin/env bash
  2. # mac-ops :: bluetooth-audit.sh
  3. # Bluetooth state: paired devices, currently connected, recent connection
  4. # issues, the Bluetooth daemon state.
  5. #
  6. # Common Bluetooth pains on Mac: keyboard/mouse disconnects, AirPods audio
  7. # quality drops, Magic Mouse cursor stutter, HomePod handoff failures. The
  8. # log usually has the smoking gun.
  9. set -u
  10. while [[ $# -gt 0 ]]; do
  11. case "$1" in
  12. --help|-h)
  13. cat <<EOF
  14. Usage: $0 [options]
  15. --json, --redact, --quiet, --verbose
  16. Reports:
  17. 1. Bluetooth power state + daemon health (bluetoothd)
  18. 2. Paired devices + connection state
  19. 3. Recent connection/disconnection events (24h)
  20. 4. Recent Bluetooth errors / faults
  21. 5. Wake-on-Bluetooth setting
  22. Common fixes:
  23. - sudo pkill bluetoothd # daemon restart
  24. - sudo defaults delete ~/Library/Preferences/com.apple.Bluetooth.plist # reset (drastic)
  25. - Remove + re-pair the misbehaving device
  26. EOF
  27. exit 0 ;;
  28. *) shift ;;
  29. esac
  30. done
  31. source "$(dirname "$0")/_lib/common.sh"
  32. parse_common_flags "$@"
  33. maybe_filter_self "$@"
  34. # ----------------------------------------------------------------------------
  35. section "1. BLUETOOTH POWER + DAEMON"
  36. # ----------------------------------------------------------------------------
  37. # Use system_profiler — slow but reliable
  38. bt_summary=$(system_profiler SPBluetoothDataType 2>/dev/null)
  39. state=$(echo "$bt_summary" | awk -F': *' '/State:/{print $2; exit}')
  40. addr=$(echo "$bt_summary" | awk -F': *' '/Address:/{print $2; exit}')
  41. case "$state" in
  42. *On*) log_pass "Bluetooth state" "On" ;;
  43. *Off*) log_info "Bluetooth state" "Off" ;;
  44. *) log_info "Bluetooth state" "${state:-unknown}" ;;
  45. esac
  46. [[ -n "$addr" ]] && note " Adapter address: $addr"
  47. # bluetoothd process
  48. if pgrep -x bluetoothd >/dev/null; then
  49. pid=$(pgrep -x bluetoothd | head -1)
  50. cpu=$(ps -p "$pid" -o pcpu= 2>/dev/null | tr -d ' ')
  51. log_pass "bluetoothd" "PID $pid (CPU ${cpu:-0}%)"
  52. else
  53. log_fail "bluetoothd" "not running"
  54. fi
  55. # ----------------------------------------------------------------------------
  56. section "2. PAIRED DEVICES"
  57. # ----------------------------------------------------------------------------
  58. # Extract device blocks from system_profiler output
  59. note " Paired devices (name | connected | type):"
  60. echo "$bt_summary" | awk '
  61. /Address:/ && /[0-9A-F][0-9A-F]-/{
  62. # We are inside the device list (not the adapter section)
  63. device_name=prev_line
  64. }
  65. /Connected:/ && device_name {
  66. connected=$0; sub(/^[[:space:]]+Connected:[[:space:]]+/, "", connected)
  67. }
  68. /Minor Type:|Major Type:/ && device_name {
  69. type=$0; sub(/^[[:space:]]+[^:]+:[[:space:]]+/, "", type)
  70. }
  71. /^$/ && device_name && connected {
  72. printf " %-35s %-10s %s\n", device_name, connected, (type ? type : "?")
  73. device_name=""; connected=""; type=""
  74. }
  75. {prev_line=$0}
  76. ' | head -20
  77. # ----------------------------------------------------------------------------
  78. section "3. RECENT CONNECTION EVENTS (24h)"
  79. # ----------------------------------------------------------------------------
  80. conn_events=$(log show --last 24h --style compact \
  81. --predicate '(subsystem == "com.apple.bluetooth" OR process == "bluetoothd") AND (eventMessage CONTAINS[c] "connect" OR eventMessage CONTAINS[c] "disconnect")' \
  82. 2>/dev/null | grep -iE "(connect|disconnect)" | tail -15)
  83. if [[ -n "$conn_events" ]]; then
  84. n=$(echo "$conn_events" | wc -l | tr -d ' \n')
  85. log_info "Bluetooth connect/disconnect events (24h)" "$n"
  86. note " Recent (last 10):"
  87. echo "$conn_events" | tail -10 | sed 's/^/ /'
  88. fi
  89. # ----------------------------------------------------------------------------
  90. section "4. RECENT BLUETOOTH ERRORS"
  91. # ----------------------------------------------------------------------------
  92. bt_errors=$(log show --last 24h --style compact \
  93. --predicate '(subsystem == "com.apple.bluetooth" OR process == "bluetoothd") AND (messageType == "Error" OR messageType == "Fault")' \
  94. 2>/dev/null | head -10)
  95. if [[ -n "$bt_errors" ]]; then
  96. n=$(echo "$bt_errors" | wc -l | tr -d ' \n')
  97. log_warn "Bluetooth error/fault events (24h)" "$n"
  98. echo "$bt_errors" | head -5 | sed 's/^/ /'
  99. else
  100. log_pass "Bluetooth error/fault events (24h)" "0"
  101. fi
  102. # ----------------------------------------------------------------------------
  103. section "5. WAKE FOR BLUETOOTH"
  104. # ----------------------------------------------------------------------------
  105. # "Wake for Bluetooth devices" — often the culprit for 3am wakes
  106. wake_setting=$(pmset -g | awk '/ttyskeepawake/{print $2; exit}')
  107. note " pmset settings (lookout for wake-for-bluetooth):"
  108. pmset -g 2>/dev/null | grep -iE "bluetooth|womp|hidwake" | sed 's/^/ /'
  109. # ----------------------------------------------------------------------------
  110. section "6. BLUETOOTH PROCESS NAMES TO KNOW"
  111. # ----------------------------------------------------------------------------
  112. note " Active BT-related processes:"
  113. ps -Ao pid,comm 2>/dev/null | grep -iE "bluetoothd|BTSupport|BTLE|AirPort|AirDrop" | head -10 | sed 's/^/ /'
  114. # ----------------------------------------------------------------------------
  115. emit_summary