01 / Задача проекта

Единая система управления салоном

Требовалось разработать Android-систему, которая объединяет штатные функции салона Geely Monjaro, работает на экранах разных форматов и позволяет пассажиру управлять разрешёнными функциями с телефона или планшета.

Исходные ограничения определили архитектуру. Комплектации автомобиля различаются, программный интерфейс может возвращать служебные коды, состояние со временем устаревает, а команда движения не должна продолжаться после разрыва связи. Поэтому приложение сначала опрашивает доступное оборудование, отдельно хранит известность и возраст каждого значения, а Client передаёт только намерение пользователя. Окончательное решение и физическое выполнение остаются у Server в штатной мультимедиа.

Приложение не подключается напрямую к CAN и не работает с проводами электронных блоков. Server использует штатные Android-интерфейсы производителя ECARX AdaptAPI/ECarXCar и отдельные интерфейсы сигналов. Проще говоря, это программный доступ к функциям, которые внутри автомобиля связаны с сетью его блоков управления.

Ключевое требование

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

Функциональный объём

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

Client остаётся пультом, а не вторым контроллером автомобиля. Он не обращается к интерфейсам производителя и не управляет приводами напрямую. Каждое действие возвращается на Server, где ещё раз проверяются доступность функции, текущее состояние, возраст измерения и ограничения безопасности.

Monjaro Passenger Control на штатном дисплее автомобиля
Server в мультимедиаКлимат передней зоны открыт на штатном дисплее автомобиля.
Одновременная работа Server в мультимедиа и Client на планшете
Две ролиServer и пассажирский Client работают одновременно и показывают единое состояние салона.
Android-интерфейс мультимедиа и пассажирский Client в салоне автомобиля
Android-контурШтатная Android-среда остаётся доступной рядом с приложением, а Client работает на отдельном устройстве.

Фотографии подготовлены из полноразмерных HEIC-оригиналов 5712×4284 без увеличения исходного изображения.

Полный цикл разработки

01Исследование API

Функции, зоны, служебные коды и различия прошивок.

02Архитектура

Две роли, единое состояние и граница доверия.

03Android HMI

Пять разделов, темы и адаптация экранов.

04Проверка

JVM-тесты, Python-стенд и полевые сценарии.

05Эксплуатация

Подпись, OTA, диагностика и поддержка релиза.

Проект полностью выполнен мной: от исследования программного интерфейса производителя и архитектуры Server/Client до Android HMI, логики приводов, LAN-протокола, Python-стенда, тестов, сборки, подписи, OTA, полевой диагностики и поддержки релиза.

02 / Архитектура системы

Server/Client и граница выполнения команд

Система разделена на четыре слоя: пользовательский Client, локальный протокол, Server в штатной мультимедиа и программный интерфейс автомобиля. Экран отделён от источника данных, а чтение состояния — от выполнения команды.

Архитектура Server Client и замкнутый контур проверки состояния
Рис. 1. Верхняя часть показывает границу выполнения команд: Client передаёт намерение, Server проверяет его и только затем обращается к внутренней шине автомобиля. Нижняя часть показывает замкнутый контур состояния с повторным чтением и резервным опросом.
Server

Установлен в штатной мультимедиа. Только он связан с программным слоем автомобиля и может передать команду исполнительной функции.

Client

Работает на телефоне или планшете. Он получает снимки по Wi‑Fi и не может обойти повторную проверку Server.

Один модуль, две сборки

Server и Client собираются из одного Android-модуля как два варианта с разными идентификаторами пакета. Модель данных, декодер, интерфейс и тесты остаются общими и не расходятся в отдельных репозиториях. Меняется только граница доступа: локальная роль обращается к автомобильному слою, удалённая отправляет запрос на Server.

PassengerControlHub, единый для процесса приложения, хранит последние снимки автомобиля и низкоуровневых подсистем. Чтения и обычные команды проходят через один последовательный поток, поэтому не образуют неконтролируемые гонки. Сторожевые таймеры кресел вынесены отдельно: они способны отправить STOP, даже если основной поток занят чтением медленного автомобильного API.

03 / Модель данных

Опрос функций и достоверное состояние

До построения рабочих экранов Server опрашивает функции конкретного автомобиля. Для каждого значения модель отдельно хранит наличие функции, достоверность чтения, возраст данных и возможность записи.

ПризнакЧто означаетКак влияет на интерфейс
supported / доступнаФункция найдена у конкретного автомобиляБез подтверждения элемент управления не показывается
known / известнаЗначение прошло проверку типа и диапазонаНеизвестность не изображается как OFF или 0
fresh / свежаяИзмерение не старше допустимого времениУстаревшее значение не разрешает команду
writable / доступна записьСостояние и правила безопасности допускают действиеКнопка включается только после всех проверок

Например, поле положения окна может содержать служебные значения 252, 254 или 255. Если просто ограничить такое число диапазоном 0…100, ошибка превратится в уверенное положение окна. Поэтому декодер сохраняет признак неизвестности и блокирует действие, которое требовало бы точного текущего положения.

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

Уведомления API для реакции, периодический опрос для страховки

Изменение автомобильной функции запускает объединённое обновление через уведомление вендорского API. Полный опрос остаётся резервом и выполняется раз в 2,2 секунды. Такие уведомления служат только сигналом «данные изменились»: достоверные скорость и обороты Server получает синхронным чтением ISensor, а перед движением водительского кресла читает их ещё раз.

Hub обновляет внутренний снимок для локальной сети и контроля возраста данных на каждом цикле, но экран получает новую модель представления только после изменения видимых полей. Одна лишь смена временной метки не запускает полную перерисовку. Пользовательские View рассчитывают геометрию в onSizeChanged(), не создают объекты в onDraw() и объединяют изменения в ближайший кадр через postInvalidateOnAnimation().

04 / Механические команды

Ограниченное по времени управление приводами

Для кресел, окон и люка реализована отдельная политика выполнения. Команда движения имеет короткий срок действия, регулярно подтверждается во время удержания и завершается STOP при отпускании, смене экрана, потере связи или срабатывании сторожевого таймера.

Шесть этапов выполнения механической команды и независимый красный контур безопасной остановки
Рис. 2. Основной путь команды и независимые условия перехода в STOP: неподтверждённая функция, устаревшие данные, истёкший срок, ошибка API, отпускание кнопки или остановка приложения.

Кресла: движение только во время удержания

Движение водительского кресла разрешается при работающем двигателе, подтверждённой скорости не выше 0,05 м/с и возрасте измерения не более 500 мс. Непосредственно перед передачей команды Server ещё раз читает скорость и обороты. Пока пользователь держит стрелку, интерфейс продлевает «аренду» движения. Для водителя она равна примерно 400 мс, для пассажирских мест не превышает одной секунды, а сетевой маршрутизатор дополнительно ограничивает команду окном около 420 мс.

Нажатие. Проверяются функция, двигатель, скорость и возраст измерения.

Пока кнопка удерживается, интерфейс отправляет очередное подтверждение движения.

Если подтверждение не пришло, сетевой срок действия истекает и Server отправляет OFF.

Отпускание, смена направления, onStop() или разрыв связи отменяют прежнюю команду и вызывают STOP.

Матрица кресел описывает четыре места и семь возможных осей. Для передних сидений проверяются продольное положение, высота, наклон и выдвижение подушки, спинка, а также вертикальная и продольная регулировки поясничного упора. Для второго ряда интерфейс показывает только те пары «место – ось», которые автомобиль подтвердил как активные; в текущей модели это наклон спинки и продольное положение.

МестоПродольноеВысотаСпинкаНаклон подушкиВыдвижениеПоясница ↑↓Поясница ↔
Водитель
Передний пассажир
Заднее левое
Заднее правое
пара «место – привод» включена в закрытый список проверки команда не допускается

Рис. 3. Это не обещание комплектации: даже отмеченная пара появляется в интерфейсе только после ответа active от конкретного автомобиля.

Граница утверждения

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

Проёмы: закрытие и открытие имеют разные риски

Закрывающая команда сохраняется как безопасный маршрут. Открытие требует свежего состояния и отдельного разрешения. Внешняя охрана от ключа, бесключевого доступа или телематики блокирует открытие; внутренний замок и автоматическое запирание при начале движения сами по себе его не запрещают. Неизвестный источник замка обрабатывается консервативно.

05 / Интерфейс устройств

Адаптация HMI для мультимедиа, планшета и телефона

Интерфейс учитывает доступную ширину, роль устройства и найденные функции автомобиля. Для вытянутого экрана штатной мультимедиа используется отдельная широкая композиция, а планшет и телефон получают адаптивную раскладку без переноса жёстких координат 1920×720.

Рис. 4. Актуальный функциональный прототип пяти разделов. Статические кадры позволяют быстро сравнить структуру экранов, а интерактивная версия ниже — проверить переключения и состояния элементов.

На вкладке кресел место выбирается на виде салона сверху, а справа показывается только подтверждённый набор приводов. Две оси поясничного упора водителя разделены, а для второго ряда не создаются несуществующие регулировки. Кнопка STOP остаётся доступной независимо от прокрутки. Светлая и тёмная темы используют отдельные изображения салона и кресел при общей геометрии интерфейса.

Функциональный прототип

Пять разделов × четыре устройства

В интерактиве можно выбрать мультимедиа, планшеты 10″ и 7″ или телефон 5″, переключить раздел и изменить значения. Макет предназначен для проверки навигации, компоновки и поведения элементов до переноса решений в Android-приложение.

Предпросмотр интерактивного функционального дизайна
Локальный модуль · без внешних CDN

06 / Внешние интеграции

Медиа, навигация и автомобильные датчики

В общую модель объединены данные из нескольких подсистем: медиасессии Android, навигационные сообщения и уведомления, служба специальных возможностей и синхронное чтение автомобильных датчиков. Для каждого источника применяются проверка значений и ограничение срока актуальности.

Композиция из Яндекс Музыки и других проигрывателей

Основной источник метаданных – активная MediaSession: название, исполнитель, альбом и состояние воспроизведения. Если сессия не дала полезных полей, приложение читает уведомления только разрешённых медиаприложений. Серии частых событий объединяются задержкой около 350 мс, подписки снимаются при остановке службы, а старые данные очищаются при разрыве связи между Server и Client.

Навигация без управления чужим приложением

Основной источник навигационных полей – широковещательная интеграция MonjaroMOD. В качестве резерва приложение читает уведомления или видимый текст Яндекс Карт, Навигатора и Yango через необязательную службу специальных возможностей. Служба ничего не нажимает и не отправляет жесты. События окна объединяются задержкой 750 мс; ограниченный обход не более 512 элементов, копирование коротких строк и разбор выполняются в одном фоновом потоке.

Навигация, Server в мультимедиа и пассажирский Client работают одновременно
Навигация и управление салономМаршрут остаётся на основном экране, Server работает на мультимедиа, а Client доступен пассажиру и получает то же проверенное состояние.

Скорость как основание для решения

Уведомление датчика лишь запускает обновление. Значение, на котором основывается решение Server, читается синхронно через ISensor.getSensorLatestValue(). Отрицательные числа, служебные коды, NaN и неправдоподобный диапазон отклоняются. После истечения допустимого возраста скорость считается неизвестной и больше не разрешает движение водительского кресла.

07 / LAN и симулятор

Локальный протокол и Python-стенд

Server объявляет себя в локальной сети по UDP. Первичное сопряжение выполняется по шестизначному коду, после чего Client получает случайный 24-байтный токен доступа. Состояние и команды передаются по HTTP/JSON внутри доверенной автомобильной Wi‑Fi-сети.

Формат пакетов остаётся компактным, но декодер проверяет версию протокола, serverId, наличие обязательных полей, типы JSON, диапазоны чисел и размеры массивов. Каждая команда движения дополнительно содержит UUID и крайний срок исполнения. Роль приложения определяется вариантом сборки и идентификатором Android-пакета, а не доверяется произвольному полю сетевого сообщения.

Этот канал нельзя называть зашифрованным: внутри локальной сети используется обычный HTTP. Токен защищает от случайного неавторизованного запроса, но не заменяет WPA и изоляцию автомобильной сети. При первом транспортном сбое Client сразу блокирует управление по старым данным, но сохраняет проверенный адрес Server и делает две прямые попытки восстановления. Только третья последовательная ошибка возвращает его к сетевому поиску.

Окно Python LAN Server Simulator с состояниями автомобиля и журналом команд
Симулятор на Python/Tkinter воспроизводит поиск Server, сопряжение, состояние, команды, матрицу кресел 4×7, сроки действия и подавление повторов.
Концепт интерфейса Monjaro Passenger Control
Концепт интерфейса служит ориентиром для реализации и явно отделён от снимков работающего приложения.

Назначение симулятора

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

08 / Проверка и выпуск

Тестовый контур, APK и эксплуатация

Проверка разделена на модульные тесты обеих ролей, строгую валидацию сетевых данных, сборку и подпись APK, испытания на Python-стенде и полевые проверки на автомобиле. В анализируемом срезе 115 рабочих Java-файлов, более 30 тысяч строк и 515 JVM-тестов на каждую роль.

Server
515
Client
515
115рабочих Java-файлов в анализируемом срезе
30 000+строк Java-кода без сгенерированных файлов
0ошибок в зафиксированном прогоне обеих ролей
Рис. 5. Один набор тестов запускается отдельно для Server- и Client-сборки; поэтому 515 показано для каждой роли, а не суммировано в одну метрику.
Шесть этапов тестирования и выпуска и четыре технических модуля проверки
Рис. 6. Верхний маршрут связывает исходный код, две тестируемые роли, Python-стенд, release APK, OTA и полевую проверку. Нижние модули показывают, что именно проверяется в модели состояния, механических командах, сетевом декодере и цепочке выпуска.

Как проверялся результат

Логику состояния, сетевой декодер и ограничение механических команд проверяет общий набор JVM-тестов, который запускается отдельно для Server и Client. Python-стенд воспроизводит частичную комплектацию, потерю связи и просроченные команды. Финальная проверка выполняется в автомобиле: приложение запускается на мультимедиа и пассажирском устройстве, считывает доступные функции и подтверждает изменение повторным чтением состояния.

Остановка фоновых работ

При onStop() приложение обязательно прекращает все удержания. Объекты ScheduledFuture хранятся явно и отменяются, пулы потоков получают shutdownNow(), после чего код ограниченное время ждёт их завершения. LAN Server закрывает принятые сокеты и завершает потоки приёма и поиска. Службы уведомлений и специальных возможностей снимают подписки, а результат старой фоновой задачи отбрасывается по номеру поколения.

OTA на старом Android

Перед установкой проверяются versionCode, размер, SHA-256, имя Android-пакета и сертификат подписи. На Android 5.1–6 используется PackageInstaller.Session; на более новых версиях APK передаётся штатному установщику через content:// URI. Приложение не использует root-доступ и не выполняет скрытую установку. Последнее системное подтверждение остаётся в ведении прошивки мультимедийной системы.

Полевые демонстрации

04:50 · 34,2 МБ · со звуком
Разделы приложения, взаимодействие с автомобилем и настройки. Текстовое содержание: запуск интерфейса, переходы между разделами, изменение параметров салона и открытие общих настроек.
00:33 · 4,0 МБ · со звуком
Короткий эпизод управления атмосферной подсветкой: выбор режима в приложении и наблюдение изменения света в салоне.

09 / Результат проекта

Рабочая система и публичный выпуск

Я самостоятельно разработал и выпустил единый продукт для штатной мультимедиа и связанных Android-устройств. Проект опубликован в разделе авторских релизов профильного Telegram-сообщества MonjaroDev, в котором на момент фиксации состояло 6595 участников.

Внутренняя телеметрия выпуска зафиксировала более 150 успешных установок Server и Client за первые 24 часа. Эта метрика помогла оценить темп распространения, быстро собрать сведения о разных прошивках и определить сценарии, которые нужно воспроизводить на стенде.

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

Итог проекта

  • Один Android-код формирует Server для мультимедиа и Client для пассажирского устройства.
  • Пять разделов объединяют климат, кресла, медиа, комфорт и пользовательские сценарии.
  • Интерфейс показывает только подтверждённые функции и не подменяет неизвестность значением OFF.
  • Команды приводов ограничены по времени и повторно проверяются на стороне Server.
  • Python-симулятор позволяет воспроизводить сетевые и конфигурационные ошибки без автомобиля.
  • Публичный выпуск получил более 150 внутренних отчётов об установке за первые сутки.

Весь проект — исследование, архитектура, разработка, дизайн интерфейса, испытательный стенд, тестирование, выпуск и поддержка — выполнен мной самостоятельно.