🔫 Phygital Лазертаг
B2B-система для парков: прошивки тагера и повязки, хаб и панель инструктора
Не кейс на заказ, а собственный продукт: демонстрация того, что команда умеет строить систему целиком — от прошивки на ESP32 до серверной части и панели инструктора. Текущий этап — виртуальный стенд: физические прототипы ещё не собраны.
Задача
Лазертаг-парку, который сдаёт оборудование в аренду корпоративным заказчикам, нужна своя система: тагеры, повязки, хаб и панель инструктора. Игра не должна останавливаться, когда за бетонными стенами пропадает связь. Урон и хитмаркер не должны зависеть от связи с хабом.
Вторая проблема — цена ошибки. Зависший комплект посреди оплаченного матча — это минус игрок на всю игру, а потерянная статистика матча — претензия клиента. Физических прототипов ещё нет, поэтому весь бэкенд и игровую логику надо отладить до того, как платы уйдут в производство.
Решение
Комплект программ и прошивок для ESP32 и Raspberry Pi 5. Что реализовано и подтверждено кодом и тестами репозитория:
- Тагер на ESP32-S3 и повязка на ESP32-C3; ИК-протокол MilesTag II с кодеком на Python и C++.
- Урон считается на краю сети: повязка сама вычитает HP и сама шлёт стрелку хитмаркер по ESP-NOW, а хаб только собирает статистику.
- Хаб на Raspberry Pi 5: приём событий по MQTT, PostgreSQL, Redis, WebSocket, статистика.
- Панель инструктора и экран для зала на React и TypeScript, вход по логину и паролю, две роли.
- Один файл
protocol.yamlкак единственный источник правды и кодогенерация констант для Python и C++. - Golden-векторы: одни и те же контрольные примеры проверяют Python и C++, проверка идёт в CI на каждый коммит.
- Брокер Mosquitto без анонимных подключений, права на топики генерируются из контракта.
- Прошивка на весь парк одна: номер комплекта привязывается в панели по MAC-адресу и уезжает в NVS.
- Обновление прошивок по воздуху с раздачей образов хабом.
- Симулятор комплектов, нагрузочный прогон и демонстрационные сценарии.
- Сторожевой таймер на контроллерах, кириллица на экране тагера, схема соединений и смета закупки.
Как это устроено
Урон на краю сети
«Край сети» — само устройство, а не сервер. Тагер шлёт ИК-пакет, повязка принимает его, вычитает HP и отвечает тагеру хитмаркером по ESP-NOW (прямой радиоканал между ESP32 без роутера). Хаб в боевой цикл не входит, он агрегирует события и считает статистику. Поэтому игра не ломается, когда связь с хабом пропала: события копятся в журнале устройства и досылаются позже.
protocol.yaml: один источник правды и кодогенерация
Весь протокол описан в одном файле protocol/protocol.yaml, вся распиновка — в hardware/pinout.yaml. Оттуда генерируются константы для Python и заголовок для C++, а также схема соединений и чертежи узлов. Ни симулятор, ни бэкенд, ни прошивка не хранят протокольных констант у себя. Генератор распиновки заодно отказывается собирать заведомо нерабочую карту: например, аналоговый вход на ADC2, который не работает при включённом Wi-Fi.
Golden-векторы: Python и C++ бит в бит
Golden-вектор — контрольный пример «входные данные и ожидаемый результат», записанный один раз. Логика упаковки битов написана руками, но Python-код и C++ проверяются одними и теми же векторами. Python — эталон, C++ обязан совпадать с ним бит в бит и микросекунда в микросекунду. Первый из четырёх прогонов CI следит, чтобы сгенерированное не разъехалось с protocol.yaml и pinout.yaml.
ИК MilesTag II на несущей 56 кГц
MilesTag II — ИК-протокол лазертага: 14 бит на посылку, несущая 56 кГц. Посылка длится от 19,8 до 28,2 мс, отсюда физический потолок скорострельности около 35 выстрелов в секунду; он зафиксирован тестом. В матче до 128 игроков и до 4 команд. CHANGELOG объясняет выбор так: версия v1 (MilesTag II) активна ради совместимости с оборудованием Laserwar и Forpost, а собственная 32-битная версия v2 спроектирована, но выключена. На настоящем оборудовании совместимость ещё не проверялась: конвенция бита чётности ждёт проверки на повязке.
MQTT-брокер: права на топики из контракта
Mosquitto поднимается с паролями и правами, анонимных подключений нет. Учётные записи две: hub пишет команды, device только отчитывается о себе. Права на топики генерируются из контракта, руками их не правят. Отдельно у брокера выключен алгоритм Нейгла: с ним два ИК-луча с разницей в 50 мс доезжали до повязки одним куском.
Одна прошивка на парк и номер комплекта по MAC
Номер комплекта в прошивку не зашит. Чистая плата приходит к хабу со своим MAC-адресом, инструктор в панели привязывает адрес к слоту, номер уезжает в NVS (энергонезависимая память контроллера). Сгоревшую плату меняют без пересборки. Плата без номера не стреляет и не считает попадания, а сменить номер можно только в первые 10 минут после включения, чтобы поддельный ответ не увёл работающие комплекты в перезагрузку посреди матча.
Обновление прошивок по воздуху
Образ заливается на хаб и объявляется парку отдельным действием: положить прошивку можно заранее, а раздать — когда в зале никого. Комплект берёт образ, только если совпала роль, версия отличается от своей, не идёт матч и заряда больше 40 процентов. Хеш сверяется до переключения загрузочного раздела, поэтому оборванная закачка оставляет исправную старую прошивку.
LoRa 433 МГц для аварийных команд
Аварийный канал предусмотрен в архитектуре: LoRa на частоте 433 МГц, только команды, не более 10 мВт, в обход Wi-Fi. Модули для него пока не закуплены, а подпись команд и защита от повтора не начаты.
Симулятор парка и нагрузочный прогон
Весь парк живёт в симуляторе: тагер, повязка, журнал и имитация NVS. Нагрузочный прогон на 82 комплекта проверяет не пропускную способность, а сходимость: события в базе плюс оставшиеся в буферах плюс потерянные должны равняться сгенерированному устройствами. Расхождение означает потерю статистики матча, за который заплатил клиент.
Отчёты для инструктора и для клиента
Отчёты разделены по аудиториям. Отчёт инструктора показывает всё, включая разрывы в журналах, отчёт клиента — только цифры. Это осознанное решение: оговорка в отчёте для корпоратива стоит дороже, чем потерянные события.
Результат
- Вся игровая логика, протокол и бэкенд отлажены на симуляторе до сборки физических прототипов.
- Полный прогон тестов на поднятом стенде проходил пять раз подряд: 284–285 тестов по записи журнала от 22 сентября 2026 года.
- Регрессия «склеенные лучи» найдена и закрыта: в 40 повторах сценария после правок потерь ноль.
- Нагрузочный прогон на 82 комплекта сверяет то, что считают устройства, с тем, что доехало до PostgreSQL.
- Смета закупки и схема соединений собраны и сверены между собой.
Технологии: что и зачем
- ESP32-S3 и ESP32-C3, C++17 (PlatformIO) — тагер и повязка; переносимая логика проверяется на хосте, а не отладочной печатью.
- ESP-NOW — радиоканал повязка — тагер без роутера.
- MQTT, Mosquitto 2 — связь устройств с хабом.
- Python, FastAPI, PostgreSQL, Redis — хаб: приём событий, статистика, состояние матча.
- React, TypeScript, Vite — панель инструктора и экран для зала.
- LoRa 433 МГц — аварийные команды в обход Wi-Fi.
- Docker Compose, GitHub Actions — стенд и четыре прогона CI на каждый коммит.
Статус
В разработке, собственный продукт. Этап — виртуальный стенд и симулятор; пакеты хаба и симулятора имеют версию 0.1.0. Последняя запись журнала разработки — 22 сентября 2026 года. Физических прототипов нет: дальность ИК, допуск приёмника TSOP4856, энергобюджет и поправка часов ждут замеров.
Открытые вопросы по README: аварийный канал LoRa не закуплен; TLS на площадке не настроен; нет журнала действий инструктора и резервного копирования истории матчей; повязка не обновляется по воздуху; миграций базы пока нет. Дальше — сборка прототипов и замеры по чек-листу.
Интересно под свой парк?
Расскажите про площадку и оборудование — прикинем, что нужно для запуска.