Когда GNSS в Москве перестал быть надёжным
В Москве GNSS-сигналы часто подавляются. Во время обычных поездок я видел, как штатный навигатор теряет позицию, останавливается или начинает прыгать между точками, хотя автомобиль продолжает ехать. В какой-то момент я решил не считать это неизбежностью и сделать слой, который продолжает оценивать положение по данным автомобиля и автономной карте, когда спутниковой опоры нет.
Так появился Car GNSS Bridge. Приложение работает на штатной мультимедийной системе Geely Monjaro под Android 9, принимает GNSS по USB, Bluetooth или TCP и собирает несколько независимых источников в один стандартный Android Location. Сам навигатор при этом менять не нужно.
Главная граница проекта принципиальна: доступ к данным внутренней шины автомобиля выполняется только для чтения через штатные программные интерфейсы. Bridge не управляет электронными блоками и ничего не записывает во внутреннюю шину. Единственный исходящий поток к внешнему приёмнику — стандартные поправки RTCM от NTRIP. Дополнительный модуль LSPosed передаёт скорость и проверяет подтверждение приёма (ACK), но координату не изменяет.
Сначала состояние сигнала, потом его значение
Одно из главных правил архитектуры: отсутствие измерения нельзя подменять нулём. Поэтому рядом с каждым значением я храню источник, качество, состояние и время последнего обновления. Устаревшая скорость означает «измерения нет», а не «машина остановилась». Неизвестная передача тоже остаётся отдельным состоянием. Даже NMEA с правильной контрольной суммой ещё не становится доверенной координатой автоматически.
Дальше единый рабочий контур проверяет позицию, объединяет скорость и курс и выбирает режим выхода. Пока спутниковое решение достоверно, используется GNSS. После его потери свободное счисление пути (dead reckoning, DR) прогнозирует перемещение, а RoadLock удерживает этот прогноз на дорожном графе. Если нужных входов не хватает, система не делает вид, что всё знает, и переходит в HOLD.
Четыре транспорта, один формат на выходе
Система принимает NMEA от любого совместимого внешнего GNSS-приёмника. Для штатной работы достаточно приёмника с одной антенной: двухантенная конфигурация не требуется. Приёмник может быть подключён через USB Serial, Bluetooth SPP, BLE UART или TCP. Различия этих каналов я спрятал за одним контрактом: выше транспортного слоя всегда приходят полные строки NMEA. Каждый канал сам замечает остановку потока, ограничивает повторные подключения и отбрасывает мусор. USB перебирает подходящие драйверы и скорости порта, TCP не принимает двоичный поток и слишком длинные строки, а BLE ограничивает очередь поправок RTCM.
На уровне NMEA важна уже не доставка, а согласованность измерений. GGA и RMC объединяются только тогда, когда их время расходится не более чем на 1,5 с. Координату, время и качество даёт GNSS. Скорость и путевой угол из RMC я намеренно не использую в объединённом решении: для них есть независимые автомобильные источники. В прототипе я отдельно испытывал двухантенный модуль как возможный независимый источник абсолютного курса: такой приёмник определяет направление по линии между антеннами. Его сообщения THS/HPR и данные второй антенны, как и GSV, оставались в диагностике и не были обязательны для работы системы.
Зачем столько уровней. Транспорт отвечает за целую строку, парсер — за общую эпоху измерений, а слой доверия — за право использовать координату как опорную. Так ошибка доставки не может незаметно превратиться в «достоверную» позицию.
Скорость выбирается, а не усредняется
ECARX — это программная платформа штатной мультимедийной системы автомобиля. В разных её версиях скорость доступна через Android Car, AdaptAPI, старый интерфейс ecarx.car или MConfig. Все эти каналы дают доступ к данным внутренней шины автомобиля только для чтения через штатные программные интерфейсы. Я не смешиваю их показания: селектор выбирает один свежий источник по приоритету. По умолчанию значение живёт 700 мс.
Автомобильная скорость записывается в каждый публикуемый Location, даже в HOLD. Это важно: если поле оставить пустым, навигатор может сам вычислить скорость по разности координат. Тогда небольшой скачок позиции будет выглядеть как резкое ложное ускорение.
Со временем похожая история. Location.time получает системные часы Android, эпоха GNSS хранится отдельно в gnss_time_ms, а порядок событий задаёт монотонный elapsedRealtimeNanos. В результате часы приёмника и мультимедийной системы больше не могут переставлять кадры местами.
Как определить направление без магнитометра
У курса нет одного безусловно надёжного источника, поэтому каждому датчику отведена своя роль. Абсолютное направление относительно севера я получаю по хорде — линии между двумя принятыми координатами GNSS. База должна быть не меньше 8 м, качество обеих точек проверяется отдельно, а при движении задним ходом азимут разворачивается на 180°. Магнитометр мультимедийной системы, Android IMU и путевой угол из RMC для этой роли оказались недостаточно надёжными.
ω = v · tan(δ) / (L + K·v²)ω = (vR − vL) / trackВ первой версии главным источником относительного поворота была разность скоростей колёс. Поездки показали, что квантование иногда делает этот сигнал слишком шумным. Я поменял роли: угол руля стал плавным прогнозом, а разность скоростей задних колёс — независимой проверкой, которая может отклонить несогласованную оценку. При смене источника состояние переносится без скачка курса.
Что показал опыт с двухантенным модулем. Это был отдельный эксперимент, а не требование к системе. В журнале модуля было 3 215 записей курса: 692 — с плавающим решением float; фиксированного решения fixed не было ни разу. Медианная неопределённость решений float составила 46,8°. В этих испытаниях модуль не подошёл на роль основного источника абсолютного курса, поэтому его данные остались диагностическими.
Что происходит после потери GNSS
Любая вернувшаяся координата сначала попадает в журнал, затем проходит проверку на подмену и процедуру восстановления. Только после этого она становится новой опорной точкой и может сбросить накопленную ошибку счисления.
При потере GNSS поставщик координат Android продолжает работать. Пока свежи скорость, курс и направление, свободный DR оценивает перемещение, а RoadLock сопоставляет его с дорожным графом и удерживает машину на выбранной дороге. На остановке или при нехватке данных координата переходит в HOLD. У свободного DR есть и жёсткая граница: без внешней поправки он работает не более десяти минут движения, после чего включается защитный HOLD-CAP.
Карта корректирует курс и поддерживает RoadLock
Для работы без сети я собрал дорожный граф OpenStreetMap в собственный бинарный формат .cgbr. Внутри — заголовок с CRC, тайлы 0,02° × 0,04°, метровое квантование и компактные приращения zigzag-varint. Файл лежит в APK без сжатия, проверяется при открытии и читается через отображение в память (mmap). Весь ресурс занимает 33 613 210 байт.
Раз в две секунды первый картографический этап ищет коридор в радиусе 100 м, проверяет качество и непрерывность совпадения и оценивает направление дороги. Здесь карта поправляет только общий сдвиг курса. Координату удерживает уже отдельный RoadLock после потери GNSS. Важно, что оба алгоритма получают свободный DR, а не собственный предыдущий результат: иначе карта начала бы подтверждать сама себя.
Удержание позиции на дорожном графе
RoadLock — рабочая часть системы на случай длительной потери GNSS. Он получает пройденное расстояние от свободного DR, сопоставляет его с графом и ведёт позицию вдоль выбранной дороги. Одновременно могут существовать до трёх гипотез. На развилке курс служит не источником движения, а переключателем: алгоритм сравнивает фактическое изменение курса с геометрией возможных ветвей.
После подтверждённой потери GNSS RoadLock публикует в Android Location координату, согласованную с дорожным коридором. Но привязка не выполняется любой ценой. Если уверенного лидера нет, выбор откладывается или сопровождение прекращается, а система возвращается к предусмотренному безопасному режиму. При отказе от гипотезы накопленное смещение гасится плавно.
Что показали реальные поездки
Одну из ранних серий RoadLock я провёл в безопасном режиме: рассчитанный дорожный коридор записывался в журнал, но не попадал в навигатор. Цель была простой — найти слабые места до включения рабочего выхода. Числа из раннего эскизного описания, для которых не оказалось экспериментальных данных, я из статьи убрал.
Журнал показал проблему, которую трудно заметить по экрану: при позднем захвате RoadLock начинал искать дорогу вокруг точки, уже смещённой примерно на 1,4 км. Расхождение тестового коридора и рабочей координаты доходило до 79,9 м. Раздельные контуры не дали этой ошибке попасть в навигацию и одновременно проявили поздний захват, неустойчивый выбор развилки, сброс накопленного подтверждения и слабое место прежнего ограничения автономного счисления.
Эти числа относятся к ранней проверочной серии. По текущему опыту эксплуатации я могу подтвердить: после доработок система стала заметно точнее и при потере GNSS поддерживает навигационную позицию в городском движении не менее 10 минут. Речь идёт только о расчёте и передаче координаты навигатору: автомобилем система не управляет.
Из этих наблюдений получился конкретный список требований: захватывать дорогу раньше, не выбирать неуверенную ветвь, контролируемо принимать вернувшийся GNSS и предсказуемо переключать режимы. После доработки RoadLock вошёл в штатный контур и теперь удерживает позицию на дорожном графе, когда спутниковая координата пропадает.
Проверка не должна мешать системе восстановиться
Слишком строгий «судья» легко запирает систему в собственном недоверии. Я столкнулся с этим, когда проверка относительно устаревшей опорной точки начала отвергать все честные координаты и не оставила пути назад. Поэтому модуль обнаружения подмены по умолчанию работает как наблюдатель в режиме MONITOR: подозрительный fix остаётся в потоке, а решение проверки записывается в телеметрию. Реальная блокировка включается отдельно.
Проверка опирается на независимые признаки: одометрию, передачу, монотонное время и расхождение с прогнозом DR. Если ненадёжен сам прогноз, уменьшается доверие к нему, а не к GNSS. После разрыва потока система ждёт последовательность согласованных координат в течение заданного времени и лишь затем принимает новую опорную точку.
Калибровка с контрольной выборкой
Коэффициенты сначала вычисляются на обучающей части поездки, а затем проверяются на отдельной контрольной выборке. Новая версия сохраняется как контрольная точка, применяется целиком только после подтверждённой остановки и допускает откат. Подозрительный GNSS, неизвестное направление и устаревшая скорость в обучение не попадают.
| Нет сигнала | Это отдельное состояние, а не ноль и не автоматическое предположение о движении вперёд. |
|---|---|
| Независимая проверка | Карта не проверяет координату, которую перед этим сформировала сама. |
| Путь к восстановлению | Устаревшее внутреннее состояние не должно бесконечно отклонять новые данные. |
| Целостное применение | Коэффициенты меняются только после контрольной выборки и подтверждённой остановки. |
Поездка должна воспроизводиться без автомобиля
Скриншота с неправильной точкой недостаточно, чтобы понять причину. Поэтому регистратор сохраняет исходные NMEA, автомобильные сигналы, решения алгоритмов, опубликованные Location и переходы состояний в NDJSON примерно с частотой 4 Гц. Размер сегментов и очередей ограничен, важные события записываются устойчиво. Загрузка на закрытый HTTPS-сервер продолжается после обрыва, а повторная отправка не создаёт дубликатов.
Цепочка «вход → решение → выход» позволяет заново проиграть работу GNSS, DR и RoadLock на одной временной шкале. В журнале видна не только конечная координата, но и причина каждого перехода. Именно так поле превращается в воспроизводимый тест.
Основные алгоритмы я вынес в обычный Java-слой без зависимостей от Android. На момент аудита проект содержал 99 файлов *Test.java и 877 аннотаций @Test. Они охватывают NMEA, транспорты, свежесть данных, объединение измерений, повторный захват, калибровку, DR/HOLD, карту, RoadLock, запись и загрузку журналов. Это статический подсчёт исходников, а не отчёт о последнем запуске CI.
Рабочая система и её границы
В результате получился не отдельный навигатор, а инженерный слой под ним: Android-сервис, алгоритмическое ядро, адаптеры автомобильных данных, компактный дорожный граф, диагностика, журналирование и воспроизводимые тесты.
На штатной мультимедийной системе Car GNSS Bridge передаёт навигатору согласованные координаты GNSS и скорость автомобиля. Когда спутниковое решение исчезает, свободный DR продолжает оценивать перемещение, а рабочий RoadLock удерживает позицию на дорожном графе. В текущей эксплуатации эта версия заметно точнее ранней и поддерживает навигационную позицию в городском движении без GNSS не менее 10 минут. Если данных уже недостаточно, система честно переходит в HOLD.
Физику обмануть нельзя: без внешней опоры свободное счисление накапливает ошибку курса, что подтвердили контролируемые отключения GNSS. RoadLock уменьшает влияние этого дрейфа на положение относительно дороги, проверяет гипотезы на развилках и отказывается от неуверенного выбора. Граница доверия остаётся видимой — это важнее, чем любой ценой рисовать непрерывную траекторию.