# MQTT Protocol ## Общее - **Брокер**: Mosquitto 2.x (порт 1883) - **Topic prefix**: `{UID}` — UUID токен устройства (из БД `GrowBox.token`) - **QoS**: 1 для команд, 0 для данных датчиков - **Subscribe**: сервер подписан на `#` (все топики) --- ## Flow: подключение устройства ```mermaid sequenceDiagram participant ESP32 as WegaBox (ESP32) participant MQTT as MQTT Broker participant Django as ponics.online (Django) participant Redis ESP32->>MQTT: publish {UID}/status = "connected" MQTT->>Django: forward Django->>Django: push_box_settings(box) Django->>MQTT: publish {UID}/set/preferences/all = {JSON} MQTT->>ESP32: apply calibration & config loop Каждые N секунд ESP32->>MQTT: publish {UID}/data-timescale/wEC = "1.85" ESP32->>MQTT: publish {UID}/data-timescale/wpH = "6.2" ESP32->>MQTT: publish {UID}/data-timescale/wNTC = "22.5" MQTT->>Django: forward Django->>Redis: update live cache (boxconfig-{token}) Django->>Django: batch → TimescaleDB (каждые 25с или 100 метрик) end ``` --- ## Flow: команда с веб-интерфейса ```mermaid sequenceDiagram participant User as Браузер participant Django as ponics.online (Django) participant MQTT as MQTT Broker participant ESP32 as WegaBox (ESP32) User->>Django: POST /api/box/{id}/pump/run/ {pump_id:1, time_ms:5000} Django->>MQTT: publish {UID}/set/pump/1/run = "5000" MQTT->>ESP32: execute pump 1 for 5000ms Django->>User: 200 {"success": true} Note over ESP32: Команды fire-and-forget, ответа нет ``` --- ## Топики: устройство → сервер (Subscribe) ### `{UID}/data-timescale/{METRIC_NAME}` Основной поток данных датчиков. Каждый датчик — отдельное сообщение. **Payload**: строка с числом (например `"6.24"`, `"1.85"`, `"22.5"`) ``` {UID}/data-timescale/wEC → EC (мСм/см, с температурной компенсацией) {UID}/data-timescale/wECnt → EC без температурной компенсации {UID}/data-timescale/wpH → pH {UID}/data-timescale/wNTC → температура воды (°C, откалиброванная) {UID}/data-timescale/RootTemp → температура субстрата/корней (°C, DS18B20) {UID}/data-timescale/AirTemp → температура воздуха (°C) {UID}/data-timescale/AirHum → влажность воздуха (%) {UID}/data-timescale/AirPress → давление (гПа) {UID}/data-timescale/wLevel → уровень воды (% или см, HC-SR04) {UID}/data-timescale/Light → освещённость (%) {UID}/data-timescale/RSSI → уровень WiFi сигнала (dBm) {UID}/data-timescale/uptime → аптайм устройства (мс) {UID}/data-timescale/CPUTemp → температура CPU ESP32 (°C) {UID}/data-timescale/FreeHeap → свободная RAM (байт) {UID}/data-timescale/Vcc → напряжение питания (мВ) {UID}/data-timescale/MixerWeight → вес миксера (г, HX711) {UID}/data-timescale/mcp/gpio → битовая маска GPIO MCP23017 (0-65535) {UID}/data-timescale/CO2 → CO2 (ppm, SCD30) {UID}/data-timescale/eCO2 → CO2 эквивалент (ppm, CCS811) {UID}/data-timescale/TVOC → летучие вещества (ppb, CCS811) ``` > **Маппинг имён на сервере**: сервер дополнительно принимает синонимы `EC`, `ph`, `ec`, `calc_dist` и маппит их в канонические имена через `field_mappings` в `config.py`. ### `{UID}/status` Payload: строка `"connected"` — устройство подключилось к брокеру. Триггер: сервер отправляет настройки (`set/preferences/all`). ### `{UID}/status/sensors` Ответ на `{UID}/cmd/status/sensors`. Поля зависят от подключённых датчиков. ```json { "rootTemp": 21.0, "waterTemp": 22.5, "airTemp": 24.1, "airHumidity": 65, "pressure": 1013, "ntcTemp": 22.4, "EC": 1.85, "pH": 6.24, "light": 80.0, "waterLevel": 45.2, "weight": 0.0 } ``` ### `{UID}/status/mixer` Ответ на `{UID}/cmd/status/mixer`. ```json { "running": false, "phase": "idle", "currentPump": 0, "weight": 480.5, "pumps": [ {"index": 1, "total_dispensed": 480.5}, {"index": 2, "total_dispensed": 320.0} ] } ``` > **Примечание**: поле `name` в элементах `pumps` отсутствует в ответе прошивки. Названия помп берутся из БД сервера. ### `{UID}/status/full` Полный статус устройства (всё включая actuators + config). Ответ на `cmd/status`. ### `{UID}/status/actuators` Состояние реле, GPIO, помп. ### `{UID}/main` (legacy) Устаревший формат. Новые устройства не используют. ```json { "sensors": {"ph": "6.5", "ec": "1.8", "temp_cal": "22.5"}, "system": {"uptime": "3600000", "RSSI": "-65"} } ``` --- ## Топики: сервер → устройство (Publish) ### Команды (`cmd/`) | Топик | Payload | Описание | |-------|---------|----------| | `{UID}/cmd/reboot` | `"1"` | Перезагрузка устройства | | `{UID}/cmd/gpio/{pin}` | `"1"` / `"0"` | GPIO HIGH/LOW (MCP23017, pin 0–15) | | `{UID}/cmd/pump/{id}/stop` | `"1"` | Стоп помпы (id 1–7, см. ниже) | | `{UID}/cmd/status` | `"1"` | Запрос полного статуса | | `{UID}/cmd/status/sensors` | `"1"` | Запрос данных датчиков | | `{UID}/cmd/status/actuators` | `"1"` | Запрос статуса актуаторов | | `{UID}/cmd/status/mixer` | `"1"` | Запрос статуса миксера | | `{UID}/cmd/ota` | `""` или URL строка | OTA обновление прошивки | | `{UID}/cmd/ota-storage` | `""` или URL строка | OTA обновление LittleFS | ### Управление помпами (`set/pump/`) | Топик | Payload | Ограничения | |-------|---------|------------| | `{UID}/set/pump/{id}/run` | `"5000"` (мс) | id: 1–7 ⚠️, время: 1–60000 мс | | `{UID}/set/pump/{id}/dispense` | `"50.5"` (граммы) | id: 1–7 ⚠️, граммы: 0.1–1000 | > ⚠️ **Pump ID расхождение**: сервер (API) принимает id 1–8, прошивка валидирует `pumpId < 0 || pumpId > 7` — id=8 будет отклонён прошивкой. Нужно либо выровнять API на 1–7, либо исправить прошивку на 0–8. Текущее рабочее значение: **1–7**. ### Настройки (`set/preferences/all`) Полная конфигурация устройства. Все поля опциональны. ```json { "ntcDAC": 4095, "ntcB": 3950, "ntcValKorr": 0.0, "ntcType": "NTC3950", "R1": 10000, "Rx1": 1000, "Rx2": 1000, "Dr": 1.0, "ec1": 1.41, "ec2": 12.88, "ec3": 80.0, "ex1": 0.5, "ex2": 0.9, "ex3": 0.95, "ea": 0.0, "eb": 0.0, "kt": 0.019, "ecKorr": 1.0, "px1": 100, "py1": 0.0, "px2": 580, "py2": 7.0, "px3": 1023, "py3": 14.0, "pHlKorr": 0.0, "pR_DAC": 4095, "pR1": 10000, "pR_raw_p1": 0, "pR_val_p1": 0, "pR_raw_p2": 512, "pR_val_p2": 50, "pR_raw_p3": 1023,"pR_val_p3": 100, "pR_Rx": 10000, "pR_T": 25, "pR_x": 0.5, "pRType": "linear", "maxLLevel": 30.0, "maxLRaw": 100, "minLLevel": 10.0, "minLRaw": 500, "hostname": "wega-box-1", "updateUrl": "https://ponics.online/static/wegabox/esp32-local/firmware.bin", "updateFsUrl": "https://ponics.online/static/wegabox/esp32-local/littlefs.bin", "ds18b20_enabled": true, "bme280_enabled": true, "aht10_enabled": false, "am2320_enabled": false, "scd30_enabled": false, "ccs811_enabled": false, "ads1115_enabled": false, "ec_enabled": true, "ntc_enabled": true, "pr_enabled": true, "hx711_enabled": false, "hx710b_enabled": false, "us025_enabled": false, "us025_reboot_on_error": false, "vl53l0x_enabled": false, "vcc_enabled": true, "cputemp_enabled": true, "mixer_enabled": false, "mixer_pumpCount": 8, "mixer_bidirectional": false, "mixer_dropThreshold": 0.5, "mixer_preloadMs": 0, "hx711_calibrationA": 1000.0, "hx711_calibrationB": 500.0, "hx711_offsetA": 0.0, "hx711_offsetB": 0.0, "hx711_updateMs": 500, "hx711_gpioDout": 4, "hx711_gpioSck": 5, "hx711_spsRate": 10, "ecDoser_enabled": false, "ecDoser_intervalSec": 3600, "ecDoser_limitEC": 3.0, "ecDoser_valueA": 10.0, "ecDoser_valueB": 10.0, "dallas_ecSensorIndex": 0 } ``` --- ## Обработка сообщений на сервере ```mermaid flowchart TD MSG[MQTT сообщение] --> CHECK{Суффикс топика} CHECK -->|"main"| REDIS[Redis hash-store] CHECK -->|"status/mixer"| MIXER[Обновить статус миксера] CHECK -->|"status/sensors"| SENS[Обновить кеш датчиков] CHECK -->|"status" + "connected"| SYNC[push_box_settings → отправить преференции] CHECK -->|"data-timescale/{METRIC}"| PROC[MessageProcessor] PROC --> PARSE[Парсинг значения] PARSE --> MAP[Маппинг имени поля
wEC→ec, wpH→ph, wLevel→calc_dist...] MAP --> BATCH[Batcher
100 метрик / 25 сек] BATCH --> TSDB[(TimescaleDB)] MAP --> CACHE[Redis live cache
boxconfig-{token}] CACHE --> COND{Проверка условий} COND -->|Сработало| TG[Telegram уведомление] ```