#!/usr/bin/env bash # mac-ops :: bluetooth-audit.sh # Bluetooth state: paired devices, currently connected, recent connection # issues, the Bluetooth daemon state. # # Common Bluetooth pains on Mac: keyboard/mouse disconnects, AirPods audio # quality drops, Magic Mouse cursor stutter, HomePod handoff failures. The # log usually has the smoking gun. set -u while [[ $# -gt 0 ]]; do case "$1" in --help|-h) cat </dev/null) state=$(echo "$bt_summary" | awk -F': *' '/State:/{print $2; exit}') addr=$(echo "$bt_summary" | awk -F': *' '/Address:/{print $2; exit}') case "$state" in *On*) log_pass "Bluetooth state" "On" ;; *Off*) log_info "Bluetooth state" "Off" ;; *) log_info "Bluetooth state" "${state:-unknown}" ;; esac [[ -n "$addr" ]] && note " Adapter address: $addr" # bluetoothd process if pgrep -x bluetoothd >/dev/null; then pid=$(pgrep -x bluetoothd | head -1) cpu=$(ps -p "$pid" -o pcpu= 2>/dev/null | tr -d ' ') log_pass "bluetoothd" "PID $pid (CPU ${cpu:-0}%)" else log_fail "bluetoothd" "not running" fi # ---------------------------------------------------------------------------- section "2. PAIRED DEVICES" # ---------------------------------------------------------------------------- # Extract device blocks from system_profiler output note " Paired devices (name | connected | type):" echo "$bt_summary" | awk ' /Address:/ && /[0-9A-F][0-9A-F]-/{ # We are inside the device list (not the adapter section) device_name=prev_line } /Connected:/ && device_name { connected=$0; sub(/^[[:space:]]+Connected:[[:space:]]+/, "", connected) } /Minor Type:|Major Type:/ && device_name { type=$0; sub(/^[[:space:]]+[^:]+:[[:space:]]+/, "", type) } /^$/ && device_name && connected { printf " %-35s %-10s %s\n", device_name, connected, (type ? type : "?") device_name=""; connected=""; type="" } {prev_line=$0} ' | head -20 # ---------------------------------------------------------------------------- section "3. RECENT CONNECTION EVENTS (24h)" # ---------------------------------------------------------------------------- conn_events=$(log show --last 24h --style compact \ --predicate '(subsystem == "com.apple.bluetooth" OR process == "bluetoothd") AND (eventMessage CONTAINS[c] "connect" OR eventMessage CONTAINS[c] "disconnect")' \ 2>/dev/null | grep -iE "(connect|disconnect)" | tail -15) if [[ -n "$conn_events" ]]; then n=$(echo "$conn_events" | wc -l | tr -d ' \n') log_info "Bluetooth connect/disconnect events (24h)" "$n" note " Recent (last 10):" echo "$conn_events" | tail -10 | sed 's/^/ /' fi # ---------------------------------------------------------------------------- section "4. RECENT BLUETOOTH ERRORS" # ---------------------------------------------------------------------------- bt_errors=$(log show --last 24h --style compact \ --predicate '(subsystem == "com.apple.bluetooth" OR process == "bluetoothd") AND (messageType == "Error" OR messageType == "Fault")' \ 2>/dev/null | head -10) if [[ -n "$bt_errors" ]]; then n=$(echo "$bt_errors" | wc -l | tr -d ' \n') log_warn "Bluetooth error/fault events (24h)" "$n" echo "$bt_errors" | head -5 | sed 's/^/ /' else log_pass "Bluetooth error/fault events (24h)" "0" fi # ---------------------------------------------------------------------------- section "5. WAKE FOR BLUETOOTH" # ---------------------------------------------------------------------------- # "Wake for Bluetooth devices" — often the culprit for 3am wakes wake_setting=$(pmset -g | awk '/ttyskeepawake/{print $2; exit}') note " pmset settings (lookout for wake-for-bluetooth):" pmset -g 2>/dev/null | grep -iE "bluetooth|womp|hidwake" | sed 's/^/ /' # ---------------------------------------------------------------------------- section "6. BLUETOOTH PROCESS NAMES TO KNOW" # ---------------------------------------------------------------------------- note " Active BT-related processes:" ps -Ao pid,comm 2>/dev/null | grep -iE "bluetoothd|BTSupport|BTLE|AirPort|AirDrop" | head -10 | sed 's/^/ /' # ---------------------------------------------------------------------------- emit_summary