//------------Сеть
  extern "C" {
  #include "esp_wifi.h"
  }
  #include <WiFiUdp.h>
  WiFiUDP udpLogger;
  String LoggerIPStr;
  bool LoggerEnable = false;
  bool norunLogger = false;

  void UdpLoggerStart() {
    if (SamSetup.udpLPort != 0)
    udpLogger.begin(SamSetup.udpLPort);
  }
  void UdpLoggerStop() {
    udpLogger.stop();
    mqttClient.disconnect(true);
    mqttClient.clearQueue();
    initMqtt();
    norunLogger = false;
  }  
  void UdpLoggerLoop() {//Определение наличия Регистратора и получение от него текущего времени если NTP его получить не может
    if (norunLogger || SamSetup.udpLPort == 0) return;
    //DbgMsg("UDP logger loop.");
    if (udpLogger.parsePacket() > 0) {
    char buf[64];
    int len = udpLogger.read(buf, 63);
      if (len > 0) {
        buf[len] = 0;
        String lastMsg = String(buf);
        DbgMsg("UDP logger: " + lastMsg);
        unsigned long epochTime = NTP.getEpochTime();
        if (lastMsg.startsWith("SamovarichLogger")) {
          IPAddress LoggerIP = udpLogger.remoteIP();
          LoggerIPStr = LoggerIP.toString();
          LoggerEnable = true;
          if (epochTime > 1500000000) {
            udpLogger.stop();
            norunLogger = true;
            DbgMsg("UDP logger searsh stopped.");
          }
          static bool mqtt_fl = true;
          if (mqtt_fl) {//только раз
            mqttClient.disconnect(true);
            mqttClient.clearQueue();
            mqttClient.setServer(LoggerIPStr.c_str(), SamSetup.MQTT_Port);
            DbgMsg("MQTT logger detected:" + LoggerIPStr);
            delay(50);
            connectToMqtt();
            mqtt_fl=false;
          }
        } else if (epochTime<1500000000 && lastMsg.startsWith("EpochTime:")) {
          int colonIndex = lastMsg.indexOf(':');
          if (colonIndex != -1) {
            String epochStr = lastMsg.substring(colonIndex + 1); // Берём всё что после двоеточия до конца строки
            epochTime = epochStr.toInt();
            if (epochTime > 1500000000) {
              NTP.setEpochTime(epochTime);
              Serial.println("Set time from Logger:" + NTP.getFormattedDate());
            }
          }
        }
      }
    }
  }
  void PauseForConnect(int timeout = 15000) {
    uint32_t startTime = millis();
    while (WiFi.status() != WL_CONNECTED && millis() - startTime < timeout) {
      Serial.print(".");
      delay(500);
    }
  }
  String current_pass;
  bool connectToWiFi(const char* ssid, const char* password, const char* attemptName, int timeout = 15000) {
      Serial.print(attemptName);
      Serial.println(ssid);
      //Serial.println(password);
      WiFi.disconnect(true);
      delay(1000);
      WiFi.mode(WIFI_OFF);
      delay(500);
      WiFi.mode(WIFI_STA);
      WiFi.persistent(false);
      WiFi.begin(ssid, password);
      PauseForConnect(timeout);
      WiFi.persistent(true);
      Serial.println();
      if (WiFi.status() == WL_CONNECTED) current_pass = String(password);
      return (WiFi.status() == WL_CONNECTED);
  }

  // Максимальное количество сетей
  #define MAX_NETWORKS 10

  struct WiFiNetwork {
      char ssid[17];      // Максимум 32 символа для SSID
      char password[33];  // Максимум 64 символа для пароля
      int32_t rssi;
      bool configured;
      bool available;
  };

  // Динамическое выделение памяти
  WiFiNetwork* networks = nullptr;
  int networkCount = 0;


  int parseWiFiConfig() {// Функция для парсинга wifi.ini
      // Освобождаем предыдущую память
      if (networks != nullptr) {
          delete[] networks;
          networks = nullptr;
      }
      networkCount = 0;
      
      if (!LittleFS.exists("/wifi.ini")) {
          Serial.println("wifi.ini not found");
          return 0;
      }
      
      File file = LittleFS.open("/wifi.ini", "r");
      if (!file) {
          Serial.println("Failed to open wifi.ini");
          return 0;
      }
      
      // Сначала посчитаем количество сетей в файле
      int tempCount = 0;
      while (file.available()) {
          String line = file.readStringUntil('\n');
          line.trim();
          
          if (line.length() == 0 || line.startsWith("#")) {
              continue;
          }
          
          int separatorPos = line.indexOf(' ');
          if (separatorPos > 0) {
              tempCount++;
          }
      }
      
      if (tempCount == 0) {
          file.close();
          return 0;
      }
      
      // Ограничиваем максимальным количеством
      networkCount = (tempCount > MAX_NETWORKS) ? MAX_NETWORKS : tempCount;
      
      // Выделяем память только под нужное количество сетей
      networks = new WiFiNetwork[networkCount];
      if (networks == nullptr) {
          Serial.println("Failed to allocate memory for networks");
          file.close();
          networkCount = 0;
          return 0;
      }
      
      // Возвращаемся к началу файла
      file.seek(0);
      
      int index = 0;
      while (file.available() && index < networkCount) {
          String line = file.readStringUntil('\n');
          line.trim();
          
          if (line.length() == 0 || line.startsWith("#")) {
              continue;
          }
          
          int separatorPos = line.indexOf(' ');
          if (separatorPos > 0) {
              String ssidStr = line.substring(0, separatorPos);
              String passStr = line.substring(separatorPos + 1);
              
              if (ssidStr.length() > 0 && ssidStr.length() <= 32) {
                  // Копируем в структуру
                  ssidStr.toCharArray(networks[index].ssid, 
                                    sizeof(networks[index].ssid));
                  passStr.toCharArray(networks[index].password, 
                                    sizeof(networks[index].password));
                  networks[index].rssi = -1000;
                  networks[index].configured = true;
                  networks[index].available = false;
                  
                  index++;
                  Serial.printf("Found network #%d: %s\n", index, ssidStr.c_str());
              }
          }
      }
      
      file.close();
      Serial.printf("Parsed %d networks from wifi.ini\n", networkCount);
      return networkCount;
  }

  // Функция высвобождения памяти
  void freeNetworksMemory() {
      if (networks != nullptr) {
          delete[] networks;
          networks = nullptr;
      }
      networkCount = 0;
  }

  // Функция сканирования сетей (используется только если сетей > 1)
  bool scanSpecificNetworks() {
      Serial.println("Starting targeted WiFi scan...");
      
      // Сбрасываем флаги доступности
      for (int i = 0; i < networkCount; i++) {
          networks[i].available = false;
          networks[i].rssi = -1000;
      }
      
      // Для каждой сети выполняем целевое сканирование
      int foundCount = 0;
      
      for (int i = 0; i < networkCount; i++) {
          // Сканируем конкретную сеть
          int scanResult = WiFi.scanNetworks(false, false, false, 300, 0, networks[i].ssid);
          
          if (scanResult == 1) {
              // Найдена искомая сеть
              networks[i].rssi = WiFi.RSSI(0);
              networks[i].available = true;
              foundCount++;
              
              Serial.printf("✓ Found: %s (RSSI: %d dBm)\n", 
                          networks[i].ssid, networks[i].rssi);
          } else if (scanResult == 0) {
              // Сеть не найдена
              Serial.printf("✗ Not found: %s\n", networks[i].ssid);
          } else if (scanResult < 0) {
              // Ошибка сканирования
              Serial.printf("! Scan error for %s: %d\n", networks[i].ssid, scanResult);
          }
          
          // Очищаем результаты после каждого сканирования
          WiFi.scanDelete();
          
          // Небольшая пауза между сканированиями
          if (i < networkCount - 1) {
              delay(100);
          }
      }
      
      Serial.printf("Targeted scan complete. Found %d/%d networks\n", foundCount, networkCount);
      return (foundCount > 0);
  }

  // Простая сортировка по RSSI (используется только если сетей > 1)
  void sortNetworksByRSSI() {
      // Сортировка пузырьком
      for (int i = 0; i < networkCount - 1; i++) {
          for (int j = i + 1; j < networkCount; j++) {
              // Сначала доступные сети, потом недоступные
              bool iAvailable = networks[i].available;
              bool jAvailable = networks[j].available;
              
              if ((!iAvailable && jAvailable) || 
                  (iAvailable && jAvailable && networks[j].rssi > networks[i].rssi)) {
                  // Меняем местами
                  WiFiNetwork temp = networks[i];
                  networks[i] = networks[j];
                  networks[j] = temp;
              }
          }
      }
  }

  // Подключение к одной сети 
  bool connectToSingleWiFi() {
      if (networkCount != 1 || networks == nullptr) {
          return false;
      }
      
      Serial.println("\n=== Single Network Connection ===");
      Serial.printf("Attempting to connect to: %s\n", networks[0].ssid);
      
      if (connectToWiFi(networks[0].ssid, networks[0].password, "Single network: ", 15000)) {
          Serial.printf("✓ Success! Connected to: %s\n", networks[0].ssid);
          Serial.printf("  IP Address: %s\n", WiFi.localIP().toString().c_str());
          return true;
      }
      
      Serial.printf("✗ Failed to connect to: %s\n", networks[0].ssid);
      return false;
  }

  // Функция подключения к лучшей сети (для 2+ сетей)
  bool connectToBestWiFiMultiple() {
      if (networkCount < 2) {
          return false;
      }
      
      Serial.println("\n=== Smart WiFi Connection (Multiple Networks) ===");
      
      // Сканируем сети
      bool scanSuccess = scanSpecificNetworks();
      
      // Сортируем сети (доступные с лучшим RSSI первыми)
      sortNetworksByRSSI();
      
      // Пробуем подключиться к найденным сетям
      for (int i = 0; i < networkCount; i++) {
          if (!networks[i].available) {
              continue;
          }
          
          Serial.printf("\n[%d/%d] Connecting to: %s (RSSI: %d dBm)\n", 
                      i+1, networkCount, networks[i].ssid, networks[i].rssi);
          
          String attemptName = String("WiFi ") + String(i+1) + ": ";
          
          if (connectToWiFi(networks[i].ssid, networks[i].password, attemptName.c_str(), 15000)) {
              Serial.printf("✓ Success! Connected to: %s\n", networks[i].ssid);
              Serial.printf("  IP Address: %s\n", WiFi.localIP().toString().c_str());
              return true;
          }
          
          Serial.printf("✗ Failed to connect to: %s\n", networks[i].ssid);
          delay(2000);
      }
      
      // Если не нашли сети при сканировании, пробуем прямое подключение
      Serial.println("\nNo networks found during scan, trying direct connection...");
      
      for (int i = 0; i < networkCount; i++) {
          Serial.printf("\nDirect attempt %d/%d: %s\n", 
                      i+1, networkCount, networks[i].ssid);
          
          String attemptName = String("Direct ") + String(i+1) + ": ";
          
          if (connectToWiFi(networks[i].ssid, networks[i].password, attemptName.c_str(), 10000)) {
              Serial.printf("✓ Direct connection successful: %s\n", networks[i].ssid);
              return true;
          }
          delay(1000);
      }
      
      Serial.println("Failed to connect to any configured network");
      return false;
  }

  // Основная функция для использования
  bool autoConnectToWiFi() {
      Serial.println("\n=== Auto WiFi Connection ===");
      
      // 1. Читаем конфигурацию
      if (parseWiFiConfig() == 0) {
          Serial.println("No networks in wifi.ini");
          return false;
      }
      
      bool connected = false;
      
      // 2. В зависимости от количества сетей выбираем стратегию
      if (networkCount == 1) {
          // Для одной сети - сразу подключаемся
          connected = connectToSingleWiFi();
      } else {
          // Для нескольких сетей - сканируем и выбираем лучшую
          connected = connectToBestWiFiMultiple();
      }
      
      // 3. Освобождаем память после использования
      freeNetworksMemory();
      
      return connected;
  }

  void InitWiFi(bool wifiAP) {
    /*
      wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
      cfg.rx_ba_win = 4;                    // Было 6
      cfg.cache_tx_buf_num = 2;             // МИНИМУМ: было 8-16  
      cfg.tx_buf_type = 0;                  // 0 = только динамические буферы
      cfg.static_tx_buf_num = 0;            // 0 = нет статических буферов
      cfg.dynamic_tx_buf_num = 4;           // Минимум динамических
      cfg.wifi_task_core_id = 1;            // Фиксировать ядро
      cfg.beacon_max_len = 100;             // Уменьшить макс. длину beacon
      esp_wifi_init(&cfg);
    */
    if (!wifiAP) {// изо всех сил пытаемся подключиться к wi-fi
        WiFi.mode(WIFI_STA);
        WiFi.setSleep(WIFI_PS_NONE);
        WiFi.setHostname(host);
        WiFi.setAutoReconnect(true);
        
        bool connected = false;
        
        #if BOARD == ESP32S3
            WiFi.setTxPower(WIFI_POWER_17dBm); 
        #endif      
        
        // 1. Автоподключение из wifi.ini со сканированием
        Serial.println("Attempt 1: Auto-connect from wifi.ini");
        connected = autoConnectToWiFi();
        if (connected) {
          // В случае успешного Autoconnect запоминаем сеть в NVS, на случай если в следующий раз wifi.ini пропадет (запишется только в случае изменения)
          strlcpy(SamSetup.SavedSSID, WiFi.SSID().c_str(), sizeof(SamSetup.SavedSSID));
          strlcpy(SamSetup.SavedPASS, current_pass.c_str(), sizeof(SamSetup.SavedPASS));
          save_profile();
        }
        // 2. Стандартная попытка (последняя сохраненная)
        if (!connected) {
            Serial.println("Attempt 2: Previous WiFi connection");
            if (SamSetup.disp == 2) {
                LD.printString_6x8("Saved SSID          ", 1, 6);
            }
            WiFi.begin(); // Пробуем предыдущее подключение
            PauseForConnect(10000);
            connected = (WiFi.status() == WL_CONNECTED);
        }
        
        // 3. Попытка из NVS
        if (!connected && SamSetup.SavedSSID[0] != '\0') {
            Serial.println("Attempt 3: NVS credentials");
            if (SamSetup.disp == 2) {
                LD.printString_6x8(("NVS SSID " + String(SamSetup.SavedSSID)).c_str(), 1, 6);
            }
            
            #if BOARD == ESP32S3
                WiFi.setTxPower(WIFI_POWER_15dBm); 
            #endif
            
            connected = connectToWiFi(SamSetup.SavedSSID, SamSetup.SavedPASS, 
                                     "Connecting to NVS WiFi ");
        }
        
        // 4. Попытка с сетью по умолчанию
        if (!connected) {
            Serial.println("Attempt 4: Default network");
            if (SamSetup.disp == 2) {
                LD.printString_6x8(("Def.SSID " + String(defaultSSID)).c_str(), 1, 6); 
            }
            connected = connectToWiFi(defaultSSID, defaultPass, 
                                     "Connecting to default WiFi ");
        }
        
        if (connected) {
            // Отображение информации о подключении
            StIP = WiFi.localIP().toString();
            
            if (SamSetup.disp == 2) {
                LD.printString_6x8((StIP + "    ").c_str(), 1, 7); 
                LD.printString_6x8("R    ", 1+16*6, 7);
                LD.printString_6x8("                    ", 1, 6);
                LD.printString_6x8((WiFi.SSID()).c_str(), 1, 6);
            }
            
            if (SamSetup.disp == 1) {
                LQ.setCursor(0,1);
                LQ.print("Connected to " + WiFi.SSID());
                LQ.setCursor(0,2);
                LQ.print("IP: " + StIP);
            }
            
            Serial.println();
            Serial.print(F("Connected to ")); 
            Serial.println(WiFi.SSID());    
            Serial.print(F("IP ")); 
            Serial.println(StIP);   
        }
    }
    
    #if BOARD == ESP32S3
        WiFi.setTxPower(WIFI_POWER_19dBm); 
    #endif
    
    // Если не удалось подключиться, запускаем AP режим
    if (WiFi.status() != WL_CONNECTED) {
        Serial.println("All connection attempts failed, starting AP mode");
        WiFi.setAutoReconnect(false); 
        WiFi.mode(WIFI_AP);
        WiFi.softAP("Samovarich", WiFiAP_Pass);
        
        Serial.println();
        Serial.println(F("Started as WiFi AP"));
        Serial.print(F("Name: Samovarich, Pass: ")); 
        Serial.print(WiFiAP_Pass); 
        Serial.println(F(",IP: 192.168.4.1"));
        
        StIP = "192.168.4.1";
        if (SamSetup.disp == 2) {
            LD.printString_6x8(F("IP: 192.168.4.1     "), 1, 7);
        } 
        if (SamSetup.disp == 1) {
                LQ.setCursor(0,1);
                LQ.print("AP MODE.            ");
                LQ.setCursor(0,2);
                LQ.print("IP: 192.168.4.1     ");
            }
    } else {
        WiFi.setAutoReconnect(true); 
    }
  }
  void reset_wifi() {
    WiFi.disconnect(true);
    vTaskDelay(200/portTICK_PERIOD_MS);
    //Сбрасываем сохраненные настройки WiFi и переподключаемся
    InitWiFi(false);
  }
  void InitOTA() {
    if (SamSetup.OTA) {
        //Send OTA events to the browser
        ArduinoOTA.onStart([]() {
          if (SamSetup.PwrType == UDP_Pr ) UDPStab_Stop();
          String type;
          if (ArduinoOTA.getCommand() == U_FLASH)
            type = "Sketch";
          else {  // U_SPIFFS
            type = "Filesystem";
            SPIFFS.end();
          }
          type = type + " update start";
          events.send(type.c_str(), "ota");
        });
        ArduinoOTA.onEnd([]() {
          events.send(("Update End"), "ota");
        }); 
        ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
          //char p[32];
          //sprintf(p, "Progress: %u%%\n", (progress / (total / 100)));
          events.send(".", "ota");
        }); 
        ArduinoOTA.onError([](ota_error_t error) {
          if (error == OTA_AUTH_ERROR) events.send("Auth Failed", "ota");
          else if (error == OTA_BEGIN_ERROR)
            events.send(("Begin Failed"), "ota");
          else if (error == OTA_CONNECT_ERROR)
            events.send(("Connect Failed"), "ota");
          else if (error == OTA_RECEIVE_ERROR)
            events.send(("Recieve Failed"), "ota");
          else if (error == OTA_END_ERROR)
            events.send(("End Failed"), "ota");
        });
        ArduinoOTA.setHostname(SAMOVAR_HOST);
        ArduinoOTA.begin(); DbgMsg("OTA started.",1);
    }
  }
  void InitTelegram(){
    if (SamSetup.UseTg) {
      if (WiFi.status() == WL_CONNECTED && SamSetup.tg_token[0] != 0 && SamSetup.tg_chat_id[0] != 0) {
        delay(50);
        //DbgMsg("Ping..");
        //if (Ping.ping("212.237.16.93", 1)) {
        //  delay(50);
          DbgMsg("TG send Hello..");
          http_sync_request_get(String("/bot") + String(SamSetup.tg_token) + "/sendMessage?chat_id=" + SamSetup.tg_chat_id + "&text=" + urlEncode("Самоварыч готов к работе; IP=http://" + StIP), "212.237.16.93");
        //} else Serial.println(F("Error ping 212.237.16.93."));      
      } else if (SamSetup.tg_chat_id[0] != 0) {
        Serial.println(F("Проблема с отправкой сообщений в Телеграм."));
        }
      delay(50);
    }
  }
  void InitBlynk(){
    //Blynk.disconnect();
    if (SamSetup.UseBlynk) {
    DbgMsg(F("Connecting to Blynk"),1);  
    if (SamSetup.blynkauth[0] != 0 && WiFi.status() == WL_CONNECTED) {
    if (SamSetup.BlynkUrl[0]!='/r')
      Blynk.config(SamSetup.blynkauth, SamSetup.BlynkUrl, 8080);
    else
        Blynk.config(SamSetup.blynkauth); //"blynk-cloud.com" 80
    } 
        Blynk.connect(BLYNK_TIMEOUT_MS);
      DbgMsg(F("Blynk started"),1);
    }
  }
//------------Blynk
  void pause_withdrawal(bool PauseOn);
  void set_current_power(float power);
  void set_pump_speed(float speed, bool msg);
  void set_body_temp();
  String get_Samovar_Status();
  float get_speed_from_rate(float rate, uint16_t s_per_l);
  String get_beer_program();
  String get_dist_program();
  String get_nbk_program();
  String get_program(uint8_t s);

  #include <BlynkSimpleEsp32.h>
    String run_lua_string(String lstr);
    WidgetTerminal terminal(V22);

    BLYNK_WRITE(V22) {
      if (SamSetup.LUA) {
        String lstr = param.asStr();  // assigning incoming value from pin V22 to a variable
        terminal.println(lstr);
        lstr = run_lua_string(lstr);
        if (lstr.length() > 0) {
          terminal.println("ERR in lua: " + lstr);
        }
        else {
          terminal.println(F("Lua run complete"));
        }
        terminal.flush();
      }
    }
  BLYNK_READ(V0) {
    static bool inReadHandler = false;
    if (inReadHandler) return; // Предотвращаем рекурсию
    inReadHandler = true;
    vTaskDelay(2 / portTICK_PERIOD_MS);
    Blynk.virtualWrite(V0, SteamSensor.avgTemp);
    vTaskDelay(2 / portTICK_PERIOD_MS);
    Blynk.virtualWrite(V4, PowerOn);
    int i;
    int k;
    if (startval > 0 && startval < 5)
      i = 1;
    else
      i = 0;
    Blynk.virtualWrite(V3, i);
    vTaskDelay(2 / portTICK_PERIOD_MS);
    if (PauseOn)
      k = 1;
    else
      k = 0;
    Blynk.virtualWrite(V13, k);
    inReadHandler = false;
  }
  BLYNK_READ(V1) {
    static bool inReadHandler = false;
    if (inReadHandler) return;
    inReadHandler = true;
    Blynk.virtualWrite(V1, PipeSensor.avgTemp);
    inReadHandler = false;
  }
  BLYNK_READ(V25) {
    static bool inReadHandler = false;
    if (inReadHandler) return;
    inReadHandler = true;
    Blynk.virtualWrite(V25, ACPSensor.avgTemp);
    inReadHandler = false;
  }
  BLYNK_READ(V2) {
    static bool inReadHandler = false;
    if (inReadHandler) return;
    inReadHandler = true;
    Blynk.virtualWrite(V2, WthdrwlProgress);
    inReadHandler = false;
  }
  BLYNK_READ(V5) {
    static bool inReadHandler = false;
    if (inReadHandler) return;
    inReadHandler = true;
    Blynk.virtualWrite(V5, bme_pressure);
    inReadHandler = false;
  }
  BLYNK_READ(V6) {
    static bool inReadHandler = false;
    if (inReadHandler) return;
    inReadHandler = true;
    Blynk.virtualWrite(V6, WaterSensor.avgTemp);
    inReadHandler = false;
  }
  BLYNK_READ(V7) {
    static bool inReadHandler = false;
    if (inReadHandler) return;
    inReadHandler = true;
    Blynk.virtualWrite(V7, TankSensor.avgTemp);
    inReadHandler = false;
  }
  BLYNK_READ(V8) {
    static bool inReadHandler = false;
    if (inReadHandler) return;
    inReadHandler = true;
    Blynk.virtualWrite(V8, get_liquid_volume_by_step(stepper.getCurrent()));
    inReadHandler = false;
  }
  BLYNK_READ(V9) {
    static bool inReadHandler = false;
    if (inReadHandler) return;
    inReadHandler = true;
    Blynk.virtualWrite(V9, ActualVolumePerHour);
    inReadHandler = false;
  }
  BLYNK_READ(V10) {
    static bool inReadHandler = false;
    if (inReadHandler) return;
    inReadHandler = true;
    Blynk.virtualWrite(V10, WthdrwTimeS + "; " + WthdrwTimeAllS);
    inReadHandler = false;
  }
  BLYNK_READ(V11) {
    static bool inReadHandler = false;
    if (inReadHandler) return;
    inReadHandler = true;
    Blynk.virtualWrite(V11, StrCrt);
    inReadHandler = false;
  }
  BLYNK_READ(V14) {
    static bool inReadHandler = false;
    if (inReadHandler) return;
    inReadHandler = true;
    Blynk.virtualWrite(V14, get_Samovar_Status());
    inReadHandler = false;
  }
  BLYNK_READ(V15) {
    static bool inReadHandler = false;
    if (inReadHandler) return;
    inReadHandler = true;
    Blynk.virtualWrite(V15, WiFi.localIP().toString().c_str());
    inReadHandler = false;
  }
  BLYNK_READ(V19) {
    static bool inReadHandler = false;
    if (inReadHandler) return;
    inReadHandler = true;
    Blynk.virtualWrite(V19, SAMOVAR_VERSION);
    inReadHandler = false;
  }
  BLYNK_READ(V20) {
    static bool inReadHandler = false;
    if (inReadHandler) return;
    inReadHandler = true;
    Blynk.virtualWrite(V20, Samovar_Mode);
    inReadHandler = false;
  }
  BLYNK_READ(V24) {
    static bool inReadHandler = false;
    if (inReadHandler) return;
    inReadHandler = true;
    if (Samovar_Mode == SAMOVAR_BEER_MODE || Samovar_Mode == SAMOVAR_SUVID_MODE) {
      Blynk.virtualWrite(V24, get_beer_program());
    } else if (Samovar_Mode == SAMOVAR_CHEESE_MODE) {
      Blynk.virtualWrite(V24, get_cheese_program());
    } else if (Samovar_Mode == SAMOVAR_DISTILLATION_MODE) {
      Blynk.virtualWrite(V24, get_dist_program());
    } else if (Samovar_Mode == SAMOVAR_NBK_MODE) {
      Blynk.virtualWrite(V24, get_nbk_program());
    } else {
      Blynk.virtualWrite(V24, get_program(MAX_PRG));
    }
    inReadHandler = false;
  }
  BLYNK_READ(V23) {
    if (SamSetup.PressureSensor) {
      static bool inReadHandler = false;
      if (inReadHandler) return;
      inReadHandler = true;
      Blynk.virtualWrite(V23, pressure_value);
      inReadHandler = false;
    }
  }

  BLYNK_READ(V21) {
    static bool inReadHandler = false;
    if (inReadHandler) return;
    inReadHandler = true;
    if (SamSetup.PwrType != NO_POVER_REG) {
      Blynk.virtualWrite(V21, "Тек:" + (String)current_power_volt + " Цель:" + (String)target_power_volt);
    }
    inReadHandler = false;
  }
  BLYNK_READ(V16) {
    static bool inReadHandler = false;
    if (inReadHandler) return;
    inReadHandler = true;
    if (SamSetup.PwrType != NO_POVER_REG) {
      Blynk.virtualWrite(V16, target_power_volt);
    }
    inReadHandler = false;
  }
  BLYNK_WRITE(V16) {
    if (SamSetup.PwrType != NO_POVER_REG) {
      float Value16 = param.asFloat();  // assigning incoming value from pin V16 to a variable
      set_current_power(Value16);
    }
  }
  BLYNK_WRITE(V17) {
    float Value17 = param.asFloat();  // assigning incoming value from pin V17 to a variable
    set_pump_speed(get_speed_from_rate(Value17, Samovar_Mode == SAMOVAR_NBK_MODE ? SamSetup.NBK_StepperStepMl : SamSetup.StepperStepMl), true);
  }
  BLYNK_WRITE(V18) {
    set_body_temp();
  }
  BLYNK_WRITE(V12) {
    if (!PowerOn) return;
    int State = param.asInt();
    if (State == 1) {
      if (Samovar_Mode == SAMOVAR_BEER_MODE) {
        sam_command_sync = SAMOVAR_BEER_NEXT;
      } else if (Samovar_Mode == SAMOVAR_CHEESE_MODE) {
        sam_command_sync = SAMOVAR_CHEESE_NEXT;
      } else if (Samovar_Mode == SAMOVAR_DISTILLATION_MODE) {
        sam_command_sync = SAMOVAR_DIST_NEXT;
      } else {
        sam_command_sync = SAMOVAR_START;
      }
    }
  }
  BLYNK_WRITE(V13) {
    pause_withdrawal(!PauseOn);
    t_min = 0;
    program_Wait = false;
  }
  BLYNK_WRITE(V3) {
    int Value3 = param.asInt();  // assigning incoming value from pin V3 to a variable
    if (Value3 == 1 && PowerOn) samovar_start();
    else
      sam_command_sync = SAMOVAR_RESET;
  }
  BLYNK_WRITE(V4) {
    //int Value4 = param.asInt();  // assigning incoming value from pin V4 to a variable
    if (Samovar_Mode == SAMOVAR_BEER_MODE && !PowerOn) {
      sam_command_sync = SAMOVAR_BEER;
    } else if (Samovar_Mode == SAMOVAR_CHEESE_MODE && !PowerOn) {
      sam_command_sync = SAMOVAR_CHEESE;
    } else if (Samovar_Mode == SAMOVAR_BK_MODE && !PowerOn) {
      sam_command_sync = SAMOVAR_BK;
    } else if (Samovar_Mode == SAMOVAR_NBK_MODE && !PowerOn) {
      sam_command_sync = RUN_NBK;
    } else if (Samovar_Mode == SAMOVAR_DISTILLATION_MODE && !PowerOn) {
      sam_command_sync = SAMOVAR_DISTILLATION;
    } else
      sam_command_sync = SAMOVAR_POWER;
    //set_power(Value4);
  }
//------------MQTT
  char mqttstr[100] = "SMV/\0";
  char mqttstr1[100];
  void DbgMsg(const String& Msg, bool ln);
  void SendMsg(const String& m, MESSAGE_TYPE msg_type);
  void onMqttConnect(bool sessionPresent);
  void onMqttDisconnect(AsyncMqttClientDisconnectReason reason);
  void onMqttPublish(uint16_t packetId);
  void connectToMqtt();

  void initMqtt() {
    //if (mqttClient.connected()) mqttClient.disconnect();
    if (SamSetup.UseMQTT) {
      char buf[10];
      itoa(chipId, buf, 10);
      mqttClient.onConnect(onMqttConnect);
      mqttClient.onDisconnect(onMqttDisconnect);
      mqttClient.onPublish(onMqttPublish);
      mqttClient.setClientId(buf);
      //mqttClient.setClientId("1234");
      mqttClient.setMaxTopicLength(PAYLOADSIZE);
      mqttClient.setCredentials(SamSetup.MQTT_User, SamSetup.MQTT_Pass);
      mqttClient.setServer(SamSetup.MQTT_Serv, SamSetup.MQTT_Port);
      strcat(mqttstr, SamSetup.blynkauth);
      strcat(mqttstr, "/");

      if (strlen(SamSetup.blynkauth) < 30) send_mqtt = false; else send_mqtt = true;

      //  mqttClient.onSubscribe(onMqttSubscribe);
      //  mqttClient.onUnsubscribe(onMqttUnsubscribe);
      //  mqttClient.onMessage(onMqttMessage);

      connectToMqtt();
      vTaskDelay(500);
    }
  }
  void connectToMqtt() {
    static unsigned long lastAttempt = millis();
    if (millis() >= lastAttempt) {          //не чаще раза в 20 секунд
      DbgMsg("Connecting to MQTT...",1);
      lastAttempt = millis() + 20000;
      mqttClient.connect();
    }
  }
  void onMqttConnect(bool sessionPresent) {
  DbgMsg("MQTT connected.",1);
  }
  void onMqttDisconnect(AsyncMqttClientDisconnectReason reason) {
    // Очищаем очередь и сбрасываем состояние
    mqttClient.clearQueue();
    DbgMsg("Disconnected from MQTT: " + String(static_cast<std::underlying_type<AsyncMqttClientDisconnectReason>::type>(reason)),1);
  }
  void onMqttPublish(uint16_t packetId) {
    //  Serial.println("Publish acknowledged.");
    //  Serial.print("  packetId: ");
    //  Serial.println(packetId);
  }
  void MqttSendMsg(const String &Str, const char *chart ) {
    if (!send_mqtt) return;
        if (!mqttClient.connected()) {
        static unsigned long lastReconnectAttempt = 0;
        unsigned long now = millis();
        DbgMsg("Connect to MQTT...");
        if (now - lastReconnectAttempt > 5000) {
            lastReconnectAttempt = now;
            mqttClient.connect();
        }
        return; // Не отправляем сообщение, пока не подключимся
    }
    strcpy(mqttstr1, mqttstr);
    strcat(mqttstr1, chart);
    static char payload[PAYLOADSIZE];

    //Версия сообщения
    strcat(mqttstr1, "/3");
    Str.toCharArray(payload, PAYLOADSIZE);
    uint16_t packetIdPub1 = mqttClient.publish(mqttstr1, 2, true, payload);
    if (packetIdPub1 == 0) {

      packetIdPub1 = mqttClient.publish(mqttstr1, 2, true, payload);
    }
  }
//
