// Библиотеки
 #include <Arduino.h>
 #include <ESP8266WiFi.h>
 #include <PZEM004Tv30.h>
 #include <SoftwareSerial.h>
 #include <PubSubClient.h>
 #include <EncButton.h>
 #include "ASOLED_ESP8266.h"
 #include "Stab_config.h"
 #include <ArduinoOTA.h> 
 #include <ESP8266mDNS.h>
 #include <WiFiUdp.h>
 #include <ESPAsyncWebServer.h>
 #include <EEPROM.h>
 #include "Stab_config.h"
 #include "web_pages.h"
// Переменные
 float voltage = 0, current = 0,  energy= 0;  // Измеряемые значения
 uint16_t power = 0;                          // Мощность текущая 
 int16_t powerSetpoint = 0;                   // Задание мощности
 uint16_t maxPower = PMAX_TEN;                // максимально достигнутая
 volatile int16_t  PowerLevel = 0;            // Текущий уровень регулировки 0-1023
 volatile const int16_t  MaxPowerLevel = 1023;// Максимальный уровень регулировки 1023 (не трогать)
 bool insufficientVoltage = false;            // Флаг недостаточного напряжения
 bool boostLoadActive = false;                // Флаг для блокировки регулировки на время прогрузки
 // PZEM
 bool PzemEnClear = false;                    // Флаг команды на обнуление счетчика
 EspSoftwareSerial::UART SSer; 
 PZEM004Tv30 pzem(SSer);
 // Для фильтра
 const int powerHistorySize = 10;                  // Количество измерений за 10 секунд 
 static float powerHistory[powerHistorySize];      // Массив для хранения значений мощности
 static int powerHistoryIndex = 0;                 // Индекс текущего значения в массиве
 static float averagePower = 0;                    // Среднее значение мощности за последние 10 секунд
 // Регулятор (triak.ino)
 bool REG = true;                                  // Флаг работы регулятора
 byte stabilizeMode = STABILIZE_MODE;              // По умолчанию стабилизировать 0-мощность, 1-напряжение
 bool filterEnabled = PZEM_FILTR;                  // Фильтр значений мощности
 int16_t PowerLevel_ust;                           // Уставка для регулировки по напряжению
 volatile bool boostMode = false;                  // Флаг режима разгона
 bool heatEnabled = false;                         // Флаг включения нагрева
 bool TrueMAXPowerTEN=(PMAX_TEN == 0);             // Флаг действительности номинала ТЭНа
 volatile byte mode = MODE;                        // Режим работы 0 - ШИМ, 1 - сортировка полуволн, 2 - фазовый регулятор.
 unsigned long timeReg = DEFAULT_TIME_REG;         // Период регулирования 
 int K_upr = DEFAULT_K_UPR;                        // Коэффициент воздействия
 // Датчик пересечения с 0
 volatile unsigned long zeroCrossTime = 0;         // Время последнего срабатывания детектора нуля
 volatile unsigned long lastZeroCrossTime = 0;     // Время предыдущего пересечения с нулём
 const unsigned long debounceDelay = 80;           // Задержка для защиты от дребезга детектора нуля (мкс)
 volatile bool zeroCrossDetected = false;          // Флаг срабатывания детектора нуля
 // Переменные для управления SSR-40DA
 volatile bool ssrTriggered = false;               // Флаг для открытия SSR
 volatile int8_t ps = 0;                           // Постоянная составляющая
 volatile int32_t PowerLevel_err = 0;              // Ошибка дискретизации
 // Переменные для управления семистором
 volatile long delayTime = 8000;                   // Задержка для открытия симистора (по умолчанию максимальная)
 long delayTimeDisplacement = DELAY_TIME_DISPLACEMENT; // Смещение, компенсирующее неточность датчика пересечения с 0
 volatile bool triacTriggered = false;             // Флаг для открытия симистора
 volatile unsigned long triacTurnOffTime = 0;      // Время выключения симистора
 bool interrTriacFlag=true;                        // Флаг триггера назначения прерывания задержки открытия симистора при включении нагрева
 unsigned long delayLow = DEFAULT_DELAY_LOW;       // Нижний предел задержки открытия симистора (2000)
 unsigned long delayHigh = DEFAULT_DELAY_HIGH;     // Верхний предел задержки открытия симистора (8000)
 // Переменные для ШИМ
 int pwmFreq = DEFAULT_PWM_FREQ;                   // Частота ШИМ
 // Переменные для кнопки (button.ino)
 #ifdef ENABLE_BUTTON
 Button btn(BUTTON_PIN);             // Указываем пин кнопки
 bool modeUP = true;                 // Режим изменения мощности (UP/DW)
 unsigned long pressStartTime = 0;   // Время начала нажатия
 #endif

 // Параметры MQTT
 byte mqtt_attempts = MQTT_ATTEMPTS; // Количество попыток подключения при инициализации
 bool mqtt_reconnection = MQTT_RECONNECTION; // Пытаться ли переподключаться при потере соединения
 unsigned long mqtt_time_publication = MQTT_TIME_PUBLICATION; // Период публикаций 
 String mqtt_server = MQTT_SERVER;
 uint mqtt_port = MQTT_PORT;
 String mqtt_user = MQTT_USER;
 String mqtt_password = MQTT_PASSWORD;
 String mqtt_topic_power = MQTT_TOPIC_POWER; // Топик для установки мощности
 String mqtt_topic_boost = MQTT_TOPIC_BOOST; // Топик для включения/выключения режима разгона
 String mqtt_topic_heat = MQTT_TOPIC_HEAT;   // Топик для включения/выключения нагрева
 String mqtt_topic_status = MQTT_TOPIC_STATUS;   // Топик для публикации статуса

 // USART с Самоваром
 String pendingResponse = "";              // Ответ, который нужно отправить с задержкой

 // Прогрузка ТЭНа в web_server.ino
 int16_t powerSetpointT;                   // Запомнить на время прогрузки ТЭНа
 unsigned long boostLoadStartTime = 0;     // Время начала прогрузки
 uint16_t boostLoadMaxPower;               // Максимальная мощность, зафиксированная во время прогрузки
 bool heatEnabledBoost;                    // Флаг на время прогрузки ТЭНа запомнить состояние нагрева
 int16_t PowerLevelT;                      // Запомнить на время разгона
 // WiFi
 String savedSSID, savedPass;
 bool hotspotMode = false;
 long RSSI = 0;
 String localIP = "";
 WiFiClient espClient;                 // Объект WiFi
 PubSubClient mqttClient(espClient);   // Объект MQTT
 AsyncWebServer server(80);            // Объект вэбсервера
// Функции
void boost(bool SW);
// Запуск...
void setup() {
  initOLED();                   // Инициализация дисплея
  InitButton();                 // Инициализация кнопок
  Serial.begin(9600);
  PZEMInit();                   // Инициализация измерений
  Serial.println("/rStart...");
                                // Инициализация пинов 
   pinMode(boostPin, OUTPUT);  digitalWrite(boostPin, LOW);
   pinMode(heatPin, OUTPUT);   digitalWrite(heatPin, LOW);
   initWiFi();                  // Загрузка сохраненных данных и  запуск Wi-Fi
   setupWebServer();            // Запуск веб-сервера
   initOTA();                   // Запуск ОТА
   setupMQTT();                 // запуск MQTT
   StaticOLEDDisplay();         // Выводим на дисплей статические названия полей
   zeroCrossInit();             // Инициализация датчика пересечения с 0
}
//Основной цикл
void loop() { 
    
 MDNS.update(); ArduinoOTA.handle(); // Обработка ОТА
 USART_GetRequests();                // Принимаем по UART запросы от Самовара
 USART_SendReport();                 // Отправляем ответ Самовару по UART
 loopMQTT();                         // Обработка MQTT
 ButtonLoop();                       // Обработка кнопки

  static unsigned long timeout = millis(); //остальное раз в секунду
  if (millis() - timeout >= 1000) { 
    #ifdef ENABLE_WEB_SERVER
     //установка флага невозможности выполнить задачу по мощности
     insufficientVoltage = (powerSetpoint > maxPower && PowerLevel == MaxPowerLevel);
    #endif // ENABLE_WEB_SERVER
    loopWIFI();                            // проверка работы WiFi и реконнект
    PZEMLoop();                            // Читаем параметры электросети
    triakLoop();                           // Обработка симистора
    updateOLEDDisplay();                   // Выводим инфу на дисплей
    timeout = millis();                    // перезапуск секундного таймера
  }    
}