WebSocket Local Access (WS)¶
Версия: 1.0
Дата: 2026-03-02
UART коды: 0x73–0x76
Назначение¶
WS — аварийный/дополнительный канал для работы без облака. Активируется из меню RP2040 через UART. Полный аналог MQTT API: те же JSON форматы, те же команды.
Архитектура¶
RP2040 (MCU) ESP32 (LINK)
┌─────────────────┐ ┌─────────────────────────────────┐
│ Меню → WsEnable │── UART 0x73 ──→│ WsServer │
│ │ │ ├─ mDNS (ESPmDNS.h) │
│ Экран ← WsStatus│←─ UART 0x74 ───│ ├─ WebSocketsServer :81 │
│ │ │ ├─ PIN auth (6 цифр, NVS) │
│ Меню → WsReset │── UART 0x75 ──→│ └─ Client binding (max 5, NVS) │
│ Меню → WsStatus │── UART 0x76 ──→│ │
│ Request │ │ CloudStateMachine + MQTT │
└─────────────────┘ └─────────────────────────────────┘
│ │
WS :81 MQTT :8883
│ │
┌────┴─────────┴────┐
│ Клиент (App) │
└───────────────────┘
UART протокол (MCU ↔ LINK)¶
Типы сообщений¶
| Код | Название | Направление | Payload | Описание |
|---|---|---|---|---|
0x73 |
WsEnable | MCU→LINK | WsEnablePayload |
Включить/выключить WS сервер |
0x74 |
WsStatus | LINK→MCU | WsStatusPayload |
Статус WS (для экрана) |
0x75 |
WsResetClients | MCU→LINK | — (пустой) | Сбросить все привязанные клиенты |
0x76 |
WsStatusRequest | MCU→LINK | — (пустой) | Запросить текущий статус WS |
Структуры payload¶
enum class WsState : uint8_t {
Disabled = 0, // WS сервер выключен
Listening = 1, // WS сервер запущен, ждёт подключения
Connected = 2, // Клиент подключён и авторизован
};
struct WsEnablePayload {
uint8_t enable; // 1 = включить, 0 = выключить
uint8_t reserved; // Выравнивание
uint16_t pin; // PIN 0-9999 (4 цифры, генерируется на MCU, хранится в EEPROM)
};
struct WsStatusPayload {
WsState state; // Текущее состояние
uint16_t pin; // PIN 0-9999 (4 цифры)
uint8_t pairedCount; // Кол-во привязанных клиентов (0-5)
uint8_t maxClients; // Максимум (5)
uint8_t reserved; // Выравнивание
};
Сценарии¶
Включение WS (из меню RP2040):
MCU → WsEnable {enable=1, pin=4829}
LINK: запускает WsServer + mDNS, использует PIN из payload
LINK → WsStatus {state=Listening, pin=4829, paired=0, max=5}
MCU: PIN отображается в меню как read-only поле
Подключение клиента:
App → WS connect ws://DEVICE_4AF988_3847291.local:81
App → {"type":"auth","pin":"4829","clientId":"app-uuid-xxx"}
LINK → {"type":"auth_ok","deviceName":"DEVICE_4AF988_3847291"}
LINK → WsStatus {state=Connected, pin=4829, paired=1, max=5}
Сброс привязок:
MCU → WsResetClients
LINK: очищает NVS, отключает клиента
LINK → WsStatus {state=Listening, pin=4829, paired=0, max=5}
Запрос статуса:
Выключение WS:
MCU → WsEnable {enable=0, pin=0}
LINK: останавливает WsServer + mDNS
LINK → WsStatus {state=Disabled, pin=0, paired=0, max=5}
WebSocket API (ESP32 ↔ App)¶
Общее¶
- Порт: 81
- Протокол: WebSocket (RFC 6455), текстовые фреймы
- Формат: JSON
- mDNS:
DEVICE_{MAC}_{RANDOM}.local(совпадает с serial number) - mDNS сервис:
_idryer._tcpпорт 81 - Максимум 1 одновременное подключение
Авторизация¶
PIN: 4 цифры (1000-9999), генерируется на MCU (RP2040) при первом включении WS. Хранится в EEPROM RP2040 (поле ws_pin в меню). Передаётся на ESP32 в WsEnablePayload.pin. Отображается в меню RP2040 как read-only поле. ESP32 не хранит и не генерирует PIN.
Первое подключение (с PIN):
→ {"type":"auth","pin":"4829","clientId":"app-uuid-xxx"}
← {"type":"auth_ok","deviceName":"DEVICE_4AF988_3847291"}
Повторное подключение (без PIN):
→ {"type":"auth","pin":"","clientId":"app-uuid-xxx"}
← {"type":"auth_ok","deviceName":"DEVICE_4AF988_3847291"}
Ошибки авторизации:
← {"type":"auth_fail","reason":"invalid_pin"}
← {"type":"auth_fail","reason":"max_clients"}
← {"type":"auth_fail","reason":"not_authorized"}
Правила:
- clientId — UUID, генерируется приложением, сохраняется на стороне клиента
- Максимум 5 привязанных clientId в NVS
- Привязанный клиент автоматически авторизуется без PIN
- Сброс привязок — через меню RP2040 (UART 0x75)
Исходящие сообщения (ESP → App)¶
JSON формат идентичен MQTT. Обёрнуты в {"type":"...", "data":{...}}.
Телеметрия:
{"type":"telemetry","data":{"units":[{"unitId":"U1","temperature":25.5,"humidity":45.0,"heaterPower":80,"fanStatus":true}]}}
Статус:
{"type":"status","data":{"units":[{"unitId":"U1","mode":"DRYING","sessionNum":15,"target":{"temperature":60.0,"duration":240},"totalElapsed":3600,"totalRemaining":10800}],"uptime":86400}}
Весы:
RFID:
Конфиг:
Info:
{"type":"info","data":{"hwVersion":"RP2040-v1","fwVersion":"3.2.1","workTime":360000,"unitsCount":2,"units":[{"unitId":"U1","capabilities":63},{"unitId":"U2","capabilities":63}]}}
Входящие команды (App → ESP)¶
{"type":"command","command":"drying","data":{"unitId":"U1","temperature":60,"duration":240}}
{"type":"command","command":"stop","data":{"unitId":"U1"}}
{"type":"command","command":"set","data":{"id":3,"unit":0,"val":55.0}}
{"type":"command","command":"get_config","data":{}}
Команды обрабатываются тем же CommandHandler что и MQTT команды.
Хранение данных¶
MCU (RP2040) — EEPROM:
| Поле | Тип | Описание |
|------|-----|----------|
| ws_pin | uint16 | PIN 4 цифры (1000-9999), генерируется при первом включении WS |
LINK (ESP32) — NVS:
| Namespace | Key | Тип | Описание |
|-----------|-----|-----|----------|
| ws | cnt | UChar | Количество привязанных клиентов |
| ws | c0..c4 | String | clientId привязанных клиентов |
Безопасность¶
- PIN генерируется на MCU (RP2040) с аппаратным random, хранится в EEPROM
- PIN передаётся на ESP32 через UART при каждом включении WS
- ESP32 не хранит PIN — получает при каждом WsEnable
- Клиент должен знать PIN для первичной привязки
- После привязки — авторизация по
clientId(без PIN) - Сброс привязок возможен только физически (через меню RP2040)
- WS по умолчанию выключен, активируется только по UART команде
См. также¶
- UART протокол — общее описание UART
- MQTT API — MQTT топики (JSON формат идентичен)