Архитектурная документация — закрытый доступ
IZI Архитектура контроллера

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-карта для хранения конфигурации

Критические проблемы текущего решения

  1. SD-карта умирает: Частые циклы записи флэша приводят к отказам в течение 6-12 месяцев (особенно при потере питания)
  2. Нет аппаратного watchdog: Если процесс зависнет, будка станет недоступна без физического присутствия
  3. Загрузка 30-60 секунд: Медленный запуск Linux + Python, потеря времени и доступности
  4. WebSocket — устаревший протокол: Бэкенд уже на MQTT/EMQX, но контроллер по-прежнему использует WebSocket
  5. Нет офлайн доступа: Потеря интернета = потеря доступа к Pod (клиент не может открыться)
  6. Сложная отладка: 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

  1. WiFi + Bluetooth конфликт на ESP32: Одна антенна, разделённая между двумя функциями, приводит к падениям соединения при одновременном использовании
  2. Ethernet на ESP32 ненадёжен: Требует SPI модуля (LAN8720A), нестабилен в условиях помех
  3. Промышленный стандарт:
    • Whoosh (аналог IZI) использует STM32F413 / NXP RT1021
    • Bird (аренда консолей) использует STM32 + процессор отдельно
  4. Фиксированное расположение (ТЦ): Pod всегда подключён к LAN проводом. WiFi вспомогательно (для резервной связи через роутер). Проводное Ethernet — оптимальный выбор.
  5. Мощность: 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 (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 → ПК → удалённый инженер видит вывод в реальном времени

Уровни отладки (от простого к сложному)

  1. MQTT логи/события → Dash0 (основной мониторинг, 90% проблем)
    • Полный мониторинг в облаке, статистика, корреляции
    • Видно все события, состояние устройства, ошибки
  2. Crash dump в Flash → отправляется при следующем подключении
    • При падении FreeRTOS, STM32 записывает стек вызовов, регистры, последние 1KB UART лога
    • При восстановлении сетевого подключения → отправляет на бэкенд
    • Инженер видит точное место краша
  3. UART логи через MeshCentral → PC terminal
    • RDP в ПК → подключение USB-UART → реал-тайм вывод STM32
    • Полезно для отладки инициализации, сетевых проблем
  4. 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

Архитектура

Токены генерируются и подписываются бэкендом при бронировании:

  1. Бронирование подтверждено → бэкенд генерирует токен (userId + deviceId + validFrom + validUntil)
  2. Бэкенд подписывает токен ED25519 приватным ключом
  3. Токен отправляется в App пользователя
  4. Токен также отправляется на STM32 через MQTT команду storeToken (пока интернет доступен)

При потере интернета:

  1. STM32 переходит в режим offline mode (определяется по потере MQTT连 несколько секунд)
  2. STM32 запускает BLE advertisement (BlueNRG-M2SP микросхема по SPI)
  3. Пользователь открывает IZI App на смартфоне
  4. App сканирует BLE, находит наше устройство (Advertised as “izi-{deviceId}”)
  5. App подключается, отправляет токен по BLE GATT
  6. STM32 проверяет:
    • Валидность подписи (ED25519, embedded public key в микросхеме)
    • deviceId в токене == мой deviceId → нельзя использовать чужой токен
    • validFrom <= now <= validUntil → время в RTC
  7. Событие логируется в Flash: offlineAccess { userId, time, signature }
  8. При восстановлении интернета → отправляет лог на бэкенд

Структура токена

[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
  • Старые токены вытесняются новыми при переполнении

Безопасность

  1. Подпись не может быть подделана — требуется ED25519 приватный ключ (есть только у бэкенда)
  2. Токен привязан к deviceId — если украсть токен и использовать на другом устройстве, STM32 отклонит (deviceId не совпадает)
  3. Время жизни токена ограничено — одна сессия бронирования. После окончания — токен неактивен.
  4. BLE не требует аутентификации — любой может подключиться, но без валидного токена отключается через 3 сек (timeout)
  5. 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 мс (скачок напряжения, проблемы с контактом, срабатывание защиты).

Решение:

  1. Brown-Out Reset (BOR) прерывание:
    • STM32 имеет встроенный BOR компаратор (мониторит Vdd)
    • При падении напряжения ниже порога (~2.7V) → прерывание BOR
    • В обработчике за 1-2 мс: сохраняем текущее состояние реле в Flash
    • Записываем последний валидный RTC snapshot
  2. Супер-конденсатор:
    • 10F @ 5V конденсатор на входе питания
    • Держит питание цепей управления (STM32, BLE, LAN8720A) ещё 50-100 мс после отключения основного питания
    • Позволяет graceful shutdown вместо холодной перезагрузки
  3. После восстановления питания:
    • 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:

  1. Кнопка паники нажата
  2. Датчик дыма сработал
  3. CO₂ > 5000 ppm
  4. Потеря питания (fail-safe замок)
  5. 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

  1. Бэкенд отправляет MQTT команду:

    {
      "action": "startOTA",
      "url": "https://firmware.izi.is/bootka/v2.3.1/firmware.bin",
      "crc32": "a1b2c3d4",
      "size": 450000
    }
  2. STM32 загружает файл:

    • HTTPS клиент (mbedTLS)
    • Скачивает в Bank B по частям (8KB буферы)
    • Вычисляет CRC32 по ходу
    • Если сеть упала → может возобновить (запомнить смещение)
  3. Верификация:

    • Сравнивает CRC32 загруженного файла с ожидаемым
    • Если не совпадает → отклоняет, просит перезагрузку
    • Если совпадает → продолжаем
  4. Переключение банков:

    • NVRAM флаг: bootFromBank = B
    • Reboot (через мягкую перезагрузку или NVIC_SystemReset)
  5. Bootloader:

    • Проверяет флаг, загружает App из Bank B
    • Если загрузка успешна → флаг переводит в bootFromBank = A (для следующего раза)
    • Если краш → bootloader видит краш, переключается обратно на Bank A и устанавливает bootFromBank = A
  6. Откат при ошибке:

    • Если новая прошивка постоянно крашится (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’ов остаются неизменны. Разработка бэкенда может продолжаться параллельно.

Что добавляется (минимально)

  1. Генерация и подпись 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')
    });
  2. MQTT команда wakePC (для удалённой отладки):

    {
      "action": "wakePC"
    }

    STM32 отправляет WoL magic packet в локальную сеть (к ПК).

  3. Получение и логирование offlineAccessGranted событий:

    {
      "type": "offlineAccessGranted",
      "userId": "user-uuid",
      "time": 1712577600,
      "method": "BLE_TOKEN"
    }

    Используется для аналитики, проверки работоспособности офлайн доступа.

  4. Генерация и распределение 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 без ручного вмешательства:

  1. Завод прошивает стандартную прошивку — одинаковую для всех устройств
  2. При первом включении устройство подключается к MQTT с временным ID = STM32 UID (уникален аппаратно)
  3. Оператор в CRM сканирует QR-код на корпусе (= STM32 UID) → привязывает к клубу/будке одной кнопкой
  4. Backend шлёт deviceId + clubId → STM32 сохраняет в Flash → перезагружается в рабочем режиме

Никаких кабелей, скриптов, ручных настроек. 2 минуты на активацию.

Замена в поле ✓

  1. Оператор вставляет новый контроллер (без конфига)
  2. Сканирует QR нового устройства в CRM → привязывает к этой будке
  3. Backend автоматически высылает конфиг + все активные BLE-токены
  4. Старый 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).