# Pump Controller v8.2 (OLED) Контроллер мониторинга насосной станции на **Wemos D1 Mini (ESP8266)**. Следит за состоянием трёх насосов (два основных + резервный), выдаёт сухие контакты аварии, показывает статус на OLED-дисплее, публикует веб-панель в локальной сети и отправляет события на HTTPS-webhook (n8n). --- ## 1. Состав репозитория | Файл | Назначение | |---|---| | `pump_controller_8_2_OLED_DONE.ino` | Весь прошивочный код (single-file Arduino sketch) | | `README.md` | Техническая документация (этот файл) | | `MANUAL.md` | Руководство пользователя / монтажника | --- ## 2. Аппаратная часть ### 2.1 Плата Wemos D1 Mini (ESP8266, 4 МБ Flash). Питание 5 В по microUSB или 3.3 В на пин `3V3`. ### 2.2 Распиновка | Пин | Имя в коде | Режим | Логика | |---|---|---|---| | `D5` | `IN_PUMP_1` | `INPUT_PULLUP` | `LOW` = насос 1 работает | | `D6` | `IN_PUMP_2` | `INPUT_PULLUP` | `LOW` = насос 2 работает | | `D7` | `IN_PUMP_EMERGENCY` | `INPUT_PULLUP` | `LOW` = резервный насос работает | | `D3` | `BTN_WIFI_RESET` | `INPUT_PULLUP` | `LOW` (замыкание на GND) = кнопка нажата | | `D8` | `OUT_ALARM_MAIN` | `OUTPUT` | `HIGH` = авария основных насосов | | `D0` | `OUT_ALARM_EMERGENCY` | `OUTPUT` | `HIGH` = работает резерв | | `D1` | I2C SDA | — | OLED | | `D2` | I2C SCL | — | OLED | Замечания по особенностям ESP8266: * `D3` (GPIO0) и `D8` (GPIO15) участвуют в выборе режима загрузки. `D8` должен быть подтянут к GND при старте — не вешайте на него нагрузку, тянущую пин вверх. Кнопку на `D3` при старте держать нажатой нельзя (плата уйдёт в режим прошивки). * `D0` (GPIO16) не имеет внутреннего pull-up (только pull-down) и не поддерживает прерывания — используется только как выход. ### 2.3 Дисплей SSD1306 128×64, I2C, адрес по умолчанию `0x3C` (константа `OLED_ADDR`). Если экран не найден — в лог уходит сообщение, прошивка продолжает работать без дисплея (`oledOK = false`). ### 2.4 Подключение датчиков Датчик — сухой контакт (реле пускателя, датчик потока, контакт КМ). Замкнут на GND → насос считается работающим. Внешние резисторы не нужны, используется внутренняя подтяжка. --- ## 3. Зависимости Устанавливаются через Arduino Library Manager: * Adafruit SSD1306 * Adafruit GFX Library * WiFiManager (tzapu) Входят в ESP8266 core: `ESP8266WiFi`, `ESP8266WebServer`, `ESP8266HTTPClient`, `WiFiClientSecure`, `EEPROM`, `Wire`. Board: **LOLIN(WEMOS) D1 R2 & mini**, Upload speed 921600, Flash size 4MB. --- ## 4. Логика работы ### 4.1 Антидребезг `updatePumpState()` для каждого входа: ``` если чтение изменилось -> сбросить таймер lastDebounceTime если чтение стабильно > 3000 мс -> зафиксировать stableState ``` `debounceDelay = 3000 мс` — длинный намеренно: фильтрует пусковые дребезги пускателя и кратковременные просадки. ### 4.2 Готовность системы `systemReady` становится `true` через `debounceDelay + 500 мс` после старта. До этого момента аварии не формируются и webhook не отправляется — иначе при включении питания система рапортовала бы ложную аварию. ### 4.3 Основная авария Условие: **оба** основных насоса стоят (`!p1 && !p2`). ``` bothStopped -> запуск таймера mainAlarmStartTime выдержка alarmDelay = 5000 мс -> OUT_ALARM_MAIN = HIGH -> webhook {"event":"MAIN PUMPS","status":"ALARM"} (однократно) любой насос запустился -> OUT_ALARM_MAIN = LOW -> webhook {"event":"MAIN PUMPS","status":"OK"} (однократно) ``` Суммарная задержка от факта остановки до аварии: `3 с` (дребезг) + `5 с` (выдержка) ≈ **8 секунд**. ### 4.4 Резервный насос Реакция без дополнительной выдержки, сразу после стабилизации входа: ``` pE = работает -> OUT_ALARM_EMERGENCY = HIGH, webhook EMERGENCY PUMP / STARTED pE = стоит -> OUT_ALARM_EMERGENCY = LOW, webhook EMERGENCY PUMP / STOPPED ``` Флаги `mainAlarmSent` / `emergencyActiveSent` гарантируют отправку ровно одного webhook на каждый переход состояния. --- ## 5. Webhook `POST` на `webhook_url`, `Content-Type: application/json`: ```json {"event":"MAIN PUMPS","status":"ALARM","device":"Wemos_D1_Pump"} ``` | `event` | `status` | |---|---| | `MAIN PUMPS` | `ALARM` / `OK` | | `EMERGENCY PUMP` | `STARTED` / `STOPPED` | TLS-соединение поднимается через `WiFiClientSecure` с `setInsecure()` — сертификат сервера **не проверяется**. Достаточно для отправки в доверенную локальную/корпоративную инфраструктуру, но не защищает от MITM. Отправка синхронная и блокирующая: на время POST (до нескольких секунд при недоступном сервере) основной цикл приостанавливается. Дисплей в этот момент не обновляется. --- ## 6. Хранение настроек (EEPROM) Эмулируемая EEPROM, 256 байт. | Адрес | Размер | Содержимое | |---|---|---| | `0` | 2 | Magic `0xA55A` — признак инициализации | | `2` | 220 | Webhook URL, `\0`-терминированный | При первом старте (magic не совпал) записывается значение константы `webhook_url_default` — она намеренно пустая, чтобы секретный токен не попадал в исходник и в git. Пока URL не задан через `/set_webhook`, `sendPostWebhook()` сразу выходит и пишет в лог `[HTTP] Webhook URL не задан`. Учётные данные Wi-Fi хранит WiFiManager в своей области флеша, не в этой EEPROM. --- ## 7. Веб-интерфейс Сервер на порту `80`. | Маршрут | Метод | Описание | |---|---|---| | `/` | `GET` | HTML-панель мониторинга | | `/status` | `GET` | JSON `{"p1":0,"p2":0,"pE":0}` | | `/reset_wifi` | `POST` | Стереть настройки Wi-Fi и перезагрузиться | | `/set_webhook` | `POST` | Параметр `url` (≤220 симв.), сохранить в EEPROM | Панель опрашивает `/status` каждые 2 секунды и перезагружает страницу при изменении состояния. Шрифты подгружаются с Google Fonts — при отсутствии интернета у клиента интерфейс отрисуется системным моноширинным шрифтом. Аутентификации нет: любой в той же сети может сбросить Wi-Fi и подменить webhook. Контроллер рассчитан на изолированный технологический сегмент. --- ## 8. OLED Обновление раз в секунду (`DISPLAY_INTERVAL`). ``` PUMP CONTROLLER v8 ──────────────────────── PUMP 1 : RUNNING PUMP 2 : STOPPED BACKUP : STANDBY ──────────────────────── 192.168.1.42 STATUS: OK ``` Строка статуса: | Условие | Текст | |---|---| | `!systemReady` | `INIT...` | | оба основных стоят | `!! ALARM: NO PUMPS !!` (инверсия, мигание 500 мс) | | работает резерв | `WARN: BACKUP RUNNING` | | иначе | `STATUS: OK` | --- ## 9. Кнопка сброса Wi-Fi Пин `D3`, удержание `BTN_HOLD_MS = 3000 мс`. Во время удержания на дисплее рисуется прогресс-бар с обратным отсчётом. По достижении порога — `wm.resetSettings()` и `ESP.restart()`. Отпускание до порога отменяет операцию. --- ## 10. Известные ограничения * `setInsecure()` — TLS без проверки сертификата. * Веб-интерфейс и `/reset_wifi` не защищены паролем. * Webhook отправляется синхронно и блокирует цикл; при недоступном сервере реакция контроллера замедляется на время таймаута. * События, произошедшие без Wi-Fi, не буферизуются — webhook просто теряется (в лог пишется `[HTTP] Нет Wi-Fi`). * Логика аварии — «оба стоят»: остановка одного насоса штатной ситуацией не считается и никак не сигнализируется. * Webhook URL хранится в EEPROM в открытом виде и отдаётся веб-панелью всем, кто может её открыть. Если URL содержит секретный токен — доступ к панели равносилен доступу к токену. --- ## 11. Сборка ```bash arduino-cli compile --fqbn esp8266:esp8266:d1_mini pump_controller_8_2_OLED_DONE.ino ``` ```bash arduino-cli upload -p COM3 --fqbn esp8266:esp8266:d1_mini pump_controller_8_2_OLED_DONE.ino ``` Монитор порта: 115200 бод.