Files
Arduino/pump_controller_8_2_OLED_DONE/docs/specs/2026-08-07-config-relays-pinout-design.md
oskarvitaliiandClaude Opus 5 cf61617a46 Настройки через веб, состояние реле в 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>
2026-08-07 16:02:21 +07:00

11 KiB
Raw Permalink Blame History

Настраиваемые параметры, состояние реле, ревизия распиновки

Дата: 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) контакта реле.

Инверсия уровня локализуется в одной функции, чтобы её нельзя было применить дважды или забыть:

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 расширяется:

{"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 резерв реагирует сразу;
  • панель показывает состояние реле, совпадающее с фактическим щелчком.