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