# Отчёт о технической проблеме: Рассинхронизация ответов Modbus RTU over TCP (Serial-over-TCP) на шлюзах WB-MGE v.3 и драйвере wb-mqtt-serial

**Дата составления:** 1 октября 2026 г.  
**Объект:** Жилой комплекс «Мосфильмовская 70» (МФ70)  
**Компоненты стенда:**
* **Контроллер:** Wiren Board 8 (SN: `AIKH5OZJ`, Debian 11 Bullseye, ПО `wb-2606`, драйвер `wb-mqtt-serial 2.248.1-wb107`)
* **Шлюзы интерфейсов:** WB-MGE v.3 (ESP32-based, прошивка `1.1.2`, build `9c10fd2_main`, IP `10.70.66.8` и `10.70.66.7`)
* **Опрашиваемые модули:** Модули аналоговых входов `WB-MAI6` (FW `2.3.3`, Slave ID `223` и `179`)
* **Подключение шлюзов:** Wi-Fi 2.4 GHz (WLAN `Home665hid`, UniFi AP, уровень сигнала -51 dBm, RSSI 61)
* **Режим порта в `wb-mqtt-serial.conf`:** `"port_type": "tcp"`, порт 502 (Serial over TCP / сырой Modbus RTU в TCP-потоке)

---

## 1. Суть проблемы и симптомы (Problem Statement)

При опросе аналоговых модулей ввода `WB-MAI6`, подключенных через шлюзы `WB-MGE v.3` в режиме `Serial over TCP` (`"port_type": "tcp"`), в каналах измерения сопротивления 2-проводных термодатчиков теплого пола (Eberle PTC 1000 и NTC) спорадически возникают **катастрофические однократные аномальные выбросы значений**.

### Примеры зафиксированных аномалий:
1. **Канал сопротивления датчика теплого пола `IN 1 N Resistance` (норма ~994...1030 Ом, стяжка 22...31 °C):**
   * Внезапное чтение: **`4411.93 Ом`**
   * Внезапное чтение: **`3 270 932.42 Ом`** (3.27 МОм)
   * Внезапные чтения: **`1 635 680 Ом`**, **`1 090 755 Ом`**
   * Внезапные чтения: **`509.54 Ом`**, **`555.14 Ом`**, **`760.33 Ом`**, **`850.33 Ом`**
   * Внезапные чтения: **`1049.18 Ом`**, **`1057.32 Ом`** (всплеск сопротивления на 50 Ом выше реального)
2. **Влияние на систему автоматизации и графики:**
   * В Home Assistant температура этого датчика кратковременно падала до **$-55.5^\circ\text{C}$**, **$-46.6^\circ\text{C}$**, **$-11.5^\circ\text{C}$**, **$+1.9^\circ\text{C}$**, либо подскакивала до $+31.4^\circ\text{C}$.
   * На графиках истории формируются гигантские «иглы», ложно срабатывала защита от перегрева, отключая нагрев зон.
3. **Характер сбоя:**
   * Выброс длится **ровно 1 цикл опроса** (60 секунд), после чего следующее же чтение возвращается в абсолютно нормальное значение.
   * На модуле `wb-mai6_166`, подключенном к WB8 **напрямую по кабелю RS-485** (`/dev/ttyRS485-2`), за всё время эксплуатации зафиксировано **0 аномалий**. Проблема возникает **исключительно на беспроводных шлюзах WB-MGE v.3**.

---

## 2. Диагностика и математическое доказательство (Root Cause Analysis)

Мы сопоставили тайминги системных логов `journalctl`, сырые записи базы SQLite `wb-mqtt-db` (`/var/lib/wirenboard/db/data.db`) и структуру карты регистров шаблона `config-wb-mai6-fw-2.2.json`.

В журнале `wb-mqtt-serial` ровно в секунды появления аномалий фиксируются сообщения:
```text
Sep 29 04:57:33 wb-mqtt-serial: WARNING: [modbus] failed to read 2 input(s) @ 1536 of device modbus:223: Serial protocol error: request timed out
Sep 29 04:57:55 wb-mqtt-serial: WARNING: [modbus] failed to read 2 input(s) @ 270 of device modbus:223: Serial protocol error: request timed out
```

### Побайтовая расшифровка аномальных чисел:

#### Факт №1: Аномалия `4411.93 Ом` — это чтение серийного номера модуля
* Значение в канале с масштабом 0.01: $4411.93 \times 100 = \mathbf{441193}$ (`0x0006BB69`).
* Серийный номер установленного модуля `WB-MAI6` (Slave ID 223) равен ровно **441193** (U32, регистр 270 `Serial`).
* Драйвер запросил сопротивление канала `IN 1 N Resistance`, но в сокет пришел задержанный ответ на предыдущий запрос серийного номера!

#### Факт №2: Аномалия `3 270 932.42 Ом` — это склейка напряжения 5V и температуры MCU
* Значение в сыром 32-битном регистре: $3\,270\,932.42 \times 100 = 327\,093\,242$ = **`0x137F0BFA`**.
* Разложим на два 16-битных слова:
  * Старшее слово: `0x137F` = **4991** (десятичное). Ровно **4.991 В (4991 мВ)** из регистра `0x0600` (1536) — канал `5V Output`!
  * Младшее слово: `0x0BFA` = **3066** (десятичное). Ровно **30.66 °C** из регистра `0x0601` (1537) — канал `Internal Temperature`!

#### Факт №3: Значения `1 635 680 Ом` и `1 090 755 Ом` — усреднение АЦП
* В конфигурации включен аппаратный оверсэмплинг АЦП (`in1_n_measurements: 5`). При наложении смещенных регистров усреднение части нормальных выборок (~1000 Ом) и склеенного регистра (3.27 МОм) давало числа $\approx 3.27\text{ МОм} / 2 = 1.635\text{ МОм}$ и $\approx 3.27\text{ МОм} / 3 = 1.09\text{ МОм}$.

#### Факт №4: Отрицательные пики $-55.5^\circ\text{C}$
* При сдвиге пакетов читалось паразитное сопротивление $509.54$ Ом (младшие слова регистров).
* Характеристика Eberle PTC 1000 пересчитывает $509.54$ Ом в $-53.0^\circ\text{C}$.
* С учётом офсета зоны $32.4$ (`offset: -2.5`): $-53.0 + (-2.5) = \mathbf{-55.5^\circ\text{C}}$.

---

## 3. Архитектурная причина в драйвере `wb-mqtt-serial`

1. **Отсутствие Transaction ID в Serial-over-TCP:**
   В режиме `"port_type": "tcp"` передаются «сырые» кадры Modbus RTU. В Modbus RTU нет заголовка MBAP и поля Transaction ID (в отличие от полноценного Modbus TCP).
2. **Преждевременный сброс ожидания:**
   По умолчанию таймаут ответа в `wb-mqtt-serial` составляет всего **500 мс** (`response_timeout_ms`). Для Wi-Fi радиоканала (особенно 2.4 ГГц при наличии других клиентов и фонового сканирования) задержка пакета в 600–800 мс является штатной ситуацией.
3. **Отсутствие очистки буфера сокета (Socket Drain/Flush):**
   Когда наступает таймаут 500 мс, драйвер:
   * Логирует `request timed out`;
   * **НЕ сбрасывает** буфер входящего TCP-сокета и не переподключает его;
   * Сразу отправляет в этот же TCP-сокет следующий Modbus-запрос (например, для канала сопротивления).
4. **Смещение ответов на 1 шаг:**
   Задержанный ответ на *предыдущий* запрос наконец приходит по TCP. Драйвер считывает его, видит корректный Slave ID (223), корректный Function Code (0x04) и валидный CRC. Драйвер считает этот пакет ответом на *текущий* запрос и помещает данные не в тот MQTT-топик.

---

## 4. Как проблема была временно купирована на объекте (Workaround)

Мы применили комплексное решение на уровне контроллера WB8:

1. **На уровне `wb-mqtt-serial.conf`:**
   * Для всех TCP-портов шлюзов WB-MGE установлен увеличенный таймаут:
     ```json
     "response_timeout_ms": 2000
     ```
   * В шаблонах опрашиваемых устройств отключен опрос неиспользуемых сервисных каналов (`5V Output`, `Internal Temperature`, `Serial`, `Uptime`), что сократило сетевой трафик Modbus RTU по Wi-Fi более чем на 70%.
2. **На уровне движка правил `wb-rules` (`floor_heating.js`):**
   * **Resistance Gate:** допустимый диапазон Eberle PTC 1000 сужен с `300..3000` до **`850..1250 Ом`** (+2...+54 °C). Все паразитные мегаомы, серийные номера и низкие сопротивления мгновенно отбрасываются.
   * **Plausible Temp Gate:** температура пола программно ограничена диапазоном **`10.0°C .. 42.0°C`**.
   * **De-glitch filter:** скачки температуры более **`2.5°C`** за 1 цикл опроса блокируются (стяжка физически не может нагреться/остыть на 3 °C за 60 секунд). Для принятия скачка требуется подтверждение 3 цикла подряд.

---

## 5. Предложения для команды разработки Wiren Board

Для устранения проблемы в кодовой базе `wb-mqtt-serial` и прошивке `WB-MGE v.3` предлагаем рассмотреть следующие доработки:

1. **Очистка TCP-буфера при таймауте (Drain socket on timeout):**
   Если при опросе по TCP произошел таймаут (`Serial protocol error: request timed out`), драйвер перед отправкой следующего PDU обязан полностью вычитать и сбросить весь хвост входящего буфера сокета с небольшой задержкой (guard interval), либо выполнить переподключение TCP-сессии (`TCP reset / reconnect`). Иначе задержанный ответ гарантированно «отравит» следующий регистр.
2. **Полноценный Modbus TCP для WB-MGE v.3:**
   Реализовать и рекомендовать на шлюзах WB-MGE режим **Modbus TCP** (порт 502) с инкапсуляцией в заголовок MBAP (с инкрементом `Transaction ID`). В этом случае драйвер сопоставляет Transaction ID ответа с запросом и в принципе не может присвоить ответ другому каналу при любых сетевых задержках.
3. **Увеличение таймаута по умолчанию для TCP-портов:**
   В схеме `wb-mqtt-serial-ports.schema.json` дефолтный `response_timeout_ms` для `tcpPort` и `modbusTcpPort` целесообразно поднять с 500 мс хотя бы до 1500–2000 мс.
4. **Оптимизация стандартных шаблонов:**
   Регистры `Serial`, `Uptime`, `5V Output` в шаблонах аналоговых модулей не должны опрашиваться на высокой частоте наравне с быстрыми измерительными каналами — для них предпочтителен опрос раз в 10–60 минут либо по требованию.
