Вот казалось бы, что такое 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/
Добавить комментарий