Skip to content

WebSocket Local Access (WS)

Версия: 1.0 Дата: 2026-03-02 UART коды: 0x730x76

Назначение

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)     │
                                         └───────────────────┘

Типы сообщений

Код Название Направление 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}

Запрос статуса:

MCU → WsStatusRequest
LINK → WsStatus {state=..., pin=..., paired=N, 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}}

Весы:

{"type":"weights","data":{"weights":[{"sensorId":"W1","value":850.5,"unitId":"U1"}]}}

RFID:

{"type":"rfid","data":{"unitId":"U1","event":"tag_detected","tag":"04A1B2C3D4E5F6","readerId":0}}

Конфиг:

{"type":"config","data":{...полный или delta JSON...}}

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 команде

См. также