Все говорят про «умный дом», но на деле большинство систем создаются не для разумных людей, а для тех, кому лень встать и открыть кран, а еще хуже интеллектуальные системы для позеров. Поэтому сегодня — статья не про умный, а про тупой дом. А именно — про автонаполнение джакузи. Потому что если ты не можешь дотянуться до смесителя, но можешь нажать кнопку в приложении — добро пожаловать в будущее.
Основа комфорта — правильная температура. Для этого я использую два электромагнитных клапана (или пропорциональных клапана, если бюджет позволяет) на трубах горячей и холодной воды. Они соединяются в общий смесительный узел, внутри которого установлен водонепроницаемый датчик температуры DS18B20.
ESP32 читает текущую температуру и регулирует скважность открытия клапанов через ШИМ (или релейное включение/выключение, если клапаны бинарные). Цель — достичь заданной пользователем температуры (например, 38°C) с точностью ±0.5°C.
Поплавковые датчики — это прошлый век. У меня — независимо стоящая джакузи на четырёх тензодатчиках (HX711 + тензометрические весы). ESP32 постоянно считывает суммарный вес.
Алгоритм прост:
Но что если человек залезёт в ванну до завершения наполнения? Тут вступает в игру логика: если вес резко вырос без соответствующего роста температуры (человек холоднее воды), система понимает — «внутри кто-то есть» — и либо приостанавливает наполнение, либо корректирует целевой объём.
Дополнительно: датчик температуры, приклеенный снизу к корпусу джакузи, помогает оценить среднюю температуру воды без погружения зонда. А счетчик воды на горячей и холодной трубе будет отправлять данные на iot панель, и эти данные можно использовать чтобы дублировать безопаность при переливе. По сути хватит и счетчиков воды, чтобы налить джакузи, но мы продублируем систему как в самолете, для надежности.
Пока не решил окончательно. Варианты:
Главная угроза — затопление. Поэтому:
Да, можно было бы использовать ультразвуковой датчик уровня, но в условиях пара, брызг и пены он быстро сходит с ума. Вес — надёжнее.
На чердаке (или в шкафу над ванной) — 2-литровая бутылка с жидкостью для пены, подключённая к электромагнитному клапану. По таймеру (например, на 30-й секунде наполнения) клапан открывается на 5 секунд — пена стекает самотёком.
Под бутылкой — мини-тензодатчики. Когда вес падает ниже порога — в IoT-панели появляется уведомление: «Пора долить пену!».
Вся система работает на ESP32, который:
Философское отступление: Мы изобретаем «умные» системы не потому, что они нужны, а потому, что лень — двигатель прогресса. И если твой дом может наполнить тебе джакузи, пока ты лежишь в нём и читаешь эту статью… возможно, ты уже победил.
Это не просто автоматизация — это автоматическая роскошь. Проект требует времени, тестов и здорового скепсиса к собственным решениям. Но когда всё заработает — ты сможешь гордо сказать: «Мой дом настолько тупой, что даже за меня ванну наполняет».
В следующей раз напишу про автоматический iot унитаз ))))
Код для теста, не проверял:
#include <WiFi.h>
#include <PubSubClient.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <HX711.h>
// === Настройки Wi-Fi и MQTT ===
const char* ssid = "YOUR_SSID";
const char* password = "YOUR_PASS";
const char* mqtt_server = "192.168.1.100"; // IP вашего MQTT-брокера
const int mqtt_port = 1883;
const char* mqtt_user = "iot";
const char* mqtt_pass = "iotpass";
// === Пины ===
#define ONE_WIRE_BUS 4 // DS18B20
#define HX711_DOUT 16
#define HX711_CLK 17
#define VALVE_HOT 25 // Клапан горячей воды
#define VALVE_COLD 26 // Клапан холодной воды
#define LEAK_OVERFLOW 32 // Датчик перелива
#define LEAK_FLOOR 33 // Датчик под ванной
// === Объекты ===
WiFiClient espClient;
PubSubClient client(espClient);
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
HX711 scale;
// === Константы ===
const float TARGET_TEMP = 38.0; // Целевая температура (°C)
const float TOLERANCE = 0.5; // Допуск
const long TARGET_MASS_GR = 280000; // 280 литров ≈ 280 кг → 280000 г
const long MAX_MASS_SAFETY = 310000;// Аварийный лимит (310 кг)
// === Переменные состояния ===
bool filling = false;
bool emergency_stop = false;
unsigned long lastPublish = 0;
const long publishInterval = 5000; // каждые 5 сек
// === MQTT топики ===
const char* topic_status = "spa/status";
const char* topic_cmd = "spa/cmd"; // ожидает "fill", "stop"
// === Вспомогательные функции ===
void connectWiFi() {
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
}
void connectMQTT() {
while (!client.connected()) {
if (client.connect("ESP32_SPA", mqtt_user, mqtt_pass)) {
client.subscribe(topic_cmd);
} else {
delay(2000);
}
}
}
void callback(char* topic, byte* payload, unsigned int length) {
payload[length] = '\0';
String cmd = String((char*)payload);
if (cmd == "fill") {
if (!emergency_stop) filling = true;
} else if (cmd == "stop") {
filling = false;
}
}
void controlValves(float temp) {
if (temp < TARGET_TEMP - TOLERANCE) {
digitalWrite(VALVE_HOT, HIGH);
digitalWrite(VALVE_COLD, LOW);
} else if (temp > TARGET_TEMP + TOLERANCE) {
digitalWrite(VALVE_HOT, LOW);
digitalWrite(VALVE_COLD, HIGH);
} else {
digitalWrite(VALVE_HOT, HIGH);
digitalWrite(VALVE_COLD, HIGH); // оба открыты для смешивания
}
}
void checkEmergency() {
if (digitalRead(LEAK_OVERFLOW) == LOW || digitalRead(LEAK_FLOOR) == LOW) {
emergency_stop = true;
}
long mass = scale.read();
if (mass > MAX_MASS_SAFETY) {
emergency_stop = true;
}
}
void publishStatus(float temp, long mass) {
char buffer[256];
snprintf(buffer, sizeof(buffer),
"{\"temp\":%.1f,\"mass_kg\":%.1f,\"filling\":%s,\"emergency\":%s}",
temp, mass / 1000.0,
filling ? "true" : "false",
emergency_stop ? "true" : "false");
client.publish(topic_status, buffer);
}
// === Setup ===
void setup() {
pinMode(VALVE_HOT, OUTPUT);
pinMode(VALVE_COLD, OUTPUT);
pinMode(LEAK_OVERFLOW, INPUT_PULLUP);
pinMode(LEAK_FLOOR, INPUT_PULLUP);
digitalWrite(VALVE_HOT, LOW);
digitalWrite(VALVE_COLD, LOW);
Serial.begin(115200);
sensors.begin();
scale.begin(HX711_DOUT, HX711_CLK);
scale.set_scale(); // откалибруйте: scale.set_scale(коэффициент)
scale.tare(); // обнулить "сухой вес"
connectWiFi();
client.setServer(mqtt_server, mqtt_port);
client.setCallback(callback);
}
// === Loop ===
void loop() {
if (!client.connected()) {
connectMQTT();
}
client.loop();
if (emergency_stop) {
digitalWrite(VALVE_HOT, LOW);
digitalWrite(VALVE_COLD, LOW);
filling = false;
// Можно отправить аварийный топик отдельно
return;
}
sensors.requestTemperatures();
float temp = sensors.getTempCByIndex(0);
long mass = scale.read();
if (filling) {
if (mass > = TARGET_MASS_GR) {
filling = false;
} else {
controlValves(temp);
}
} else {
digitalWrite(VALVE_HOT, LOW);
digitalWrite(VALVE_COLD, LOW);
}
checkEmergency();
if (millis() - lastPublish > publishInterval) {
publishStatus(temp, mass);
lastPublish = millis();
}
delay(100);
}
Блог только запустил, все статьи генерирую через нейросеть т.к. лень, возможны ошибки. Просто чтобы вы знали и не запускали ядерный реактор по моим статьям ))
Если у вас есть вопросы, или Нашли неточность? пишите в коментах — вместе поправим и сделаем статью более качественной. Я лично объясню нюансы из практики.
Комментарии
Пока нет комментариев. Будьте первым!