HMI — Локальный интерфейс¶
Локальная панель управления: 2 кнопки + 7-сегментный дисплей. Позволяет смотреть и менять пропорцию канала без сети.
Источник: src/hmi.cpp.
1. Железо¶
| Компонент | Спецификация |
|---|---|
| Контроллер LED | TM1650, bit-bang протокол через 2 GPIO |
| Дисплей | 2 физические цифры (по 2 LED на сегмент — левый/правый) |
| Кнопки | K1 (down/prev), K2 (up/next) — pull-up, нажатие = LOW |
Пины (см. include/config.h)¶
| Сигнал | GPIO | Направление | Назначение |
|---|---|---|---|
| CLK | 18 | OUT | Тактирование TM1650 |
| DAT | 23 | OUT (OD) | Данные TM1650 (нужен внешний pull-up) |
| KEYM (K1) | 33 | IN (pull-up) | Левая кнопка — «–», предыдущий |
| KEYP (K2) | 32 | IN (pull-up) | Правая кнопка — «+», следующий |
Особенность дисплея¶
Физически 2 цифры, но TM1650 управляет 4 GRID-выходами. Каждая цифра
состоит из двух «полу-цифр»: GRID1+GRID2 → левая, GRID3+GRID4 → правая.
В коде это скрыто: showNumber(N, dp_pos) пишет одно и то же значение в
обе позиции каждой цифры, плюс точку (dp) ставит в одной из трёх позиций
(0, 1, 2) — это служит индикатором номера активного канала.
2. Режимы¶
Конечный автомат:
stateDiagram-v2
[*] --> IDLE
IDLE --> VALUE_EDIT: K2 long (≥500 мс)
VALUE_EDIT --> IDLE: BOTH short (одновременное нажатие)
IDLE --> ALARM: hmiSetAlarm() из кода
ALARM --> IDLE: hmiClearAlarm()
IDLE — основной режим¶
- Дисплей: пропорция выбранного канала (00…99).
- K1 short — переключить на предыдущий канал (с wraparound).
- K2 short — переключить на следующий канал.
- K2 long (≥500 мс) — войти в
VALUE_EDITредактировать пропорцию текущего канала. - K1 hold ≥1 сек — purge / принудительная продувка выбранного канала на сохранённой пропорции. Дозатор работает пока удерживается кнопка, останавливается при отпускании. Эквивалент DISPENSE-команды по REST API, локально с панели.
Покрытие purge: требует чтобы (1) канал был включён в конфигурации,
(2) сохранённая пропорция была > 0, (3) HMI-редактирование не было
заблокировано (dosator:hmi_edit_lock = false, флажок «разрешить
изменение с панели» в Web UI → Система → Дозаторы). Если хоть одно
условие нарушено — purge не стартует, нажатие игнорируется.
Остановка purge триггерится также любым изменением состояния: переход HMI из IDLE в ALARM (например, поднятая внешняя авария), случайное нажатие K2 одновременно с K1, потеря питания. Это защита от «прилипшей» принудительной сессии.
dp-точки (позиция 0/1/2 = канал 0/1/2) ведут себя по-разному в зависимости от того, идёт ли дозирование:
| Ситуация | Поведение dp |
|---|---|
| Дозирования нет | dp выбранного кнопками канала горит ровно (индикатор выбора) |
| Идёт дозирование | dp каждого дозирующего канала мигает 500/500 мс (индикатор активности); кнопки на dp не влияют |
То есть когда канал реально льёт, оператор видит мигающую точку этого канала на железке, независимо от того, какой канал выбран кнопками.
VALUE_EDIT — редактирование пропорции¶
- Дисплей: редактируемое значение мигает 500 мс / 500 мс, точка на позиции редактируемого канала.
- K2 short —
+1(0…99, clamp на верхнем краю). - K2 long + auto-repeat (200 мс) —
+10. - K1 short —
-1(clamp на 0). - K1 long + auto-repeat —
-10. - BOTH short (одновременное короткое) — сохранить в NVS и вернуться в IDLE. После сохранения — три вспышки всего дисплея 100 мс / 100 мс как индикация подтверждения.
ALARM — авария¶
- Дисплей:
E<код>(напримерE03), мигает 500/500 мс. - Кнопки игнорируются.
- Активируется вызовом
hmiSetAlarm(code)из других модулей. - Снимается
hmiClearAlarm()(например, после устранения причины или сброса через API).
3. Тайминги (config.h)¶
| Параметр | Значение | Описание |
|---|---|---|
HMI_TASK_TICK_MS |
10 мс | Период task'а HMI |
HMI_BTN_DEBOUNCE_MS |
20 мс | Антидребезг кнопок |
HMI_BTN_LONG_MS |
500 мс | Порог short vs long |
HMI_BTN_BOTH_LONG_MS |
3000 мс | Долгое нажатие обоих кнопок (BOTH_LONG) |
HMI_BTN_AUTOREPEAT_MS |
200 мс | Автоповтор в long-режиме |
HMI_BTN_PURGE_MS |
1000 мс | K1-hold для purge (принудительная продувка) |
HMI_BLINK_ON_MS |
500 мс | Мигание VALUE_EDIT/ALARM, фаза ON |
HMI_BLINK_OFF_MS |
500 мс | Мигание, фаза OFF |
HMI_FLASH_ON_MS |
100 мс | Вспышка подтверждения, ON |
HMI_FLASH_OFF_MS |
100 мс | Вспышка подтверждения, OFF |
HMI_FLASH_COUNT |
3 | Количество вспышек |
4. События¶
HMI публикует и слушает события через esp_event:
Публикует (HMI_EVENTS)¶
| Событие | Когда | Payload |
|---|---|---|
HMI_EVT_CHANNEL_CHANGE |
Сменили канал кнопками | {ch} |
HMI_EVT_STATE_CHANGE |
IDLE / VALUE_EDIT / ALARM | {state} |
HMI_EVT_PROPORTION_CHANGE |
Сохранили новую пропорцию (BOTH short) | {ch, value} |
Слушает (DOSING_EVENTS)¶
| Событие | Эффект |
|---|---|
DOSING_EVT_PROPORTION_CHANGE |
Обновляет s_proportion[ch] → дисплей моментально перерисовывается |
Это даёт двунаправленную синхронизацию HMI и Web: меняешь пропорцию
кнопкой — Web подхватит через GET /api/dosator/status; меняешь
через POST /api/dosator/config — HMI обновит дисплей по событию.
5. Яркость дисплея¶
Яркость TM1650 (1…7) хранится в NVS, но не настраивается в runtime — управляется только boot-логикой:
hmiPreInit()вызывается сразу послеSerial.begin()— гасит TM1650 (setupDisplay(false)+clearDisplay). Это критично при warm-reboot: чип сохраняет состояние с прошлой сессии, без гашения дал бы пик тока.hmiInit()поднимает дисплей на минимальной яркости (1).- Через
HMI_BOOT_RAMP_DELAY_MS(2 с) задача HMI поднимает яркость до сохранённого в NVS значения.
Цель — ограничить пиковый ток при включении. HMI_DEFAULT_BRIGHTNESS = 1.
Runtime-API яркости и UI-элемент сознательно убраны — менять яркость оператору незачем, а dim-on-boot важнее.
6. NVS¶
Namespace hmi:
| Ключ | Тип | Default | Описание |
|---|---|---|---|
active_ch |
uchar | 0 | Канал, который показывается при старте (последний выбранный) |
hmi_bright |
uchar | 1 | Яркость дисплея (1…7), применяется через 2с после boot |
Подробнее — nvs-reference.md.
7. Шпаргалка оператора¶
| Хочу… | Кнопки |
|---|---|
| Посмотреть пропорцию канала | Короткое K1 или K2 — листать каналы |
| Изменить пропорцию текущего | Долго K2 → войти, K1/K2 ±1, K2 long ±10 |
| Сохранить изменения | Одновременно K1+K2 коротко (трижды мигнёт) |
| Отменить редактирование | (нет отмены — выйти можно только сохранением; чтобы вернуть старое значение — выставь его снова) |
| Продуть дозатор | K1 удерживать ≥1 сек в IDLE на нужном канале — пока держишь, льёт; отпустил — стоп. Требует hmi_edit_lock = false, пропорцию > 0, канал включён. |
Точки на дисплее (позиция 0/1/2 = канал 0/1/2):
- дозирования нет — ровно горит точка выбранного канала (где сейчас стоит «листалка» каналов);
- идёт дозирование — мигают точки тех каналов, что реально льют (можно несколько разом), независимо от выбора кнопками.
Двузначное число — пропорция выбранного канала.