JohnYezub/WaterTankFiles

Wireless water tank level sensor: ESP32 + ultrasonic + ESP-NOW, with an OLED display and a local Wi-Fi dashboard for your phone

★ 0Forks 0C++GitHub ↗Compare
arduinocppesp32esp32-arduinoiot

README

Датчик уровня воды — ESP32 + JSN-SR04T + ESP-NOW

Беспроводной датчик уровня воды для бака Penguin TB 110 (1050 л, высота 1420 мм, диаметр 1050 мм). Ультразвуковой замер сверху через люк, передача по ESP-NOW на отдельный дисплей — без роутера, без облака, без приложения.

Как устроено

Схема системы

[ Узел А: датчик, на баке ]  --ESP-NOW-->  [ Узел Б: дисплей, в доме ]
  ESP32 + JSN-SR04T v3.0                     ESP32 + OLED 1.3" SH1106
  питание 5 В                                питание USB 5 В

Узел А раз в 5 секунд измеряет расстояние до воды, считает уровень и объём, шлёт один пакет по ESP-NOW. Узел Б принимает и рисует на экране: сантиметры, литры, процент заполнения.

Железо

Узел Компонент Примечание
А ESP32 DevKit V1, 30 pin Arduino IDE: плата DOIT ESP32 DEVKIT V1
А JSN-SR04T v3.0 ультразвук, диапазон 20–600 см
А Резистор 10 кОм + 20 кОм делитель на линии Echo, см. ниже
А БП 5 В / 1 А
А Конденсаторы 470 мкФ + 100 нФ опционально, гасят просадку при TX
Б ESP32 DevKit V1, 30 pin та же модель платы
Б OLED 1.3" I2C, SH1106 подписан на плате как «1.3 IIC V2.2»

⚠️ Дисплей — не SSD1306, хотя многие 0.96" модули так называют по привычке. 1.3" I2C-экраны почти всегда на контроллере SH1106. Библиотека — Adafruit_SH110X.

Схема подключения

Узел А — датчик

Датчик сейчас в режиме TRIG/ECHO (без резистора на MODE):

JSN-SR04T ESP32 Как
5V VIN напрямую
GND GND напрямую
Trig D17 напрямую (это выход ESP32, 3,3 В — датчику достаточно)
Echo D16 через делитель 10к/20к, см. ниже

Делитель на Echo обязателен. JSN-SR04T выдаёт на Echo 5 В логики, вход ESP32 держит максимум 3,6 В. Без делителя пин рискует деградировать при длительной работе.

Echo ──[10 кΩ]──┬── D16
                 │
               [20 кΩ]
                 │
                GND

Переключить на UART-режим (без Trig-провода, но с резистором):

  1. Впаять резистор 47 кОм на пятачок MODE платы JSN-SR04T.
  2. В tank_sensor_node.ino поставить #define SENSOR_UART 1.
  3. Провод Trig (D17) можно убрать.

Узел Б — дисплей

OLED SH1106 ESP32
VCC 3V3
GND GND
SDA D21
SCL (иногда подписан SCK) D22

Прошивка

Файл Куда Библиотеки
tank_sensor_node.ino узел А (на баке) нет, только встроенные WiFi.h, esp_now.h, esp_wifi.h
tank_display_node.ino узел Б (дисплей) Adafruit GFX Library, Adafruit SH110X
i2c_scanner.ino диагностика Wire.h (встроенная)

Плата в Arduino IDE — DOIT ESP32 DEVKIT V1 на обоих узлах.

Константы под конкретный монтаж

Перед заливкой на постоянное место правятся значения в tank_sensor_node.ino:

Константа Что Как получить
H0_MM расстояние от датчика до дна бака, мм рулеткой на пустом баке
MAX_LEVEL_MM столб воды при полном баке, мм из паспорта бака (диаметр × объём)
LITERS_PER_MM литров на 1 мм уровня π/4 × d² для цилиндра
RECEIVER_MAC MAC-адрес узла Б печатается в Serial Monitor при старте узла Б
WIFI_CHANNEL канал ESP-NOW должен совпадать на обоих узлах
BLIND_MM слепая зона датчика 230 для v3.0 (запас над паспортными 200)

Для бака Penguin TB 110 уже посчитано и стоит по умолчанию: H0_MM=1380 (уточнить рулеткой), MAX_LEVEL_MM=1213, LITERS_PER_MM=0.866.

Поправка на скорость звука

Формула перевода времени эха в мм использует скорость звука 343 м/с (20°C). В тропическом климате воздух теплее, звук идёт быстрее, датчик занижает расстояние. Откалибровано опытным путём в readOnce():

return (int16_t)(us * 0.17400f);   // было 0.1715 (343 м/с при 20°C)

Коэффициент подобран по замеру: реальные 700 мм датчик показывал как 690. Если меняешь место установки на сильно другой климат — можно перекалибровать тем же способом: новый = старый × (реальное_расстояние / показанное).

Калибровка при первом монтаже

  1. Прошить оба узла, свести MAC-адреса.
  2. На столе — направить датчик на пол с известного расстояния, сверить D= в Serial Monitor с рулеткой.
  3. Смонтировать датчик по центру крышки люка бака.
  4. На пустом баке замерить H0 рулеткой от мембраны датчика до дна, вписать в код, перезалить.
  5. Налить бак известным объёмом (например, ведро 10 л) и проверить, что уровень/литры на экране растут ожидаемо.

Известные грабли

  • Порт не появляется в Arduino IDE → кабель без линий данных («только для зарядки»). Проверять через ls /dev/cu.* в терминале, не через IDE.
  • Adafruit_GFX.h: No such file or directory → не поставлены библиотеки через Library Manager.
  • esp_wifi_set_channel' was not declared → нужен #include <esp_wifi.h>, функция объявлена не в WiFi.h.
  • Экран показывает «снег» вместо текста → неверная библиотека контроллера. I2C-сканер (i2c_scanner.ino) находит адрес 0x3C, но это не помогает, если физически на плате SH1106, а прошивка ждёт SSD1306 — команды инициализации разные. Решение: Adafruit_SH110X, не Adafruit_SSD1306.
  • Датчик всегда D=0, st=1 → резистор 47 кОм на MODE не впаян, модуль по умолчанию в TRIG/ECHO и не шлёт кадры сам. Либо впаять резистор и SENSOR_UART 1, либо оставить SENSOR_UART 0 и подключить Trig отдельным проводом (текущая рабочая конфигурация).

Просмотр с телефона

Узел Б поднимает собственную Wi-Fi точку доступа и отдаёт данные веб-страницей — без установки приложения, работает одинаково в Safari (iOS) и Chrome (Android).

  1. На телефоне подключиться к сети TankSensor (пароль tank12345, задан в AP_SSID / AP_PASS в tank_display_node.ino — стоит сменить свой).
  2. Открыть в браузере http://192.168.4.1 (стандартный адрес точки доступа ESP32; точный адрес также печатается в Serial Monitor при старте узла Б).
  3. Страница сама обновляется каждые 3 секунды через /data — там же лежит голый JSON, если понадобится дёрнуть его из своего приложения: {"seen":true,"age_s":2,"level_cm":86,"liters":745,"percent":71,"distance_mm":167,"status":0}.

Почему собственная точка доступа, а не подключение к домашнему роутеру: у ESP32 один радиомодуль на Wi-Fi и ESP-NOW одновременно, и оба должны сидеть на одном канале. С софт-AP канал жёстко фиксирован кодом (WIFI_CHANNEL) и никак не зависит от роутера — узел А трогать не нужно. Подключение к домашней сети было бы удобнее (телефон не переключает сеть), но канал тогда диктует роутер, и после его перезагрузки с автовыбором канала связь с узлом А может порваться. Минус софт-AP — на время просмотра телефон отключён от домашнего Wi-Fi и, соответственно, от интернета.

Возможные доработки (не реализовано)

  • JSON /data уже готов для нативного приложения — вместо браузерной страницы можно дёргать этот же эндпоинт из своего Swift/SwiftUI-виджета, когда телефон в сети TankSensor.
  • Подключение узла Б к домашнему Wi-Fi вместо своей точки доступа — удобнее (не надо переключать сеть), но требует синхронизировать канал ESP-NOW с каналом роутера и мириться с тем, что он может измениться сам.
  • Питание от батареи для узла А. Требует голый модуль ESP32-WROOM-32 (не DevKit V1 — у него ~8 мА паразитного тока в deep sleep из-за стабилизатора и USB-моста) и MOSFET для отключения питания JSN-SR04T между замерами — сам датчик не спит, пока на него подано питание.
  • Компенсация температуры датчиком DS18B20 вместо константы.
  • MQTT/Home Assistant — узел Б можно научить дублировать показания в сеть.

Contributors

EugeneYezub

Issues