diff --git a/skeleton/SYSTEM/tg5040/etc/bluetooth/bt_init.sh b/skeleton/SYSTEM/tg5040/etc/bluetooth/bt_init.sh index 3b3bda444..a5d89a031 100755 --- a/skeleton/SYSTEM/tg5040/etc/bluetooth/bt_init.sh +++ b/skeleton/SYSTEM/tg5040/etc/bluetooth/bt_init.sh @@ -44,6 +44,14 @@ start_bt() { start_hci_attach fi + # Allow headsets to auto-reconnect without user re-pairing. + # XRadio BT firmware sets store_hint=0, so link keys are never + # persisted; JustWorksRepairing=always lets earbuds re-initiate + # the bond from their side after a reboot. + if ! grep -q 'JustWorksRepairing = always' /etc/bluetooth/main.conf 2>/dev/null; then + sed -i 's/.*JustWorksRepairing.*/JustWorksRepairing = always/' /etc/bluetooth/main.conf 2>/dev/null + fi + # Start bluetooth daemon if not running d=`ps | grep bluetoothd | grep -v grep` [ -z "$d" ] && { @@ -69,6 +77,22 @@ start_bt() { # Set adapter name bluetoothctl system-alias "$DEVICE_NAME" 2>/dev/null + + # Proactively reconnect trusted A2DP audio devices after BT restart. + # Some headphones (e.g. Sony) won't fall back to JustWorks re-pairing + # when authentication fails; they expect the host to initiate using the + # stored link key. Runs in background to avoid blocking startup. + { + sleep 5 + for dev_dir in /var/lib/bluetooth/*/; do + for paired_dir in "${dev_dir}"*/; do + [ -f "${paired_dir}info" ] || continue + grep -q "0000110b" "${paired_dir}info" || continue + mac=$(basename "${paired_dir%/}") + bluetoothctl connect "$mac" >/dev/null 2>&1 + done + done + } & } } diff --git a/skeleton/SYSTEM/tg5050/etc/bluetooth/bt_init.sh b/skeleton/SYSTEM/tg5050/etc/bluetooth/bt_init.sh index e7a49309c..20c74dab0 100755 --- a/skeleton/SYSTEM/tg5050/etc/bluetooth/bt_init.sh +++ b/skeleton/SYSTEM/tg5050/etc/bluetooth/bt_init.sh @@ -52,6 +52,14 @@ start_bt() { start_hci_attach fi + # Allow headsets to auto-reconnect without user re-pairing. + # Some BT controller firmware never persists link keys to disk; + # JustWorksRepairing=always lets earbuds re-initiate the bond + # from their side after a reboot without user interaction. + if ! grep -q 'JustWorksRepairing = always' /etc/bluetooth/main.conf 2>/dev/null; then + sed -i 's/.*JustWorksRepairing.*/JustWorksRepairing = always/' /etc/bluetooth/main.conf 2>/dev/null + fi + # Start bluetooth daemon if not running d=`ps | grep bluetoothd | grep -v grep` [ -z "$d" ] && { @@ -77,8 +85,24 @@ start_bt() { # Set adapter name bluetoothctl system-alias "$DEVICE_NAME" 2>/dev/null + + # Proactively reconnect trusted A2DP audio devices after BT restart. + # Some headphones (e.g. Sony) won't fall back to JustWorks re-pairing + # when authentication fails; they expect the host to initiate using the + # stored link key. Runs in background to avoid blocking startup. + { + sleep 5 + for dev_dir in /var/lib/bluetooth/*/; do + for paired_dir in "${dev_dir}"*/; do + [ -f "${paired_dir}info" ] || continue + grep -q "0000110b" "${paired_dir}info" || continue + mac=$(basename "${paired_dir%/}") + bluetoothctl connect "$mac" >/dev/null 2>&1 + done + done + } & } - + } stop_bt() { diff --git a/workspace/all/audiomon/audiomon.cpp b/workspace/all/audiomon/audiomon.cpp index 6fafd66f7..79247ce62 100644 --- a/workspace/all/audiomon/audiomon.cpp +++ b/workspace/all/audiomon/audiomon.cpp @@ -33,12 +33,27 @@ enum DeviceType { bool use_syslog = false; bool running = true; +static std::string connected_a2dp_mac; void log(const std::string& msg) { if (use_syslog) syslog(LOG_INFO, "%s", msg.c_str()); else std::cout << msg << std::endl; } +static void initBtStateFromAsoundrc() { + std::ifstream f(AUDIO_FILE); + if (!f) return; + std::string content((std::istreambuf_iterator(f)), std::istreambuf_iterator()); + auto pos = content.find("defaults.bluealsa.device \""); + if (pos == std::string::npos) return; + pos += strlen("defaults.bluealsa.device \""); + auto end = content.find("\"", pos); + if (end == std::string::npos) return; + connected_a2dp_mac = content.substr(pos, end - pos); + log("Restored BT state from .asoundrc: " + connected_a2dp_mac); + SetAudioSink(AUDIO_SINK_BLUETOOTH); +} + void ensureDirExists(const std::string& path) { mkdir(path.c_str(), 0755); } @@ -203,6 +218,7 @@ void handleDeviceConnected(DBusConnection* conn, const std::string& path) { std::string mac = pathToMac(path); if (hasUUID(conn, path, UUID_A2DP)) { log("Audio device connected: " + mac); + connected_a2dp_mac = mac; writeAudioFile(mac, DEVICE_BLUETOOTH); SetAudioSink(AUDIO_SINK_BLUETOOTH); } else { @@ -212,8 +228,12 @@ void handleDeviceConnected(DBusConnection* conn, const std::string& path) { void handleDeviceDisconnected(DBusConnection* conn, const std::string& path) { std::string mac = pathToMac(path); - if (hasUUID(conn, path, UUID_A2DP)) { + // Use cached MAC rather than querying BlueZ: after an abrupt power-off the + // device's service cache may already be gone, causing hasUUID to return false + // and silently skip the audio switch-back. + if (!connected_a2dp_mac.empty() && mac == connected_a2dp_mac) { log("Audio device disconnected: " + mac); + connected_a2dp_mac.clear(); clearAudioFile(); // TODO: we could maintain a stack here, if USBC was connected before and restore that instead SetAudioSink(AUDIO_SINK_DEFAULT); @@ -285,6 +305,7 @@ int main(int argc, char* argv[]) { InitSettings(); // This will be updated as soon as something connects SetAudioSink(AUDIO_SINK_DEFAULT); + initBtStateFromAsoundrc(); signal(SIGINT, signalHandler); signal(SIGTERM, signalHandler); diff --git a/workspace/all/common/generic_bt.c b/workspace/all/common/generic_bt.c index 1f017f4e2..71fac47a8 100644 --- a/workspace/all/common/generic_bt.c +++ b/workspace/all/common/generic_bt.c @@ -479,6 +479,12 @@ void PLAT_bluetoothPair(char *addr) { } } + // Connect after pairing to initiate A2DP audio profile. + // Some headsets (store_hint=0 controllers) disconnect ~2s after + // pairing if the host doesn't open the audio channel first. + snprintf(cmd, sizeof(cmd), "bluetoothctl connect %s 2>/dev/null", addr); + system(cmd); + // Remove from discovered list since it's now paired bt_remove_discovered_device(addr); }