Категории

Автонаполнение iot джакузи - или мой тупой дом

2025-10-22 10:41:08 | Статья из категории: IOT умный дом

Все говорят про «умный дом», но на деле большинство систем создаются не для разумных людей, а для тех, кому лень встать и открыть кран, а еще хуже интеллектуальные системы для позеров. Поэтому сегодня — статья не про умный, а про тупой дом. А именно — про автонаполнение джакузи. Потому что если ты не можешь дотянуться до смесителя, но можешь нажать кнопку в приложении — добро пожаловать в будущее.

1. Температура воды: микс горячей и холодной

Основа комфорта — правильная температура. Для этого я использую два электромагнитных клапана (или пропорциональных клапана, если бюджет позволяет) на трубах горячей и холодной воды. Они соединяются в общий смесительный узел, внутри которого установлен водонепроницаемый датчик температуры DS18B20.

ESP32 читает текущую температуру и регулирует скважность открытия клапанов через ШИМ (или релейное включение/выключение, если клапаны бинарные). Цель — достичь заданной пользователем температуры (например, 38°C) с точностью ±0.5°C.

2. Уровень наполнения: вес вместо поплавка

Поплавковые датчики — это прошлый век. У меня — независимо стоящая джакузи на четырёх тензодатчиках (HX711 + тензометрические весы). ESP32 постоянно считывает суммарный вес.

Алгоритм прост:

Но что если человек залезёт в ванну до завершения наполнения? Тут вступает в игру логика: если вес резко вырос без соответствующего роста температуры (человек холоднее воды), система понимает — «внутри кто-то есть» — и либо приостанавливает наполнение, либо корректирует целевой объём.

Дополнительно: датчик температуры, приклеенный снизу к корпусу джакузи, помогает оценить среднюю температуру воды без погружения зонда. А счетчик воды на горячей и холодной трубе будет отправлять данные на iot панель, и эти данные можно использовать чтобы дублировать безопаность при переливе. По сути хватит и счетчиков воды, чтобы налить джакузи, но мы продублируем систему как в самолете, для надежности.

3. Пробка: механика vs электроника

Пока не решил окончательно. Варианты:

4. Безопасность: перелив и протечки

Главная угроза — затопление. Поэтому:

Да, можно было бы использовать ультразвуковой датчик уровня, но в условиях пара, брызг и пены он быстро сходит с ума. Вес — надёжнее.

5. Пена для ванны: гравитационная подача

На чердаке (или в шкафу над ванной) — 2-литровая бутылка с жидкостью для пены, подключённая к электромагнитному клапану. По таймеру (например, на 30-й секунде наполнения) клапан открывается на 5 секунд — пена стекает самотёком.

Под бутылкой — мини-тензодатчики. Когда вес падает ниже порога — в IoT-панели появляется уведомление: «Пора долить пену!».

6. Интеграция в 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);
}

Комментарии

Пока нет комментариев. Будьте первым!

Оставить комментарий

← Назад к списку статей

Важно: Блог-эксперимент

Блог только запустил, все статьи генерирую через нейросеть т.к. лень, возможны ошибки. Просто чтобы вы знали и не запускали ядерный реактор по моим статьям ))
Если у вас есть вопросы, или Нашли неточность? пишите в коментах — вместе поправим и сделаем статью более качественной. Я лично объясню нюансы из практики.

Посетителей сегодня: 0


Кто я | Контакты и регион

© Digital Specialist | Не являемся сотрудниками Google, Яндекса и NASA