
В предыдущих сериях мы научились ковыряться под капотом: доставать данные с датчиков через VHAL и прятать кнопки от водителя, когда машина едет. Но давайте честно: просто выводить скорость на экран — это скучно. Этим занимались инженеры еще в 90-х.
Сегодня мы живем в эпоху ИИ. У нас есть железка стоимостью в десятки тысяч евро, напичканная датчиками, и операционная система, которая может всё это читать. Так давайте дадим машине «мозги»!
В этой статье мы напишем концепт Smart Driving Assistant — фонового сервиса на Android Automotive, который будет анализировать телеметрию (руль, педали, скорость) и с помощью локальной нейросети (On-Device ML) понимать, что водитель начал "играть в шашечки" на дороге.
Почему On-Device ML, а не облако?
Казалось бы, почему не гонять данные на бэкенд и там всё красиво считать? Добро пожаловать в суровую реальность автопрома:
Интернет в машине — штука нестабильная. Вы заехали в туннель где-то под Альпами, и ваше облако превратилось в тыкву.
Трафик стоит денег. OEM-производители (автоконцерны) удавятся за лишний мегабайт сотовых данных.
Приватность. Отправлять сырую телеметрию педалей и руля на сторонние сервера — это лучший способ получить судебный иск по GDPR.
Поэтому наш выьор — это Edge AI (вычисления на границе). Мы будем использовать TensorFlow Lite прямо на головном устройстве (Head Unit).
Шаг 1: Собираем "Биг Дату" из VHAL
Чтобы нейросеть могла что-то предсказывать, ей нужна пища. В прошлой статье мы подписывались на скорость. Теперь нам нужны более интересные метрики из VehiclePropertyIds.
Нам понадобятся:
STEERING_WHEEL_ANGLE— угол поворота руля.BRAKE_PEDAL_POSITION— насколько сильно вдавлен тормоз (от 0 до 100).ACCELERATOR_PEDAL_POSITION— положение педали газа.
Напишем коллектор, который будет собирать это в небольшой буфер "окна времени" (Time Window):
import android.car.hardware.property.CarPropertyManager import android.car.VehiclePropertyIds class TelemetryCollector(private val carPropertyManager: CarPropertyManager) { // Храним последние N снапшотов телеметрии для передачи в модель private val telemetryBuffer = FloatArray(150) // Например, 50 тиков * 3 параметра private var currentIndex = 0 private val callback = object : CarPropertyManager.CarPropertyEventCallback { override fun onChangeEvent(event: CarPropertyValue<Any>) { val value = event.value as Float when (event.propertyId) { VehiclePropertyIds.PERF_STEERING_ANGLE -> updateBuffer(0, value) VehiclePropertyIds.PERF_VEHICLE_SPEED -> updateBuffer(1, value) VehiclePropertyIds.BRAKE_PEDAL_POSITION -> updateBuffer(2, value) } // Если буфер заполнен, отдаем в нейронку if (isBufferFull()) { analyzeDrivingStyle(telemetryBuffer) } } override fun onErrorEvent(propId: Int, zone: Int) {} } fun startCollecting() { // Подписываемся с высокой частотой (SENSOR_RATE_FAST), нам же нужна динамика! carPropertyManager.registerCallback( callback, VehiclePropertyIds.PERF_STEERING_ANGLE, CarPropertyManager.SENSOR_RATE_FAST ) // ... аналогично для других свойств } }
Важный нюанс: Не все машины отдают эти данные. Некоторые OEM прячут положение руля за секьюрными вендорными свойствами (Vendor Extensions). Но для эмулятора и стандартных AOSP сборок этого хватит
Шаг 2: Прикручиваем TensorFlow Lite
Допустим, у нас уже есть предобученная модель driving_style.tflite, которая принимает на вход наш массив из 150 флоатов и выдает вероятность того, что водитель агрессивен (от 0.0 до 1.0).
Кладем модельку в папку assets и инициализируем TFLite интерпретатор.
import org.tensorflow.lite.Interpreter import java.nio.MappedByteBuffer import java.nio.channels.FileChannel import android.content.Context class DrivingAnalyzer(context: Context) { private var interpreter: Interpreter init { val model = loadModelFile(context, "driving_style.tflite") val options = Interpreter.Options().apply { setNumThreads(2) // Если головное устройство поддерживает NPU (Neural Processing Unit), // можно подключить NNAPI Delegate для аппаратного ускорения. // addDelegate(NnApiDelegate()) } interpreter = Interpreter(model, options) } fun analyze(telemetry: FloatArray) { // Модель ожидает на входе [1, 150], на выходе [1, 1] (вероятность) val input = arrayOf(telemetry) val output = Array(1) { FloatArray(1) } // Магия ИИ! interpreter.run(input, output) val aggressionScore = output[0][0] if (aggressionScore > 0.85f) { Log.w("SmartDrive", "Водитель исполняет! Оценка агрессии: $aggressionScore") // Тут можно, например, принудительно включить расслабляющую музыку // или сделать UI климата холоднее :) } } private fun loadModelFile(context: Context, modelName: String): MappedByteBuffer { val fileDescriptor = context.assets.openFd(modelName) val inputStream = FileInputStream(fileDescriptor.fileDescriptor) val fileChannel = inputStream.channel return fileChannel.map(FileChannel.MapMode.READ_ONLY, fileDescriptor.startOffset, fileDescriptor.declaredLength) } }
Суровая реальность: Троттлинг и "железо" автомобиля
Выглядит просто, правда? Скормил данные — получил результат. Но в реальном Automotive проекте вас ждет пара сюрпризов:
Термальный троттлинг. Если ваша нейросеть будет молотить 60 раз в секунду, процессор магнитолы нагреется. В машине летом и так бывает +60°C. Когда процессор перегревается, система не просто снижает частоты, она может принудительно убить ваше приложение или вырубить экран ради безопасности. Решение: Оптимизируйте модели (квантование
int8— ваше всё) и запускайте inference не на каждый тик, а батчами раз в пару секунд.Борьба за ресурсы. Навигация (Google Maps) всегда в приоритете. Если вашей нейронке и картам станет тесно в оперативной памяти, Android убьет именно вас.
Итого
Android Automotive — это не просто планшет на колесах. Это огромная платформа для интеграции сенсоров и алгоритмов машинного обучения. Сегодня мы собрали фундамент: взяли сырые данные из CAN-шины через VHAL и прогнали их через on-device модель.
Именно на стыке Automotive и ML сейчас рождаются самые интересные фичи: от анализа усталости водителя по камере до предиктивного обслуживания двигателя.
В следующей статье мы поговорим о том... а о чем вы хотите поговорить? Пишите в комментах, погнали обсуждать!
