Real-time в мобильном приложении для EV-зарядок: SSE, офлайн и обрывы связи в Amperic
Как в Amperic устроено обновление статусов зарядки в реальном времени: почему активная сессия идёт SSE-потоком, а карта — нет, и что делает приложение, когда пропадает связь.
Задача
Amperic — мобильный клиент для сети зарядных станций Malanka в Беларуси. Во время зарядки владелец электромобиля хочет видеть, что происходит прямо сейчас: текущую мощность, переданную энергию в кВт·ч, прошедшее время и стоимость сессии. А на карте — какие разъёмы свободны, заняты или неисправны.
Классический подход — опрашивать сервер каждые несколько секунд. Он создаёт лишнюю нагрузку, расходует батарею и всё равно показывает данные с задержкой до интервала опроса. Нам нужна была другая модель.
Ограничение: мы клиент, а не оператор
Главное архитектурное условие проекта: Amperic не владеет ни станциями, ни биллингом. Данные о станциях, тарифах и статусах приходят через API оператора, счета выставляет Malanka, а вход и смена пароля выполняются на её стороне — приложение вообще не хранит пароли.
Отсюда принцип, который проходит через весь дизайн: источник истины — станция и оператор, а не телефон. Приложение не пытается вести собственную копию состояния сессии; оно показывает то, что знает система оператора, и умеет быстро с ней сверяться.
Две скорости данных
Не все данные нужно обновлять одинаково быстро. В Amperic их две категории.
Активная зарядная сессия — поток в реальном времени. Мощность и энергия меняются каждую секунду, а пользователь в этот момент смотрит на экран. Здесь сервер сам присылает обновления потоком (Server-Sent Events), без опросов.
Карта и доступность станций — по запросу. Станций сотни, и держать подписку на каждый разъём ради карты, на которую пользователь бросает взгляд, дорого и бессмысленно. Список станций загружается при запуске приложения и обновляется, когда пользователь двигает или масштабирует карту, а также по жесту «потянуть вниз».
Такое разделение — главное решение: поток там, где свежесть критична, и дешёвые запросы там, где достаточно актуальности «на момент взгляда».
Почему SSE, а не WebSocket
WebSocket — двусторонний канал: он нужен, когда клиент и сервер постоянно обмениваются сообщениями. Экрану активной сессии нужно другое — получать односторонний поток событий. Для этого SSE подходит лучше:
- Обычный HTTP. Не нужен отдельный протокол, прокси и балансировщики работают как с любым запросом.
- Переподключение из коробки. Протокол SSE предусматривает автоматическое восстановление соединения после обрыва — ровно то, что нужно мобильному клиенту.
- Простая модель. Управляющие действия — запуск и остановка зарядки, бронирование — это разовые запросы, а не сообщения в общем канале.
Когда пропадает связь
Мобильная сеть на трассе или в подземном паркинге нестабильна, поэтому обрыв связи — штатная ситуация, а не исключение.
- Зарядка не зависит от телефона. Станция продолжает сессию автономно. Приложение нужно только для мониторинга и удалённой остановки — остановить зарядку можно и на экране самой станции.
- После переподключения — сверка с истиной. Когда соединение восстанавливается, экран сессии подтягивает актуальные энергию, стоимость и время. Ничего не нужно «догонять» локально: данные просто берутся у оператора.
- Частичная работа офлайн. Закэшированные станции и история зарядок остаются доступны. Функции, которым нужны живые данные — свежий статус станций, запуск новой зарядки, синхронизация аккаунта, — честно требуют сети.
Итоговая согласованность — в интерфейсе
Некоторые данные в системе оператора появляются не мгновенно. Например, завершённая сессия может синхронизироваться с историей с задержкой в несколько минут, а бронь разъёма снимается автоматически по истечении окна бронирования. Интерфейс это учитывает: история обновляется жестом «потянуть вниз», а статус разъёма всегда можно перепроверить в списке устройств станции.
Что мы вынесли из проекта
- Сначала определите источник истины. Если им не является ваш сервер, не пытайтесь дублировать состояние — стройте клиент вокруг быстрой сверки.
- Разделяйте данные по требуемой свежести. Поток — только там, где пользователь действительно смотрит на меняющиеся цифры.
- Для одностороннего real-time начинайте с SSE. WebSocket оправдан, когда нужен настоящий двусторонний обмен.
- Проектируйте под обрыв связи. Критичный процесс — зарядка — не должен зависеть от того, есть ли сеть у телефона.
Приложение сейчас в открытом бета-тестировании на iOS и Android — подробности на странице проекта.