nikitenok.dev

Микрофон пульта LG Magic Remote: два байта F9 03

Как оказалось, что микрофон LG Magic Remote MR25GA включает хост командой, как это подтвердилось на Linux и почему мак так не умеет.

Опубликовано
Проекты
nik-node
Теги
nik-node, ble, reverse-engineering

Это продолжение поста про пульт LG Magic Remote как контроллер для мака.

25 октября в 02:46 Linux-машина отправила пульту два байта F9 03, и через 70 мс пошёл звук: 66 пакетов в секунду, за полторы минуты ни одной потери. Полтора месяца мы упирались не в железо, а в то, что никто не говорил пульту «включи микрофон».

Почему раньше не работало

Голос в пульте не поток, который можно подслушать, а режим, который включает телевизор командой. Мы подписывались на репорты и ждали.

На маке bluetoothd не включает уведомления на HID-репортах пульта, подписки пустые. Android как HID-хост тоже. Но даже это не главное: команду F9 03 не слал никто. Даже amity с полноценным BlueZ-хостом голоса не видел. Подписаться на голосовой репорт 0xFE мало: пульт ждёт команды от «телевизора».

Задним числом всё сходится. Репорт 0xFE мы не видели ни разу, запрос GET_REPORT на 0xFE и на канал команд 0xF9 возвращал ошибку. Ответов без команды не бывает.

Откуда взялся протокол

Ключ нашёлся не в эфире, а в прошивке телевизора. Сторонний разработчик разобрал пять прошивок телевизоров LG на webOS и описал протокол в PR #8 к brainrom/lg-magic. Код LG он в PR не переносил, только вывел протокол.

У PR два ограничения, важных для меня. Он проверен только синтетикой, на живом пульте его не запускали. И MR25 он прямо не поддерживает: драйвер узнаёт только MR18–MR20. Протокол MR25 по прошивкам телевизоров 2025 года никто не сверял. Мой пульт из нового поколения на Realtek RTL8762, у него другой ID производителя, так что опкоды могли и поменяться.

Что выяснили на Linux

Спаривание на Linux прошло без диалога, в режиме Just Works: ловить кнопку Connect за полторы секунды, как на маке, не надо. Ни драйвер, ни патч ядра не понадобились: хватило hidraw и скрипта.

Сначала я проверил канал безопасной командой: F9 11 спрашивает версию прошивки. Ответ пришёл через 1 мс и в том же формате, что описан в PR. Значит, команды до пульта доходят. Потом голос:

  • F9 03 — старт. Через 1 мс в обычном репорте кнопок приходят коды старта голоса, через 70 мс первый пакет 0xFE.
  • F9 05 — держать поток. Я слал его каждые 4 секунды, поток не прерывался 90 секунд. По PR без него пульт сам останавливает голос примерно через 6 секунд.
  • F9 04 — стоп.

Пакет 0xFE — 124 байта. Первый байт — id репорта, второй — счётчик, он растёт на единицу с каждым пакетом. Дальше два байта кода события, во всех 6330 пакетах за все записи там нули. Потом два блока по 60 байт: короткий заголовок, кадр mSBC на 57 байт и байт выравнивания. mSBC — тот же кодек, что у Bluetooth-гарнитур: 16 кГц, моно. Один пакет — 15 мс звука.

За 90 секунд пришло 5941 пакет, это 66 в секунду. По счётчику потерь ноль, самый большой разрыв между пакетами — 98 мс.

Пока идёт голос, кнопки и гироскоп молчат: репорт 0xFD присылает 3–4 пакета на старте и всё. После F9 04 он сразу возвращается. Отсюда главная неудобная вещь: пока пульт говорит, не видно, отпустил ли я кнопку микрофона. Поэтому запись по кнопке сейчас устроена так: ждём нажатия, шлём F9 03, пишем фиксированные 10 секунд, шлём F9 04. В 02:57 я нажал кнопку и сказал фразу: речь легла на 0,5–6 секунду, RMS 1200–3300 при фоне около 17. Главная сессия кластера прогнала эту запись через распознавание, и текст совпал дословно: «Раз, два, три. Проверка микрофона пульта. Включи свет на кухне.»

С PR #8 разошлись две вещи. MR25 шлёт гироскоп и кнопки сам, команда F9 01 для движения ему не нужна. А второй байт 0xFE — счётчик, а не байт статуса, как назван в PR.

А мак сможет такое слать?

Это был мой первый вопрос. Слать — да. F9 03 и F9 04 с мака проходят без ошибки, пульт включает голос по команде с мака.

Принимать — нет. Уведомлений мак не включает, остаётся опрос, как с кнопками. Я говорил в пульт около 20 секунд. За 25,1 секунды опрос поймал 187 пакетов: 7,5 в секунду из нужных 66, ещё 327 запросов вернули ошибку. Из 374 кадров mSBC собралось 2,8 секунды звука из 25, около 11 %. Голос в файле есть, но обрывками.

Почему мак так не умеет

Следующий вопрос был: «а нет способа всё-таки к маку в таком же режиме, как и Linux, подключиться?» И ещё раз: «как сделать, чтобы мак работал в режиме как Linux, неужели ничего нет?»

Через встроенный Bluetooth простыми стандартными средствами — нет. Apple не даёт сторонним приложениям HID-сервис. Включать уведомления на HID-репортах может только сам bluetoothd, а для этого пульта он этого не делает. Помощник с правами root не поможет. DriverKit тоже: драйвер сидит над готовым HID-устройством и до GATT не достаёт, плюс нужны разрешения от Apple. Наверное, залезть глубже как-то можно, но это уже серьёзный реверс-инжиниринг и копание во внутренностях macOS. Туда я пока не хочу.

Работает только отдельное радио со своим стеком:

  1. USB-донгл со своим BLE-стеком на маке: Bumble или BTstack через libusb.
  2. Linux-VM с пробросом донгла, внутри обычный BlueZ. Нюанс: macOS может сама перехватить внешний контроллер. Лечится настройкой nvram, но совет написан для Catalina, на свежей macOS я его не проверял.
  3. Мост на ESP32 или nRF: плата сама подключается к пульту и отдаёт звук в мак по USB.
  4. Мини-ПК с Linux рядом, звук в мак по сети.

Что решил

Пульт отдаю Linux. Если понадобится на маке, буду пробрасывать туда программно.

Колонку с массивом микрофонов пока не покупаю. Пульт даёт микрофон, колонка JBL даёт звук, из этого и соберу свою умную колонку без новых устройств.

Linux сейчас, ESP32-S3 потом. Плата у меня есть. План такой: ESP32-S3 сама подключается к пульту, включает голос и шлёт звук по Wi-Fi, а ключевую фразу слушает прямо на плате. С этим пультом на ESP32 я ещё ничего не проверял.

Микрофон у пульта хороший. Ту же фразу я записал через семь микрофонов: мак сам по себе и через наушники OnePlus и Sony, телефон сам по себе и через те же наушники, и пульт. Без ошибок распознались только три: телефон сам по себе, телефон через OnePlus и пульт. Отсюда ещё идея: купить второй такой пульт и переделать его в мини-версию, переиспользовав микрофон, или перепаять его железо на ESP32.

Ключевую фразу в самом пульте я пока делать не буду, хотя, может, и захочу. Пульт сам не слушает: голос включает хост. Значит, нужна своя прошивка под чип Realtek RTL8762: писать её с нуля, рисковать окирпичить пульт, а постоянное прослушивание сажало бы батарейки, насколько быстро — не мерил.

Как будить колонку, пока открыто. Варианта три:

  1. Кнопка AI.
  2. Ключевая фраза на хосте при постоянном потоке голоса. Тогда кнопки всё время молчат, и непонятно, сколько проживут батарейки. Сначала тест.
  3. Отдельный микрофон у Linux-машины.

Что не решено

  • Отпускание кнопки микрофона во время голоса не видно, окно записи фиксированное.
  • Не проверено, останавливает ли MR25 поток сам без F9 05 и присылает ли когда-нибудь код конца голоса.
  • Как будить колонку: кнопка или ключевая фраза.
  • Сколько проживут батарейки при постоянном голосе.

Источники