IZI Pod — Архитектура контроллера
Исследование и архитектурные решения
Дата: 8 апреля 2026
Статус: Draft
Авторы: James Akwuh, Vladimir Prutkovsky
1. Контекст и постановка задачи
Что такое IZI Pod
IZI — оператор стоек самообслуживания для игр (Gaming As A Service), размещённые в торговых центрах (ТЦ). Каждый IZI Pod содержит:
- Игровой ПК на Windows — арендуемый клиентом продукт
- Аппаратный контроллер — управляет доступом, LED, датчиками, реле питания
- Собственная интернет-связь — независимо от инфраструктуры ТЦ
Ключевая особенность: IZI — оператор Pod, не владелец ТЦ. Владельцы клубов нетехнические. Инженеры должны отлаживать и управлять устройствами удалённо, без штата на местах.
Текущее решение: Raspberry Pi + Python + WebSocket
Сегодня контроллер реализован на: - Raspberry Pi 3/4
(Linux, ~$35-80) - Python 3 с WebSocket клиентом -
Подключение к kabina.cariot.ru (WebSocket сервер) -
I2C/GPIO управление реле, LED, датчиками (PCF8575, PCA9685, WS2812B) -
SD-карта для хранения конфигурации
Критические проблемы текущего решения
- SD-карта умирает: Частые циклы записи флэша приводят к отказам в течение 6-12 месяцев (особенно при потере питания)
- Нет аппаратного watchdog: Если процесс зависнет, будка станет недоступна без физического присутствия
- Загрузка 30-60 секунд: Медленный запуск Linux + Python, потеря времени и доступности
- WebSocket — устаревший протокол: Бэкенд уже на MQTT/EMQX, но контроллер по-прежнему использует WebSocket
- Нет офлайн доступа: Потеря интернета = потеря доступа к Pod (клиент не может открыться)
- Сложная отладка: SSH требует интернета; логи разбросаны; нет структурированного способа собирать crash dumps
2. Сравнение платформ
Таблица сравнения основных вариантов
| Параметр | ESP32 | STM32F103 | STM32F429 | Raspberry Pi | Zelio Logic | Промышленный ПЛК |
|---|---|---|---|---|---|---|
| Цена (USD) | ~$3-5 | ~$2 | ~$8-10 | $35-80 | $200+ | $500+ |
| Архитектура | Xtensa, 2 ядра | Cortex-M3 72MHz | Cortex-M4F 180MHz | ARM 4-ядра 1.5GHz | Контакторы | Пользовательское ПО |
| Встроенная ОС | FreeRTOS | FreeRTOS | FreeRTOS | Linux | —— | Linux |
| Встроенное хранилище | 4MB Flash | 64-256KB Flash | 2MB Flash dual-bank | SD-карта | —— | HDD/SSD |
| WiFi + Bluetooth | Есть (1 антенна) | Нет | Нет (Ethernet) | WiFi модуль | —— | Нет |
| Ethernet встроенный | Нет (SPI модуль) | Нет | Есть (RMII) | Гигабит | —— | Нет |
| LED WS2812B | Да (GPIO, SPI) | Да (GPIO, SPI) | Да (GPIO, SPI) | Да (GPIO) | Нет | Нет |
| OTA обновления | Да (dual-bank) | Да (с модулем) | Да (dual-bank, встроено) | Да (SSH/Docker) | Нет | Нет |
| Кто программирует | Embedded developer | Embedded developer | Embedded developer | DevOps/Backend | PLC specialist | Инженер ПЛК |
| Надёжность в боевых условиях | Средняя (Wi-Fi нестабилен) | Высокая | Высокая | Средняя (SD-карта) | Высокая | Высокая |
| Потребление энергии | ~50-150 мА | ~50-100 мА | ~50-150 мА | 600 мА+ | ~100 мА | ~200 мА |
| Используют похожие игровые сервисы | Whoosh (неофициально) | —— | Whoosh, Bird | —— | —— | —— |
| Главная проблема | WiFi + Ethernet конфликт (1 антенна) | Маломощен (72MHz) | —— | SD-карта умирает | Нет сетевых опций | Дорого, избыточно |
Почему Zelio Logic отклонён
- Нет Ethernet встроенного: требует дополнительного модема
- Требует PLC специалиста: компания в области игровых технологий, не промышленной автоматики
- Нет OTA: обновления только физически
- Дорого и избыточно: логика контроллера проста (несколько реле, LED, датчики)
Почему STM32F429 выбран вместо ESP32
- WiFi + Bluetooth конфликт на ESP32: Одна антенна, разделённая между двумя функциями, приводит к падениям соединения при одновременном использовании
- Ethernet на ESP32 ненадёжен: Требует SPI модуля (LAN8720A), нестабилен в условиях помех
- Промышленный стандарт:
- Whoosh (аналог IZI) использует STM32F413 / NXP RT1021
- Bird (аренда консолей) использует STM32 + процессор отдельно
- Фиксированное расположение (ТЦ): Pod всегда подключён к LAN проводом. WiFi вспомогательно (для резервной связи через роутер). Проводное Ethernet — оптимальный выбор.
- Мощность: F429 достаточно для FreeRTOS + MQTT + TLS + обработки датчиков. F103 — мало (72MHz).
3. ADR-001: Выбор контроллера
Решение: STM32F429ZIT6
Спецификация: - Процессор: Cortex-M4F @ 180MHz - Память: 256KB RAM, 2MB Flash (dual-bank) - Периферия: Ethernet MAC (RMII), SPI, UART, I2C, 16× PWM таймеры - Цена: ~$8-10 за чип, ~$15-20 за готовую плату (DIP/BGA)
Потребление памяти (расчёт)
FreeRTOS kernel: ~5 KB
LWIP стек (TCP/IP): ~35 KB
mbedTLS (пик при TLS): ~60 KB
MQTT клиент: ~20 KB
Приложение (логика): ~30 KB
Буфер UART/SPI: ~10 KB
────────────────────
Итого пик: ~160 KB из 256 KB ✓
Вывод: Хватает с запасом.
Разметка Flash (2MB = 2048KB)
0x00000000 - 0x00007FFF Bootloader (32 KB)
0x00008000 - 0x0007FFFF App Bank A (480 KB) ← активная прошивка
0x00080000 - 0x000F7FFF App Bank B (480 KB) ← OTA staging
0x000F8000 - 0x000FBFFF Config + Public Key (16 KB)
0x000FC000 - 0x000FFFFF Token Storage (16 KB) ← до 100 токенов
Что исчезает по сравнению с Raspberry Pi
| Убирается | На STM32 |
|---|---|
| PCF8575 (I2C GPIO expander) | GPIO контроллера напрямую |
| PCA9685 (PWM контроллер) | 16 встроенных PWM таймеров |
| SD-карта (запись, отказы) | Internal Flash (надёжнее) |
| Linux + Python (медленно) | FreeRTOS + C (быстро, 200мс boot) |
| WebSocket (устаревший) | MQTT TLS (как бэкенд) |
| SSH отладка (нужен интернет) | MeshCentral + GDB/SWD (всегда доступно) |
4. ADR-002: Сетевая архитектура
Проблема
Pod стоит в ТЦ, владелец нетехнический, нет гарантированной инфраструктуры. Нужна полная независимость от сети ТЦ и гарантированная связь с облаком.
Решение: GL.iNet роутер + Dual-WAN Failover
Компоненты: 1. GL.iNet роутер (OpenWrt) — встроенный Linux, управляемый firewall, dual-WAN 2. Канал 1 (Primary): LAN от ТЦ (провод, если доступна) 3. Канал 2 (Secondary): LTE модем (USB, собственная SIM), автоматический переход при разрыве Primary 4. Канал 3 (Tertiary): WiFi ТЦ (если есть гостевая сеть, как резервная)
Роутер настраивается один раз, автоматически переключает каналы. ПК и STM32 подключены к локальной LAN роутера по кабелю, они об этом не знают.
Облако
- EMQX брокер — всегда онлайн, работает через NAT (MQTT поддерживает NAT)
- Все устройства подключаются к брокеру TLS на облаке через интернет-канал роутера
Сетевая диаграмма
[ТЦ LAN]
↓ (кабель)
[GL.iNet роутер] ← primary
↑↓ (переключение при разрыве)
[LTE USB модем] ← secondary
↓
[Интернет NAT]
↓
[EMQX в облаке]
[ПК Windows] ─ кабель ─ [роутер LAN]
[STM32F429] ─ кабель ─ [роутер LAN]
Оба устройства видят интернет через роутер, общее соединение.
5. ADR-003: Удалённое управление и отладка
Проблема
Pod по всему миру в неконтролируемых помещениях. Владельцы нетехнические. Инженер не может выехать в каждый ТЦ для отладки. Требуется удалённый доступ без port forwarding.
Решение: MeshCentral + ST-Link V2 + USB-UART
Компоненты
MeshCentral (mesh.izi.is): - Open-source remote access / mesh network - Агент на роутере (Linux) — всегда онлайн, может шлать WoL пакеты в локальную сеть - Агент на ПК (Windows) — RDP доступ через браузер, без port forwarding, шифрование End-to-End
ST-Link V2 ($3): - USB программатор для STM32 - SWD интерфейс — подключение к STM32 ноги (4 провода: GND, 3.3V, SWDIO, SWDCLK) - Через OpenOCD + GDB — полный доступ к памяти, регистрам, остановка в точках останова
USB-UART адаптер ($1): - Форвардит логи STM32 (UART1) → USB → ПК → удалённый инженер видит вывод в реальном времени
Уровни отладки (от простого к сложному)
- MQTT логи/события → Dash0 (основной мониторинг, 90%
проблем)
- Полный мониторинг в облаке, статистика, корреляции
- Видно все события, состояние устройства, ошибки
- Crash dump в Flash → отправляется при следующем
подключении
- При падении FreeRTOS, STM32 записывает стек вызовов, регистры, последние 1KB UART лога
- При восстановлении сетевого подключения → отправляет на бэкенд
- Инженер видит точное место краша
- UART логи через MeshCentral → PC terminal
- RDP в ПК → подключение USB-UART → реал-тайм вывод STM32
- Полезно для отладки инициализации, сетевых проблем
- SWD через OpenOCD+GDB → полный доступ (редкие
случаи)
- RDP в ПК → OpenOCD (слушает на localhost:3333)
- GDB подключается → точки останова, пошаговое выполнение, читать/писать память
- Для критических багов или разработки
Wake-on-LAN сценарий
1. ПК спит в режиме S5 (питание от роутера)
2. Инженер через MeshCentral: "разбудить ПК на booth-123"
3. MeshCentral агент на роутере → шлёт magic packet (FF:FF:...) в локальную LAN
4. ПК просыпается (5-10 сек)
5. Инженер RDP → подключается USB-UART/ST-Link
6. Отладка, синхронизация логов, перезагрузка устройства
USB безопасность на Windows
Pod находится в ТЦ, доступен посетителям. Требуется защита от утечек данных через USB.
Group Policy (для Windows Pro): - Отключить USB Mass Storage Read/Write для пользователя - Разрешить только нужные VID/PID пары: - ST-Link V2: VID=0483, PID=3748 - USB-UART (CH340): VID=1A86, PID=7523
Физическое: - Пользовательские USB порты (спереди) → заглушки или отпилены - Задние порты (где ST-Link/UART) → в сервисном отсеке, закрытом ключом - Или кабель припаен, разъём внутри корпуса
6. ADR-004: Офлайн доступ через BLE
Проблема
Интернет отключился (потеря LAN, LTE модем вышел из строя, маршрутизатор перезагружается). Клиент пришёл, хочет открыть Pod. Текущее решение: невозможно, нужен звонок оператору.
Решение: ED25519 подписанные токены + BLE
Архитектура
Токены генерируются и подписываются бэкендом при бронировании:
- Бронирование подтверждено → бэкенд генерирует токен (userId + deviceId + validFrom + validUntil)
- Бэкенд подписывает токен ED25519 приватным ключом
- Токен отправляется в App пользователя
- Токен также отправляется на STM32 через MQTT команду
storeToken(пока интернет доступен)
При потере интернета:
- STM32 переходит в режим
offline mode(определяется по потере MQTT连 несколько секунд) - STM32 запускает BLE advertisement (BlueNRG-M2SP микросхема по SPI)
- Пользователь открывает IZI App на смартфоне
- App сканирует BLE, находит наше устройство (Advertised as “izi-{deviceId}”)
- App подключается, отправляет токен по BLE GATT
- STM32 проверяет:
- Валидность подписи (ED25519, embedded public key в микросхеме)
- deviceId в токене == мой deviceId → нельзя использовать чужой токен
- validFrom <= now <= validUntil → время в RTC
- Событие логируется в Flash:
offlineAccess { userId, time, signature } - При восстановлении интернета → отправляет лог на бэкенд
Структура токена
[userId: 16 bytes UUID]
[deviceId: 16 bytes UUID]
[validFrom: 8 bytes Unix timestamp]
[validUntil: 8 bytes Unix timestamp]
[signature: 64 bytes ED25519]
───────────────────────
Всего: 112 bytes
Хранение на STM32
- Token Storage раздел в Flash: 16 KB
- Каждый токен: ~112 bytes
- Максимум токенов: 16000 / 112 ≈ 143, но логично хранить последние 100
- Старые токены вытесняются новыми при переполнении
Безопасность
- Подпись не может быть подделана — требуется ED25519 приватный ключ (есть только у бэкенда)
- Токен привязан к deviceId — если украсть токен и использовать на другом устройстве, STM32 отклонит (deviceId не совпадает)
- Время жизни токена ограничено — одна сессия бронирования. После окончания — токен неактивен.
- BLE не требует аутентификации — любой может подключиться, но без валидного токена отключается через 3 сек (timeout)
- RTC батарейка CR2032 — сохраняет время даже при полной потере питания. Если батарейка разряжена → BLE доступ отключен до синхронизации времени по NTP
BLE Гатт Характеристики
Service: izi-offline (UUID: 12345678-1234-5678-1234-567812345678)
├─ Characteristic: token-submit (Write)
│ └─ Write 112 bytes токена → STM32
├─ Characteristic: door-status (Read)
│ └─ Read 1 byte: 0x00 = closed, 0x01 = open
└─ Characteristic: battery (Read)
└─ Read 1 byte: процент батареи
7. ADR-005: Надёжность и питание
Перебои питания (Power Loss)
Сценарий: Отключается питание на 1-100 мс (скачок напряжения, проблемы с контактом, срабатывание защиты).
Решение:
- Brown-Out Reset (BOR) прерывание:
- STM32 имеет встроенный BOR компаратор (мониторит Vdd)
- При падении напряжения ниже порога (~2.7V) → прерывание BOR
- В обработчике за 1-2 мс: сохраняем текущее состояние реле в Flash
- Записываем последний валидный RTC snapshot
- Супер-конденсатор:
- 10F @ 5V конденсатор на входе питания
- Держит питание цепей управления (STM32, BLE, LAN8720A) ещё 50-100 мс после отключения основного питания
- Позволяет graceful shutdown вместо холодной перезагрузки
- После восстановления питания:
- STM32 просыпается (bootloader проверяет Flash, загружает app)
- App читает Flash → восстанавливает состояние реле (если оно не совпадает с текущим)
- Синхронизирует RTC по NTP (если интернет доступен)
- Отправляет событие
powerRestoredна бэкенд
Аппаратный Watchdog (IWDG)
Сценарий: FreeRTOS задача зависла в бесконечном цикле → микросхема не отвечает → Pod недоступен.
Решение: IWDG (Independent Watchdog Timer)
WatchdogTask (период 100мс):
├─ ждёт события от SensorTask
├─ ждёт события от MqttTask
├─ ждёт события от LedTask
├─ ждёт события от OutputTask
└─ если все пришли → кормим IWDG (пишем magic value)
Если хоть одна задача зависла:
├─ WatchdogTask не получит событие вовремя
├─ IWDG не будет накормлен
└─ через 3 сек → IWDG сбрасывает чип (reset)
Параметры IWDG: - Timeout: 3 сек - Период кормления: 100 мс (3 попытки перед reset) - Логирование: перед reset пишем в Flash “IWDG timeout at {time}”
Manual режим (физическая кнопка)
Где: Сервисный отсек (закрыт ключом), рядом с ST-Link
Функция: Открыть дверь вручную, если MQTT, BLE и RTC недоступны.
Приоритет управления (от высокого к низкому): 1. MQTT команда (от облака, наиболее безопасно) — требует аутентификации, логирование полное 2. BLE токен (офлайн, подписанный) — требует валидного токена + RTC 3. Manual кнопка (последняя линия защиты) — требует физического доступа, но не логируется на облаке
События: - Любое действие (открытие/закрытие) логируется с меткой времени в Flash - При восстановлении интернета → отправляется на бэкенд полная история
RTC (Real-Time Clock)
Компонент: Встроенный STM32 RTC + внешняя батарейка CR2032
Функции: - Сохранение текущего времени при потере основного питания - Проверка validFrom/validUntil для BLE токенов - Меток времени для событий в offline режиме
Синхронизация: - При подключении к MQTT → запрашиваем время (NTP через бэкенд или SNTP) - Обновляем RTC каждый час (если есть интернет)
Проблема: Если батарейка разряжена → RTC показывает случайное время → BLE доступ отключен (токены не валиднюются) - Решение: Мониторить батарейку, отправлять alert на бэкенд “RTC battery low”
8. ADR-006: Логика состояний и Fail-Safe поведение
Модель состояний (из бэкенда)
Бэкенд (computeDesiredPodState) определяет желаемое
состояние Pod на основе двух сущностей:
Session — игровая сессия клиента. Статусы:
PENDING → ACTIVE → STARTED → ENDED
DeviceHold — техническая блокировка устройства
(обслуживание, резерв). Имеет startAt, endAt,
reason.
Desired State: правила из бэкенда
| Условие | doorStatus | lightsInner | lightsOuter | cooling | ventilation | glassDimming |
|---|---|---|---|---|---|---|
| Сессия активна | OPEN | ON | ON | ON | ON | OFF |
| Нет сессии / hold | CLOSE | OFF | BLINK | OFF | ON | OFF |
| Ручной override | по override | по override | по override | по override | по override | по override |
Вентиляция — всегда ON. Overrides сбрасываются при старте/завершении сессии.
Reconciliation: бэкенд сравнивает observed state (от STM32) с desired state и шлёт одну корректирующую команду за цикл.
Автономная работа STM32 при потере MQTT
STM32 хранит sessionEnd в Flash — при потере связи
работает по локальному таймеру:
При получении MQTT "начать сессию":
→ Flash: sessionEnd = now + duration, sessionActive = true
Локальный цикл каждую минуту:
если sessionActive и now > sessionEnd:
→ применить IDLE (дверь закрыть, свет/кондер выключить)
→ sessionActive = false
если sessionActive и sessionEnd - now < 5 минут:
→ мигнуть светом (предупреждение клиенту)
Таблица Fail-Safe
| Нагрузка | Сессия активна | Нет сессии / hold | Потеря питания |
|---|---|---|---|
| Дверь (замок) | OPEN | CLOSE | OPEN — человек не должен быть заперт |
| Кондиционер | ON | OFF | OFF |
| Свет внутри | ON | OFF | OFF |
| Свет снаружи | ON | BLINK | BLINK |
| LED лента | Анимация | Idle | OFF |
| Вентиляция | ON | ON | OFF |
| Аварийный свет | — | — | ON (батарейный) |
Триггеры экстренного открытия двери
Независимо от сессии и MQTT:
- Кнопка паники нажата
- Датчик дыма сработал
- CO₂ > 5000 ppm
- Потеря питания (fail-safe замок)
- MQTT команда
setLock: OPEN
Логика CO₂
< 1000 ppm → норма
1000–2000 → вентиляция на максимум
2000–5000 → вентиляция + алерт оператору + LED мигание клиенту
> 5000 → открыть дверь + немедленный алерт
9. ADR-007: Алертинг (Telegram)
Принцип
Все алерты — в Telegram канал оператора через бота. Один сервис
AlertService.send() на бэкенде, внутри — POST в Telegram
Bot API.
Формат сообщения
🚨 [КРИТИЧЕСКИЙ] Датчик дыма
📍 ТЦ Мега · Pod #3
⏰ 08.04.2026 21:34
💬 Дверь открыта автоматически
⚠️ [ПРЕДУПРЕЖДЕНИЕ] CO₂ высокий
📍 ТЦ Европейский · Pod #1
📊 2400 ppm (норма < 1000)
⏰ 08.04.2026 20:11
Таблица алертов
| Событие | Уровень | Источник |
|---|---|---|
| Датчик дыма сработал | 🚨 Критический | STM32 → MQTT → бэкенд |
| CO₂ > 5000 ppm | 🚨 Критический | STM32 → MQTT → бэкенд |
| Кнопка паники нажата | 🚨 Критический | STM32 → MQTT → бэкенд |
| Устройство офлайн > 5 мин | 🔴 Высокий | Бэкенд (нет heartbeat) |
| CO₂ 2000–5000 ppm | ⚠️ Средний S | TM32 → MQTT → бэкенд |
| Дверь открыта > 10 мин без сессии | ⚠️ Средний Б | экенд (reconcile) |
| Observed ≠ Desired > 3 мин | ⚠️ Средний Б | экенд (reconcile) |
| LTE failover активирован | ℹ️ Инфо S | TM32 → MQTT |
| Питание восстановлено | ℹ️ Инфо S | TM32 → MQTT |
| OTA завершено (успех/неудача) | ℹ️ Инфо S | TM32 → MQTT |
Реализация
await alertService.send({
level: 'critical', // critical | warning | info
clubName: 'ТЦ Мега',
deviceName: 'Pod #3',
message: 'Датчик дыма сработал. Дверь открыта автоматически.',
data: { co2: 5200 } // опционально
})
10. OTA обновления прошивки
Dual-Bank Flash архитектура
Банк A (активная): 480 KB [0x08000 - 0x7FFFF]
Банк B (staging): 480 KB [0x80000 - 0xF7FFF]
Процесс OTA
Бэкенд отправляет MQTT команду:
{ "action": "startOTA", "url": "https://firmware.izi.is/bootka/v2.3.1/firmware.bin", "crc32": "a1b2c3d4", "size": 450000 }STM32 загружает файл:
- HTTPS клиент (mbedTLS)
- Скачивает в Bank B по частям (8KB буферы)
- Вычисляет CRC32 по ходу
- Если сеть упала → может возобновить (запомнить смещение)
Верификация:
- Сравнивает CRC32 загруженного файла с ожидаемым
- Если не совпадает → отклоняет, просит перезагрузку
- Если совпадает → продолжаем
Переключение банков:
- NVRAM флаг:
bootFromBank = B - Reboot (через мягкую перезагрузку или NVIC_SystemReset)
- NVRAM флаг:
Bootloader:
- Проверяет флаг, загружает App из Bank B
- Если загрузка успешна → флаг переводит в
bootFromBank = A(для следующего раза) - Если краш → bootloader видит краш, переключается обратно на Bank A и
устанавливает
bootFromBank = A
Откат при ошибке:
- Если новая прошивка постоянно крашится (5 перезагрузок за 30 сек) → bootloader автоматически откатывает на Bank A
- Отправляет событие
OTA_ROLLBACKна бэкенд
Безопасность OTA
- HTTPS верификация — сертификат бэкенда проверяется (внедрённая цепочка сертификатов в прошивке)
- CRC32 — на транспортные ошибки
- Подпись при необходимости — если требуется защита от MITM, добавить ED25519 подпись в заголовок bin файла
11. Программный стек
Архитектура слоёв
┌─────────────────────────────────────┐
│ Приложение (логика устройства) │
│ - StateManager │
│ - MQTT обработчик команд │
│ - BLE обработчик токенов │
├─────────────────────────────────────┤
│ FreeRTOS Real-Time ОС │
│ ├─ SensorTask (GPIO, датчики) │
│ ├─ MqttTask (сетевой клиент) │
│ ├─ LedTask (WS2812B анимация) │
│ ├─ OutputTask (реле, PWM) │
│ ├─ BleTask (BlueNRG-M2SP) │
│ └─ WatchdogTask (мониторинг) │
├─────────────────────────────────────┤
│ Сетевые протоколы │
│ ├─ MQTT-C библиотека │
│ ├─ mbedTLS (TLS 1.3, ED25519) │
│ └─ LWIP (TCP/IP стек) │
├─────────────────────────────────────┤
│ STM32 Hardware Abstraction Layer │
│ ├─ RCC (тактирование) │
│ ├─ GPIO (портов ввода-вывода) │
│ ├─ UART, SPI, I2C │
│ ├─ TIMx PWM │
│ ├─ ETH MAC + RMII │
│ ├─ RTC + BOR │
│ └─ IWDG, NVRAM │
├─────────────────────────────────────┤
│ Периферия (микросхемы) │
│ ├─ LAN8720A (Ethernet PHY, RMII) │
│ ├─ BlueNRG-M2SP (BLE, SPI) │
│ ├─ 74HC595 (сдвиговый регистр) │
│ ├─ WS2812B (LED strip, 1-wire) │
│ └─ CR2032 батарейка (RTC) │
└─────────────────────────────────────┘
Зависимости
FreeRTOS 10.x
LWIP 2.1.x (с поддержкой RMII Ethernet)
mbedTLS 3.x (для TLS 1.3, ED25519)
MQTT-C v1.x или Paho Embedded C
ST HAL для STM32F4xx
OpenOCD (для отладки, не в прошивке)
MQTT топики (совместимо с существующим бэкендом)
Subscribe (слушаем команды):
izi/v1/{clubId}/devices/pod/{deviceId}/commands
Publish (отправляем состояние):
izi/v1/{clubId}/devices/pod/{deviceId}/controller/state/fast
Events (события датчиков):
izi/v1/{clubId}/devices/pod/{deviceId}/controller/events/sensor
Offline logs (при восстановлении интернета):
izi/v1/{clubId}/devices/pod/{deviceId}/controller/events/offline
Payload формат (ControllerFastStatePayload)
{
"timestamp": 1712577600,
"lightsInner": "OFF",
"lightsOuter": "ON",
"glassDimming": "ON",
"doorStatus": "CLOSED",
"pcPower": "ON",
"temperature": 32.5,
"humidity": 45,
"internetStatus": "ONLINE",
"batteryLevel": 100
}
12. Периферия: что это и зачем
Компоненты и пояснения
Геркон (дверной датчик) Стеклянная трубочка с двумя металлическими контактами внутри. На дверь клеится магнит, на косяк — геркон. Дверь закрыта → магнит рядом → контакты замкнуты. Дверь открылась → магнит ушёл → контакты разомкнуты. Никакой электроники внутри, просто выключатель.
Pull-up резистор STM32 держит пин на 3.3V через внутренний резистор (pull-up). Когда геркон или кнопка замыкается — пин тянется к GND. STM32 читает: 3.3V = открыто, 0V = замкнуто. Без pull-up пин “болтается” в воздухе и даёт случайные значения.
PIR (Passive InfraRed) Детектор движения, реагирующий на тепловое излучение тела. “Passive” — не излучает ничего, только принимает. Используется чтобы: не гасить свет если клиент сидит неподвижно, детектировать присутствие без сессии, подтверждать что клиент зашёл.
300 Ом перед WS2812B Защитный резистор на линии данных LED-ленты. Длинный провод от платы к ленте работает как антенна и создаёт отражения сигнала. Резистор гасит помехи, иначе первый светодиод глючит. Стандарт из даташита WS2812B.
TIM3 PWM (диммер) Аппаратный таймер STM32 в режиме ШИМ — генерирует сигнал с переменной скважностью 0-100%. Используется для плавного диммирования обычных (не адресных) светильников: 0% = выключен, 100% = полная яркость. Зарезервирован для внешней подсветки будки.
Датчик дыма vs CO₂ — разные вещи:
| Датчик дыма | CO₂ сенсор (MH-Z19) | |
|---|---|---|
| Что измеряет | Частицы дыма в воздухе | Концентрацию углекислого газа |
| Норма / тревога | 0 / есть дым | 400 ppm норма / >2000 ppm плохо / >5000 ppm опасно |
| Зачем | Пожарная безопасность | Качество воздуха в закрытом пространстве |
| Действие | Открыть дверь, тревога | Включить вентиляцию; при критическом — открыть дверь |
Оба нужны. Человек за 1-2 часа в закрытой будке поднимает CO₂ до некомфортного уровня — без сенсора это невидимо.
Микрофон (уровень звука) Аналоговый датчик уровня громкости — не запись, только одно число (0-100%). Будка стоит в ТЦ рядом с людьми. Если клиент орёт или музыка на максимуме — оператор получает уведомление. Дёшево и полезно для качества сервиса.
Кнопка паники Механическая кнопка внутри будки. Клиент нажимает если застрял, плохо, или нужна помощь. Пассивная (нет питания, нет электроники) — просто замыкает GPIO на GND. STM32 → MQTT → уведомление оператору немедленно. Юридическое требование для закрытых кабин в большинстве стран.
13. Маппинг железо → бэкенд модель
Соответствие физических компонентов логическим полям
| Физический компонент | Тип сигнала | Логическое поле (GraphQL) | Значение |
|---|---|---|---|
| Реле Light1 (внутреннее) | GPIO output | lightsInner | OFF / ON |
| Реле Light2 (внешнее) | GPIO output + TIM3 PWM | lightsOuter | OFF / BLINK / ON |
| WS2812B LED лента | TIM1+DMA | lightsOuter (анимация) | BLINK = RGB анимация |
| Электрозамок | GPIO output (MOSFET) | doorLock | OPEN / CLOSED |
| Контактор кондиционер | GPIO output (ULN2003) | cooling | OFF / ON |
| Вентилятор | GPIO output | ventilation | OFF / ON |
| Геркон дверь | GPIO input (pull-up) | doorStatus | CLOSED / OPEN |
| PIR движение | GPIO input | event | motionDetected |
| Датчик дыма | GPIO input | event | smokeDetected |
| CO₂ сенсор (MH-Z19) | UART | event + state | co2Level (ppm) |
| Температура/влажность (SHT31) | I2C | state | temperature, humidity |
| Микрофон | ADC input | event | volumeHigh (если > порога) |
| Кнопка паники | GPIO input (pull-up) | event | panicPressed |
Логика управления (из MQTT команды)
{
"action": "setLights",
"inner": "ON",
"outer": "BLINK",
"duration": 30000
}
STM32: 1. inner: ON → включить GPIO Light1 реле 2.
outer: BLINK → запустить PWM на Light2, включить WS2812B
анимацию 3. duration: 30000 → через 30 сек автоматически
отключить (если нет нового приказа)
14. Что меняется на бэкенде
Хорошие новости: протокол остаётся прежним
Бэкенд не видит внутренних изменений контроллера. MQTT топики, формат payload’ов остаются неизменны. Разработка бэкенда может продолжаться параллельно.
Что добавляется (минимально)
Генерация и подпись BLE токенов (при бронировании):
// В resolver для bookingCreate или bookingConfirm const token = generateOfflineAccessToken({ userId, deviceId, validFrom: now, validUntil: now + 3600 }); const signedToken = signED25519(token, PRIVATE_KEY); // Отправить на устройство: await mqttClient.publish(`izi/v1/{clubId}/devices/pod/{deviceId}/commands`, { action: "storeToken", token: Buffer.from(signedToken).toString('base64') });MQTT команда
wakePC(для удалённой отладки):{ "action": "wakePC" }STM32 отправляет WoL magic packet в локальную сеть (к ПК).
Получение и логирование
offlineAccessGrantedсобытий:{ "type": "offlineAccessGranted", "userId": "user-uuid", "time": 1712577600, "method": "BLE_TOKEN" }Используется для аналитики, проверки работоспособности офлайн доступа.
Генерация и распределение ED25519 публичного ключа:
- Приватный ключ хранится в
.envбэкенда (не в гит) - Публичный ключ встраивается в прошивку STM32 при компиляции
- При смене ключа → требуется OTA прошивки всех устройств
- Приватный ключ хранится в
15. Итоговое сравнение: было / стало
| Параметр | Raspberry Pi (текущее) | STM32F429 (план) | Улучшение |
|---|---|---|---|
| Язык | Python | C + FreeRTOS | Типобезопасность, скорость |
| Протокол | WebSocket | MQTT TLS | Совместимость с бэкендом, подмена посередине невозможна |
| Хранилище | SD-карта | Internal Flash dual-bank | Надёжность (нет износа), OTA встроено |
| Время загрузки | 30-60 сек | ~200 мс | 150x быстрее |
| Watchdog | Программный (ненадёжный) | Аппаратный IWDG | Гарантированный reset при зависании |
| OTA обновления | SSH вручную | MQTT автоматическое | Масштабируется, no downtime |
| Офлайн доступ | ❌ Нет | ✓ BLE + ED25519 токены | Клиент может открыться при потере интернета |
| Удалённая отладка | SSH (требует интернет) | MeshCentral + SWD | Всегда доступна, независимо от интернета |
| Цена контроллера | $35-80 | $15-20 (плата) | 2-5x дешевле |
| Надёжность в поле | Средняя (SD-карта, SD-карта, SD-карта) | Высокая (промышленный стандарт) | Меньше отказов, больше аптайма |
| Потребление энергии | 600+ мА | ~150 мА | 4x экономнее |
| Кто может отлаживать | Только backend инженер | Backend + embedded инженер | Больше гибкости в команде |
16. Управление 220V нагрузками (кондиционер, освещение)
Проблема
STM32 GPIO работает на 3.3V логике и выдаёт максимум 25мА. Напрямую подключить 220V AC нельзя — это уничтожит микросхему и создаст опасность для жизни.
Решение: гальваническая развязка через реле
Цепочка управления:
STM32 GPIO (3.3V, 8мА)
│
▼
Резистор 1кОм
│
▼
NPN транзистор (2N2222) или ULN2003A (управляет до 8 реле)
│
▼
Обмотка реле (5V или 12V, 50-150мА) + диод 1N4007 (защита от обратного тока)
│
▼
Контакты реле (гальванически изолированы от обмотки)
│
▼
220V AC нагрузка (кондиционер, освещение, вентиляция)
Ключевой принцип: контакты реле и обмотка реле электрически изолированы. STM32 управляет обмоткой (5V/12V), контакты коммутируют 220V. Эти цепи не соединены напрямую.
Выбор реле для кондиционера
Кондиционер — активная нагрузка с пусковым током до 5-6× рабочего. Нужен запас:
| Параметр | Значение |
|---|---|
| Рабочее напряжение контактов | 250V AC |
| Ток контактов (рабочий) | 16A (с запасом для кондиционера 2.5-3.5кВт) |
| Ресурс переключений | ≥100 000 циклов |
| Примеры | Omron G5Q-1A-EU, Songle SRM-5VDC-SL-C |
Схема для одного канала 220V
STM32 220V цепь
GPIO ─── 1кОм ─── B(NPN) ────────────────────────
C(NPN) ── Обмотка реле ── 5V │
E(NPN) ── GND │
║ Контакт COM
╚══ 1N4007 ══╗ Контакт NO ── Фаза 220V
║ └── к нагрузке
5V/GND Ноль 220V ──────── к нагрузке
Что важно при монтаже
- Клеммники для 220V отдельно от клеммников 3.3V/5V (разные части платы)
- Предохранитель на каждую 220V цепь (защита от КЗ в нагрузке)
- Заземление корпуса будки
- Маркировка проводов (фаза/ноль/земля — цветами по стандарту)
- Реле-модуль покупать с оптроном на входе — дополнительная изоляция от GPIO
Маппинг нагрузок
| Нагрузка | Ток | Реле | Канал STM32 |
|---|---|---|---|
| Освещение внутреннее | ~2A | 5A/250VAC | GPIO PC6 |
| Освещение внешнее | ~2A | 5A/250VAC | GPIO PC7 |
| LED лента (питание) | ~3A | 5A/250VAC | GPIO PC8 |
| Кондиционер | ~10-16A | 16A/250VAC | GPIO PC9 |
| Вентиляция | ~3A | 5A/250VAC | GPIO PC10 |
17. Инженерные решения
LTE модем ✓
Модуль: Quectel EC25-E (mini PCIe) в слот GL.iNet GL-X750 (Spitz). Промышленный стандарт — используется в Bird, Lime и других IoT-продуктах. Надёжнее USB-модемов.
SIM-карта: - Старт (1 страна): M2M тариф МТС/Билайн (РФ) или du/Etisalat (ОАЭ) — ~$3–5/мес - Масштаб (несколько стран): Hologram или 1NCE ($10 единовременно, 500MB/год) — глобальный роуминг
Плата контроллера ✓
Nucleo-F429ZI — готовая плата STMicroelectronics, STM32 уже распаян. Паять не нужно: - Реле-модули подключаются джамперами к GPIO-заголовкам (у модулей есть клеммники) - BLE: X-NUCLEO-IDB05A2 втыкается сверху как шилд - Ethernet: встроен на плате (LAN8742A) - Отладчик ST-Link: встроен, отдельный не нужен
Nucleo влезает в DIN-корпус через стандартный адаптер. Для будки размером с кабину — размер платы не проблема.
Охлаждение
Не требуется. STM32 + периферия потребляют ~1–2W. DIN-корпус с вентиляционными прорезями достаточен. Mean Well PSU рассчитан на ambient +70°C. Кондиционер будки поддерживает нормальную температуру в сервисном отсеке.
Корпус ✓
DIN-рейка в сервисном отсеке будки. Пластиковый бокс на 12 модулей (Phoenix Contact или Schneider Kaedra). Nucleo крепится через DIN-адаптер, рядом — Mean Well PSU, реле, клеммники. Монтаж в существующий отсек, не новый корпус. IP54 достаточно.
Provisioning ✓
Backend-provisioning без ручного вмешательства:
- Завод прошивает стандартную прошивку — одинаковую для всех устройств
- При первом включении устройство подключается к MQTT с временным ID = STM32 UID (уникален аппаратно)
- Оператор в CRM сканирует QR-код на корпусе (= STM32 UID) → привязывает к клубу/будке одной кнопкой
- Backend шлёт deviceId + clubId → STM32 сохраняет в Flash → перезагружается в рабочем режиме
Никаких кабелей, скриптов, ручных настроек. 2 минуты на активацию.
Замена в поле ✓
- Оператор вставляет новый контроллер (без конфига)
- Сканирует QR нового устройства в CRM → привязывает к этой будке
- Backend автоматически высылает конфиг + все активные BLE-токены
- Старый deviceId деактивируется
Полностью автоматизировано. Инженер не нужен — справится персонал клуба.
RTC батарейка CR2032: ~3–5 лет (STM32 RTC потребляет 1–2 мкА). Менять по алерту “RTC battery low” при плановом обслуживании.
Сертификация
Использовать уже сертифицированные модули: - Quectel EC25: CE, FCC, сертификат Роскомнадзора РФ, TRA ОАЭ - STM BlueNRG: CE, FCC - Mean Well PSU: CE, UL
Тогда сертификация конечного устройства = декларация соответствия, без полного тестирования. Крипто ED25519 в РФ — уточнить у юриста (скорее уведомление, не лицензия).
Открытые вопросы
- Финальные цены компонентов при серийном заказе (ресёрч в процессе)
17а. Юридические риски и митигация
Полный ресёрч:
Legal_Risk_Research.md. Ниже — критические выводы влияющие на архитектуру.
Критические риски (existential)
1. Человек заперт при пожаре — уголовная ответственность
Автоматический замок — самый опасный элемент с юридической точки зрения.
- РФ: ст. 238 и ст. 109 УК РФ (причинение смерти по неосторожности). После пожара в «Зимней вишне» (2018, 60 погибших) МЧС проверяет все закрытые пространства в ТЦ с особым вниманием. Штраф за нарушение ПБ: 300 000 — 1 000 000 руб + приостановка деятельности до 90 дней.
- ОАЭ: Civil Defence NOC обязателен до открытия — без него ТЦ не разрешит установку. Требует подтверждения fail-safe замка в проектной документации.
- ЕС: EN 13637 (electrically controlled exit systems) — обязателен для автоматических замков в общественных местах.
Митигация (уже в архитектуре): - Замок fail-safe: при потере питания открывается ✓ - Дым / CO₂ / паника → принудительное открытие ✓ - Механическая кнопка аварийного открытия внутри будки ✓
2. CO₂ — санитарные нормы
Человек в кабине ~1.5 м³ достигает 1000 ppm (предельная норма СанПиН и Dubai Municipality) за 10–15 минут при закрытой двери. Пассивный мониторинг недостаточен — требуется активное управление вентиляцией.
- РФ: СанПиН 1.2.3685-21, штраф Роспотребнадзора до 200 000 руб + приостановка.
- ОАЭ: Dubai Municipality Air Quality — CO₂ ≤ 1000 ppm, активно проверяется.
Митигация (уже в архитектуре): - CO₂ сенсор MH-Z19 → управляет вентилятором ✓ - При >5000 ppm → принудительное открытие двери ✓
Важные требования по юрисдикциям
| РФ | ОАЭ | ЕС | |
|---|---|---|---|
| Пожарная безопасность | МЧС, обязательно | DCD NOC до установки | EN 13637 |
| Воздух (CO₂) | СанПиН ≤ 1000 ppm | DM ≤ 1000 ppm | EN 15251 |
| Персональные данные | ФЗ-152, регистрация с мая 2025 | UAE PDPL, DIFC | GDPR + DPIA |
| Электробезопасность | ГОСТ Р, ТР ЕАЭС | — | CE (LVD + EMC) |
| Замок | Fail-safe обязателен | Fail-safe в NOC | EN 13637 |
Персональные данные
Логи сессий = персональные данные (кто, когда, сколько).
- РФ ФЗ-152: обязательная регистрация оператора в Роскомнадзоре с мая 2025. Штраф за утечку: до 15 000 000 руб.
- Камера внутри будки без информированного согласия = ст. 137 УК РФ (нарушение неприкосновенности частной жизни).
- ОАЭ PDPL: штраф AED 50 000 — 5 000 000.
- ЕС GDPR: внутренние камеры = биометрические данные = запрещено без явного согласия.
Митигация: не устанавливать камеры внутри будки; хранить минимум данных; добавить consent flow в приложение.
Реальные инциденты
- Кейп-Таун, март 2026: пожар уничтожил игровой Pod на выставке — соседние тоже пострадали.
- РФ, Ставрополь + Евпатория, 2025: люди застряли в аттракционах — операторы оштрафованы, объекты закрыты.
Заключение
Переход с Raspberry Pi на STM32F429 решает критические проблемы надёжности, отладки и масштабируемости. STM32 — промышленный стандарт в IoT-продуктах (Whoosh, Bird), хорошо изучен, есть опыт боевого применения.
Ключевые преимущества: - 200 мс загрузка вместо 30–60 сек - Надёжное хранилище (Flash вместо SD-карты, которая умирает) - Нативный MQTT — совместим с существующим бэкендом без изменений - Офлайн доступ через BLE + ED25519 токены — критично для UX - Удалённая отладка через MeshCentral + SWD без выезда - 2–5× дешевле Raspberry Pi в серийном производстве
Next steps: 1. Заказ Nucleo-F429ZI для прототипирования 2. Реализация Bootloader + MQTT стека на STM32 3. Обновление бэкенда: BLE token gen, provisioning flow, offline log handling 4. Тестирование по протоколу (раздел 18) — стенд с полным набором датчиков 5. Пилот в одном реальном Pod — 1–2 месяца
Риски: - Проблемы с LTE модемом в разных странах → тестировать локально перед запуском - Юридические требования по юрисдикциям → см. раздел 17а и Legal_Risk_Research.md
18. Протокол тестирования
Протокол описывает процедуры функционального тестирования STM32F429 контроллера, сгруппированные по семи бизнес-целям. Каждая цель имеет критическое значение для безопасности, надёжности, финансовой целостности, пользовательского опыта и операционного управления системой IZI Bootka.
18.1 Жизнь и безопасность
Человек может оказаться внутри будки при любом сбое, поэтому дверь должна открыться немедленно. Все критические сценарии (потеря питания, датчики угрозы, паника) должны приоритизировать выход.
TC-101: Потеря питания → дверь открывается в течение 100 мс
- Цель: Гарантировать физический выход при отключении питания.
- Условие: STM32 загружен, сессия может быть активна или IDLE. Дверь CLOSE.
- Действие: Отключить питание (вытащить штекер или отключить БП).
- Ожидаемый результат: Супер-конденсатор держит управление 50-100 мс. STM32 получает BOR interrupt, активирует реле дверного замка на выключение (fail-safe). Дверь открывается механически.
- Критерий приёмки: Дверь открывается в течение 100
мс. На UART видны логи
[BROWNOUT] trigger. Состояние сохранено в Flash. STM32 не повреждается.
TC-102: CO₂ критический (>5000 ppm) → дверь открывается, Telegram алерт
- Цель: Предотвратить удушье при полном отсутствии вентиляции.
- Условие: STM32 загружен, MQTT подключен. Дверь CLOSE.
- Действие: Имитировать CO₂ >5000 ppm (подать сигнал на UART2 датчика MH-Z19B).
- Ожидаемый результат: STM32 получает значение >5000 ppm. Немедленно активирует реле дверного замка (OPEN), публикует критический статус в MQTT, отправляет Telegram алерт оператору.
- Критерий приёмки: Дверь открывается в течение 500
мс. MQTT алерт в течение 2 сек. Telegram уведомление в течение 5 сек. На
UART логируется
[CO2_CRITICAL].
TC-103: Датчик дыма активирован → дверь открывается, Telegram алерт
- Цель: Немедленно выпустить человека при пожаре.
- Условие: STM32 загружен. Датчик дыма подключен к GPIO PE4.
- Действие: Нажать тестовую кнопку на датчике дыма (или подать дым).
- Ожидаемый результат: GPIO PE4 переходит в LOW.
STM32 открывает дверь, публикует статус
{smoke: true}, отправляет Telegram алерт. - Критерий приёмки: Дверь открывается в течение 500
мс. MQTT и Telegram алерты в течение 5 сек. На UART логируется
[SMOKE_ALERT].
TC-104: Кнопка паники нажата → дверь открывается, мигает красная LED, Telegram
- Цель: Обеспечить экстренный выход в любой ситуации.
- Условие: STM32 загружен. Кнопка паники подключена к GPIO PA8.
- Действие: Нажать кнопку паники на 1-2 сек.
- Ожидаемый результат: GPIO PA8 переходит в LOW.
STM32 открывает дверь, включает красную мигающую LED, публикует
{panic: true}, отправляет Telegram алерт. - Критерий приёмки: Дверь открывается в течение 500 мс. LED мигает красным с видимой частотой (1 Гц). Алерты отправлены в течение 5 сек.
TC-105: BLE offline + валидный токен → дверь открывается без интернета
- Цель: Позволить выход, если сервер недоступен (сеть вышла из строя).
- Условие: STM32 в MQTT offline mode. BLE включен. RTC синхронизирован. В Flash валидный ED25519 токен.
- Действие: App отправляет валидный токен (deviceId совпадает, подпись корректна, время в диапазоне).
- Ожидаемый результат: STM32 обрабатывает токен независимо от MQTT. Проверяет подпись и время. Активирует реле дверного замка.
- Критерий приёмки: Токен проверяется в течение 500
мс. Дверь открывается. На UART логируется
[BLE_TOKEN] Valid, processing locally.
TC-106: MQTT offline → локальный таймер контролирует sessionEnd (±2 сек за 30 сек)
- Цель: Гарантировать, что сессия завершится вовремя даже без сервера.
- Условие: STM32 загружен, сессия ACTIVE с duration 30 сек. MQTT подключен.
- Действие: Отключить интернет, дождаться истечения sessionEnd.
- Ожидаемый результат: STM32 обнаруживает потерю
MQTT. Переходит в offline mode. Использует локальный RTC для отсчёта
sessionEnd. Когда
now >= sessionEnd, переходит в IDLE (закрывает дверь). - Критерий приёмки: Offline обнаружен в течение 10
сек. Локальный таймер отсчитывает ±2 сек за 30 сек. IDLE переход
происходит точно. На UART логируется
[OFFLINE_MODE].
TC-107: IWDG перезагружает STM32 при зависании задачи
- Цель: Гарантировать восстановление при критическом зависании.
- Условие: STM32 загружен. Все задачи работают нормально.
- Действие: Имитировать зависание MqttTask (бесконечный цикл, не публикует события).
- Ожидаемый результат: WatchdogTask не может накормить IWDG. IWDG таймер (3 сек) истекает. STM32 автоматически перезагружается.
- Критерий приёмки: Reset происходит в течение 3 сек.
На UART логируется
[IWDG]события. STM32 восстанавливается в течение 5 сек.
TC-108: Восстановление питания → состояние восстанавливается из Flash (sessionEnd, реле)
- Цель: Восстановиться в правильном состоянии после скачков питания.
- Условие: STM32 работал в состоянии ACTIVE. Питание отключено на 5-10 мин.
- Действие: Записать sessionEnd, отключить питание, подключить обратно.
- Ожидаемый результат: Bootloader загружается,
проверяет Flash CRC. App восстанавливает
sessionEndи состояние реле. ЕслиsessionEndв будущем, остаётся в ACTIVE. - Критерий приёмки: Bootloader загружается в течение
500 мс. App восстанавливается в течение 1 сек.
sessionEndкорректен. MQTT публикует восстановление.
18.2 Энергоэффективность
Минимум кВт·ч в простое, минимум кВт·ч при активной сессии, быстрое выключение оборудования. Измеряется потребление в режиме IDLE и ACTIVE.
TC-201: Режим IDLE → вентиляция минимальная, кондер OFF, внешний свет BLINK
- Цель: Минимизировать энергопотребление когда будка не используется.
- Условие: STM32 загружен, нет активной сессии.
- Действие: Дождаться автоматического перехода в IDLE (или отправить MQTT команду).
- Ожидаемый результат: Дверь CLOSE, свет внутри OFF, внешний свет мигает (BLINK, 2-сек цикл), кондер OFF, вентиляция включена на минимум (~30% PWM).
- Критерий приёмки: Все выходы переходят в состояние
в течение 1 сек. На UART логируется
[IDLE_MODE]. Потребление вентилятора минимальное (слышимость низкая).
TC-202: Режим ACTIVE → кондер ON, вентиляция максимум, свет ON, LED яркая
- Цель: Обеспечить комфорт при активной сессии (приоритет к энергопотреблению).
- Условие: STM32 загружен, MQTT подключен.
- Действие: Бэкенд отправляет
{action: startSession, duration: 3600}. - Ожидаемый результат: Дверь OPEN, свет внутри ON (100%), свет снаружи ON, кондер ON (контактор включает компрессор), вентиляция ON (100% PWM), LED ACTIVE (яркая анимация).
- Критерий приёмки: Все выходы переходят в течение 1
сек. На UART логируется
[SESSION] START. Компрессор издаёт звук. Вентилятор работает на максимум.
TC-203: Выключение кондера при завершении сессии (экономия после сессии)
- Цель: Немедленно остановить энергоёмкий компрессор после сессии.
- Условие: STM32 в состоянии ACTIVE, кондер ON.
- Действие: Дождаться истечения sessionEnd.
- Ожидаемый результат: Когда
now >= sessionEnd, STM32 переходит в IDLE. Обесточивает реле контактора кондера. Компрессор останавливается. - Критерий приёмки: Кондер выключается в течение 1000
мс. На UART логируется
[COOLING] OFF. Компрессор перестаёт гудеть.
TC-204: LED лента плавно переходит из режима ACTIVE в IDLE (без резкого выключения)
- Цель: Визуально показать переход между состояниями без резких скачков.
- Условие: STM32 в ACTIVE режиме, LED яркая.
- Действие: Завершить сессию (sessionEnd истекает).
- Ожидаемый результат: LED плавно переходит из яркой анимации ACTIVE в пульсирующий IDLE цвет (например, синий или зелёный). Без чёрной вспышки, без мерцания.
- Критерий приёмки: Переход происходит плавно в
течение <500 мс. На UART логируется
[LED] Transition from ACTIVE to IDLE. Нет резких мерцаний.
18.3 Защита оборудования (анти-вор)
Мышь, монитор, периферия внутри будки — должны быть детектированы и зафиксированы в случае попытки кражи.
TC-301: PIR датчик движения детектирует присутствие в течение 100 мс
- Цель: Зафиксировать присутствие человека и наличие движения.
- Условие: STM32 загружен, IDLE режим. PIR датчик (HC-SR501) подключен к GPIO PD2.
- Действие: Пройти перед датчиком на расстояние 1-2 метра.
- Ожидаемый результат: GPIO PD2 переходит в HIGH.
STM32 публикует
{motion: true, motionTime: timestamp}в MQTT. - Критерий приёмки: Событие регистрируется в течение
100 мс. На UART логируется
[MOTION] DETECTED. MQTT обновляется.
TC-302: Микрофон фиксирует уровень звука (крик, драка)
- Цель: Обнаружить громкие звуки, указывающие на нарушение спокойствия или кражу.
- Условие: STM32 загружен, IDLE режим. Микрофон подключен к ADC (PF11).
- Действие: 1) Тихо в помещении. 2) Говорить нормально. 3) Кричать или издавать громкий звук.
- Ожидаемый результат: ADC значения: ~200 (тихо), ~600-800 (голос), ~1000+ (крик). STM32 публикует уровень в MQTT.
- Критерий приёмки: Уровни соответствуют
интенсивности звука. На UART логируется
[MICROPHONE] Level: XXX. Значения линейно масштабируются.
TC-303: Датчик двери (reed switch) регистрирует открытие/закрытие
- Цель: Зафиксировать несанкционированное открытие двери при отсутствии сессии.
- Условие: STM32 загружен, IDLE режим. Датчик подключен к GPIO PC0.
- Действие: 1) Вручную открыть дверь (магнит отходит). 2) Закрыть дверь (магнит возвращается).
- Ожидаемый результат: GPIO PC0 переходит в HIGH (открыто), затем LOW (закрыто). STM32 публикует события в MQTT.
- Критерий приёмки: События регистрируются в течение
50 мс. На UART логируется
[DOOR_SENSOR]. MQTT обновляется при каждом переходе.
TC-304: Температурно-влажностный датчик отслеживает условия (обнаруживает скачки)
- Цель: Обнаружить попытку открыть окно или сломать герметичность.
- Условие: STM32 загружен, IDLE режим. SHT31 подключен к I2C1 (0x44).
- Действие: 1) Нормальные условия 2-3 минуты. 2) Резко открыть окно / впустить холодный воздух.
- Ожидаемый результат: STM32 публикует температуру ~22°C и влажность ~45%. При резком охлаждении температура падает, при впуске влажного воздуха влажность растёт.
- Критерий приёмки: Значения обновляются каждые 10
сек. Скачки обнаруживаются и логируются. На UART логируется
[TEMP_HUM].
18.4 Финансовая целостность
Без оплаты дверь не открывается, точный учёт времени сессии, BLE доступ только на оплаченные сессии.
TC-401: Сессия не начинается без явной MQTT команды от бэкенда
- Цель: Гарантировать, что каждая сессия привязана к платежу.
- Условие: STM32 загружен, IDLE режим. MQTT подключен.
- Действие: Человек входит в будку, закрывает дверь. Ничего не отправляется с бэкенда.
- Ожидаемый результат: STM32 остаётся в IDLE. Дверь
не откроется, свет не включится, кондер не включится. На UART логируется
[SESSION] Waiting for backend command. - Критерий приёмки: Без команды
startSessionникаких действий. MQTT статус показываетsessionActive: false.
TC-402: Бэкенд отправляет startSession с duration, STM32
отсчитывает sessionEnd = now + duration
- Цель: Точно отсчитать оплаченное время.
- Условие: STM32 загружен, IDLE режим. RTC синхронизирован.
- Действие: Бэкенд отправляет
{action: startSession, duration: 1800}(30 минут). - Ожидаемый результат: STM32 сохраняет
sessionEnd = now + 1800в Flash. В IDLE режиме обновляетsessionEndпри каждом heartbeat (чтобы синхронизировать с бэкендом, если тот отправляет обновлённое значение). - Критерий приёмки:
sessionEndсохраняется в Flash с CRC. На UART логируется[SESSION] START, duration: 1800s, ends at: {timestamp}. Значение корректно проверяется по NTP.
TC-403: sessionEnd истекает точно → IDLE переход
- Цель: Не переплатить, не занять будку дольше оплаченного.
- Условие: STM32 в ACTIVE режиме, sessionEnd = now + 30 сек.
- Действие: Дождаться истечения 30 сек.
- Ожидаемый результат: Когда локальный RTC показывает
now >= sessionEnd, STM32 автоматически переходит в IDLE. Дверь закрывается, свет выключается, кондер выключается. - Критерий приёмки: Переход происходит в момент
истечения (±0.5 сек). На UART логируется
[SESSION] END, duration expired. MQTT публикуетsessionActive: false.
TC-404: Истекший BLE токен → отклонение (не даёт доступ)
- Цель: Запретить доступ по старому офлайн токену после истечения оплаты.
- Условие: STM32 в MQTT offline mode. BLE включен.
RTC синхронизирован. Есть старый токен с
validUntil < now. - Действие: App отправляет истекший токен.
- Ожидаемый результат: STM32 проверяет
validUntil. Видит, чтоnow > validUntil. Отклоняет токен:{success: false, error: "Token expired"}. Дверь не открывается. - Критерий приёмки: Токен отклоняется в течение 500
мс. На UART логируется
[BLE_TOKEN] Rejected - token expired. Дверь остаётся CLOSE.
TC-405: Override команда ограничена временем (expires automatically)
- Цель: Предотвратить бесконечный ручной доступ оператора.
- Условие: STM32 загружен. Оператор отправляет
override команду:
{action: override, overrides: {doorStatus: OPEN}, durationSec: 300}. - Действие: Override применяется на 300 сек. Ждём 300+ сек.
- Ожидаемый результат: STM32 применяет override на 300 сек. Через 300 сек override автоматически сбрасывается. Desired state возвращается к текущему (IDLE или ACTIVE сессии).
- Критерий приёмки: Override применяется в течение
500 мс. На UART логируется время истечения. После 300 сек на UART
логируется
[OVERRIDE] Expired, clearing.
18.5 Качество клиентского опыта
Климат готов к сессии, BLE офлайн работает, световая индикация информативна, предупреждение о конце сессии.
TC-501: Кондер включается при startSession (новая сессия имеет холодный климат)
- Цель: Клиент входит в прохладную будку, приятный климат.
- Условие: STM32 загружен, IDLE режим, кондер OFF.
- Действие: Бэкенд отправляет
{action: startSession, ...}. - Ожидаемый результат: STM32 активирует реле контактора. Компрессор включается и гудит. Воздух начинает охлаждаться.
- Критерий приёмки: Кондер включается в течение 500
мс. На UART логируется
[COOLING] ON. Компрессор издаёт заметный звук.
TC-502: Внутренний свет ON при startSession (видимость в будке)
- Цель: Клиент сразу видит внутренне содержимое и может начать игру.
- Условие: STM32 загружен, IDLE режим, свет INNER OFF.
- Действие: Бэкенд отправляет
{action: startSession, ...}. - Ожидаемый результат: STM32 активирует реле внутреннего света. Лампа включается на 100% яркости.
- Критерий приёмки: Свет включается в течение 200 мс.
На UART логируется
[LIGHT_INNER] ON. Яркость на уровне 100%.
TC-503: LED лента переходит в режим ACTIVE (яркая, привлекательная анимация)
- Цель: LED индикация показывает статус: “будка готова к игре”.
- Условие: STM32 загружен, IDLE режим, LED мигает (BLINK).
- Действие: Бэкенд отправляет
{action: startSession, ...}. - Ожидаемый результат: LED лента переходит в режим ACTIVE (например, яркая синяя или зелёная пульсация, или яркий цвет по конфигу). Видимо ярче и привлекательнее, чем IDLE.
- Критерий приёмки: LED переходит в течение 100 мс.
На UART логируется
[LED] Animation: ACTIVE. Переход плавный без мерцания.
TC-504: BLE офлайн работает при потере MQTT (пользователь может выйти)
- Цель: Клиент может выйти через смартфон даже если сервер недоступен.
- Условие: STM32 в MQTT offline mode несколько минут. BLE включен. RTC синхронизирован. Есть валидный офлайн токен.
- Действие: App отправляет валидный BLE токен с правильной подписью.
- Ожидаемый результат: STM32 проверяет подпись и
время. Дверь открывается. На UART логируется
[BLE_TOKEN] Valid, processing locally. - Критерий приёмки: Дверь открывается в течение 500 мс. На UART видны логи обработки в offline режиме.
TC-505: Внешний свет BLINK в IDLE режиме (видимость будки, привлечение внимания)
- Цель: Потенциальный клиент видит, что будка доступна.
- Условие: STM32 загружен, IDLE режим.
- Действие: Монитор внешний свет.
- Ожидаемый результат: LED мигает с периодом 2 сек (1
сек ON, 1 сек OFF). На UART логируется
[LIGHT_OUTER] BLINK. - Критерий приёмки: BLINK видимо с частотой ~0.5 Гц. Светодиоды четко переключаются.
TC-506: Вентиляция всегда включена (даже в IDLE)
- Цель: Воздух всегда циркулирует, нет затхлого запаха.
- Условие: STM32 загружен, IDLE режим.
- Действие: Монитор вентилятор.
- Ожидаемый результат: PWM установлен на ~30%.
Вентилятор крутится тихо, но воздух движется. На UART логируется
[VENTILATION] Speed: 30%. - Критерий приёмки: Вентилятор работает всегда, даже в IDLE. Слышимость низкая.
18.6 Надёжность сервиса
IWDG, OTA rollback, MQTT reconnect, power restore без потери данных.
TC-601: Инициализация → MQTT подключение в течение 5 сек
- Цель: Быстро синхронизировать состояние с бэкендом после загрузки.
- Условие: STM32 обесточен. В Flash правильная прошивка. Роутер подключен к интернету.
- Действие: Подать напряжение 5V на контроллер.
- Ожидаемый результат: STM32 загружается, инициализирует GPIO, подключается к MQTT. Публикует первый статус-пакет.
- Критерий приёмки: MQTT подключение устанавливается
в течение 5 сек. На UART логируется
[MQTT] Connected. Статус-пакет содержит все необходимые поля.
TC-602: NTP синхронизация RTC в течение 10 сек
- Цель: Гарантировать точное время для отсчёта sessionEnd и временных меток.
- Условие: STM32 загружен, MQTT подключен.
- Действие: Монитор RTC счётчик до и после инициализации.
- Ожидаемый результат: STM32 запрашивает NTP (или получает время из MQTT). RTC обновляется на корректное значение.
- Критерий приёмки: RTC синхронизируется в течение 10
сек. Разница между RTC и системным временем <1 сек после
синхронизации. На UART логируется
[NTP] Synchronized.
TC-603: MQTT reconnect при потере сети (восстанавливается за 5-10 сек)
- Цель: Быстро переподключиться, чтобы продолжить работу.
- Условие: STM32 работает нормально, MQTT подключен.
- Действие: Отключить интернет на 10 сек, затем восстановить.
- Ожидаемый результат: STM32 обнаруживает потерю за 5-10 сек. Переходит в offline mode. При восстановлении интернета переподключается к MQTT и отправляет накопленные события/статусы.
- Критерий приёмки: Offline обнаружен за 10 сек.
Reconnect происходит в течение 5 сек после восстановления. На UART
логируется
[MQTT] Reconnecting.
TC-604: BOR interrupt сохраняет состояние в Flash
- Цель: Восстановиться после скачка питания без потери sessionEnd.
- Условие: STM32 работает, сессия активна, sessionEnd сохранён в Flash.
- Действие: Опустить напряжение с 5V до 3.5V на 500 мс (скачок).
- Ожидаемый результат: STM32 получает BOR interrupt.
Сохраняет
sessionEndи состояние реле в Flash с CRC. Gracefully завершает работу. - Критерий приёмки: BOR срабатывает при Vdd <2.7V.
На UART логируется
[BROWNOUT] Detected, state saved. После восстановленияsessionEndвосстанавливается корректно.
TC-605: OTA загрузка с CRC проверкой (450 KB за ~5 мин)
- Цель: Гарантировать целостность прошивки при обновлении.
- Условие: STM32 загружен из Bank A. MQTT подключен. Интернет доступен.
- Action: Бэкенд отправляет OTA команду с URL и CRC.
- Ожидаемый результат: STM32 загружает файл по частям (8KB буферы) в Bank B. Вычисляет CRC по ходу. После полной загрузки сравнивает CRC. На UART видна динамика загрузки: 0%, 25%, 50%, 75%, 100%.
- Критерий приёмки: Загрузка происходит со скоростью
>100 KB/s. CRC совпадает. На UART логируется
[OTA] Download complete, CRC: {expected} ✓.
TC-606: OTA rollback при повторяющихся крашах (auto-rollback на Bank A за 30 сек)
- Цель: Автоматически откатиться на работающую версию, если обновление сломалось.
- Условие: STM32 обновился на Bank B, но в коде баг. App крашится при инициализации.
- Action: STM32 перезагружается на Bank B, App крашится, перезагружается снова. Повторить 5+ раз за 30 сек.
- Ожидаемый результат: Bootloader обнаруживает crash loop. После 5-го краша за 30 сек переключается на Bank A. App из Bank A запускается успешно.
- Критерий приёмки: Auto-rollback происходит за ~30
сек. На UART логируется
[BOOT] Bank B crash loop detected, rolling back to Bank A. STM32 восстанавливается.
18.7 Операционное управление
WoL+RDP, OTA update, CRM override, Telegram alerts, техническое обслуживание.
TC-701: DeviceHold (техническое обслуживание) → переход в IDLE независимо от сессии
- Цель: Заблокировать будку для обслуживания, не прерывая активную сессию грубо.
- Условие: STM32 может быть в состоянии ACTIVE или IDLE.
- Действие: CRM отправляет
{action: hold, startAt: now, endAt: now + 3600, reason: "maintenance"}. - Ожидаемый результат: STM32 получает команду. Переводит desired state в IDLE: дверь CLOSE, свет снаружи BLINK (показывает статус обслуживания), все остальное OFF.
- Критерий приёмки: Desired state переходит в течение
1 сек. На UART логируется
[HOLD] Maintenance activated. MQTT публикуетhold: true.
TC-702: Hold имеет приоритет над сессией (сессия паузируется)
- Цель: Оперативно остановить работу при критическом обслуживании.
- Условие: STM32 в ACTIVE (сессия активна). Оператор нажимает hold в CRM.
- Действие: CRM отправляет hold команду.
- Ожидаемый результат: STM32 мгновенно переводит
desired state в IDLE. Дверь закрывается. На UART логируется
[HOLD] Overrides session, session paused. - Критерий приёмки: Переход происходит в течение 500 мс. Дверь закрывается. Сессия логируется как приостановленная.
TC-703: CRM override команда (ручное управление) с лимитом по времени
- Цель: Позволить оператору вручную открыть дверь из CRM для помощи клиенту.
- Условие: STM32 в IDLE режиме.
- Действие: CRM отправляет
{action: override, overrides: {doorStatus: OPEN, lightsInner: ON}, durationSec: 300}. - Ожидаемый результат: STM32 применяет override на
300 сек. Дверь открывается, свет включается. На UART логируется
[OVERRIDE] Applied for 300s. - Критерий приёмки: Override применяется в течение 500 мс. Дверь открывается. На UART видно время истечения.
TC-704: Reconciliation logic → бэкенд замечает observed ≠ desired и отправляет коррекцию
- Цель: Гарантировать, что реальное состояние совпадает с желаемым (обнаруживать и исправлять ошибки).
- Условие: STM32 в IDLE, дверь застряла в полуоткрытом состоянии (observed: OPEN, desired: CLOSE).
- Действие: Монитор MQTT команды от бэкенда.
- Ожидаемый результат: Бэкенд видит несоответствие.
Отправляет корректирующую команду:
{action: setLock, value: CLOSE}. - Критерий приёмки: Несоответствие обнаруживается за <30 сек. Корректирующая команда отправляется за <60 сек. На UART логируется обнаружение и коррекция.
TC-705: Критический алерт (паника, дым, CO₂) → Telegram уведомление оператору
- Цель: Оперативно уведомить оператора о критических событиях.
- Условие: STM32 загружен, MQTT подключен. Telegram бот настроен на бэкенде.
- Действие: Триггер критическое событие (нажать кнопку паники).
- Ожидаемый результат: STM32 публикует статус в MQTT. Бэкенд отправляет POST в Telegram Bot API. Оператор получает сообщение в Telegram.
- Критерий приёмки: MQTT публикуется за 2 сек. Telegram уведомление приходит за <5 сек. Сообщение содержит локацию, время, тип события.
TC-706: Offline алерт → “устройство не отвечает >5 мин”
- Цель: Уведомить оператора о проблеме с подключением.
- Условие: STM32 работает нормально, публикует heartbeat каждые 10 сек.
- Действие: Отключить интернет на 5+ мин.
- Ожидаемый результат: Бэкенд замечает отсутствие heartbeat за 30-60 сек. Ждёт 5 мин. Отправляет алерт в Telegram: “Устройство не отвечает 5 минут, проверьте интернет”.
- Критерий приёмки: Бэкенд обнаруживает offline за 60 сек. Алерт отправляется после 5 мин. На UART STM32 логирует переход в offline mode.
TC-707: LED показывает статус обслуживания (BLINK при hold)
- Цель: Визуально показать, что будка на техническом обслуживании.
- Условие: STM32 получает hold команду.
- Действие: CRM отправляет hold.
- Ожидаемый результат: LED лента переходит в режим ожидания/обслуживания (например, оранжевый BLINK или другой фиксированный цвет). Визуально отличается от IDLE (синий BLINK).
- Критерий приёмки: LED переходит в течение 100 мс.
На UART логируется
[LED] Maintenance mode. Цвет отличается от обычного IDLE.
18.8 Итоги и требования к стенду
Данный протокол охватывает критические сценарии тестирования STM32F429 контроллера, сгруппированные по семи бизнес-целям. Все тест-кейсы проверяют как штатное функционирование, так и отказоустойчивость при различных сбоях (потеря питания, MQTT offline, ошибки датчиков, критические ситуации).
Ключевые метрики успеха: - Все тесты TC-101 – TC-707 должны пройти без критических ошибок. - Все измеренные временные интервалы должны соответствовать спецификации (±10%). - Все критические события должны логироваться и отправляться в мониторинг (MQTT/Dash0). - Система должна восстанавливаться из любого failure state без потери данных сессии.
Проведение тестов: - Использовать стенд с полным набором датчиков и реле. - Мониторить MQTT, UART логи и Telegram уведомления одновременно. - Для теста OTA использовать контролируемый firmware бинарный файл. - Для критических тестов power loss использовать БП с управляемым отключением.
Версионирование документа: - V2.0 – Переструктурировано по 7 бизнес-целям (8 апреля 2026).