Распиновка. Пина 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>
11 KiB
Настраиваемые параметры, состояние реле, ревизия распиновки
Дата: 2026-08-07 Статус: согласовано, к реализации
Задачи
- Пересмотреть распиновку под доступный набор пинов (A0, D1–D8) и релейные модули active-LOW.
- Вынести в веб-панель три параметра: имя устройства, выдержку аварии основных насосов, выдержку пуска резервного насоса.
- Показать состояние обоих реле в веб-панели.
Исходные условия
- Доступны только 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 резерв реагирует сразу;
- панель показывает состояние реле, совпадающее с фактическим щелчком.