Linux-десктоп своими руками: подключаем bluetooth-наушники

от автора

Вот казалось бы, что такое 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, что было удобно для отладки.

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


ссылка на оригинал статьи https://habr.com/ru/articles/893346/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *