feat(ui): переработать веб-панель и макет OLED
Панель как световое табло, а не дашборд: главный ответ занимает верх страницы и читается с расстояния. Прежде самым крупным элементом были часы браузера - единственное, что не несло информации о приборе, - а ответ "есть подача или нет" был набран 11 кеглем в панели размером с кнопку. Норма и авария выглядели почти одинаково. Исправлены три содержательные ошибки: - лампа выхода "Авария" горела ЗЕЛЁНЫМ при срабатывании: стиль "включено" был общим для всех ламп, и сработавшая сигнализация подсвечивалась цветом "всё хорошо"; - страница делала location.reload() при любом изменении состояния, стирая недописанный адрес в форме вместе с прокруткой и фокусом; - CSS подключал шрифты с Google Fonts, тогда как сеть на объекте не разрешает внешние имена - нарисованный макет там не появлялся никогда. Страница стала статической и уехала во флеш (PAGE_HTML PROGMEM, ~8 КБ, отдаётся через send_P). Раньше HTML собирался строкой в куче на каждый запрос: ~14 КБ при ~40 КБ свободной кучи. Состояние подставляет JS, забирая /status и новый /config. В /status добавлено поле rdy. /config отдаёт только имя хоста webhook: полный URL содержит секретный токен, и выводить его на экран, видимый всем в помещении, незачем. Цвета по конвенции сигнальных ламп: резерв теперь ЖЁЛТЫЙ, а не красный - работающий резерв это предупреждение, система ещё справляется. Красный оставлен для остановки основных. Убраны декоративные коды SYS_OK / 0xFF, не кодировавшие ничего, и тройное дублирование состояния резерва. Настройки свёрнуты в details. OLED: верхняя строка отдана статусу кеглем 2 - прежде её постоянно занимал заголовок "PUMP CONTROLLER v8", который никогда не менялся. Три строки датчиков сжаты в одну, освободившееся место отдано состоянию обоих реле. Текст остаётся латиницей: встроенный шрифт Adafruit GFX кириллицы не содержит. Проверено: сборка -Wall -Wextra без предупреждений; все три состояния панели просмотрены в браузере на 375 и 800 px. На железе не проверялось. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -395,46 +395,81 @@ Wi-Fi настройки из `/settings.json` не затрагивает, и
|
||||
|
||||
| Маршрут | Метод | Описание |
|
||||
|---|---|---|
|
||||
| `/` | `GET` | HTML-панель мониторинга |
|
||||
| `/status` | `GET` | JSON `{"p1":0,"p2":0,"pE":0,"rMain":0,"rEmg":0}` |
|
||||
| `/` | `GET` | Статическая HTML-панель из PROGMEM |
|
||||
| `/status` | `GET` | Живое состояние, JSON |
|
||||
| `/config` | `GET` | Настройки и адрес в сети, JSON |
|
||||
| `/reset_wifi` | `POST` | Стереть настройки Wi-Fi и перезагрузиться |
|
||||
| `/set_config` | `POST` | Параметры `url`, `device`, `alarm_delay`, `emerg_delay` |
|
||||
|
||||
### 7.1 `/set_config`
|
||||
### 7.1 Почему страница статическая
|
||||
|
||||
Одна форма на все настройки. **Пустое или неверное поле означает «не менять»** —
|
||||
так частично заполненная форма не обнуляет остальные параметры. Запись в файл
|
||||
выполняется только если что-то реально изменилось.
|
||||
Раньше `getHTML()` собирал разметку строкой в куче на каждый запрос: около
|
||||
**14 КБ при примерно 40 КБ свободной кучи** у ESP8266. Теперь страница целиком
|
||||
лежит во флеше (`PAGE_HTML[] PROGMEM`, ~8 КБ) и отдаётся через `server.send_P()`,
|
||||
а состояние подставляет JS, забирая `/status` и `/config`. **Куча не тратится.**
|
||||
|
||||
| Параметр | Валидация | При отказе |
|
||||
|---|---|---|
|
||||
| `url` | 1…220 символов | поле не меняется |
|
||||
| `device` | фильтр до `[A-Za-z0-9_-]`, ≤31 симв.; пусто после фильтрации → отказ | поле не меняется |
|
||||
| `alarm_delay` | строка целиком из цифр, 0…3600 | поле не меняется |
|
||||
| `emerg_delay` | строка целиком из цифр, 0…3600 | поле не меняется |
|
||||
Побочный, но важный эффект: прежняя панель при любом изменении состояния делала
|
||||
`location.reload()`. Если оператор в этот момент вводил адрес уведомлений, ввод
|
||||
пропадал вместе с прокруткой и фокусом. Теперь JS точечно правит DOM, и форма не
|
||||
трогается.
|
||||
|
||||
Фильтрация `device` вместо экранирования выбрана осознанно: разрешённый набор
|
||||
символов безопасен и в теле JSON, и в HTML, поэтому экранирование не нужно
|
||||
нигде.
|
||||
Ответы:
|
||||
|
||||
### 7.2 Состояние реле
|
||||
```json
|
||||
/status {"p1":1,"p2":0,"pE":0,"rMain":0,"rEmg":0,"rdy":1}
|
||||
/config {"device":"Nasosnaya_1","alarm":5,"emerg":0,
|
||||
"host":"n8n.example.com","fs":1,"ip":"192.168.1.128"}
|
||||
```
|
||||
|
||||
`rMain` и `rEmg` в `/status` — это **логическое** состояние реле из переменных
|
||||
`relayMainOn` / `relayEmergOn`, а не `digitalRead()` с пина. Так сделано
|
||||
намеренно: выходы инверсные, и чтение уровня с пина показало бы на панели
|
||||
обратное действительности.
|
||||
`/config` отдаёт **только имя хоста**, а не полный webhook: URL содержит
|
||||
секретный токен, и выводить его на экран, который видно всем в помещении, незачем.
|
||||
|
||||
### 7.3 Автообновление
|
||||
### 7.2 Логика статуса и цвета
|
||||
|
||||
Панель опрашивает `/status` каждые 2 секунды и перезагружает страницу при любом
|
||||
изменении JSON. Поскольку состояние реле теперь входит в ответ, страница
|
||||
обновляется и при срабатывании реле. Шрифты подгружаются с Google Fonts — при
|
||||
отсутствии интернета у клиента интерфейс отрисуется системным моноширинным
|
||||
шрифтом.
|
||||
Цвета взяты по конвенции сигнальных ламп, а не по вкусу:
|
||||
|
||||
Аутентификации нет: любой в той же сети может сбросить Wi-Fi, подменить webhook
|
||||
и изменить выдержки. Контроллер рассчитан на изолированный технологический
|
||||
сегмент.
|
||||
| Состояние | Цвет | Заголовок | Условие |
|
||||
|---|---|---|---|
|
||||
| Запуск | серый | «Запуск» | `rdy = 0` |
|
||||
| Норма | зелёный | «Подача есть» | работает хотя бы один основной |
|
||||
| Внимание | **жёлтый** | «Работает резерв» | работает резерв, основные в порядке |
|
||||
| Авария | красный | «Насосы стоят» | оба основных стоят |
|
||||
|
||||
Резерв намеренно **жёлтый, а не красный**: работающий резерв — предупреждение,
|
||||
система ещё справляется. Красный оставлен для случая, когда основной подачи нет.
|
||||
|
||||
При аварии плашка медленно пульсирует; пульсация отключается при
|
||||
`prefers-reduced-motion`.
|
||||
|
||||
### 7.3 Состояние реле
|
||||
|
||||
`rMain` и `rEmg` — **логическое** состояние из переменных `relayMainOn` /
|
||||
`relayEmergOn`, а не `digitalRead()` с пина. Выходы инверсные, и чтение уровня
|
||||
показало бы на панели обратное действительности.
|
||||
|
||||
Лампа выхода «Авария» — **красная**. В первой версии панели она была зелёной:
|
||||
стиль «включено» был общим для всех ламп, и сработавшая аварийная сигнализация
|
||||
подсвечивалась цветом «всё хорошо».
|
||||
|
||||
### 7.4 Никаких внешних запросов
|
||||
|
||||
Веб-шрифты не подключаются. В первой версии был `@import` с Google Fonts, но
|
||||
сеть на объекте не разрешает внешние имена (раздел 5.1), поэтому шрифты там не
|
||||
загружались никогда — нарисованный макет на месте не появлялся. Сейчас
|
||||
используются системные стеки, а характер задан контрастом кеглей: заголовок
|
||||
статуса `clamp(28px, 8.5vw, 44px)` против меток 11–12 px.
|
||||
|
||||
### 7.5 Подпись состояния во вкладке
|
||||
|
||||
Заголовок вкладки и favicon меняют цвет вместе со статусом: `Насосы стоят ·
|
||||
Nasosnaya_1` с красным кружком. Вкладка, забытая в фоне на телефоне, продолжает
|
||||
сигналить, не требуя переключения на неё.
|
||||
|
||||
### 7.6 Доступ
|
||||
|
||||
Аутентификации нет: любой в той же сети может сбросить Wi-Fi, подменить адрес
|
||||
уведомлений и **увеличить выдержки**, то есть замедлить реакцию сигнализации.
|
||||
Контроллер рассчитан на изолированный технологический сегмент.
|
||||
|
||||
---
|
||||
|
||||
@@ -442,28 +477,32 @@ Wi-Fi настройки из `/settings.json` не затрагивает, и
|
||||
|
||||
Обновление раз в секунду (`DISPLAY_INTERVAL`).
|
||||
|
||||
Верхняя строка отдана главному ответу кеглем 2 (12×16 px) — читается от двери.
|
||||
Прежде её постоянно занимал заголовок `PUMP CONTROLLER v8`, который никогда не
|
||||
менялся и ничего не сообщал.
|
||||
|
||||
```
|
||||
PUMP CONTROLLER v8
|
||||
NO SUPPLY <- кегль 2, до 10 символов
|
||||
────────────────────────
|
||||
PUMP 1 : RUNNING
|
||||
PUMP 2 : STOPPED
|
||||
BACKUP : STANDBY
|
||||
P1:OFF P2:OFF BK:RUN <- датчики, одна строка вместо трёх
|
||||
ALARM:ON BACKUP:ON <- состояние выходов
|
||||
────────────────────────
|
||||
192.168.1.42
|
||||
STATUS: OK
|
||||
192.168.1.128
|
||||
```
|
||||
|
||||
Строка статуса:
|
||||
|
||||
| Условие | Текст |
|
||||
| Условие | Слово |
|
||||
|---|---|
|
||||
| `!systemReady` | `INIT...` |
|
||||
| оба основных стоят | `!! ALARM: NO PUMPS !!` (инверсия, мигание 500 мс) |
|
||||
| работает резерв | `WARN: BACKUP RUNNING` |
|
||||
| иначе | `STATUS: OK` |
|
||||
| `!systemReady` | `STARTING` |
|
||||
| оба основных стоят, резерв стоит | `NO SUPPLY` (мигает инверсией, 500 мс) |
|
||||
| оба основных стоят, резерв работает | `BACKUP ON` (мигает) |
|
||||
| работает резерв, основные в порядке | `BACKUP RUN` |
|
||||
| иначе | `SUPPLY OK` |
|
||||
|
||||
Состояние реле на экран не выводится — свободной строки в макете нет. Реле видны
|
||||
только в веб-панели и, для «Резерва», по встроенному светодиоду платы на `D4`.
|
||||
Текст только латиницей: встроенный шрифт Adafruit GFX кириллицы не содержит, а
|
||||
тащить её во флеш ради четырёх слов не стоит.
|
||||
|
||||
Состояние обоих реле теперь выводится — освободились две строки после
|
||||
объединения датчиков в одну.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user