Files
ridebus-board/README.md
2026-10-08 22:56:41 +03:00

15 KiB
Raw Blame History

RideBus Board

Табло остановки: React + TypeScript + Vite, сервер Node.js / Express. Фон строго #000000, локальный шрифт Manrope, контрастная белая типографика, настраиваемый акцент, иконки @vkontakte/icons и официальный SVG-логотип из RideBus PWA. По умолчанию — Полоцк, Швейная фабрика, stopId=1. Реальный API подтверждает эту остановку и 22 маршрута.

Запуск

Требуется Node.js 24+ (проверено с Node 24.14.1).

npm install
cp .env.example .env
# Добавьте RIDEBUS_API_KEY в .env.
npm run build
npm start

Откройте http://127.0.0.1:5180. Для разработки — npm run dev. В этом workspace ключ уже подключён локально из существующей конфигурации RideBus; .env исключён из Git и Docker. При отсутствии ключа первая загрузка включает явно обозначенный деморежим. Демонстрацию также можно открыть по /?demo=1; она никогда не подменяет реальные данные при сбое API.

Возможности

  • Главный экран целиком отдан рейсам: ближайший крупно и следующие 2–6 рейсов. Строки рассчитываются по фактической доступной высоте, нижняя панель занимает отдельное место. Даже 6 следующих отправлений помещаются на 1280×720 и 390×667. На очень низких экранах список сменяется равными страницами каждые 7 секунд с явным счётчиком; рейсы не обрезаются.
  • В шапке — название и направление конкретной площадки из API, часы и дата в Europe/Minsk. Поле «основная» не выдаётся за направление; если направления нет, оно не придумывается. Город, подпись «Остановка», «RideBus API» и «Все направления» убраны из шапки.
  • Информационный экран: маршруты и статус полного кеша, погода, QR-код и предупреждения. Часы и ближайший рейс остаются видимыми. В вертикальной компоновке показывается одна карточка; при следующих посещениях информационного экрана карточки сменяются. Их также можно выбрать кнопками «Маршруты», «Погода», «RideBus».
  • Цикл 25 секунд / 8 секунд, настраиваемый. До ближайшего рейса ≤2 минут главный экран удерживается; можно отключить в настройках. Отмены и объезды исключают рекламную сцену из цикла.
  • Опциональная рекламная сцена с текстом и длительностью; по умолчанию выключена, помечена как предпросмотр. Загрузка медиа и кампании в MVP не входят.
  • Настройки остановки, маршрута, числа строк, интервалов и деморежима сохраняются на устройстве. Новая остановка загружает свои данные и свой кеш.
  • Полный экран, пауза, ручная смена сцен. Клавиши: S — настройки; F — полный экран; → — следующий экран; пробел — пауза. Esc закрывает настройки.
  • Кнопки управления и курсор скрываются после 5 секунд без взаимодействия. Движение мыши, касание или клавиатура возвращают кнопки; открытые настройки и работа с клавиатурным фокусом не скрываются. Место панели сохраняется, поэтому список рейсов не скачет.
  • Изменившиеся цифры часов, времени отправления, минут ожидания и номеров маршрутов прокручиваются вертикально. Неизменившиеся позиции остаются на месте; увеличение идёт вверх, уменьшение — вниз. При prefers-reduced-motion движение выключено.
  • В настройках есть 6 контрастных схем: лайм, янтарь, голубой, мята, лаванда и белый. Акценты и официальный логотип меняются вместе, фон остаётся чёрным. Все цвета имеют контраст с чёрным не меньше 7:1. Схема сохраняется на устройстве.
  • Длинные номера, включая «16тк», автоматически вписываются с отступами; текст не касается рамок.

Данные и работа без связи

Сервер запрашивает GET /api/v1/stops, /stops/:id, /eta/stop/:id, /alerts/active, передавая X-City-Id и серверный X-API-Key. Интерфейс опрашивает сервер каждые 20 секунд; одновременные запросы объединяются, ответ кешируется на 12 секунд. Ключ, ошибки и сырые ответы upstream не отправляются в браузер. Прокси разрешает только явно заданные GET-операции: /api/config, /api/stops, /api/board, /api/timetable, /api/weather.

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

Полный кеш выбранной остановки загружается независимо через /api/timetable: все варианты /routes/:id/schedules, остановки маршрутов с временными смещениями и /holidays/year/:year. Сохраняются будние, субботние, воскресные и праздничные варианты, сроки действия и ограничения отдельных рейсов. Праздник без своего графика использует воскресный, как в RideBus API. Учитываются укороченные и обратные рейсы; прибытие после полуночи сохраняет настоящую дату и обслуживающий день. Обновление каждые 30 минут, ограниченная параллельная загрузка, одновременные запросы объединяются. Кеш публикуется целиком: сбой отдельного маршрута или календаря не заменяет предыдущую полную копию неполными данными.

При наличии полного кеша табло рассчитывает все прибытия сегодня и завтра локально. Поэтому оно продолжает выбирать рейсы после полуночи без подключения; завтрашние времена явно помечены «завтра». Кеш хранится в IndexedDB и резервно в localStorage отдельно по городу и площадке. Срок использования без обновления ограничен 7 сутками; по истечении этого срока табло ожидает свежую загрузку. Переводы рабочих дней могут отражаться только в календаре и расписании, которые предоставляет API. Демоданные не сохраняются вместо реального расписания. Если полный кеш ещё не получен, используется прежний снимок до трёх ближайших рейсов каждого маршрута, только для его исходного дня и не старше суток.

При сбое появляется пометка сохранённых данных. Истёкшие рейсы удаляются. Если сохранённые рейсы закончились, табло честно сообщает об этом и продолжает переподключение. Предупреждения фильтруются по городу, маршрутам, срокам действия; сохраняются полный текст и детали отмены. Полный кеш расписания не подтверждает актуальность предупреждений — без свежего ответа API это явно указано.

QR-код создаётся локально, без стороннего сервиса генерации, и открывает https://pwa.ridebus.by/?city=1&stop=1 для текущей остановки (параметры меняются вместе с площадкой). Адрес PWA можно переопределить серверной переменной BOARD_PWA_URL. QR использует чёрные модули, светлый фон текущей схемы и свободное поле шириной 4 модуля.

Погода запрашивается независимо через /api/weather у Open-Meteo по публичным координатам остановки из API. В шапке — температура и состояние; на информационном экране — ощущаемая температура, ветер в м/с и три следующих часа. Процент осадков — максимальная почасовая вероятность в этих трёх часах. Это модельные погодные данные. Обновление раз в 10 минут; сохранённая погода помечается и показывается не дольше часа, затем скрывается. При отсутствии координат или сбое погоды транспорт продолжает работать. Данные сохраняются по городу и остановке; RideBus-ключ не передаётся поставщику погоды. Указаны источник и CC BY 4.0.

Бесплатный Open-Meteo подходит для прототипа; условия сервиса требуют коммерческую подписку для приложения с рекламой. Сервер уже поддерживает OPEN_METEO_API_KEY и автоматически выбирает коммерческий endpoint, ключ остаётся на сервере. Панель оператора планируется отдельно в polotsk-transit-api.

Production-сборка устанавливает Service Worker, сохраняющий оболочку, шрифты и иконки для перезагрузки без сети. Требуются HTTPS или localhost и хотя бы одна успешная загрузка; в режиме разработки Service Worker выключен. API-ответы не кешируются Service Worker. В приватном режиме браузера постоянное хранилище может быть недоступно.

Проверки

npm test
npm run build
npm run probe

Тесты проверяют календарное время, устаревшие снимки, идентичность города/остановки, источники ETA, многомаршрутные предупреждения, сбои отдельных источников, объединение запросов и HTTP-контракт. probe — проверка реального API без вывода ключа.

Развёртывание

docker compose up --build -d

Контейнер доступен на 127.0.0.1:5180. Для удалённого плеера поставьте HTTPS reverse proxy перед этим портом. API-ключ передаётся только через environment; не включайте его в URL или frontend-переменные VITE_*. /healthz проверяет работу процесса, а не доступность RideBus API.

На Linux-плеере откройте страницу Chromium в kiosk mode и настройте автозапуск графической сессии. Пример службы — deploy/ridebus-kiosk.service; пути Chromium и переменные дисплея зависят от ОС. Restart=always восстанавливает процесс браузера. Docker восстанавливает сервер после перезагрузки. Настройку и испытание на физическом плеере нужно выполнить отдельно: служба не обнаруживает зависший renderer, а удалённый мониторинг устройств в MVP отсутствует.

Изменения кода выкладывайте целой сборкой; Service Worker загружает новую оболочку в фоне, активируя обновление при следующем закрытии/перезапуске табло.