diff --git a/DEVICE_BEHAVIOR.md b/DEVICE_BEHAVIOR.md
index 48f8830..e939c76 100644
--- a/DEVICE_BEHAVIOR.md
+++ b/DEVICE_BEHAVIOR.md
@@ -5,35 +5,39 @@
```mermaid
stateDiagram-v2
[*] --> Booting: Включение питания
- Booting --> WiFiConnect: Инициализация
+ Booting --> WiFiConnect: Инициализация периферии
WiFiConnect --> MQTTConnect: WiFi OK
- WiFiConnect --> WiFiConnect: Retry (30s)
+ WiFiConnect --> WiFiConnect: Retry (exponential backoff)
MQTTConnect --> Connected: MQTT OK
- MQTTConnect --> MQTTConnect: Retry (10s)
+ MQTTConnect --> MQTTConnect: Retry (exponential backoff)
Connected --> Publishing: Таймер сбора данных
Publishing --> Connected: Данные отправлены
- Connected --> ExecutingCmd: Получена команда
+ Connected --> ExecutingCmd: Получена команда MQTT
ExecutingCmd --> Connected: Команда выполнена
- Connected --> Disconnected: Потеря связи
- Disconnected --> MQTTConnect: Reconnect
+ Connected --> OTA: Получена cmd/ota
+ OTA --> [*]: Перезагрузка после обновления
- Connected --> [*]: Команда reboot / WDT reset
+ Connected --> Disconnected: Потеря связи
+ Disconnected --> MQTTConnect: Reconnect (exponential backoff)
+
+ Connected --> [*]: cmd/reboot или WDT reset
```
---
## Последовательность запуска
-1. **Boot** — инициализация I2C, SPI, OneWire периферии
-2. **WiFi** — подключение, получение IP
+1. **Boot** — инициализация I2C, SPI, OneWire, MCP23017
+2. **WiFi** — подключение, получение IP (exponential backoff при неудаче)
3. **MQTT connect** — handshake с брокером
-4. **Publish `{UID}/status = "connected"`** — уведомление сервера
-5. **Получить preferences** — сервер отвечает `{UID}/set/preferences/all`
-6. **Применить калибровку** — записать коэффициенты в память
-7. **Запуск основного цикла** — периодический сбор и публикация данных
+4. **LWT настройка** — `{UID}/availability = "offline"` при разрыве
+5. **Publish `{UID}/status = "connected"`** — уведомление сервера
+6. **Получить preferences** — сервер отвечает `{UID}/set/preferences/all`
+7. **Применить калибровку** — записать коэффициенты в NVS/RAM без перезагрузки
+8. **Запуск основного цикла** — периодический сбор и публикация данных
---
@@ -41,11 +45,13 @@ stateDiagram-v2
| Датчик | Интервал |
|--------|----------|
-| pH, EC, Temp | ~10 сек |
-| AirTemp, AirHum | ~30 сек |
-| RSSI, uptime | ~60 сек |
+| pH, EC, wNTC | ~10 сек |
+| AirTemp, AirHum, AirPress | ~30 сек |
+| RootTemp (DS18B20) | ~15 сек |
+| RSSI, CPUTemp, FreeHeap, Vcc | ~60 сек |
+| uptime | ~60 сек |
| MixerWeight | ~5 сек (если mixer_enabled) |
-| readGPIO | при изменении |
+| mcp/gpio | при изменении состояния |
---
@@ -58,44 +64,64 @@ stateDiagram-v2
2. `ESP.restart()`
### `cmd/gpio/{pin}`
-1. Проверить pin ∈ [0, 15]
-2. MCP23017 → setPin(pin, state)
+- Проверить pin ∈ [0, 15], payload ∈ {"0", "1"}
+- MCP23017 → setPin(pin, state)
### `set/pump/{id}/run`
-1. Проверить id ∈ [1, 8], time ∈ [1, 60000]
-2. Включить помпу
-3. Запустить таймер
-4. По истечении времени — выключить
+- Проверить id ∈ [0, 7] ⚠️ (см. расхождение ниже), time ∈ [1, 60000]
+- Включить помпу → таймер → выключить
### `set/pump/{id}/dispense`
-1. Проверить id ∈ [1, 8], grams ∈ [0.1, 1000]
-2. Рассчитать время через `ml/sec` калибровку помпы
-3. Включить → таймер → выключить
-4. Обновить счётчик `total_dispensed`
+- Проверить id ∈ [0, 7] ⚠️, grams ∈ [0.1, 1000]
+- Рассчитать время через ml/sec калибровку помпы
+- Включить → таймер → выключить
+- Обновить счётчик `total_dispensed`
### `cmd/pump/{id}/stop`
-1. Немедленно выключить помпу
-2. Сбросить таймер
+- Проверить id ∈ [0, 7] ⚠️
+- Немедленно выключить помпу, сбросить таймер
### `set/preferences/all`
-1. Разобрать JSON
-2. Обновить только пришедшие поля в NVS / RAM
-3. Применить новые коэффициенты калибровки без перезагрузки
+- Разобрать JSON, обновить только пришедшие поля в NVS / RAM
+- Применить новые коэффициенты без перезагрузки
+
+### `cmd/ota`
+- Payload: пустой (использует `preferences.updateUrl`) или URL строка (override)
+- Скачать и прошить firmware.bin
+- Перезагрузиться
+
+### `cmd/ota-storage`
+- Аналогично `cmd/ota` но для LittleFS (`preferences.updateFsUrl`)
+
+---
+
+## ⚠️ Pump ID расхождение
+
+| Сторона | Диапазон | Код |
+|---------|----------|-----|
+| Сервер (API serializer) | 1–8 | `min_value=1, max_value=8` |
+| Прошивка (валидация) | 0–7 | `pumpId < 0 \|\| pumpId > 7` |
+| Прошивка (итерация mixer) | 1–pumpCount | `for pumpId = 1...pumpCount` |
+
+**Следствие**: сервер отправляет `set/pump/8/run` → прошивка валидирует `8 > 7` → команда игнорируется.
+**Рекомендация**: привести к единому стандарту 1-based (1–8). Исправить валидацию в `mqtt_callbacks.cpp` строки 457, 482, 507 на `pumpId < 1 || pumpId > 8`.
---
## Reconnect логика
-- WiFi: авто-реконнект встроенный в ESP-IDF
-- MQTT: переподключение каждые 10 секунд
-- При восстановлении MQTT: снова публикует `{UID}/status = "connected"`
-- Сервер снова отправляет preferences (идемпотентно)
+- WiFi: встроенный авто-реконнект ESP-IDF
+- MQTT: экспоненциальный backoff (1s → 2s → 4s → ... → max 60s)
+- При восстановлении: публикует `{UID}/status = "connected"` → сервер снова шлёт preferences
---
-## OTA обновления
+## Home Assistant интеграция (отдельный канал)
-URL прошивки: `preferences.updateUrl`
-По умолчанию: `https://ponics.online/static/wegabox/esp32-local/firmware.bin`
-
-Триггер обновления: команда (TBD) или автоматически при запуске если новая версия.
+Прошивка поддерживает параллельную интеграцию с Home Assistant через MQTT.
+Топики (не используются сервером ponics.online):
+- `{UID}/availability` — online/offline
+- `{UID}/pin/{i}/cmd` — управление GPIO
+- `{UID}/pin/{i}/state` — состояние GPIO
+- `{UID}/mixer/cmd` — управление миксером
+- Autodiscovery топики для HA
diff --git a/MQTT.md b/MQTT.md
index 68f3ee5..f4194ec 100644
--- a/MQTT.md
+++ b/MQTT.md
@@ -25,8 +25,8 @@ sequenceDiagram
MQTT->>ESP32: apply calibration & config
loop Каждые N секунд
- ESP32->>MQTT: publish {UID}/data-timescale/EC = "1.85"
- ESP32->>MQTT: publish {UID}/data-timescale/ph = "6.2"
+ 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})
@@ -63,21 +63,30 @@ sequenceDiagram
**Payload**: строка с числом (например `"6.24"`, `"1.85"`, `"22.5"`)
```
-{UID}/data-timescale/EC → значение EC (мСм/см)
-{UID}/data-timescale/ph → значение pH
+{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/RootTemp → температура субстрата/корней (°C, DS18B20)
-{UID}/data-timescale/calc_dist → уровень воды (см/%)
-{UID}/data-timescale/calc_pr → освещённость (%)
+{UID}/data-timescale/wLevel → уровень воды (% или см, HC-SR04)
+{UID}/data-timescale/Light → освещённость (%)
{UID}/data-timescale/RSSI → уровень WiFi сигнала (dBm)
{UID}/data-timescale/uptime → аптайм устройства (мс)
-{UID}/data-timescale/MixerWeight → вес миксера (г)
-{UID}/data-timescale/readGPIO → битовая маска GPIO MCP23017 (0-65535)
+{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"` — устройство подключилось к брокеру.
@@ -85,50 +94,59 @@ Payload: строка `"connected"` — устройство подключил
### `{UID}/status/sensors`
-Ответ на запрос `{UID}/cmd/status/sensors`.
+Ответ на `{UID}/cmd/status/sensors`. Поля зависят от подключённых датчиков.
```json
{
- "ph": 6.24,
- "ec": 1.85,
- "temp_cal": 22.5,
- "calc_dist": 45.2,
+ "rootTemp": 21.0,
+ "waterTemp": 22.5,
"airTemp": 24.1,
- "airHumidity": 65.0,
- "RSSI": -65,
- "uptime": 3600000
+ "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`.
+Ответ на `{UID}/cmd/status/mixer`.
```json
{
+ "running": false,
+ "phase": "idle",
+ "currentPump": 0,
+ "weight": 480.5,
"pumps": [
- {"index": 0, "name": "CaNO3", "total_dispensed": 480.5},
- {"index": 1, "name": "KNO3", "total_dispensed": 320.0},
- {"index": 2, "name": "MgSO4", "total_dispensed": 150.0}
+ {"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"
- }
+ "sensors": {"ph": "6.5", "ec": "1.8", "temp_cal": "22.5"},
+ "system": {"uptime": "3600000", "RSSI": "-65"}
}
```
@@ -142,22 +160,26 @@ Payload: строка `"connected"` — устройство подключил
|-------|---------|----------|
| `{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–8) |
+| `{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–8, время: 1–60000 мс |
-| `{UID}/set/pump/{id}/dispense` | `"50.5"` (граммы) | id: 1–8, граммы: 0.1–1000 |
+| `{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
{
@@ -169,40 +191,83 @@ Payload: строка `"connected"` — устройство подключил
"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,
- "px2": 580,
- "px3": 1023,
- "py1": 0.0,
- "py2": 7.0,
- "py3": 14.0,
+
+ "px1": 100, "py1": 0.0,
+ "px2": 580, "py2": 7.0,
+ "px3": 1023, "py3": 14.0,
"pHlKorr": 0.0,
- "maxLLevel": 30.0,
- "maxLRaw": 100,
- "minLLevel": 10.0,
- "minLRaw": 500,
+
+ "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_limitEC": 3.0,
+ "ecDoser_valueA": 10.0,
+ "ecDoser_valueB": 10.0,
+
+ "dallas_ecSensorIndex": 0
}
```
-**Все поля опциональны** — устройство применяет только те, что пришли.
-
---
## Обработка сообщений на сервере
@@ -217,7 +282,7 @@ flowchart TD
CHECK -->|"data-timescale/{METRIC}"| PROC[MessageProcessor]
PROC --> PARSE[Парсинг значения]
- PARSE --> MAP[Маппинг имени поля]
+ PARSE --> MAP[Маппинг имени поля
wEC→ec, wpH→ph, wLevel→calc_dist...]
MAP --> BATCH[Batcher
100 метрик / 25 сек]
BATCH --> TSDB[(TimescaleDB)]
MAP --> CACHE[Redis live cache
boxconfig-{token}]