Вот казалось бы, что такое Bluetooth?
По сути - просто протокол связи, некая замена проводам, который в том числе позволяет передавать через себя разные данные.
Как мог бы он выглядеть с точки зрения классического UNIX-way: при подключении, скажем, наушников создавались бы некоторые устройства, ну например /dev/bt/pcm, и возможно /dev/bt/control, с помощью команд отправляемых в control можно было бы управлять свойствами pcm.
А какая-нибудь программа могла бы вместо /dev/snd/pcm отправлять звуковой поток в /dev/bt/pcm.
Ну, примерно так, как это происходит сегодня при подключении флешки (/dev/sdX) или usb-tty (/dev/ttyUSBX) - всё работает аналогично встроенным дискам или COM-портам, программам разницы нет.
Но это с точки зрения всяких динозавров.
Но Bluetooth появился не так давно, и разработкой софта для него занимались те, для кого UNIX-way - это что-то древнее и непонятно зачем, поэтому сделали по-современному, модно-стильно-молодежно.
Работой с bluetooth-у нас занимается специальный демон, bluetoothd. Что и как он там конкретно делает - простому смертному знать не положено, для простого смертного есть специальная книга заклинаний - bluetoothctl.
Нужно сказать ему правильные заклинания - например, "scan on" или там "connect XXXXXXX" - все остальное демон делает сам.
Но одного демона для такого важного дела мало - поэтому, например для наушников, нужен еще один демон, pulseaudio. Этот как раз занимается тем, куда отправлять звук - в родную "железку" или по bluetooth-каналу.
Для того, чтобы они могли общаться между собой - нужен еще один демон, D-Bus. Они отправляют друг другу сообщения через него, и таким образом обеспечивается взаимодействие.
Вообще, идея D-Bus, на самом деле - отличная: просто передавайте нужные сообщения и команды, и реагируйте на них - и всё будет хорошо. Магия!
Но есть нюанс...
Чем вообще магия отличается от науки?
Наука оперирует формулами и пониманием: камень падает потому что на него действует гравитация, перо летает по воздуху потому сопротивление воздуха падению пера противодействует силе гравитации, а движение воздушных потоков может изменить вектор движения, и всё это хоть и может быть сложно, но поддается пониманию и расчетам.
Магия же оперирует заклинаниями: нужно просто правильно произнести "Вингардиум левиосса!" (почему именно Вингардиум? хз, такое заклинание) и взмахнуть специальной волшебной палочкой.
А если не получается - значит, вы произнесли неправильно, или не так взмахнули, или палочка не волшебная, или место заколдованное, или в этом мире магия просто не работает.
Причина неизвестного характера - пробуйте еще.
Так и тут. Пока вы находитесь в магической среде, скажем, Gnome DE - всё более-менее работает, если не глючит.
Ну, может быть надо иногда "выйти и войти", плюнуть через левое плечо или постучать по дереву - должно работать, просто произнесите правильно заклинание.
Возьмем, к примеру, Убунту: сконнектили наушники, все прекрасно работает - убрали наушники.
Достали наушники снова - ии? И надо снова зайти в настройки блютуса, выбрать подключенные наушники, отключить подключенные наушники, включить их снова - оппа, "коннектед" - можно пользоваться дальше.
Почему? - Потому что "так здесь заведено!", таков ритуал.
Но стоит выйти за пределы этого мира...
Вот например, не проходит заклинание коннекта. Устройство обнаруживается - но не хочет подключаться.
В процессе колдунства выясняется, что это не оно "не хочет", и даже не bluetoothd-демон вредничает, а проблема в том, что pulseaudio почему-то падает в обморок от вида наушников, и тогда блютуз-демон, видимо, из солидарности, говорит что подключение не удалось.
Если на pulseaudio побрызгать святой водой и поднять заново, но очень быстро, пока блютусный демон не передумал его пугать - то внезапно наушники подключаются и даже прекрасно работают. Магия!
(это не стёб - оно РЕАЛЬНО так себя ведет)
То есть, это не аппаратная проблема - а проблема в межличностных взаимоотношениях демонов. Просто прекрасно.
Или вот например, D-Bus. В теории, согласно магическим книгам, через него демону Блютуса можно отправить команду "начни сканирование" - и он начнет, а о найденном сообщит через тот же D-Bus.
Но в реальности, команду-то он получит, и даже начнет ее выполнять - но D-Bus тут же скажет "отставить!".
Почему? Потому что если вы, отдав команду, не стоите у него над душой и не требуете немедленного результата - значит, вам не очень-то и хотелось, недостаточно выражено намерение включить поиск.
Видимо так, потому что других, более логичных причин, почему D-Bus отправляет команду завершения сканирования - не видно.
Его кто просил, спрашивается?
Возможно, изучение исходного кода дало бы понимание логики разработчика - но вот еще демонологией заниматься некогда.
Опять же, пресловутая "интеграция".
Как уже говорил - идея-то прекрасная, обмен сообщениями - но почему, если понадобилось что-то поменять в конфигах и перезапустить D-Bus - тут же молча падает браузер? (не говоря про обмороки у pulseaudio - это само собой).
Причем тут браузер?!
Что, современные разработчики unix-софта никогда не слышали про какие-нибудь сокеты, про сеть, обрывы, реконнекты?
Почему какой-нибудь MQTT-сервер можно прекрасно перезапускать, и куча железок, обменивавшихся через него сигналами и командами, продолжит работать (если не совсем криворукие программисты их программировали), а остановка D-Bus приводит к крашу программ?
Может тогда заменить D-Bus на MQTT?
Опять же, модно-стильно-молодежно, IoT, беcшовное взаимодействие...
В общем, магия и колдовство.
Все "советы из интернета" по данной теме сводятся к повторению "попробуйте ещё такое заклинание - оно точно должно работать!" - поэтому поддержу эту традицию и вот мой набор работающих заклинаний:
Итак, допустим, ставим всё с нуля:
Для начала устанавливаем того самого Bluetooth-демона и его напарника для Pulseaudio:
apt install bluez pulseaudio-module-bluetooth
Теперь найти и обезвредить то, что заставляет падать в обморок Pulseaudio
vim /etc/pulse/default.pa
...
# закомментировать вот это
#load-module module-suspend-on-idle
...
vim /etc/pulse/client.conf
autospawn = yes
daemon-binary = /usr/bin/pulseaudio
vim /etc/pulse/daemon.conf
...
exit-idle-time = -1
...
(что-то из этого лишнее, но магия требует больше заклинаний)
Перезапускаем pulseaudio:
pulseaudio --kill
pulseaudio --start
Теперь пытаемся что-то настроить:
bluetoothctl
show - должен показать список имеющихся работающих контроллеров.
Возможно, стоит попробовать включить их, если они выключены (Powered: no)
power on - включение
scan on - сканирование
Попробовать что-то подключить из найденного
connect XX:XX:XX:XX:XX:XX:XX
Всякие наушники-колонки должны просто подключиться, без спаривания и прочего.
Если подключение прошло успешно - можно из "запомнить"
trust XX:XX:XX:XX:XX:XX:XX
Тогда при повторном обнаружении в зоне видимости они сами автоматически должны подключиться.
А вот чем хороша магия - если заклинания сработали как надо - то это всё, можно пользоваться, дальше разбираться не обязательно.
Подключенное устройство должно появиться в pavucontrol:
если это колонка или наушники - то в Outputs, если с микрофоном - то еще и в Inputs.
Остается только сделать простейшую графическую обертку для этих команд (да, есть blueman, знаю).
По традиции - снова Perl:
apt install libgtk3-perl
#!/usr/bin/perl -w
use strict;
use warnings;
use IPC::Open2;
use Gtk3 -init;
# Создание окна GTK3
my $window = Gtk3::Window->new('toplevel');
$window->set_title("Bluetooth Devices");
$window->set_default_size(400, 300);
$window->signal_connect(destroy => sub {
Gtk3->main_quit;
});
# Создаем главный контейнер GtkBox (вертикальный)
my $vbox = Gtk3::Box->new('vertical', 5);
$window->add($vbox);
# Список устройств
my $list_store = Gtk3::ListStore->new('Glib::String', 'Glib::String', 'Glib::String');
my $tree_view = Gtk3::TreeView->new($list_store);
$vbox->pack_start($tree_view, 1, 1, 0);
# Определение колонок для отображения
my $col1 = Gtk3::TreeViewColumn->new_with_attributes('Device Address', Gtk3::CellRendererText->new, text => 0);
my $col2 = Gtk3::TreeViewColumn->new_with_attributes('Name', Gtk3::CellRendererText->new, text => 1);
my $col3 = Gtk3::TreeViewColumn->new_with_attributes('Status', Gtk3::CellRendererText->new, text => 2);
$tree_view->append_column($col1);
$tree_view->append_column($col2);
$tree_view->append_column($col3);
# Создание кнопок для подключения/удаления
my $hbox = Gtk3::Box->new('horizontal', 5);
$vbox->pack_start($hbox, 0, 0, 5);
my $button_box = Gtk3::ButtonBox->new('horizontal');
$hbox->pack_start($button_box, 0, 0, 5);
my $btn_connect = Gtk3::Button->new_with_label("Connect");
$button_box->add($btn_connect);
my $btn_remove = Gtk3::Button->new_with_label("Remove");
$button_box->add($btn_remove);
my $btn_stop = Gtk3::Button->new_with_label("Stop");
$button_box->add($btn_stop);
$|=1;
my $red_button = Gtk3::CssProvider->new;
$red_button->load_from_data(<<TEMP_CSS);
button {
background-color: #dd5733;
}
TEMP_CSS
my $green_button = Gtk3::CssProvider->new;
$green_button->load_from_data(<<TEMP_CSS);
button {
background-color: #57dd33;
}
TEMP_CSS
# подключение программы управления
my $monitor=undef;
my $control=undef;
open2($monitor, $control, "bluetoothctl")
or die "Cannot start bluetoothctl: $!";
$|=1;
print "Startn";
my %devices = ();
# запуск сканирования
print $control "scan onn";
my $scan = 1;
my $askmode = 'list';
Glib::Timeout->add(3000, sub {
if($askmode eq 'list'){
print $control "devicesn";
}
elsif($askmode eq 'list_connected'){
print $control "devices Connectedn";
}
return 1;
});
Glib::IO->add_watch(fileno($monitor), ['in'], sub {
my ($fh, $a) = @_;
my $line = <$monitor>;
if ($line) {
chomp $line;
print "$linen";
if($askmode eq 'list' && $line =~ /^Device ([w:]+) (.+)$/){
my $address = $1;
my $name = $2;
if(! $devices{ $address }){
$devices{ $address } = { name => $name };
my $iter = $list_store->append;
$devices{ $address }->{iter} = $iter;
$list_store->set($iter, 0 => $address, 1 => $name);
}
}
elsif($askmode eq 'connect'){
if($line =~ /Connection successful/m){
$askmode = 'connected';
my $style_context = $btn_connect->get_style_context;
$style_context->add_provider($green_button, -10);
my $sel = $tree_view->get_selection;
my ($model, $iter) = $sel->get_selected;
$model->set($iter, 2 => "OK");
my $device_address = $model->get($iter, 0);
print $control "trust $device_addressn";
}
elsif($line =~ /Failed to connect/m){
$askmode = 'failed';
my $style_context = $btn_connect->get_style_context;
$style_context->add_provider($red_button, -10);
my $sel = $tree_view->get_selection;
my ($model, $iter) = $sel->get_selected;
$model->set($iter, 2 => "Error");
}
}
}
return 1;
});
# Обработка нажатия кнопок
$btn_connect->signal_connect('clicked', sub {
my $style_context = $btn_connect->get_style_context;
$style_context->remove_provider($red_button);
$style_context->remove_provider($green_button);
my $sel = $tree_view->get_selection;
if($sel){
my ($model, $iter) = $sel->get_selected;
my $device_address = $model->get($iter, 0);
$askmode = 'connect';
print $control "connect $device_addressn";
}
});
$btn_remove->signal_connect('clicked', sub {
my $sel = $tree_view->get_selection;
if($sel){
my ($model, $iter) = $sel->get_selected;
my $device_address = $model->get($iter, 0);
$askmode = 'disconnect';
print $control "disconnect $device_addressn";
print $control "remove $device_addressn";
$list_store->remove($iter);
delete $devices{ $device_address };
}
});
$btn_stop->signal_connect('clicked',sub {
if($scan){
print $control "scan offn" ;
$askmode = '';
$scan = 0;
$btn_stop->set_label("Scan");
}
else{
print $control "scan onn" ;
$askmode = 'list';
$scan = 1;
$btn_stop->set_label("Stop");
}
});
$window->show_all;
Gtk3->main;
Просто создается окошко с кнопками, и через программу bluetoothctl управляем подключением.
Если запускать из консоли - еще и видны ответы bluetoothctl, что было удобно для отладки.

По этой схеме прекрасно подключаются наушники нескольких видов, колонки, муз.центр - и всё само переподключается при необходимости.
Автор: JBFW