Настройки через веб, состояние реле в UI, распиновка под реле active-LOW
Распиновка. Пина D0, который использовался под выход «Резерв», в доступном наборе нет. Кнопка сброса Wi-Fi удалена как избыточная: при пропавшей сети wm.autoConnect() сам поднимает портал. Освободившийся D3 закрыл дефицит. Выходы переехали на D3 и D4 и стали инверсными. Причина: для реле active-LOW безопасное состояние — пин в HIGH, а D3/D4 подтянуты к HIGH внешними резисторами платы и держат этот уровень всю загрузку. D8, наоборот, подтянут к LOW и щёлкал бы реле при каждом включении питания. В setup() digitalWrite() идёт до pinMode(), иначе защёлка выхода даёт короткий LOW. Инверсия живёт только в setRelay(); логическое состояние дублируется в relayMainOn/relayEmergOn, чтобы панель показывала смысл, а не уровень пина. Настройки. device, выдержка аварии и выдержка резерва вынесены в /set_config и EEPROM. Раскладка EEPROM версионирована: magic 0xA55B, старый 0xA55A распознаётся и переносится, поэтому прошитые приборы не теряют webhook. Значения по умолчанию воспроизводят прежнее поведение (5 с и 0 с). Для резерва добавлена выдержка, которой раньше не было, симметрично основной аварии. Обе выдержки гасят реле и вебхук одновременно. device_name фильтруется до [A-Za-z0-9_-] вместо экранирования: такой набор безопасен и в JSON, и в HTML. Пустое или неверное поле формы означает «не менять», поэтому частичное заполнение не сбрасывает остальное. Состояние реле выведено в /status и в панель. Проверено: xtensa-lx106-elf-g++ -fsyntax-only -Wall -Wextra против ядра 3.1.2 — предупреждений в скетче нет (попутно убран неиспользуемый isEmergency); валидация настроек прогнана на 22 граничных случаях; раскладка EEPROM проверена на перекрытия. На железе не проверялось. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
# Настраиваемые параметры, состояние реле, ревизия распиновки
|
||||
|
||||
Дата: 2026-08-07
|
||||
Статус: согласовано, к реализации
|
||||
|
||||
## Задачи
|
||||
|
||||
1. Пересмотреть распиновку под доступный набор пинов (A0, D1–D8) и релейные
|
||||
модули active-LOW.
|
||||
2. Вынести в веб-панель три параметра: имя устройства, выдержку аварии основных
|
||||
насосов, выдержку пуска резервного насоса.
|
||||
3. Показать состояние обоих реле в веб-панели.
|
||||
|
||||
## Исходные условия
|
||||
|
||||
* Доступны только A0 и D1–D8. Пина `D0`, который использует текущий код, нет.
|
||||
* A0 — только аналоговый вход: ни цифровым выходом, ни входом с подтяжкой быть
|
||||
не может, поэтому в бюджет цифровых пинов не входит.
|
||||
* Реле — готовые модули для Arduino с обвязкой, **active-LOW** (вход на GND =
|
||||
реле включено). На момент проектирования ещё не подключены, поэтому логику
|
||||
выходов задаём с нуля.
|
||||
|
||||
## 1. Распиновка
|
||||
|
||||
Кнопка сброса Wi-Fi удаляется. Обоснование: при сохранённых учётных данных и
|
||||
пропавшей сети `wm.autoConnect()` не подключается и сам поднимает портал
|
||||
`Pump_Control_Set` — кнопка для этого не нужна. Для случая «сеть есть, но надо
|
||||
переехать в другую» остаётся кнопка на веб-панели; физически то же достигается
|
||||
выключением роутера и перезагрузкой прибора.
|
||||
|
||||
Освободившийся D3 закрывает дефицит пинов.
|
||||
|
||||
Для реле active-LOW безопасное состояние — пин в **HIGH**, поэтому выходы должны
|
||||
сидеть на пинах, уже подтянутых к HIGH во время загрузки, пока `setup()` ещё не
|
||||
выполнился:
|
||||
|
||||
| Пин | Уровень при загрузке | Реле active-LOW при загрузке |
|
||||
|---|---|---|
|
||||
| D3 | подтянут HIGH (10к) | выключено ✓ |
|
||||
| D4 | подтянут HIGH (10к) | выключено ✓ |
|
||||
| D8 | подтянут **LOW** (10к) | включено ✗ ложная авария при каждом старте |
|
||||
| D1/D2/D5/D6/D7 | не определён до `pinMode` | неопределённо |
|
||||
|
||||
Итоговая распиновка:
|
||||
|
||||
```
|
||||
D1 SDA — OLED (без изменений)
|
||||
D2 SCL — OLED (без изменений)
|
||||
D5 IN_PUMP_1 (INPUT_PULLUP, LOW = работает)
|
||||
D6 IN_PUMP_2 (INPUT_PULLUP, LOW = работает)
|
||||
D7 IN_PUMP_EMERGENCY (INPUT_PULLUP, LOW = работает)
|
||||
D3 OUT_ALARM_MAIN (выход, active-LOW: LOW = реле включено)
|
||||
D4 OUT_ALARM_EMERGENCY (выход, active-LOW: LOW = реле включено)
|
||||
D8, A0, D0 — не используются
|
||||
```
|
||||
|
||||
Особенности, принятые осознанно:
|
||||
|
||||
* D4 — встроенный светодиод платы, тоже активен от LOW. Реле «Резерв» получает
|
||||
бесплатную индикацию на плате.
|
||||
* D3 — пин выбора режима прошивки. Схема автосброса USB дёргает его вниз при
|
||||
заливке скетча, поэтому реле «Авария» щёлкает при каждой прошивке. Косметика.
|
||||
* Схема не fail-safe: авария включает реле, поэтому обесточенный контроллер
|
||||
сигнала не даёт. При необходимости решается на монтаже — снятием нагрузки с
|
||||
нормально замкнутого (NC) контакта реле.
|
||||
|
||||
Инверсия уровня локализуется в одной функции, чтобы её нельзя было применить
|
||||
дважды или забыть:
|
||||
|
||||
```cpp
|
||||
void setRelay(int pin, bool on) { digitalWrite(pin, on ? LOW : HIGH); }
|
||||
```
|
||||
|
||||
В `setup()` `digitalWrite(pin, HIGH)` вызывается **до** `pinMode(pin, OUTPUT)`:
|
||||
иначе защёлка выхода может кратковременно выдать LOW и щёлкнуть реле.
|
||||
|
||||
## 2. Конфигурация в EEPROM
|
||||
|
||||
Три новых параметра не влезают в оставшиеся 34 байта (текущая раскладка занимает
|
||||
222 из 256), поэтому `EEPROM_SIZE` увеличивается до 512.
|
||||
|
||||
| Адрес | Размер | Поле | По умолчанию |
|
||||
|---|---|---|---|
|
||||
| 0 | 2 | magic `0xA55B` | — |
|
||||
| 2 | 220 | `webhook_url` | пусто |
|
||||
| 222 | 32 | `device_name` | `Wemos_D1_Pump` |
|
||||
| 254 | 2 | `alarm_delay_sec` (uint16) | 5 |
|
||||
| 256 | 2 | `emerg_delay_sec` (uint16) | 0 |
|
||||
|
||||
Значения по умолчанию совпадают с текущим поведением прошивки: `5` — нынешний
|
||||
`alarmDelay = 5000 мс`, `0` — нынешняя мгновенная реакция на резерв,
|
||||
`Wemos_D1_Pump` — нынешнее захардкоженное значение поля `device` в теле хука.
|
||||
|
||||
Миграция по magic:
|
||||
|
||||
* `0xA55B` — читаются все поля.
|
||||
* `0xA55A` (старая раскладка) — сохраняется `webhook_url`, новые поля получают
|
||||
значения по умолчанию, magic перезаписывается. Прошитые приборы не теряют
|
||||
адрес webhook.
|
||||
* иное — первый старт, все поля по умолчанию.
|
||||
|
||||
## 3. Валидация
|
||||
|
||||
`device_name` попадает в тело JSON, поэтому кавычка или обратный слэш сломали бы
|
||||
запрос. Вместо экранирования — ограничение набора символов: разрешены
|
||||
`A-Z a-z 0-9 _ -`, до 31 символа, остальные символы отбрасываются. Если после
|
||||
фильтрации строка пуста, поле не меняется. Это же снимает вопрос с выводом
|
||||
значения в HTML.
|
||||
|
||||
Задержки задаются целыми секундами, допустимый диапазон 0…3600. Значение вне
|
||||
диапазона или нечисловое — поле не меняется.
|
||||
|
||||
Пустой `webhook_url` — легальное состояние: отправка событий пропускается,
|
||||
автономная работа это штатный режим.
|
||||
|
||||
## 4. Логика задержек
|
||||
|
||||
Одна выдержка на канал, гасит и реле, и вебхук одновременно.
|
||||
|
||||
`alarm_delay_sec` заменяет константу `alarmDelay` в существующей паре
|
||||
`mainAlarmPending` / `mainAlarmStartTime` — поведение основных насосов не
|
||||
меняется, кроме настраиваемости.
|
||||
|
||||
Для резерва добавляется симметричная пара `emergPending` / `emergStartTime`,
|
||||
которой сейчас нет:
|
||||
|
||||
```
|
||||
pE работает:
|
||||
выдержка не запущена -> запустить
|
||||
выдержка истекла -> setRelay(EMERG, true) + вебхук STARTED (однократно)
|
||||
pE стоит:
|
||||
setRelay(EMERG, false) + вебхук STOPPED (однократно), выдержка сброшена
|
||||
```
|
||||
|
||||
При `emerg_delay_sec = 0` условие выполняется на следующей же итерации, то есть
|
||||
нынешнее мгновенное поведение сохраняется.
|
||||
|
||||
Полная задержка до сигнала = 3 с (дребезг, константа `debounceDelay`) плюс
|
||||
настроенная выдержка.
|
||||
|
||||
## 5. Веб-интерфейс
|
||||
|
||||
`/set_webhook` заменяется на `/set_config` — одна форма на четыре поля (URL, имя
|
||||
устройства, две задержки). Ломающее изменение для маршрута; внешних потребителей
|
||||
у него нет.
|
||||
|
||||
`/status` расширяется:
|
||||
|
||||
```json
|
||||
{"p1":0,"p2":0,"pE":0,"rMain":0,"rEmg":0}
|
||||
```
|
||||
|
||||
Состояние реле берётся из логических переменных `relayMainOn` / `relayEmergOn`,
|
||||
а не из `digitalRead` — иначе на панели легко получить инвертированную неправду.
|
||||
Побочный эффект: скрипт панели сравнивает JSON целиком, поэтому страница теперь
|
||||
перезагружается и при изменении состояния реле.
|
||||
|
||||
На панель добавляются:
|
||||
|
||||
* ряд из двух индикаторов реле (существующие стили `.spanel`);
|
||||
* блок настроек с текущими значениями полей.
|
||||
|
||||
## 6. Вне объёма
|
||||
|
||||
* OLED не меняется — строки для реле там нет без переработки макета.
|
||||
* Ретраи и очередь вебхуков не добавляются (решение принято ранее).
|
||||
* Пауза мониторинга до 2 минут при старте без сохранённой сети остаётся как есть.
|
||||
* Попутно удаляется неиспользуемая переменная `isEmergency` в `getHTML()` —
|
||||
единственное предупреждение компилятора в скетче.
|
||||
|
||||
## 7. Проверка
|
||||
|
||||
Автоматически:
|
||||
|
||||
* Синтаксическая сборка `xtensa-lx106-elf-g++ -fsyntax-only -Wall -Wextra`
|
||||
против ядра 3.1.2 — в скетче не должно остаться предупреждений.
|
||||
* Логика фильтрации имени устройства и разбора чисел — таблицей граничных
|
||||
случаев.
|
||||
|
||||
На железе (остаётся за пользователем):
|
||||
|
||||
* при подаче питания оба реле молчат;
|
||||
* миграция EEPROM сохранила ранее заданный webhook;
|
||||
* заданные выдержки соблюдаются, при 0 резерв реагирует сразу;
|
||||
* панель показывает состояние реле, совпадающее с фактическим щелчком.
|
||||
Reference in New Issue
Block a user