Initial commit: контроллер насосов v8.2 + документация

Прошивка Wemos D1 Mini для мониторинга трёх насосов: OLED,
веб-панель, webhook-уведомления, сброс Wi-Fi кнопкой.

Адрес webhook задаётся через веб-панель и хранится в EEPROM;
в исходнике не хранится, так как содержит секретный токен.

Добавлены README.md (техдокументация) и MANUAL.md (руководство).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-07 13:51:22 +07:00
co-authored by Claude Opus 5
commit 75430a3732
4 changed files with 1105 additions and 0 deletions
+250
View File
@@ -0,0 +1,250 @@
# 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 бод.