Chapter Audio Configuration kita tutup dengan kasus klasik: headset yang sudah pair tetapi menolak tersambung. Bluetooth di Linux memang punya reputasi itu, padahal tumpukannya cukup rapi: semua perangkat Bluetooth ditangani satu stack bernama BlueZ, dan antarmuka paling andalnya adalah bluetoothctl di terminal. Chapter ini membedah alur klasik power → scan → pair → trust → connect, membedakan tiga status yang sering dikira sama, lalu menyiapkan kita menghadapi masalah pairing yang paling sering muncul.

BlueZ dan bluetoothctl #

BlueZ adalah stack Bluetooth resmi Linux. Inti berjalannya adalah daemon bluetoothd, yang mengelola adapter (pengontrol radio, biasa muncul sebagai hci0) dan mengekspos semua perangkat lewat API D-Bus. Baik panel Bluetooth di GNOME/KDE maupun bluetoothctl sebenarnya hanyalah klien yang berbicara ke daemon yang sama — jadi ilmunya tidak hangus ketika kita pindah desktop environment.

bluetoothctl adalah klien CLI tersebut, dan layak dikuasai karena tiga alasan: berjalan di atas SSH untuk mesin headless, perilakunya sama di semua distro, dan pesan kesalahannya jujur. Perintahnya bisa dijalankan satu-satu (bluetoothctl power on) maupun dari dalam sesi interaktif — rincian seluruh perintahnya ada di bluetoothctl(1) dan ikhtisar ekosistemnya di wiki Bluetooth .

Alur Klasik: power, agent, scan, pair, trust, connect #

Menghubungkan perangkat baru selalu mengikuti urutan yang sama. Berikut contoh tampilan sesi bluetoothctl interaktif untuk sebuah headset — baris [bluetooth]# adalah prompt yang menandakan kita berada di dalam sesi:

console
$ bluetoothctl
Agent registered
[bluetooth]# power on
Changing power on succeeded
[bluetooth]# agent on
Agent is already registered
[bluetooth]# default-agent
Default agent request successful
[bluetooth]# scan on
Discovery started
[NEW] Device 00:1A:7D:DA:71:13 Logitech G435
[bluetooth]# pair 00:1A:7D:DA:71:13
Attempting to pair with 00:1A:7D:DA:71:13
[CHG] Device 00:1A:7D:DA:71:13 Paired: yes
Pairing successful
[bluetooth]# trust 00:1A:7D:DA:71:13
[CHG] Device 00:1A:7D:DA:71:13 Trusted: yes
[bluetooth]# connect 00:1A:7D:DA:71:13
Attempting to connect to 00:1A:7D:DA:71:13
[CHG] Device 00:1A:7D:DA:71:13 Connected: yes
Connection successful
[bluetooth]# exit

Tiap tahap punya tugasnya sendiri. power on menyalakan radio adapter; tanpa langkah ini, scan pun tidak akan menemukan apa pun. agent on dan default-agent menyiapkan pairing agent — pihak yang menjawab konfirmasi angka atau PIN saat pairing, tampil sebagai dialog konfirmasi di layar. scan on memulai pencarian dan mencetak tiap perangkat yang ditemukan lewat baris [NEW]; setelah cukup, scan off menghentikannya. Tiga perintah terakhir — pair, trust, connect — sering dianggap satu paket padahal statusnya berbeda.

Paired, Trusted, Connected: Tiga Status Berbeda #

Kebingungan terbesar pemakai Bluetooth Linux datang dari tiga status yang saling bertumpuk:

StatusYang tersimpanEfek praktis
Pairedlink key di kedua pihakboleh connect kapan pun tanpa proses pairing ulang
Trustedpenanda pada daemonkoneksi dari perangkat itu diterima otomatis, tanpa konfirmasi
Connectedtautan radio yang sedang aktifperangkat benar-benar siap dipakai

Pairing adalah pertukaran kunci: kedua perangkat menyimpan link key sehingga percakapan berikutnya tidak perlu kenalan ulang. Trust adalah keputusan kebijakan di sisi komputer: perangkat tepercaya boleh membangunkan tautan tanpa kita konfirmasi satu per satu — inilah yang membuat headset tersambung sendiri begitu dinyalakan. Connect baru menegakkan tautan radio sungguhan. Headset yang sudah pair tetapi tidak kunjung connect adalah masalah yang sama sekali berbeda dari headset yang belum dikenal.

Urutan dan syarat antar status itu bisa kita amati tanpa perangkat sungguhan. Skrip simulasi-pairing.sh di bundle chapter ini meniru state machine bluetoothctl dengan menyimpan status tiap perangkat ke berkas kecil — kolomnya Name, Paired, Trusted, Connected:

bash
export BT_STATE="$PWD/state-bt"
rm -rf "$BT_STATE"
bash simulasi-pairing.sh connect 00:1A:7D:DA:71:13
bash simulasi-pairing.sh pair 00:1A:7D:DA:71:13
bash simulasi-pairing.sh trust 00:1A:7D:DA:71:13
bash simulasi-pairing.sh connect 00:1A:7D:DA:71:13
bash simulasi-pairing.sh status 00:1A:7D:DA:71:13

Output yang Dihasilkan:

text
Failed to connect: device 00:1A:7D:DA:71:13 belum di-pair
[CHG] Device 00:1A:7D:DA:71:13 Paired: yes
Pairing successful
[CHG] Device 00:1A:7D:DA:71:13 Trusted: yes
[CHG] Device 00:1A:7D:DA:71:13 Connected: yes
Connection successful
Device 00:1A:7D:DA:71:13 (Logitech G435)
  Name: Logitech G435
  Paired: yes
  Trusted: yes
  Connected: yes

Baris pertama gagal justru disengaja: connect sebelum pair ditolak karena perangkatnya belum dikenal. Setelah pair, status berubah dan connect pun lolos. Bagian penting skripnya:

  1. Baris state=${BT_STATE:-$(mktemp -d)}: direktori penyimpanan status diambil dari variabel lingkungan BT_STATE, atau direktori sementara segar bila tidak diatur — sehingga skrip aman dijalankan dari folder mana pun.
  2. Fungsi nama_perangkat(): peta MAC ke nama ramah pembaca, pengganti basis data nama asli BlueZ.
  3. Fungsi set_flag(): mengubah satu baris status (Paired, Trusted, Connected) lewat sed -i pada berkas perangkat — state machine sungguhan menyimpan kunci kriptografi, simulasi ini cukup menyimpan bendera.
  4. Blok connect): memeriksa berkas perangkat lebih dulu; perangkat tak dikenal ditolak dengan pesan ala bluetoothctl, perangkat yang sudah tersambung juga ditolak.
  5. Blok demo): dipakai saat skrip berjalan tanpa argumen — menjalankan urutan pair-trust-connect di direktori sementara, lalu keluar dengan status sukses.

Kebijakan Daemon di main.conf #

Perilaku bluetoothd diatur oleh /etc/bluetooth/main.conf — berkas INI bersisi kebijakan global. Salah satu keluhan paling klasik, “adapter selalu mati setelah reboot”, diselesaikan lewat berkas ini. Kita bedah bentuknya lewat salinan latihan:

bash
mkdir -p bluetooth
cat > bluetooth/main.conf <<'EOF'
[General]
ControllerMode = dual
FastConnectable = true

[Policy]
AutoEnable = true
ReconnectAttempts = 7
ReconnectIntervals = 1,2,4,8,16
EOF
cat bluetooth/main.conf

Output yang Dihasilkan:

text
[General]
ControllerMode = dual
FastConnectable = true

[Policy]
AutoEnable = true
ReconnectAttempts = 7
ReconnectIntervals = 1,2,4,8,16

Mari kita bedah tiap pasangan kunci-nilainya:

  1. Bagian [General]: pengaturan perilaku adapter secara umum.
  2. ControllerMode = dual: adapter bekerja dua mode sekaligus, BR/EDR klasik (headset, keyboard) dan LE modern (sensor) — mode bredr atau le membatasi salah satunya.
  3. FastConnectable = true: adapter memancar lebih sering agar siap disambung, dengan konsekuensi daya — berguna di desktop, boros di laptop.
  4. Bagian [Policy]: kebijakan otomatis daemon, bukan kemampuan adapter.
  5. AutoEnable = true: adapter dinyalakan begitu daemon mulai — inilah obat klasik untuk Bluetooth yang mati sendiri setiap reboot; setelah mengubah berkas ini, muat ulang dengan sudo systemctl restart bluetooth.
  6. ReconnectAttempts dan ReconnectIntervals: daemon mencoba menyambung ulang perangkat tepercaya yang terputus, sampai tujuh kali dengan jeda yang membesar mengikuti daftar interval — menjelaskan mengapa headset kadang tersambung sendiri beberapa detik setelah dinyalakan.

Radio yang Terkunci: rfkill #

“Bluetooth saya hilang” sering kali bukan hilang, melainkan diblokir. Kernel menyimpan status radio lewat rfkill: soft block dipasang perangkat lunak (mode pesawat, tombol fn), sedangkan hard block dipasang saklar fisik. Berikut contoh tampilan rfkill list pada laptop yang radionya diblokir mode pesawat:

text
0: hci0: Bluetooth
    Soft blocked: yes
    Hard blocked: no
1: phy0: Wireless LAN
    Soft blocked: no
    Hard blocked: no

Adapter hci0 dalam keadaan soft blocked tidak akan muncul sebagai perangkat yang bisa dipakai walau daemon sehat. Membukanya satu perintah: rfkill unblock bluetooth (halaman rinciannya di rfkill(8) ). Bila barisnya menulis Hard blocked: yes, tidak ada perintah yang membantu — cari saklar atau tombol fisiknya dulu.

Data Pairing di /var/lib/bluetooth #

bluetoothd menyimpan identitas adapter dan kunci tiap perangkat di /var/lib/bluetooth/: satu direktori per adapter (dinamai MAC adapter), berisi subdirektori per perangkat yang di dalamnya ada berkas info dengan link key hasil pairing. Struktur ini penting dipahami karena dua hal. Pertama, kunci yang basi di kedua sisi membuat pairing berulang kali gagal — obatnya bluetoothctl remove 00:1A:7D:DA:71:13 yang menghapus subdirektori tersebut, lalu pair ulang dari nol. Kedua, dua komputer tidak boleh memakai perangkat dengan kunci yang sama-sama tersimpan basi; hapus di satu sisi agar perangkat tidak bingung.

Masalah Umum dan Solusinya #

Sisanya adalah pola yang berulang. Empat keluhan paling sering beserta jalur keluarnya:

GejalaPenyebab khasSolusi
Pairing berhasil, tidak mau connectperangkat tidur atau profil audio belum siapnyalakan perangkat, connect ulang, cek profil A2DP/HFP lewat pactl seperti di Chapter Audio Configuration
Tidak muncul saat scan onperangkat tidak dalam mode discoverableaktifkan mode pairing di perangkat, dekati jaraknya
Pairing gagal berkali-kalilink key lama basi di salah satu sisiremove perangkat lalu pair ulang
Adapter tidak terdeteksi sama sekalisoft block, atau pengontrol USB kehilangan akalrfkill unblock bluetooth; bila tetap, reset modul dengan sudo modprobe -r btusb && sudo modprobe btusb

Dua pola pertama selesai dalam hitungan detik; pola keempat adalah satu-satunya yang kadang butuh reboot. Setelah perangkat nirkabel beres, satu ranah kustomisasi desktop yang nyaris selalu membuat kaget pendatang baru dari Windows masih tersisa: printer — dan ternyata di Linux semuanya berporos pada satu layanan bernama CUPS. Kita bongkar pada Chapter Printer Management .

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

simulasi-pairing.sh

Unduh berkas
bash
#!/usr/bin/env bash
# simulasi-pairing.sh -- simulasi state machine pairing ala bluetoothctl
# Pemakaian: BT_STATE=<direktori> bash simulasi-pairing.sh <perintah> [MAC]
# Perintah : pair | trust | connect | disconnect | status
# Tanpa argumen: menjalankan demo singkat di direktori sementara.

state=${BT_STATE:-$(mktemp -d)}
mkdir -p "$state"

nama_perangkat() {
    case "$1" in
        00:1A:7D:DA:71:13) echo "Logitech G435" ;;
        88:C9:D0:F5:12:34) echo "Keyboard Kantor" ;;
        *)                 echo "Perangkat Tanpa Nama" ;;
    esac
}

berkas_perangkat() {
    echo "$state/$(echo "$1" | tr ':' '_')"
}

set_flag() {  # $1=MAC $2=Paired|Trusted|Connected $3=yes|no
    sed -i "s/^$2: .*/$2: $3/" "$(berkas_perangkat "$1")"
}

perintah=${1:-demo}
mac=${2:-00:1A:7D:DA:71:13}
f=$(berkas_perangkat "$mac")

case "$perintah" in
    pair)
        cat > "$f" <<EOF2
Name: $(nama_perangkat "$mac")
Paired: yes
Trusted: no
Connected: no
EOF2
        echo "[CHG] Device $mac Paired: yes"
        echo "Pairing successful"
        ;;
    trust)
        if [ ! -f "$f" ]; then
            echo "Device $mac not available"
        else
            set_flag "$mac" Trusted yes
            echo "[CHG] Device $mac Trusted: yes"
        fi
        ;;
    connect)
        if [ ! -f "$f" ]; then
            echo "Failed to connect: device $mac belum di-pair"
        elif grep -q '^Connected: yes' "$f"; then
            echo "Failed to connect: perangkat sudah tersambung"
        else
            set_flag "$mac" Connected yes
            echo "[CHG] Device $mac Connected: yes"
            echo "Connection successful"
        fi
        ;;
    disconnect)
        if [ ! -f "$f" ] || ! grep -q '^Connected: yes' "$f"; then
            echo "Device $mac not connected"
        else
            set_flag "$mac" Connected no
            echo "[CHG] Device $mac Connected: no"
        fi
        ;;
    status)
        if [ ! -f "$f" ]; then
            echo "Device $mac not available"
        else
            echo "Device $mac ($(nama_perangkat "$mac"))"
            sed 's/^/  /' "$f"
        fi
        ;;
    demo)
        echo "== demo di direktori sementara =="
        BT_STATE=$state bash "$0" pair "$mac"
        BT_STATE=$state bash "$0" trust "$mac"
        BT_STATE=$state bash "$0" connect "$mac"
        BT_STATE=$state bash "$0" status "$mac"
        ;;
    *)
        echo "Perintah tidak dikenal: $perintah"
        echo "Pilihan: pair | trust | connect | disconnect | status"
        ;;
esac