Samsung не подключается к Bluetooth-колонке: Codex сдался, Claude Code нашёл баг в самой колонке

поделиться

Мой Samsung Galaxy S25 FE отказывался сопрягаться с маленькой безымянной Bluetooth-колонкой с AliExpress, хотя она спокойно работала с ноутбуком и моим старым Xiaomi. Я разбирался с этим два вечера с двумя ИИ-агентами. Codex на модели Sol 6.1 собрал хорошие логи и посоветовал писать в Samsung. Claude Code на Opus 5.5 воспроизвёл сбой на Linux, нашёл ошибку в прошивке колонки и подключил телефон без root. Ниже — как снять Bluetooth-логи с Samsung, что в них искать и как обойти такой баг.

Колонка работала со всеми, кроме моего телефона

Колонка — безымянный кубик с AliExpress, который продавец называет «мини-колонкой с костной проводимостью»: 40x40x28 мм, 5 Вт, аккумулятор на 400 мА·ч. По Bluetooth она представляется как ES2401. С моим прошлым телефоном, Xiaomi Redmi Note 9 Pro, она работала без проблем, а ноутбук с Ubuntu подключился к ней с первого раза. Сам Samsung нормально работает с другими Bluetooth-устройствами. Не работала только эта пара: нажимаю на колонку в списке, телефон пару секунд думает и молча возвращает «не сопряжено».

Обычные советы не помогли. Выключение Wi-Fi ничего не дало. Безопасный режим тоже, значит, сторонние приложения ни при чём. Сброс только Bluetooth через ADB Android отклонил с ошибкой factoryReset requires a Root shell. А общий сброс сети в настройках Samsung стирает ещё и сохранённые пароли Wi-Fi, и делать его наугад я не хотел.

Как достать Bluetooth-логи с Samsung без root

Для такой диагностики root не нужен. В параметрах разработчика есть пункт «Журнал отслеживания HCI Bluetooth». После включения телефон записывает весь обмен между системным Bluetooth-стеком и радиочипом. Дальше нужно один раз повторить неудачное сопряжение и снять отчёт командой adb bugreport. Внутри архива лежат btsnoop-лог и logcat.

На Linux btsnoop читается командой btmon -r файл.log, она есть в пакете bluez. В выводе нужно найти адрес своего устройства и посмотреть последовательность команд вокруг попытки. Всё это за меня сделал агент, но шаги простые, и их стоит знать: без логов любые советы про Bluetooth — это гадание.

Codex нашёл, где ломается, но не нашёл почему

В первый вечер я отдал задачу Codex на Sol 6.1 в режиме high. Подготовительную работу он сделал добротно: через ADB снял bugreport, вытащил из него попытки подключения к колонке, для второго захвата включил подробный журнал производителя Samsung, а потом сам его выключил.

Логи точно показали место сбоя. Телефон запрашивает у колонки имя и получает ES2401. Затем отправляет Create Connection и через 6,4 секунды получает Page Timeout (0x04): колонка просто не ответила. До PIN-кода и обмена ключами дело не доходит. Так закончились все восемь попыток в двух записях.

< Remote Name Request          xx:xx:xx:xx:xx:xx
> Remote Name Req Complete     Status: Success, Name: ES2401
< Create Connection            Clock offset: 0x0000
> Connect Complete             Status: Page Timeout (0x04)   (+6,4 с)

Вывод Codex был такой: «проблема взаимодействия Bluetooth-стека или контроллера Samsung с этой колонкой». Он написал аккуратное обращение в поддержку Samsung и на этом остановился. В измеренном он не ошибся. Но он так и не поставил опыт, который отличил бы «сломан Samsung» от «сломана колонка».

У Claude Code первая гипотеза тоже оказалась неверной

На следующий день я открыл ту же папку в Claude Code с Opus 5.5. Честно говоря, ему было проще: логи и отчёт Codex уже лежали рядом. Opus заметил деталь: при запросе имени телефон передаёт смещение часов колонки, полученное при поиске, а в Create Connection — 0x0000, то есть «неизвестно». Радиочип у телефона тоже необычный. В HCI-логе видно собственный контроллер Samsung S5N6175, а не Broadcom или Qualcomm, как у большинства телефонов.

Гипотеза выглядела логично: этот контроллер не находит колонку без правильного смещения часов. Чтобы проверить её, Opus предложил снять лог с ноутбука, у которого подключение работает. Sudo у агента не было, поэтому sudo btmon я запустил сам в отдельном терминале. Агент отключил колонку и подключил обратно через bluetoothctl.

Ноутбук тоже отправил Clock offset: 0x0000, так что гипотеза отпала. Зато случайно получилось кое-что поважнее. Первая попытка переподключения, через пять секунд после разрыва, на ноутбуке тоже закончилась page timeout. Вторая, через полминуты, прошла за 2,7 секунды.

Ноутбук сломался точно так же, как телефон

Отсюда появилась другая версия: после разрыва соединения колонка какое-то время не отвечает. А запрос имени — это короткое соединение, которое тут же рвётся, и Android отправляет Create Connection через 10–30 мс после него. Opus повторил на ноутбуке ровно ту последовательность, которую выполняет телефон.

Что делал ноутбук Пауза перед Create Connection Результат
Разрыв -> подключение ~5 с после разрыва Page Timeout
Повторное подключение ~28 с после разрыва Подключился за 2,7 с
hcitool name -> подключение (как Samsung) 18 мс после запроса имени Page Timeout
hcitool name -> пауза 10 с -> подключение 10 с после запроса имени Подключился

Как только ноутбук стал действовать в том же порядке, что и телефон, он получил ту же ошибку. Значит, виновата прошивка колонки, а не Samsung. Запрос имени и быстрое подключение сразу после него — нормальное поведение по спецификации Bluetooth, и колонка обязана на него отвечать.

Почему Linux подключается, а Android нет

При сопряжении с новым устройством BlueZ на Linux сначала устанавливает соединение, а имя спрашивает уже внутри него. Лишнего разрыва нет, поэтому «глухой» период колонки ему не мешает. Android с незнакомым устройством поступает наоборот: спрашивает имя через временное соединение, разрывает его и сразу подключается снова.

Этот порядок зашит в Bluetooth-стек Android, в параметрах разработчика его не поменять. Время ожидания 6,4 секунды без root тоже не увеличить. Почему старый Redmi Note 9 Pro подключался, точно сказать не могу. У него более старый Android и другой Bluetooth-чип, так что тайминги или порядок команд могут отличаться, но его логи я не снимал.

Обход: сначала соединение, потом сопряжение

Android запрашивает имя только тогда, когда соединения с устройством ещё нет. Если соединение уже есть, сопряжение идёт через него. Значит, нужно заставить телефон подключиться к колонке без сопряжения и попросить пару, пока это соединение не закрылось.

У пользователя shell, под которым работает ADB, есть права BLUETOOTH_CONNECT и BLUETOOTH_PRIVILEGED, этого хватает. Opus написал небольшую программу на Java, скачал переносимый JDK и компилятор D8 от Google, собрал из неё DEX-файл и запустил на телефоне через app_process. Главное в ней — три вызова:

device.fetchUuidsWithSdp();   // открывает соединение для SDP без запроса имени
while (!device.isConnected()) Thread.sleep(20);
device.createBond();          // сопряжение по уже открытому соединению

С первого раза не получилось: внутри app_process метод BluetoothAdapter.getDefaultAdapter() возвращал null, потому что никто не зарегистрировал системный менеджер Bluetooth. Помог вызов ActivityThread.initializeMainlineModules(). Со второй попытки соединение установилось за пять секунд, телефон показал обычный запрос сопряжения, я нажал «Подключить», и колонка стала аудиоустройством по умолчанию. Теперь она подключается обычным способом из меню Bluetooth.

Чем на самом деле отличались два агента

Это один баг и по одной попытке на агента, так что это не бенчмарк. К тому же Opus начинал с данных Codex и сэкономил на этом целый вечер. Разница всё равно показательная. Оба правильно прочитали логи и нашли шаг, на котором всё ломается. Codex на логах закончил расследование. Opus проверил свою гипотезу на втором, рабочем устройстве. А когда проверка неожиданно провалилась, он пошёл за этим сбоем, а не стал его объяснять.

Почти всю ручную работу сделал агент: ADB, bugreport, разбор btmon, сборка и запуск программы. От меня понадобились три вещи: ввести пароль sudo, нажать «Подключить» на телефоне и запретить сбросы, которые удалили бы пароли Wi-Fi. Такое разделение мне понравилось. Это та же идея, что и в песочнице для ИИ-агентов с явными разрешениями: агент делает работу, а необратимые решения остаются за человеком.

Что проверить, если телефон не видит колонку

Если устройство работает со всеми, кроме одного телефона, не начинайте со сбросов. Включите журнал HCI, один раз попробуйте подключиться и снимите bugreport. Если запрос имени прошёл, а следующий за ним Create Connection закончился Page Timeout, в первую очередь подозревайте второе устройство. С Linux-ноутбука это проверяется за минуту: hcitool name и сразу bluetoothctl connect.

В итоге у меня работает колонка, есть программа на 60 строк, которую можно запустить снова, если пара потеряется, и точное описание бага для производителя колонки вместо расплывчатой жалобы в Samsung. Похожая история была с API-сервером для Vocalinux: от ИИ-агента полезнее всего не готовый ответ, а готовность поставить следующий опыт. Другие такие эксперименты собраны в рубрике Эксперименты с ИИ.

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