Беспроводной датчик уровня воды для бака 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» |
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-провода, но с резистором):
- Впаять резистор 47 кОм на пятачок MODE платы JSN-SR04T.
- В
tank_sensor_node.inoпоставить#define SENSOR_UART 1. - Провод 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.
Если меняешь место установки на сильно другой климат — можно перекалибровать
тем же способом: новый = старый × (реальное_расстояние / показанное).
- Прошить оба узла, свести MAC-адреса.
- На столе — направить датчик на пол с известного расстояния, сверить
D=в Serial Monitor с рулеткой. - Смонтировать датчик по центру крышки люка бака.
- На пустом баке замерить
H0рулеткой от мембраны датчика до дна, вписать в код, перезалить. - Налить бак известным объёмом (например, ведро 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).
- На телефоне подключиться к сети
TankSensor(парольtank12345, задан вAP_SSID/AP_PASSвtank_display_node.ino— стоит сменить свой). - Открыть в браузере
http://192.168.4.1(стандартный адрес точки доступа ESP32; точный адрес также печатается в Serial Monitor при старте узла Б). - Страница сама обновляется каждые 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 — узел Б можно научить дублировать показания в сеть.
