Все работы

Кейс · 2026

BMW iDrive Launcher

Собственный проект · Дизайн и разработка

В разработке

BMW iDrive Launcher

Заводской лаунчер на китайских магнитолах XTRONS тормозит и не умеет главного. Мы начали с малого — заменить его быстрым домашним экраном в эстетике современного iDrive и надёжно поднимать бортовой компьютер при старте машины. А выросло это в полноценную приборную панель: 3D-карусель плиток над живой картой, приборный янтарь на графите, музыка из Яндекса и собственный борткомпьютер, который читает шину I-Bus машины напрямую — а заодно складывает зеркала при выключении зажигания.

Всё на Kotlin и Jetpack Compose, со скриншот-тестами, без root. Обновляется по воздуху, исходный код открыт на GitHub. Машина — настоящая, ездит каждый день.

Как сделано

Чистый Kotlin + Jetpack Compose, без тяжёлых фреймворков. Инженерная часть — там же, где и «характер» интерфейса:

  • Логика отделена от UI и покрыта тестами — геометрия карусели, разбор кадров шины I-Bus, форматирование времени, маппинг воспроизведения, разрешение темы.
  • Скриншот-тесты Paparazzi рендерят каждый экран в родном разрешении головы: визуальная регрессия валит сборку, интерфейс не «уезжает» между версиями.
  • Лаунчер — это HOME, он не имеет права падать: все внешние вызовы, USB и карта обёрнуты в защиту и деградируют молча. Всё работает без root.
  • OTA-обновления по кнопке — правки прилетают на голову без флешки и без ADB.
  • Приложение следит за собой — падения, зависания и чёрный экран оно замечает само, пишет отчёт на диск раньше, чем пытается его отправить (в машине сети может не быть), и умеет восстановиться без участия водителя.

История: решения и грабли

Проект интересен не фичами, а тем, с чем пришлось столкнуться — и как это обошли. Почти каждый шаг упирался в особенности дешёвой магнитолы.

Голова без разработчика. Стандартный трюк «семь тапов по номеру сборки» на этих XTRONS не работает — режим разработчика просто не появляется. Вечер ушёл на то, чтобы найти недокументированный вход: в заводских настройках пароль adbon открывает ADB. Написали отдельную инструкцию — таких голов в стране много.

Эквалайзер, который врал. Сначала на экране музыки крутился «живой» эквалайзер. Оказалось, ROM магнитолы блокирует захват аудио, поэтому любой анализатор был синтетическим — просто дёргался не в такт. Честнее убрать: оставили спокойную янтарную полосу прогресса.

Серый экран и зависания. Периодически лаунчер уходил в серый экран. Добавили «сторожа» главного потока и отчёты о зависаниях, которые переживают перезагрузку и сами прилетают на сервер, — так нашли причину: экран музыки цеплялся к пустой медиа-сессии Яндекса и рисовал пустоту поверх играющей музыки.

Чёрный экран, который не оставлял следов. Хуже оказался другой отказ: по приезде главный экран иногда становился чёрным, при этом приложение продолжало работать. Ни падения, ни зависания — ловить нечего. Сначала подозрение пало на карту, и её рендер перевели на другой механизм: стало реже, но не прошло. Тогда сделали детектор, который раз в пару секунд смотрит на собственные пиксели. Остаётся окно пустым дольше нескольких секунд — он пишет отчёт на диск (даже если сети нет) и отправляет при первой возможности.

Первый же пойманный случай переписал диагноз. Ярких пикселей было ноль: исчезли и часы, и плитки, не только карта, — а главный поток отвечал нормально. Значит, ломается слой ниже интерфейса. И происходило это через полсекунды после того, как карта пересоздавала свой графический контекст, — а пересоздавала она его при каждом возврате на главный экран, десятки раз за поездку. Карту подняли выше по дереву экранов: теперь она создаётся один раз за запуск. На случай повторения детектор научили чинить самому — по шагам, от «убрать карту» до перезапуска, — и записывать, какой шаг вернул картинку. Ответ на этот вопрос и будет диагнозом.

Карта, которая капризничала. Хотелось живую карту фоном — в нашем стиле, а не дефолтный жёлтый Яндекс. Проприетарный формат стиля Яндекс.Карт молча не применялся. Перешли на открытый движок MapLibre + бесплатные тайлы OpenFreeMap со своим style.json на нашем сервере — теперь цвета карты правятся на сервере без пересборки приложения. А когда выяснилось, что провайдер магнитолы не пускает заграничный CDN тайлов, подняли прокси на своём сервере: голова ходит только к нам, мы тянем тайлы за неё.

Свой борткомпьютер вместо чужого приложения. Изначально лаунчер просто запускал стороннее i-Bus приложение. Оно глючило и дралось за единственный USB-порт адаптера. Решение — написать свой ридер шины I-Bus: подключаемся к адаптеру Resler (CP210x, 9600 8E1) по USB и сами разбираем сообщения приборной панели — скорость, обороты, температуры, зажигание. Протокол подтвердили декомпилом родного приложения. Собирали, по сути, вслепую — доступа к машине не было — но цифры на приборке сошлись с первого раза. Стороннее приложение удалили: порт теперь наш.

Кирпич — и воскрешение. Самый напряжённый эпизод: эксперимент с правами root залил на голову несовместимый загрузочный образ, и она ушла в вечный цикл «логотип → чёрный экран». Инструкции продавца (меню по кнопке, флешка) на этой Qualcomm-платформе не работают; предлагали возврат по почте — это месяцы без машины. Разобрались сами: нашли, что сервисный режим (fastboot) выходит только через один конкретный USB-разъём — четырёхпиновый OTG, а не «флешечные» порты, — и через него перезалили родной загрузочный образ, снятый с этой же головы. Всё восстановилось без потери данных и настроек. Заодно написали пошаговый разбор, чтобы такой кирпич больше не был приговором.

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

Зеркала и цена догадки. Захотелось, чтобы зеркала складывались сами. Коды взяли из открытых таблиц по I-Bus — и на этой машине они опустили стёкла. Команду сразу убрали, а вместо догадок сделали экран-пробник: он отправляет одну выбранную телеграмму и показывает, что ответила шина. Так нашли коды именно этого автомобиля, проверив каждый по одному.

Второй урок оказался не про протокол, а про питание. Напрашивается повесить складывание на замок: закрыл машину — сложились. Но голова питается от зажигания и гаснет через секунды после поворота ключа, и пока машина стоит запертой, слушать шину попросту некому. Поэтому автоматика привязана к зажиганию: выключил — сложились, включил — разложились.

Парктроник, который пищит поздно. Штатный парктроник E53 молчит, пока не подъедешь вплотную, — это ощущается как «тупит». Дистанции при этом есть на шине I-Bus (модуль отвечает на запрос). Добавили режим записи, который опрашивает датчики и логирует ответы, — чтобы раскодировать их и сделать более ранний, ступенчатый сигнал, чем штатный зуммер.

Разработка вслепую. Машины под рукой нет — итерации идут через OTA-кнопку «обновить» и диагностические логи с устройства. Логи стали нашими «глазами»: именно по ним настраивается разбор шины и добавляются новые показатели.

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

Уже добавили среднюю скорость поездки (со сбросом), считаемую прямо из шины. Дальше — топливо и расход в том же борткомпьютере и ранний сигнал парктроника.

Результат

Живой, ежедневно используемый лаунчер для головного устройства BMW X5 E53 — 3D-карусель в стиле iDrive над живой картой, собственный борткомпьютер (читает шину I-Bus машины напрямую по USB), автоскладывание зеркал, музыка из Яндекса, OTA-обновления и самодиагностика, которая сама чинит чёрный экран. Активная разработка, открытый код на GitHub.