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