У меня на участке стоит большая бочка (куб) с дождевой водой — она питает систему капельного полива через клапан Sonoff SWV. Клапан управляется по Zigbee, работает по расписанию, и всё бы хорошо, но… иногда вода в бочке заканчивается, а полив продолжается. Хотя Sonoff SWV и имеет защиту от сухого хода (до 8 бар, 0.7 МПа), он всё равно — просто клапан: открыл/закрыл. Он не знает, есть ли вода в бочке или нет.
Решил сделать «умный» датчик уровня, который будет:
Цель простая: автоматически отключать полив, если воды мало.
Конечно, сейчас можно купить «умный» ультразвуковой датчик уровня воды с поддержкой Zigbee или Wi-Fi. Например, такие продаются за ~2966 ₽. Но у них есть проблемы:
А у меня уже валялся JSN-SR04T — водонепроницаемый, дешёвый, точный. И ESP-01 — тоже с полки. Решил собрать всё сам. Вышло дешевле, гибче и энергоэффективнее.
Сначала думал использовать ESP32, но он «жирный» для такой простой задачи. В итоге остановился на ESP-01 — дешёвый, маленький, Wi-Fi есть, а больше ничего и не нужно.
Для измерения уровня — водонепроницаемый ультразвуковой датчик JSN-SR04T. Он работает в диапазоне до 5 метров, не боится влаги и конденсата, и отлично подходит для установки сверху в бочку.
Питание — от аккумулятора 18650. Чтобы не сажать батарейку за неделю, добавил таймер TPL5110. Он полностью отключает питание ESP-01 между измерениями. Потребление в простое — десятки наноампер!
Измерение происходит раз в час: ESP-01 просыпается, делает замер, отправляет данные по HTTP или MQTT на Raspberry Pi, подаёт сигнал «DONE» — и TPL5110 снова обесточивает всю схему.
уровень = высота_бочки - расстояние_до_поверхности
.Благодаря TPL5110, ESP-01 и датчик питаются только ~4–5 секунд в час. Остальное время — полное отключение.
Расчётный срок работы от одного 18650 (3000 мА·ч): более года. На практике — 8–12 месяцев (с учётом зимы, саморазряда и т.п.).
Прошивка написана в Arduino IDE с использованием ESP8266 Core. Ниже — два варианта отправки данных.
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#define TRIG_PIN 0 // GPIO0 (TX на ESP-01)
#define ECHO_PIN 2 // GPIO2 (RX на ESP-01)
#define DONE_PIN 3 // GPIO3 (если используется, иначе закомментируй)
const char* ssid = "MyWiFi";
const char* password = "password123";
const char* serverURL = "http://192.168.1.100/water-level.php"; // IP Raspberry Pi
const float TANK_HEIGHT = 100.0; // см
void setup() {
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
pinMode(DONE_PIN, OUTPUT);
digitalWrite(DONE_PIN, LOW);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) delay(100);
float level = measureWaterLevel();
if (level >= 0) {
sendToMySQL(level);
}
// Подтверждение завершения работы для TPL5110
digitalWrite(DONE_PIN, HIGH);
delay(100);
}
void loop() {
// Ничего не делаем — уйдём в выключенный режим через TPL5110
}
float getDistance() {
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
long duration = pulseIn(ECHO_PIN, HIGH, 30000);
if (duration == 0) return -1;
return duration * 0.034 / 2;
}
float measureWaterLevel() {
float distances[3];
for (int i = 0; i < 3; i++) {
distances[i] = getDistance();
delay(50);
}
// Простая медиана
if (distances[0] > distances[1]) swap(distances[0], distances[1]);
if (distances[1] > distances[2]) swap(distances[1], distances[2]);
if (distances[0] > distances[1]) swap(distances[0], distances[1]);
if (distances[1] < 20 || distances[1] > TANK_HEIGHT + 20) return -1;
return TANK_HEIGHT - distances[1];
}
void sendToMySQL(float level) {
if (WiFi.status() == WL_CONNECTED) {
HTTPClient http;
http.begin(serverURL);
http.addHeader("Content-Type", "application/x-www-form-urlencoded");
String payload = "device=barrel_1&level=" + String(level);
int httpResponseCode = http.POST(payload);
http.end();
}
}
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#define TRIG_PIN 0
#define ECHO_PIN 2
#define DONE_PIN 3
const char* ssid = "MyWiFi";
const char* password = "password123";
const char* mqtt_server = "192.168.1.100"; // IP Raspberry Pi с Mosquitto
const int mqtt_port = 1883;
WiFiClient espClient;
PubSubClient client(espClient);
const float TANK_HEIGHT = 100.0;
void setup() {
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
pinMode(DONE_PIN, OUTPUT);
digitalWrite(DONE_PIN, LOW);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) delay(100);
client.setServer(mqtt_server, mqtt_port);
while (!client.connected()) {
client.connect("water_sensor_barrel");
delay(500);
}
float level = measureWaterLevel();
if (level >= 0) {
String msg = String(level);
client.publish("sensors/water/barrel_1", msg.c_str());
}
digitalWrite(DONE_PIN, HIGH);
delay(100);
}
void loop() {
// Ничего
}
// Функции measureWaterLevel() и getDistance() — такие же, как в варианте 1
// (скопируй их сюда)
WayGuard подключается к той же MySQL или подписывается на MQTT — и принимает решение: включать полив или нет.
Пока минимальная версия работает. В планах:
Но даже сейчас система спасает мой сад от «сухого полива» — и я спокоен, уезжая на неделю.
Простой, дешёвый и энергоэффективный IoT-датчик уровня воды — это реально. ESP-01 + JSN-SR04T + TPL5110 + Raspberry Pi — отличный тандем для автономных задач на даче. Главное — не перестраховываться: не нужен ESP32, если хватает ESP-01. Иногда «меньше — лучше».
Простой и безопасный (насколько возможно для локального IoT) PHP-скрипт water-level.php, который принимает данные от ESP-01 и сохраняет их в MySQL.
📁 Требования на Raspberry Pi:
CREATE DATABASE iot_sensors;
USE iot_sensors;
CREATE TABLE water_levels (
id INT AUTO_INCREMENT PRIMARY KEY,
device VARCHAR(50) NOT NULL,
level_cm FLOAT NOT NULL,
recorded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
📄 2. Файл water-level.php (положи в /var/www/html/)
<?php
// water-level.php — приём данных с датчика уровня воды
// Настройки БД
$host = 'localhost';
$db = 'iot_sensors';
$user = 'iot_user'; // лучше не root!
$pass = 'strong_password';
$charset = 'utf8mb4';
// Проверка метода
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
die('Method Not Allowed');
}
// Обязательные параметры
$device = $_POST['device'] ?? null;
$level = $_POST['level'] ?? null;
if (!$device || !is_numeric($level)) {
http_response_code(400);
die('Bad Request: missing or invalid data');
}
// Очистка входных данных
$device = substr(trim($device), 0, 50); // макс. 50 символов
$level = floatval($level);
// Защита: уровень не может быть отрицательным или больше 200 см
if ($level < 0 || $level > 200) {
http_response_code(400);
die('Invalid level value');
}
// Подключение к БД
$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
try {
$pdo = new PDO($dsn, $user, $pass, $options);
$stmt = $pdo->prepare("INSERT INTO water_levels (device, level_cm) VALUES (?, ?)");
$stmt->execute([$device, $level]);
// Успех
http_response_code(200);
echo "OK: Level saved for $device = $level cm";
} catch (PDOException $e) {
error_log("DB Error: " . $e->getMessage());
http_response_code(500);
die('Internal Server Error');
}
?>
🔐 3. (Опционально) Создай отдельного пользователя в MySQL
Не используй root! Выполни в MySQL:
CREATE USER 'iot_user'@'localhost' IDENTIFIED BY 'strong_password';
GRANT INSERT ON iot_sensors.water_levels TO 'iot_user'@'localhost';
FLUSH PRIVILEGES;
🧪 4. Проверка работы
curl -X POST http://192.168.1.100/water-level.php \
-d "device=test_barrel&level=85.3"
Блог только запустил, все статьи генерирую через нейросеть т.к. лень, возможны ошибки. Просто чтобы вы знали и не запускали ядерный реактор по моим статьям ))
Если у вас есть вопросы, или Нашли неточность? пишите в коментах — вместе поправим и сделаем статью более качественной. Я лично объясню нюансы из практики.
Комментарии
Пока нет комментариев. Будьте первым!