#include <HX711.h>
// Пины ESP32
const int DOUT_PIN = 16;
const int SCK_PIN = 4;
#define POMPA_PIN 25
#define POWER_PIN 26
#define BUTTON_PIN 27
// Калибровочный коэффициент
const float calibration_factor = 0.42;
// Вес системы: подставка + пустой чайник
const float EMPTY_SYSTEM_WEIGHT = 1060.0f;
// Пороговые значения массы ВОДЫ (в граммах)
const float LOWER_THRESHOLD = 0.0f; // Нижний порог воды
const float UPPER_THRESHOLD = 1700.0f; // Верхний порог воды (1,7 литра) - для проверок безопасности
const float POWER_THRESHOLD = 500.0f; // Порог включения питания (0,5 литра)
// Объемы налива
const float FULL_VOLUME = 1700.0f; // Полный объем чайника - используется при наливе
const float CUP_VOLUME = 250.0f; // Объем кружки
// Создание объекта для работы с тензодатчиком
HX711 scale;
// Переменные для работы с кнопкой
unsigned long lastButtonPress = 0;
unsigned long buttonPressTime = 0;
bool buttonPressed = false;
byte buttonClickCount = 0;
// Переменные для управления насосом
bool autoFillMode = false;
float targetWeight = 0.0f;
float initialWeight = 0.0f;
byte fillMode = 0; // 0=нет, 1=до 0.5л, 2=полный, 3=кружка
// Переменные для состояния системы
bool kettlePresent = false;
bool kettleOverflow = false; // Флаг переполнения чайника
unsigned long lastKettleCheck = 0;
const unsigned long KETTLE_CHECK_INTERVAL = 1000; // Проверка каждую секунду
void setup() {
Serial.begin(115200);
// Настройка пинов реле
pinMode(POMPA_PIN, OUTPUT);
pinMode(POWER_PIN, OUTPUT);
pinMode(BUTTON_PIN, INPUT_PULLUP);
// Изначально отключаем все реле
digitalWrite(POMPA_PIN, LOW);
digitalWrite(POWER_PIN, LOW);
// Инициализация тензодатчика
scale.begin(DOUT_PIN, SCK_PIN);
scale.set_scale(calibration_factor);
Serial.println("=== СИСТЕМА УПРАВЛЕНИЯ НАСОСОМ (ESP32) ===");
Serial.println("Режимы кнопки:");
Serial.println("0-500г воды: 1x=до 0,5л, 2x=полный");
Serial.println(">500г воды: 1x=кружка, 2x=полный");
Serial.println("Долгое наж (>3с) = отмена");
Serial.println("================================");
}
void loop() {
// Регулярная проверка наличия чайника
if (millis() - lastKettleCheck >= KETTLE_CHECK_INTERVAL) {
updateKettleStatus();
lastKettleCheck = millis();
}
// Получение веса ВОДЫ
float waterWeight = getWaterWeight();
// Проверка на переполнение
updateOverflowStatus(waterWeight);
// Обработка кнопки (только если чайник установлен и не переполнен)
if (kettlePresent && !kettleOverflow) {
checkButton(waterWeight);
} else {
// Если чайник сняли или переполнен во время налива - останавливаем
if (autoFillMode) {
Serial.println("Чайник снят или переполнен! Налив остановлен.");
cancelAutoFill();
}
}
// Автоматический режим налива
if (autoFillMode) {
handleAutoFill(waterWeight);
} else {
// Ручной режим управления
handleManualControl(waterWeight);
}
delay(100);
}
// Обновление статуса чайника
void updateKettleStatus() {
float currentWeight = scale.get_units(1);
bool oldKettlePresent = kettlePresent;
// Простая проверка: если вес меньше 1060г - чайника нет
if (currentWeight < EMPTY_SYSTEM_WEIGHT - 50.0f) { // Допуск 50г на шум
kettlePresent = false;
kettleOverflow = false; // При снятии чайника сбрасываем флаг переполнения
} else {
kettlePresent = true;
}
// Уведомление об изменении состояния
if (oldKettlePresent != kettlePresent) {
if (kettlePresent) {
Serial.println("Чайник обнаружен!");
} else {
Serial.println("Чайник снят!");
// Выключаем питание и помпу при снятии чайника
digitalWrite(POWER_PIN, LOW);
digitalWrite(POMPA_PIN, LOW);
}
}
}
// Проверка на переполнение чайника
void updateOverflowStatus(float waterWeight) {
bool oldOverflow = kettleOverflow;
if (waterWeight >= UPPER_THRESHOLD) {
kettleOverflow = true;
// При переполнении выключаем помпу на всякий случай
digitalWrite(POMPA_PIN, LOW);
} else if (waterWeight < UPPER_THRESHOLD - 100.0f) { // Гистерезис 100г
kettleOverflow = false;
}
// Уведомление об изменении состояния переполнения
if (oldOverflow != kettleOverflow) {
if (kettleOverflow) {
Serial.println("ПРЕДУПРЕЖДЕНИЕ: Чайник переполнен!");
Serial.println("Кнопка отключена до опустошения чайника.");
} else if (!kettleOverflow && oldOverflow) {
Serial.println("Чайник опустошен. Кнопка активирована.");
}
}
}
// Функция получения веса ВОДЫ (только если чайник присутствует)
float getWaterWeight() {
if (!kettlePresent) {
return 0.0f;
}
// Получаем общий вес
float totalWeight = scale.get_units(1);
// Вычитаем вес системы без воды (подставка + пустой чайник = 1060г)
float waterOnly = totalWeight - EMPTY_SYSTEM_WEIGHT;
// Не возвращаем отрицательные значения (шум) - используем тернарный оператор
return (waterOnly > 0.0f) ? waterOnly : 0.0f;
}
// Обработка кнопки (работает с весом воды)
void checkButton(float waterWeight) {
// Если чайника нет или переполнен - игнорируем кнопку
if (!kettlePresent || kettleOverflow) {
return;
}
int buttonState = digitalRead(BUTTON_PIN);
// Кнопка нажата
if (buttonState == LOW && !buttonPressed) {
buttonPressed = true;
buttonPressTime = millis();
}
// Кнопка отпущена
if (buttonState == HIGH && buttonPressed) {
buttonPressed = false;
unsigned long pressDuration = millis() - buttonPressTime;
// Игнорируем дребезг
if (pressDuration < 50) return;
// Определяем тип нажатия
if (millis() - lastButtonPress < 500) {
buttonClickCount = 2;
} else {
buttonClickCount = 1;
}
lastButtonPress = millis();
// Обработка нажатий (теперь по весу ВОДЫ)
if (waterWeight < 0.0f) {
Serial.println("Ошибка: Отрицательный вес воды!");
return;
}
if (pressDuration >= 3000) {
// Долгое нажатие - отмена
cancelAutoFill();
return;
}
if (pressDuration < 1000) {
if (waterWeight < POWER_THRESHOLD) {
// Воды от 0 до 500 г
if (buttonClickCount == 1) {
startAutoFillToTarget(POWER_THRESHOLD, 1); // До 0,5л
} else if (buttonClickCount == 2) {
startAutoFillToTarget(FULL_VOLUME, 2); // Полный (используем FULL_VOLUME)
}
} else {
// Воды больше 500 г
if (buttonClickCount == 1) {
startAutoFillByVolume(CUP_VOLUME, 3); // Кружка
} else if (buttonClickCount == 2) {
startAutoFillToTarget(FULL_VOLUME, 2); // Полный (используем FULL_VOLUME)
}
}
}
}
// Проверка долгого нажатия
if (buttonPressed && millis() - buttonPressTime >= 3000) {
cancelAutoFill();
buttonPressed = false;
}
}
// Запуск автоматического налива до целевого веса ВОДЫ
void startAutoFillToTarget(float target, byte mode) {
// Проверка наличия чайника и переполнения
if (!kettlePresent) {
Serial.println("Ошибка: Чайник не установлен!");
return;
}
if (kettleOverflow) {
Serial.println("Ошибка: Чайник переполнен!");
return;
}
if (autoFillMode) {
Serial.println("Налив уже идет!");
return;
}
float currentWater = getWaterWeight();
if (currentWater >= target) {
Serial.print("Уже достигнут! ");
Serial.print(currentWater);
Serial.print("г из ");
Serial.println(target);
return;
}
// Ограничиваем максимумом (используем UPPER_THRESHOLD для безопасности)
if (target > UPPER_THRESHOLD) {
target = UPPER_THRESHOLD;
}
initialWeight = currentWater;
targetWeight = target;
autoFillMode = true;
fillMode = mode;
Serial.println("=== НАЛИВ ===");
printMode(mode);
Serial.print("Начало: ");
Serial.print(initialWeight);
Serial.print("г воды");
Serial.print(" | Цель: ");
Serial.print(targetWeight);
Serial.print("г воды");
Serial.print(" (+");
Serial.print(targetWeight - initialWeight);
Serial.println("г)");
// Включаем помпу (только если чайник есть и не переполнен)
if (kettlePresent && !kettleOverflow) {
digitalWrite(POMPA_PIN, HIGH);
}
}
// Запуск автоматического налива по объему ВОДЫ
void startAutoFillByVolume(float volume, byte mode) {
// Проверка наличия чайника и переполнения
if (!kettlePresent) {
Serial.println("Ошибка: Чайник не установлен!");
return;
}
if (kettleOverflow) {
Serial.println("Ошибка: Чайник переполнен!");
return;
}
if (autoFillMode) {
Serial.println("Налив уже идет!");
return;
}
float currentWater = getWaterWeight();
float target = currentWater + volume;
// Ограничиваем максимумом (используем UPPER_THRESHOLD для безопасности)
if (target > UPPER_THRESHOLD) {
target = UPPER_THRESHOLD;
volume = target - currentWater;
}
initialWeight = currentWater;
targetWeight = target;
autoFillMode = true;
fillMode = mode;
Serial.println("=== НАЛИВ ===");
printMode(mode);
Serial.print("Начало: ");
Serial.print(initialWeight);
Serial.print("г воды");
Serial.print(" | Цель: ");
Serial.print(targetWeight);
Serial.print("г воды");
Serial.print(" (+");
Serial.print(volume);
Serial.println("г)");
// Включаем помпу (только если чайник есть и не переполнен)
if (kettlePresent && !kettleOverflow) {
digitalWrite(POMPA_PIN, HIGH);
}
}
// Обработка автоматического налива
void handleAutoFill(float waterWeight) {
static unsigned long lastUpdate = 0;
// Проверка наличия чайника и переполнения
if (!kettlePresent || kettleOverflow) {
Serial.println("Чайник снят или переполнен! Налив остановлен.");
cancelAutoFill();
return;
}
if (millis() - lastUpdate >= 1000) {
lastUpdate = millis();
Serial.print("Налив: ");
Serial.print(waterWeight, 0);
Serial.print("г/");
Serial.print(targetWeight, 0);
Serial.print("г (");
if (targetWeight > initialWeight) {
float progress = ((waterWeight - initialWeight) / (targetWeight - initialWeight)) * 100.0f;
Serial.print(progress, 0);
Serial.print("%");
}
Serial.println(")");
}
// Проверка завершения (используем UPPER_THRESHOLD для безопасности)
if (waterWeight >= targetWeight || waterWeight >= UPPER_THRESHOLD) {
finishAutoFill();
}
// Проверка безопасности (используем UPPER_THRESHOLD)
if (waterWeight < LOWER_THRESHOLD - 100.0f) { // Допуск -100г
Serial.println("Ошибка: Вес воды слишком мал!");
cancelAutoFill();
}
}
// Завершение автоматического налива
void finishAutoFill() {
digitalWrite(POMPA_PIN, LOW);
float finalWater = getWaterWeight();
float filledVolume = finalWater - initialWeight;
Serial.println("=== ЗАВЕРШЕНО ===");
printMode(fillMode);
Serial.print("Начало: ");
Serial.print(initialWeight);
Serial.println("г");
Serial.print("Конец: ");
Serial.print(finalWater);
Serial.println("г");
Serial.print("Налито воды: ");
Serial.print(filledVolume);
Serial.println("г");
autoFillMode = false;
fillMode = 0;
}
// Отмена автоматического налива
void cancelAutoFill() {
digitalWrite(POMPA_PIN, LOW);
if (autoFillMode) {
float currentWater = getWaterWeight();
float filledVolume = currentWater - initialWeight;
Serial.println("=== ОТМЕНА ===");
Serial.print("Успешно налито: ");
Serial.print(filledVolume);
Serial.println("г воды");
}
autoFillMode = false;
fillMode = 0;
}
// Ручное управление
void handleManualControl(float waterWeight) {
static unsigned long lastManualUpdate = 0;
if (millis() - lastManualUpdate >= 2000) {
lastManualUpdate = millis();
Serial.print("Статус: ");
Serial.print(kettlePresent ? "Чайник есть" : "НЕТ ЧАЙНИКА");
if (kettlePresent && kettleOverflow) {
Serial.print(" [ПЕРЕПОЛНЕН!]");
}
Serial.print(" | Вода: ");
if (kettlePresent) {
Serial.print(waterWeight, 0);
Serial.print("г");
if (waterWeight >= UPPER_THRESHOLD) {
Serial.print(" [MAX]");
}
} else {
Serial.print("---");
}
Serial.print(" | Насос: ");
Serial.print(digitalRead(POMPA_PIN) == HIGH ? "ВКЛ" : "ВЫКЛ");
// Управление питанием чайника (по весу ВОДЫ и наличию чайника)
if (kettlePresent && waterWeight > POWER_THRESHOLD && !kettleOverflow) {
digitalWrite(POWER_PIN, HIGH);
Serial.print(" | Питание: ВКЛ");
} else {
digitalWrite(POWER_PIN, LOW);
Serial.print(" | Питание: ВЫКЛ");
}
// Показываем доступные команды
Serial.print(" | Доступно: ");
if (!kettlePresent) {
Serial.print("Установите чайник");
} else if (kettleOverflow) {
Serial.print("Чайник наполнен. Кнопка отключена");
} else if (waterWeight < 0.0f) {
Serial.print("ОШИБКА");
} else if (waterWeight < POWER_THRESHOLD) {
Serial.print("1x=до 0,5л | 2x=полный");
} else if (waterWeight >= UPPER_THRESHOLD) {
Serial.print("Чайник наполнен. Кнопка отключена");
} else {
Serial.print("1x=кружка (");
Serial.print(CUP_VOLUME, 0);
Serial.print("г) | 2x=полный");
}
Serial.println();
}
}
// Вспомогательная функция для печати режима
void printMode(byte mode) {
switch(mode) {
case 1: Serial.println("Режим: До 0,5л воды"); break;
case 2: Serial.println("Режим: Полный чайник (1,7л)"); break;
case 3:
Serial.print("Режим: Кружка (");
Serial.print(CUP_VOLUME, 0);
Serial.println("г)");
break;
}
}