// файл: smart_pump_esp32.ino
#include "config.h"
#include "platform_config.h"
#include "ButtonHandler.h"
#include "ScaleHandler.h"
#include "PumpController.h"
#include "DisplayHandler.h"
#include "StateMachine.h"
#include "TuyaConfigHandler.h"
#include "TuyaDataHandler.h"
#include <EEPROM.h>
#include <WiFi.h>
// Учетные данные Tuya (из IoT Platform) (будут использоваться только на реальном железе)
#if defined(PLATFORM_REAL_HARDWARE)
const char* TUYA_PRODUCT_ID = "your_product_id_here";
const char* TUYA_DEVICE_ID = "your_device_id_here";
const char* TUYA_DEVICE_SECRET = "your_device_secret_here";
#else
// Заглушки для WOKWI
const char* TUYA_PRODUCT_ID = "wokwi_emulation";
const char* TUYA_DEVICE_ID = "wokwi_device_001";
const char* TUYA_DEVICE_SECRET = "wokwi_secret";
#endif
// ============= КОНЕЦ НОВОГО БЛОКА =============
// Глобальные объекты
ButtonHandler button(PIN_BUTTON);
ScaleHandler scale;
PumpController pump;
DisplayHandler display; // ИЗМЕНЕНО: теперь используем новый класс
StateMachine* stateMachine = nullptr;
// Глобальные переменные
unsigned long pressStartTime = 0;
bool countdownActive = false;
int lastDisplayedSeconds = -1;
bool wifiResetPhase = false;
// ============= НОВЫЙ БЛОК: объекты Tuya =============
// Вариант 2: для конфигурации через приложение
TuyaConfigHandler tuyaConfig;
TuyaDataHandler* tuyaData = nullptr;
// ============= КОНЕЦ НОВОГО БЛОКА =============
// Переменные для отслеживания состояния налива (нужны для дисплея)
float currentFillTarget = 0;
float currentFillStart = 0;
// ============= НОВЫЙ БЛОК: функция подключения WiFi (для варианта 1) =============
/*
void connectToWiFi() {
Serial.print("Connecting to WiFi");
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 40) {
delay(500);
Serial.print(".");
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\nWiFi connected");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
display.setWiFiStatus(true, true);
} else {
Serial.println("\nWiFi connection failed");
display.setWiFiStatus(true, false);
}
}
*/
// ============= КОНЕЦ НОВОГО БЛОКА =============
// -------------------------------------------------------------------
// Инициализация
// -------------------------------------------------------------------
void setup() {
Serial.begin(115200);
delay(100);
Serial.println("Smart Pump Starting...");
EEPROM.begin(512);
// ============= ИЗМЕНЕНО: инициализация дисплея =============
// Инициализация дисплея
display.begin();
display.setWiFiStatus(false, false); // По умолчанию WiFi не настроен
display.update(ST_INIT, ERR_NONE, false, 0, 0, 0, false, 0);
// ============= КОНЕЦ ИЗМЕНЕНИЙ =============
// ============= НОВЫЙ БЛОК: инициализация Tuya Config =============
// Инициализация Tuya Config (до WiFi)
tuyaConfig.begin();
// Обновляем статус WiFi на дисплее
display.setWiFiStatus(tuyaConfig.isNetworkConfigured(), tuyaConfig.isNetworkConnected());
// ============= КОНЕЦ НОВОГО БЛОКА =============
// ============= ИЗМЕНЕНО: сообщение на дисплее =============
display.update(ST_INIT, ERR_NONE, false, 0, 0, 0, false, 0);
// ============= КОНЕЦ ИЗМЕНЕНИЙ =============
// Инициализация весов
if (!scale.begin()) {
Serial.println("HX711 not found");
display.update(ST_ERROR, ERR_HX711_TIMEOUT, false, 0, 0, 0, false, 0);
stateMachine = new StateMachine(scale, pump, display);
stateMachine->toError(ERR_HX711_TIMEOUT);
} else {
scale.loadCalibrationFromEEPROM(0);
stateMachine = new StateMachine(scale, pump, display);
if (!scale.isReady()) {
display.setCalibrationMode(true);
display.update(ST_CALIBRATION, ERR_NONE, false, scale.getCurrentWeight(), 0, 0, false, 0);
stateMachine->toCalibration();
} else {
stateMachine->toIdle();
}
}
pump.begin();
pump.beepShort(1);
// ============= НОВЫЙ БЛОК: инициализация Tuya Data =============
// Инициализация обработчика данных Tuya (будет активен после подключения)
tuyaData = new TuyaDataHandler(scale, pump, *stateMachine);
// ============= КОНЕЦ НОВОГО БЛОКА =============
Serial.println("Setup complete.");
}
// -------------------------------------------------------------------
// Главный цикл
// -------------------------------------------------------------------
void loop() {
// 1. Обновляем состояние кнопки (всегда)
button.tick();
// ============= ОБНОВЛЕНИЕ СТАТУСА WIFI =============
static unsigned long lastWiFiStatusUpdate = 0;
if (millis() - lastWiFiStatusUpdate > 1000) {
lastWiFiStatusUpdate = millis();
display.setWiFiStatus(tuyaConfig.isNetworkConfigured(), tuyaConfig.isNetworkConnected());
}
// ============= ОБРАБОТКА СБРОСА С ОБРАТНЫМ ОТСЧЕТОМ =============
if (button.isPressed()) {
// Кнопка нажата - начинаем отсчет времени
if (pressStartTime == 0) {
pressStartTime = millis();
countdownActive = true;
lastDisplayedSeconds = -1;
wifiResetPhase = false;
}
unsigned long pressDuration = millis() - pressStartTime;
// Обновляем отсчет на дисплее (после 5 секунд)
if (pressDuration >= 5000) {
if (pressDuration < 10000) {
// Первая фаза: сброс калибровки (5-10 секунд)
int secondsLeft = 10 - (pressDuration / 1000);
if (secondsLeft < 0) secondsLeft = 0;
if (secondsLeft != lastDisplayedSeconds || wifiResetPhase) {
lastDisplayedSeconds = secondsLeft;
wifiResetPhase = false;
display.showResetCountdown(secondsLeft, false);
}
} else {
// Вторая фаза: сброс WiFi (10-15 секунд)
int secondsLeft = 15 - (pressDuration / 1000);
if (secondsLeft < 0) secondsLeft = 0;
if (secondsLeft != lastDisplayedSeconds || !wifiResetPhase) {
lastDisplayedSeconds = secondsLeft;
wifiResetPhase = true;
display.showResetCountdown(secondsLeft, true);
}
}
}
} else {
// Кнопка отпущена - проверяем, было ли достаточно долгое нажатие
if (countdownActive && pressStartTime > 0) {
unsigned long pressDuration = millis() - pressStartTime;
if (pressDuration >= 15000) {
// 15+ секунд: ПОЛНЫЙ СБРОС (калибровка + WiFi)
Serial.println("VERY LONG PRESS (>15s): Factory reset (calibration + WiFi)");
pump.beepLong(3);
// Показываем сообщение о сбросе
display.showResetMessage(true); // true = полный сброс
// 1. Сброс калибровки
scale.resetCalibration();
// 2. Сброс WiFi конфигурации Tuya
#if defined(PLATFORM_REAL_HARDWARE)
tuyaConfig.resetConfig();
#endif
// 3. Сброс настроек WiFi ESP32
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
delay(3000);
ESP.restart();
} else if (pressDuration >= 10000) {
// 10-15 секунд: СБРОС КАЛИБРОВКИ (WiFi сохраняется)
Serial.println("LONG PRESS (10-15s): Calibration reset only");
pump.beepLong(2);
// Показываем сообщение о сбросе калибровки
display.showResetMessage(false); // false = только калибровка
// Только сброс калибровки
scale.resetCalibration();
delay(2000);
ESP.restart();
}
}
// Сбрасываем переменные (только если не было перезагрузки)
pressStartTime = 0;
countdownActive = false;
lastDisplayedSeconds = -1;
wifiResetPhase = false;
}
// ============= ОБРАБОТКА LONG_PRESS ДЛЯ КОНФИГУРАЦИИ WIFI =============
static bool configModeActivated = false;
if (button.isLongPress() && !configModeActivated) {
if (!tuyaConfig.isNetworkConfigured()) {
Serial.println("Long press: Start EZ config mode");
display.update(ST_IDLE, ERR_NONE, scale.isKettlePresent(),
scale.getCurrentWeight(), 0, 0,
pump.isPowerRelayOn(), scale.getEmptyWeight());
pump.beepShort(2);
tuyaConfig.startEZConfig();
configModeActivated = true;
}
}
// Сброс флага при отпускании кнопки
if (!button.isPressed()) {
configModeActivated = false;
}
// ============= ОБНОВЛЕНИЕ КОНТРОЛЛЕРОВ =============
pump.update();
// ============= ОБНОВЛЕНИЕ TUYA =============
tuyaConfig.loop();
// Если Tuya подключилась к облаку, активируем обмен данными
static bool tuyaDataInitialized = false;
if (tuyaConfig.isNetworkConnected() && !tuyaDataInitialized) {
if (tuyaData) {
tuyaData->begin(TUYA_PRODUCT_ID, TUYA_DEVICE_ID);
tuyaDataInitialized = true;
pump.beepShort(1);
}
}
// Обновление обмена данными Tuya
if (tuyaData && tuyaDataInitialized) {
tuyaData->loop();
}
// ============= ОБНОВЛЕНИЕ КОНЕЧНОГО АВТОМАТА =============
if (stateMachine != nullptr) {
stateMachine->update();
stateMachine->handleButton(button);
}
// ============= ОБНОВЛЕНИЕ ДИСПЛЕЯ =============
static unsigned long lastDisplayUpdate = 0;
if (millis() - lastDisplayUpdate > 200) { // Обновляем дисплей 5 раз в секунду
lastDisplayUpdate = millis();
// Получаем текущее состояние из stateMachine
SystemState currentState = ST_IDLE;
ErrorType currentError = ERR_NONE;
if (stateMachine != nullptr) {
currentState = stateMachine->getCurrentStateEnum();
currentError = stateMachine->getCurrentError();
// Обновляем цели налива, если мы в режиме FILLING
if (currentState == ST_FILLING) {
// Здесь можно получить цели из stateMachine
// Для этого нужно добавить методы в StateMachine
}
}
// Обновляем дисплей
display.update(currentState, currentError, scale.isKettlePresent(),
scale.getCurrentWeight(), currentFillTarget, currentFillStart,
pump.isPowerRelayOn(), scale.getEmptyWeight());
}
// ============= ОБРАБОТКА КОМАНД ИЗ СЕРИЙНОГО ПОРТА =============
tuyaConfig.processSerialCommands();
delay(LOOP_DELAY);
}