Вот казалось бы, что такое 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 "Start\n"; my %devices = (); # запуск сканирования print $control "scan on\n"; my $scan = 1; my $askmode = 'list'; Glib::Timeout->add(3000, sub { if($askmode eq 'list'){ print $control "devices\n"; } elsif($askmode eq 'list_connected'){ print $control "devices Connected\n"; } return 1; }); Glib::IO->add_watch(fileno($monitor), ['in'], sub { my ($fh, $a) = @_; my $line = <$monitor>; if ($line) { chomp $line; print "$line\n"; 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_address\n"; } 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_address\n"; } }); $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_address\n"; print $control "remove $device_address\n"; $list_store->remove($iter); delete $devices{ $device_address }; } }); $btn_stop->signal_connect('clicked',sub { if($scan){ print $control "scan off\n" ; $askmode = ''; $scan = 0; $btn_stop->set_label("Scan"); } else{ print $control "scan on\n" ; $askmode = 'list'; $scan = 1; $btn_stop->set_label("Stop"); } }); $window->show_all; Gtk3->main;
Просто создается окошко с кнопками, и через программу bluetoothctl управляем подключением.
Если запускать из консоли - еще и видны ответы bluetoothctl, что было удобно для отладки.

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