//================ 
  #define ON true
  #define OFF false
  void StatusKVIC(); //Регулятор KVIC
  void Sort_Response(String& resp, uint8_t stage);
  void StatusStabAVR();
  String resp;
  static bool online = false, SWRead = true;
  static unsigned long lastOnline=0, time_to_status=0;
//==========================================================RMVK
  #define RMVK_DEFAULT_READ_TIMEOUT 1100 // Таймаут по умолчанию (мс)
  #define RMVK_READ_DELAY 300 // Задержка между запросами (мс)

  String ReadRegStatus(const String& par) {
    static String resp;
    resp="";
    if (xSemaphoreTake(xSemaphoreAVR, (TickType_t)((RMVK_DEFAULT_READ_TIMEOUT) / portTICK_RATE_MS)) != pdTRUE) return resp;
    Serial2.flush();
    ClearSerialInBuff(); //очистка входного буфера от мусора
    if (SWRead) Serial2.println(par);
    Serial2.flush();
    //for (int i = 0; i < 2; i++) {
      vTaskDelay(RMVK_READ_DELAY / portTICK_RATE_MS);
      if (Serial2.available()) {
        resp = Serial2.readStringUntil('\r');
        //DbgMsg(resp);
        //break;
      }
    //}
    xSemaphoreGive(xSemaphoreAVR);
    return resp;
  }
  void RMVK_Set_Target_U(uint16_t v) {
    char num[4];
    sprintf(num, "%03d", v);
    Serial2.println("AT+VS=" + String(num) + "\r");
    Serial2.flush();
    target_power_volt = v;
    current_power_volt = v;
  }
  void RMVK_Power(bool SW) {
    Serial2.flush();    
    if (SW) {
      Serial2.println("AT+ON=1\r");
      Serial2.flush();
      String resp = ReadRegStatus("AT+VI?\r");//запрос напряжения сети
      uint16_t v = resp.toInt();
      if (v<150 || v>230) v=230;
      delay(100);
      RMVK_Set_Target_U(v);//его и устанавливаем для разгона
    }
    else  {
      Serial2.println("AT+ON=0\r");
      Serial2.flush();
    }
  }
  void StatusRMVK() { //  Регулятор RMVK
   static  uint16_t v;
   static String resp;
    if (PowerOn) delay(500); else delay(2000);
    resp = ReadRegStatus("AT+ON?\r");
    if (resp.length()>0) {
      if ((current_power_mode == POWER_SPEED_MODE || current_power_mode == POWER_WORK_MODE) && resp == "OFF") {
        current_power_mode = POWER_SLEEP_MODE;// В работающем режиме пришло отключенное состояние, отображаем
        if (SamSetup.CheckPower) {
          set_power(OFF);                      //  надо процесс гасить
          SendMsg(("Останов Процесса! Нагреватель отключился."), ALARM_MSG);
          }
      }
      // Если Самоварыч включен и пришло включенное состояние меняем статус на Вкл
      if (current_power_mode == POWER_SLEEP_MODE && SamovarStatusInt != 0
                              && resp == "ON") current_power_mode = POWER_WORK_MODE;  
      // Самоварыч считает, что разгон, а от РВМК приходит, что он работает, меняем статус на разгон
      if (PowerOn && SamovarStatusInt == 50 && resp == "ON") current_power_mode = POWER_SPEED_MODE; 
      if (SamovarStatusInt == 0 && resp == "ON") RMVK_Power(OFF);//Нагрев в Самоварыче выключен, а РВМК нет, выключаем...
      online = true;
      lastOnline = millis();
    }
    resp = ReadRegStatus("AT+VO?\r");
    if (resp.length()>0) {      
      if (current_power_mode == POWER_SLEEP_MODE) { v = 0; current_power_volt = 0;}
      else v = resp.toInt();
      if (v >= 40 && v < 255) {
        current_power_volt = v; 
      }
    }
    /*resp = ReadRegStatus("AT+VS?\r");
    if (resp.length()>0) {
      v = resp.toInt();
      target_power_volt = v;
    }*/
  }
//==========================================================Universal Protocol (Stab AVR)
  class PowerStabilizer {
  private:
      static const uint8_t HEX_CONV_ERROR = 255;
      static const uint8_t MODE_NORMAL = 0b00;
      static const uint8_t MODE_BOOST = 0b01;
      static const uint8_t MODE_OFF = 0b10;
      static const uint8_t MODE_EMERGENCY = 0b11;
      static const uint8_t ERROR_NETWORK_VOLTAGE = 0b100;
      static const uint8_t ERROR_INSUFFICIENT_V = 0b1000;
      static const uint8_t PARAM_VOLTAGE = 0b01;
      static const uint8_t PARAM_CURRENT = 0b10;
      static const uint8_t PARAM_POWER = 0b11;
      
      static const uint8_t REPORT_LENGTH = 14;
      char reportBuffer[REPORT_LENGTH];
      uint8_t bufferIndex = 0;
      uint8_t lastMode = 0;
      uint16_t lastMainValue = 0;
      uint16_t lastAuxValue = 0;
      uint8_t lastDataComposition = 0;
      uint32_t lastUpdateTime = 0;
      bool deviceConnected = false;

      uint8_t charToHex(const char c);
      bool parseReport();
      
  public:
      void begin(uint32_t baudRate = 9600);
      bool update();
      
      // Методы получения данных
      bool isNormalMode();
      bool isBoostMode();
      bool isOffMode();
      bool isEmergencyOff();
      bool hasNetworkVoltage();
      bool voltageIsSufficient();
      uint16_t getMainValue();
      float getMainVoltage();
      float getMainCurrent();
      float getMainPower();
      uint16_t getAuxValue();
      float getAuxVoltage();
      float getAuxCurrent();
      float getAuxPower();
      float getNetworkVoltage();
      float getLoadResistance();
      float getRatedPower();
      uint32_t getLastUpdateTime();
      bool isDataFresh(uint32_t timeout = 2000);
      bool isConnected() { return deviceConnected; }

      // Методы управления
      void setNormalMode();
      void setBoostMode();
      void setOffMode();
      void setPower(uint16_t power);
      void requestAuxParameter(uint8_t paramCode);
  };
 // Реализация методов класса PowerStabilizer
  uint8_t PowerStabilizer::charToHex(const char c) {
      if (c >= '0' && c <= '9') return c - '0';
      if (c >= 'A' && c <= 'F') return c - 'A' + 10;
      if (c >= 'a' && c <= 'f') return c - 'a' + 10;
      return HEX_CONV_ERROR;
  }
  bool PowerStabilizer::parseReport() {
      if (bufferIndex != REPORT_LENGTH || reportBuffer[0] != 'T') {
          return false;
      }
      for (uint8_t i = 1; i < REPORT_LENGTH-1; i++) {
          if (charToHex(reportBuffer[i]) == HEX_CONV_ERROR) {
              return false;
          }
      }
      lastMode = (charToHex(reportBuffer[3]) << 4) | charToHex(reportBuffer[4]);
      lastMainValue = ((uint32_t)charToHex(reportBuffer[5]) << 12) |
                    ((uint32_t)charToHex(reportBuffer[6]) << 8) |
                    ((uint32_t)charToHex(reportBuffer[7]) << 4) |
                    charToHex(reportBuffer[8]);
      lastDataComposition = (charToHex(reportBuffer[1]) << 4) | charToHex(reportBuffer[2]);
      lastAuxValue = ((uint32_t)charToHex(reportBuffer[9]) << 12) |
                    ((uint32_t)charToHex(reportBuffer[10]) << 8) |
                    ((uint32_t)charToHex(reportBuffer[11]) << 4) |
                    charToHex(reportBuffer[12]);
      lastUpdateTime = millis();
      return true;
  }
  void PowerStabilizer::begin(uint32_t baudRate) {
      Serial2.begin(baudRate);
  }
  bool PowerStabilizer::update() {
      while (Serial2.available() > 0) {
          char c = Serial2.read();
          if (bufferIndex == 0 && c != 'T') {
              continue;
          }
          reportBuffer[bufferIndex++] = c;
          if (c == 0x0D && bufferIndex == REPORT_LENGTH) {
              bool result = parseReport();
              deviceConnected = result; // Обновляем статус подключения
              bufferIndex = 0;
              return result;
          }
          if (bufferIndex >= REPORT_LENGTH) {
              bufferIndex = 0;
          }
      }
      return false;
  }
  bool PowerStabilizer::isNormalMode() { return (lastMode & 0b11) == MODE_NORMAL; }
  bool PowerStabilizer::isBoostMode() { return (lastMode & 0b11) == MODE_BOOST; }
  bool PowerStabilizer::isOffMode() { return (lastMode & 0b11) == MODE_OFF; }
  bool PowerStabilizer::isEmergencyOff() { return (lastMode & 0b11) == MODE_EMERGENCY; }
  bool PowerStabilizer::hasNetworkVoltage() { return !(lastMode & ERROR_NETWORK_VOLTAGE); }
  bool PowerStabilizer::voltageIsSufficient() { return !(lastMode & ERROR_INSUFFICIENT_V); }
  uint16_t PowerStabilizer::getMainValue() { return lastMainValue; }
  float PowerStabilizer::getMainVoltage() { return (lastDataComposition & 0b11) == PARAM_VOLTAGE ? lastMainValue : 0; }
  float PowerStabilizer::getMainCurrent() { return (lastDataComposition & 0b11) == PARAM_CURRENT ? lastMainValue : 0;}
  float PowerStabilizer::getMainPower() { return (lastDataComposition & 0b11) == PARAM_POWER ? lastMainValue : 0; }
  uint16_t PowerStabilizer::getAuxValue() { return lastAuxValue; }
  float PowerStabilizer::getAuxVoltage() { return ((lastDataComposition >> 2) & 0b111111) == 0b000001 ? lastAuxValue : 0; }
  float PowerStabilizer::getAuxCurrent() { return ((lastDataComposition >> 2) & 0b111111) == 0b000010 ? lastAuxValue : 0; }
  float PowerStabilizer::getAuxPower() { return ((lastDataComposition >> 2) & 0b111111) == 0b000011 ? lastAuxValue : 0; }
  float PowerStabilizer::getNetworkVoltage() { return ((lastDataComposition >> 2) & 0b111111) == 0b000101 ? lastAuxValue : 0; }
  float PowerStabilizer::getLoadResistance() { return ((lastDataComposition >> 2) & 0b111111) == 0b000110 ? lastAuxValue : 0; }
  float PowerStabilizer::getRatedPower() { return ((lastDataComposition >> 2) & 0b111111) == 0b000111 ? lastAuxValue : 0; }
  uint32_t PowerStabilizer::getLastUpdateTime() { return lastUpdateTime; }
  bool PowerStabilizer::isDataFresh(uint32_t timeout) {   return (millis() - lastUpdateTime) <= timeout; }
  void PowerStabilizer::setNormalMode() {      Serial2.write("M0\r");  }
  void PowerStabilizer::setBoostMode() {Serial2.write("M1\r");  }
  void PowerStabilizer::setOffMode() { Serial2.write("M2\r"); }
  void PowerStabilizer::setPower(uint16_t power) {
      char cmd[6];
      cmd[0] = 'P';
      uint8_t nibble1 = (power >> 12) & 0x0F;
      uint8_t nibble2 = (power >> 8) & 0x0F;
      uint8_t nibble3 = (power >> 4) & 0x0F;
      uint8_t nibble4 = power & 0x0F;
      cmd[1] = nibble1 < 10 ? nibble1 + '0' : nibble1 - 10 + 'A';
      cmd[2] = nibble2 < 10 ? nibble2 + '0' : nibble2 - 10 + 'A';
      cmd[3] = nibble3 < 10 ? nibble3 + '0' : nibble3 - 10 + 'A';
      cmd[4] = nibble4 < 10 ? nibble4 + '0' : nibble4 - 10 + 'A';
      cmd[5] = '\r';
      Serial2.write(cmd, 6);
  }
  void PowerStabilizer::requestAuxParameter(uint8_t paramCode) {
      char cmd[4];
      cmd[0] = 'N';
      uint8_t nibble1 = paramCode >> 4;
      uint8_t nibble2 = paramCode & 0x0F;
      cmd[1] = nibble1 < 10 ? nibble1 + '0' : nibble1 - 10 + 'A';
      cmd[2] = nibble2 < 10 ? nibble2 + '0' : nibble2 - 10 + 'A';
      cmd[3] = '\r';
      Serial2.write(cmd, 4);
  }
  PowerStabilizer UP_Reg;
  
  void StatusUniProtocol() {
    //  if (PowerOn) {               
          if (xSemaphoreTake(xSemaphoreAVR, (TickType_t)((200) / portTICK_RATE_MS)) == pdTRUE) {              
              // Обновляем данные через класс PowerStabilizer
              bool dataUpdated = UP_Reg.update();
              if (dataUpdated && UP_Reg.isDataFresh()) {
                  // Получаем основные параметры из стабилизатора
                  if (UP_Reg.getMainPower() > 0) {
                      current_power_volt = UP_Reg.getMainPower();
                  } else if (UP_Reg.getMainVoltage() > 0) {
                      current_power_volt = UP_Reg.getMainVoltage();
                  } else {
                      current_power_volt = UP_Reg.getMainValue();
                  }
                  
                  // Получаем целевое значение
                  if (UP_Reg.getAuxPower() > 0) {
                      target_power_volt = UP_Reg.getAuxPower();
                  } else if (UP_Reg.getAuxVoltage() > 0) {
                      target_power_volt = UP_Reg.getAuxVoltage();
                  } else {
                      target_power_volt = UP_Reg.getAuxValue();
                  }
                  
                  // Определяем режим работы
                  if (UP_Reg.isOffMode() || UP_Reg.isEmergencyOff()) {
                      current_power_mode = POWER_SLEEP_MODE;
                  } else {
                      current_power_mode = UP_Reg.isBoostMode() ? POWER_SPEED_MODE : POWER_WORK_MODE;
                  }
              } else if (!UP_Reg.isDataFresh(5000)) {
                  // Данные устарели
                  DbgMsg("Нет связи со стабилизатором.",1);
              }
              xSemaphoreGive(xSemaphoreAVR);
          }
    //  } else {
          // Сброс значений при выключенном питании
          current_power_volt = 0;
          target_power_volt = 0;
          current_power_mode = POWER_SLEEP_MODE;
    //  }
      vTaskDelay(RMVK_READ_DELAY / 5 / portTICK_PERIOD_MS);
  }
//==========================================================UDP Stabilizer Control
  #include <WiFi.h>
  #include <WiFiUdp.h>

  class UDPStabilizer {
  private:
      WiFiUDP udp;
      String stabilizerIP = "";
      bool connected = false;
      bool udpActive = false;
      unsigned long lastValidUpdate = 0;
      unsigned long lastAttempt = 0;
      // Состояние стабилизатора
      float currentPower = 0;
      float targetPower = 0;
      bool boostMode = false;
      bool heatEnabled = false;
      bool errorMode = false;
      bool calibrateMode = false;
      bool onStart;
      String StabCommand;
      WiFiClient client;

    bool validatePower(float value) {
        return value >= 0 && value <= 10000;
    }
    bool validateMode(uint8_t mode) {
        return mode <= 0b00001111; // Только биты 0, 1, 2, 3
    }
    bool isValidIPv4(const IPAddress& ip) {
      return (ip[0] != 0) && (ip != IPAddress(255,255,255,255));
    }
    bool getLastPacket(String &message, IPAddress &senderIP) {
        if (!udpActive) return false;
        
        String lastMsg;
        IPAddress lastIP;
        
        // Читаем все пакеты, сохраняем последний
        while (udp.parsePacket() > 0) {
            char buf[64];
            int len = udp.read(buf, 63);
            if (len > 0) {
                buf[len] = 0;
                lastMsg = String(buf);
                lastIP = udp.remoteIP();
            }
        }
        
        if (!lastMsg.isEmpty()) {
            message = lastMsg;
            senderIP = lastIP;
            return true;
        }
        
        return false;
    }
  public:
    UDPStabilizer() {}
      
    void begin(uint16_t port = 12345) {
      StabCommand = "";
      if (!udpActive) {
        udp.begin(port);
        udpActive = true;
        DbgMsg("UDP listener on port " + String(port), 1);
      }
      onStart = true;
    }
    void stop() {
      if (udpActive) {
        udp.stop();
        udpActive = false;
        stabilizerIP = "";
        connected = false;
        DbgMsg("UDP stopped", 1);
      }
    }
    bool update() {      
      if (!udpActive) return false;
      static uint32_t t=0;
      if (millis() > t) { // Раз в секунду пытаемся
        t = millis() + 1000;
        //DbgMsg("UDP <--", 0);
          String message;
          IPAddress senderIP;

          if (getLastPacket(message, senderIP)) {
            //DbgMsg(senderIP.toString() + " send: " + message);
            if (message.startsWith("StPr|")) {
              String data = message.substring(5);
              int pos1 = data.indexOf('|');
              int pos2 = data.indexOf('|', pos1 + 1);
              if (pos1 != -1 && pos2 != -1) {
                float power = data.substring(0, pos1).toFloat();
                float target = data.substring(pos1 + 1, pos2).toFloat();
                uint8_t mode = data.substring(pos2 + 1).toInt();
                if (validatePower(power) && validatePower(target) && validateMode(mode)) {
                  if (isValidIPv4(senderIP)) { 
                    stabilizerIP = senderIP.toString(); 
                  }
                  currentPower = power;
                  targetPower = target;
                  heatEnabled =   (mode & 0b00000001);
                  boostMode =     (mode & 0b00000010);
                  errorMode =     (mode & 0b00000100);
                  calibrateMode = (mode & 0b00001000);
                  lastValidUpdate = millis();
                  connected = true;
                  if (onStart && stabilizerIP != "") { // После получения IP впервые отключаем нагрев
                    onStart = false;
                    setHeatEnabled(false);
                  }
                  return true;
                }
              }
            }
          }// else if (SamSetup.dbg) Serial.println();
        connected = (millis() - lastValidUpdate < 30000) && !stabilizerIP.isEmpty();
        return false;
      }
    }
    bool send_stab_command() {
        if (StabCommand.isEmpty() || stabilizerIP.isEmpty()) {
            return false;
        }
        client.setTimeout(100);//в локальной сети всё быстро
        if (client.connect(stabilizerIP.c_str(), 80, 200)) {
            String request = "GET " + StabCommand + " HTTP/1.1\r\nHost: " + stabilizerIP + "\r\nConnection: close\r\n\r\n";
            client.print(request);
            client.flush();     // Ждем отправки данных
            delay(10);
            bool success = false;
            unsigned long timeout = millis();
            while (client.connected() && millis() - timeout < 300) {
                if (client.available()) {//вот здесь эти 100 мс
                    String response = client.readString();
                    // Проверяем успешность по HTTP коду
                    if (response.indexOf("HTTP/1.1 200") >= 0 || 
                        response.indexOf("HTTP/1.1 302") >= 0) { 
                        success = true;
                        DbgMsg("Ok!");
                    }
                    break;
                }
                delay(10);
            }
            client.stop();
            if (success) {
                StabCommand = "";
            }
            return success;
        } else {
            DbgMsg("Connection failed to " + stabilizerIP);
            return false;
        }
    }
    void commandProcessor() {// Повтор команды в случае неуспешной отправки, до победы
      if (millis() - lastAttempt > 1000) { // Раз в секунду пытаемся
        if (StabCommand != "") {
          DbgMsg("Send command to stabilizer...",1);
          bool result = send_stab_command();
          DbgMsg("Result = " + String(result? "Sended.":"Fail send."), 1);
        }
        lastAttempt = millis();
      }
    }
    bool setPower(uint16_t power) {
        if (!connected) return false;
        StabCommand = "/set_power?power=" + String(power);
        lastAttempt = 0;
        DbgMsg("Set power: " + String(power) + "W");
        lastAttempt = millis();
        bool result = send_stab_command();
        return result;
    }
    bool setBoostMode(bool enable) {
        if (!connected) return false;
        StabCommand = "/set_boost?boost=" + String(enable ? "1" : "0");
        lastAttempt = 0;
        DbgMsg("Set boost: " + String(enable ? "ON" : "OFF"));
        lastAttempt = millis();
        bool result = send_stab_command();
        return result;
    }
    bool setHeatEnabled(bool enable) {
        if (!connected) return false;
        StabCommand = "/set_heat?heat=" + String(enable ? "1" : "0");
        lastAttempt = 0;
        DbgMsg("Set heat: " + String(enable ? "ON" : "OFF"));
        lastAttempt = millis();
        bool result = send_stab_command();
        return result;
    }
    // Методы получения данных
    bool isConnected() { return connected; }
    bool isNormalMode() { return heatEnabled && !boostMode; }
    bool isBoostMode() { return heatEnabled && boostMode; }
    bool isOffMode() { return !heatEnabled; }
    bool isErrorMode() { return errorMode; }
    bool isCalibrateMode() { return calibrateMode; }
    float getCurrentPower() { return currentPower; }
    float getTargetPower() { return targetPower; }
    String getStabilizerIP() { return stabilizerIP; }
  };

  UDPStabilizer udpStabilizer;
  
  void StatusUDPStabilizer() {
    udpStabilizer.update();//читаем статус
      if (udpStabilizer.isConnected()) {
          current_power_volt = udpStabilizer.getCurrentPower();
          target_power_volt = udpStabilizer.getTargetPower();
          if (udpStabilizer.isCalibrateMode()) {
              current_power_mode = POWER_CALIBRATE_MODE;
          } else if (udpStabilizer.isErrorMode()) {
              current_power_mode = POWER_ERROR_MODE;
          } else if (udpStabilizer.isOffMode()) {
              current_power_mode = POWER_SLEEP_MODE;
          } else {
              current_power_mode = udpStabilizer.isBoostMode() ? POWER_SPEED_MODE : POWER_WORK_MODE;
          }
      }
  }
  void UDP_Tick() {
    if (SamSetup.PwrType==UDP_Pr) {
      udpStabilizer.commandProcessor();//повторные команды если прошлые с неудачей
      StatusUDPStabilizer();
    }
  }  
  void UDPStab_Stop() {//для остановки при начале ОТА
    udpStabilizer.stop();
  }
//==========================================================Modbus RTU
 #include <ModbusMaster.h>
 ModbusMaster inverter;
  int modbusAddress = 10;
  int modbus_baudRate = 9600;
  int registerWriteAddr = 0x2000;
  int registerReadAddr = 0x3000;
  int frequencyRegister = 0x2001;
  bool startInverter() {
    uint8_t result = inverter.writeSingleRegister(registerWriteAddr, 0x0001);
    return (result == inverter.ku8MBSuccess);
  }

  bool stopInverter() {
    uint8_t result = inverter.writeSingleRegister(registerWriteAddr, 0x0000);
    return (result == inverter.ku8MBSuccess);
  }

  bool ModbusSetPower(float percent) {
    float freq = 50.0 * (percent / 100.0);
    uint16_t value = freq * 100; // 0.01 Гц
    uint8_t result = inverter.writeSingleRegister(frequencyRegister, value);
    return (result == inverter.ku8MBSuccess);
  }

  bool writeRegister(int addr, int value) {
    uint8_t result = inverter.writeSingleRegister(addr, value);
    return (result == inverter.ku8MBSuccess);
  }

  uint16_t readRegister(int addr) {
    uint8_t result = inverter.readHoldingRegisters(addr, 1);
    if (result == inverter.ku8MBSuccess) {
      return inverter.getResponseBuffer(0);
    }
    return 0xFFFF;
  }
  void StatusMODBUS_RTU() {
    vTaskDelay(2000 / portTICK_PERIOD_MS);//код
  }
//Основная логика================================================================
  uint8_t isOnlineStab() {
    if (millis() - lastOnline > 10000) online = false; //Если 10 сек нет отчетов считаем регулятор офлайн
    switch (SamSetup.PwrType) { 
      case KVIC: case KVIC9600: return online;
      case RMVK: return online;
      case STAB_AVR: return online;
      case UNI_PROTOCOL: return UP_Reg.isConnected();
      case MODBUS_RTU: // У этих двоих пока обратной связи нет
      case PWM: return 2;
      case UDP_Pr: return udpStabilizer.isConnected();
      default: return 2;
    }
  }
  void ClearSerialInBuff() { // Быстрая очистка буфера (максимум 100 символов)
    uint8_t cleared = 0;
    while (Serial2.available() && cleared < 100) {
        Serial2.read();
        cleared++;
    }
  }
  void InitPower() {
    if (xSemaphoreTake(xSemaphoreAVR, (TickType_t)((200) / portTICK_RATE_MS)) == pdTRUE) {
        if (Serial2) {
          Serial2.end();
          delay(50);
          Serial2.flush();
      }
      switch (SamSetup.PwrType) { 
      case KVIC:{ 
          DbgMsg("Init KVIC",1);
          Serial2.setTimeout(300);
          Serial2.setRxBufferSize(0);
          Serial2.begin(38400, SERIAL_8N1, RXD2, TXD2);
        break; }
      case KVIC9600:{
          DbgMsg("Init KVIC9600",1);
          Serial2.setTimeout(300);
          Serial2.setRxBufferSize(0);
          Serial2.begin(9600, SERIAL_8N1, RXD2, TXD2);
        break; }
      case RMVK: {// работаем с RMVK
          DbgMsg("Init RMVK",1);
          Serial2.begin(9600, SERIAL_8N1, RXD2, TXD2);
        break;}
      case STAB_AVR: {//Если SEM_AVR иницииурем порт
        DbgMsg("Init STAB_AVR",1);
        Serial2.setTimeout(500);
        Serial2.begin(9600, SERIAL_8N1, RXD2, TXD2);
        break;}
      case UNI_PROTOCOL: {
        DbgMsg("Init STAB_AVR Uni protocol",1);
        UP_Reg.begin();
        break;}
      case MODBUS_RTU: {//Если  иницииурем порт
        DbgMsg("Init MODBUS_RTU",1);
        Serial2.setTimeout(500);
        Serial2.begin(modbus_baudRate, SERIAL_8N1, RXD2, TXD2);
        inverter.begin(modbusAddress, Serial2);
        break;}
      case PWM: {
        DbgMsg("Init PWM",1);
        Power_PWM.attachPin(TXD2, 3, 1, 50, 10);       // channel 3, timer 1//10- разрядность 0-1023, 50-частота, TXD2 - пин TX Serial2
        break;}
      case UDP_Pr: { 
        DbgMsg("Init UDP+HTTP Stabilizer", 1);
        udpStabilizer.begin(SamSetup.UDP_Port);
        break;
        }
        default:{
          
        break;}
    }
      switch (SamSetup.PwrType) { // единицы измерений регуляторов
      case KVIC: case KVIC9600: case RMVK: {
          PwrMSG_str = "Напряжение";
          PwrSign = "В";
        break;}
      case STAB_AVR: case UNI_PROTOCOL: case MODBUS_RTU: case PWM: case UDP_Pr:{
          PwrMSG_str = "Мощность";
          PwrSign = "Вт";
        break;}
      default:{
          PwrMSG_str = "";
        break;}
    }
      switch (SamSetup.PwrType) { // множитель инкрементов регуляторов
      case KVIC: case KVIC9600: {PwrFactor = 1; break;}
      case RMVK: {PwrFactor = 2; break;}
      case STAB_AVR: case UNI_PROTOCOL: case MODBUS_RTU: case PWM: case UDP_Pr: {PwrFactor = 1; break;}
      default: {PwrFactor = 0; break;}
    }
    xSemaphoreGive(xSemaphoreAVR);
    }
  }
  void PowerStatus_Loop(void *parameter) {// Чтение состояния регуляторов, запускается как отдельный таск
      while (true) {
        if (time_to_status < millis()) {
          SWRead = true;
          time_to_status=0; //защита от перезапуска millis(), вдруг мало 49.7 дней :)
          switch (SamSetup.PwrType) { // единицы измерений регуляторов
          case KVIC: case KVIC9600:
              {StatusKVIC();
            break;}
          case RMVK:
              {StatusRMVK();
            break; }     
          case STAB_AVR:
              {StatusStabAVR();
            break;}
          case UNI_PROTOCOL:
              {StatusUniProtocol();
            break;}
          case MODBUS_RTU:
              {StatusMODBUS_RTU();
            break;}
          case PWM: {
              current_power_volt = target_power_volt; //при ШИМе проверять нечего
              vTaskDelay(2000 / portTICK_PERIOD_MS);
          break;}  
          //case UDP_Pr:{   Проверка статуса Udp крутится вместе с другими сетевыми задачами в NET_Loop, там вызов раз в секунду
           //    StatusUDPStabilizer();
           // break;}
          default:
              vTaskDelay(2000 / portTICK_PERIOD_MS);
            break;
          }
        } else vTaskDelay(2000 / portTICK_PERIOD_MS);
      }
  }
  void StatusKVIC() {
    vTaskDelay(500 / portTICK_PERIOD_MS);
    if (xSemaphoreTake(xSemaphoreAVR, (TickType_t)(200 / portTICK_RATE_MS)) != pdTRUE) {
        return;
    }
    static String buffer;
    buffer = "";
    while (Serial2.available()) {
        char c = Serial2.read(); // Явно читаем как char
        buffer += c;
    }
    // Если в буфере есть данные
    if (buffer.length() > 8 ) {
        // Ищем последние 8 символов перед \r
        int lastCR = buffer.lastIndexOf('\r');
        if (lastCR >= 8 ) {
            // Берем 8 символов перед \r (формат T1234567)
            String data = buffer.substring(lastCR - 8, lastCR);
            Serial.println(data);
            
            // Проверяем что первый символ 'T'
            if (data.charAt(0) == 'T') {
                String hexData = data.substring(1); // убираем 'T'
                
                int cpv = hexToDec(hexData.substring(0, 3));
                if (cpv > 30 && cpv < 2550) {
                    current_power_volt = cpv / 10.0F;
                    target_power_volt = hexToDec(hexData.substring(3, 6)) / 10.0F;
                    current_power_mode = hexData.substring(6, 7);   
                }
              online = true;
              lastOnline = millis();  
            }
        }
    }

    xSemaphoreGive(xSemaphoreAVR);
  }
  void Sort_Response(String& resp, uint8_t stage){ // Сортировака ответов для 
    uint16_t v;
   if (resp.length()>0) {
      online = true;
      lastOnline = millis();
      switch (resp[0]) { // Сортируем ответы по префиксу
          case 'M':
            resp.remove(0, 1);
            current_power_mode =resp;
            break;
          case 'C':
            resp.remove(0, 1);
            current_power_volt = resp.toInt();
            break;
          case 'T':
            resp.remove(0, 1);
            v = resp.toInt();
            if (v != 0)  target_power_volt = v;
            break;
          default://вариант для старых STAB AVR, всё равно работать должно
              switch (stage) { 
                    case 0:
                    current_power_mode =resp;
                    break;
                    case 1:
                    current_power_volt = resp.toInt();
                    break;
                    case 2:
                    v = resp.toInt();
                    if (v != 0)  target_power_volt = v;
                    break;
              }
            break;
        }
    }
  }   
  void StatusStabAVR() {  //Регулятор SТAВ AVR
   static  uint16_t v;
    if (PowerOn) delay(500); else delay(2000);
    if (SWRead) {
      resp = ReadRegStatus("АТ+SS?");
      delay(RMVK_DEFAULT_READ_TIMEOUT);
      Sort_Response(resp, 0);
      
      resp = ReadRegStatus("АТ+VO?");
      delay(RMVK_DEFAULT_READ_TIMEOUT);
      Sort_Response(resp, 1);

      resp = ReadRegStatus("АТ+VS?");
      delay(RMVK_DEFAULT_READ_TIMEOUT);
      Sort_Response(resp, 2);
      if (SamSetup.CheckPower && (current_power_mode == POWER_SLEEP_MODE)
                       && PowerOn) {// В работающем режиме пришло отключенное состояние, отображаем
        set_power(OFF);                      //  надо процесс гасить
        SendMsg(("Останов Процесса! Нагреватель отключился."), ALARM_MSG);
      }
      if (SamovarStatusInt == 0 && (current_power_mode == POWER_SPEED_MODE || current_power_mode == POWER_WORK_MODE))
       Serial2.println("АТ+ON=0");//Нагрев в Самоварыче выключен, а StabAVR нет, выключаем...
    }
  }
  unsigned int hexToDec(String hexString) {
    unsigned int decValue = 0;
    int nextInt;

    for (uint8_t i = 0; i < hexString.length(); i++) {

      nextInt = int(hexString.charAt(i));
      if (nextInt >= 48 && nextInt <= 57) nextInt = map(nextInt, 48, 57, 0, 9);
      if (nextInt >= 65 && nextInt <= 70) nextInt = map(nextInt, 65, 70, 10, 15);
      if (nextInt >= 97 && nextInt <= 102) nextInt = map(nextInt, 97, 102, 10, 15);
      nextInt = constrain(nextInt, 0, 15);

      decValue = (decValue * 16) + nextInt;
    }

    return decValue;
  }
  void nullStatus(){
    if (!PowerOn) {
      current_power_volt = 0;
      target_power_volt = 0;
      current_power_mode = POWER_SLEEP_MODE;
      current_power_p = 0;
      
    }    
  }
  void get_current_power() { //получаем текущие параметры работы регулятора напряжения
    switch (SamSetup.PwrType) { // отображение в web интерфейсе текущего состояния регуляторов
      case KVIC: case KVIC9600:  case RMVK:
        current_power_p = current_power_volt * current_power_volt / SamSetup.HeaterResistant;
        break;
      case STAB_AVR:
        current_power_p = current_power_volt;
      break;
      case PWM:
        if (!PowerOn) { nullStatus(); return;}
        current_power_volt = target_power_volt; //при ШИМе проверять нечего
        current_power_p = target_power_volt;
      break;
      case UDP_Pr:
        current_power_p = current_power_volt;
      break;
      default:{
        if (!PowerOn) { nullStatus(); return;}
        current_power_p = current_power_volt;
      break;
      }
    }
  }
  void set_current_power(float Volt) { //устанавливаем напряжение для регулятора напряжения
    if (!PowerOn) return;
    SWRead = false; // запрещаем чтение статусов
    time_to_status = millis() + 1000; // 1 секунда на отработку перед чтением статуса
    
    DbgMsg("Set current power =" + (String)Volt,1);
    uint8_t minV = (SamSetup.PwrType >= STAB_AVR) ? 100 : 40;
    if (Volt < minV) {
      set_power_mode(POWER_SLEEP_MODE);
      target_power_volt = 0;
      return;
    } else {
      set_power_mode(POWER_WORK_MODE);
    }
    target_power_volt = Volt;
    vTaskDelay(100 / portTICK_PERIOD_MS);
    //DbgMsg("Ждем семафор...",1);
   if (xSemaphoreTake(xSemaphoreAVR, (TickType_t)((1000) / portTICK_RATE_MS)) == pdTRUE) {
    //DbgMsg("Семафор получен",1);
    switch (SamSetup.PwrType) { // формирование и отправка команды
      case KVIC: case KVIC9600:
        {String hexString = String((int)(Volt * 10), HEX);
        Serial2.print("S" + hexString + "\r");
        break;}
      case RMVK:
        {RMVK_Set_Target_U(Volt);   
        break;}      
      case STAB_AVR:
        {  String Cmd;
        int V = Volt;
        if (V < 100) Cmd = "0";
        else
        Cmd = "";
        Cmd = Cmd + (String)V;
        Serial2.print("АТ+VS=" + Cmd + "\r");//rus
        //Serial2.print("AT+VS=" + Cmd + "\r");//eng
        break;}
      case UNI_PROTOCOL:  
        UP_Reg.setPower(Volt);
        break;
      case UDP_Pr:
        udpStabilizer.setPower(round(Volt));
        break;    
      case MODBUS_RTU:
        ModbusSetPower(Volt);
        break;  
      case PWM: {
        Power_PWM.write(map(Volt, 0, MaxPower, 0, 1023));
        break; }
    }
    target_power_volt = Volt;
    xSemaphoreGive(xSemaphoreAVR);
    } else  DbgMsg("set_current_power Недождались семафора!",1);
  }
  void set_power_mode(String Mode) {
    if (current_power_mode == Mode) return;
    SWRead = false; // запрещаем чтение статусов
    time_to_status = millis() + 1000; // 1 секунда на отработку перед чтением статуса
    
    current_power_mode = Mode;
    vTaskDelay(50 / portTICK_PERIOD_MS);
    DbgMsg("Set power mode= " + Mode,1);
    if (Mode == POWER_SLEEP_MODE) {
    if (xSemaphoreTake(xSemaphoreAVR, (TickType_t)((1000) / portTICK_RATE_MS)) == pdTRUE)  { 
      switch (SamSetup.PwrType) { // формирование и отправка команды
        case KVIC:  case KVIC9600:
          {Serial2.print("M" + Mode + "\r"); //kvic
          break;}
        case RMVK:
          {RMVK_Power(OFF);// rmvk
          break; }     
        case STAB_AVR:
          { Serial2.print("АТ+ON=0\r");
          break;}
        case UNI_PROTOCOL:  
          UP_Reg.setOffMode();
          break;
        case UDP_Pr:
          udpStabilizer.setHeatEnabled(false);
          break;    
        case MODBUS_RTU:
          stopInverter();
          break; 
        case PWM: {
          Power_PWM.write(0);
        break; } 
      }
      xSemaphoreGive(xSemaphoreAVR);
    }
    } else if (Mode == POWER_SPEED_MODE) {
      vTaskDelay(SamSetup.PwStartPause / portTICK_PERIOD_MS);//настраиваемая задержка для включения регулятора
      if (xSemaphoreTake(xSemaphoreAVR, (TickType_t)((1000) / portTICK_RATE_MS)) == pdTRUE)  {
      switch (SamSetup.PwrType) { // формирование и отправка команды
        case KVIC: case KVIC9600:{
          Serial2.print("M" + Mode + "\r"); //kvic
          break;}
        case RMVK:{
          RMVK_Power(ON); // rmvk
          break;}      
        case STAB_AVR: {
          Serial2.print("АТ+ON=1\r");
          break;}
        case UNI_PROTOCOL:  
          UP_Reg.setBoostMode();
          break;
        case UDP_Pr:
          udpStabilizer.setBoostMode(true);
        break;    
        case MODBUS_RTU:
          startInverter();
          ModbusSetPower(100);
          break;
        case PWM: {
          Power_PWM.write(1023);
        break; }
      }
      xSemaphoreGive(xSemaphoreAVR);
      }
    }
  }
  void check_power_error() { //Проверим, что заданное напряжение/мощность не сильно отличается от реального (наличие связи с регулятором, пробой семистора)
    if (PowerOn && SamSetup.CheckPower &&current_power_mode == POWER_ERROR_MODE) {// Если стаб сам может диагностировать неисправность
        delay(1000);  //Пауза на всякий случай, чтобы прошли все другие команды
        set_buzzer(ON);
        set_power(OFF);
        SendMsg(("Аварийное отключение! Ошибка нагревателя."), ALARM_MSG);
    }
    if (SamSetup.CheckPower && current_power_mode == POWER_WORK_MODE && current_power_volt > 0 
                            && abs((current_power_volt - target_power_volt) / target_power_volt) > 0.2) {//20%
      power_err_cnt++;
      if (power_err_cnt == 4) DbgMsg("Ошибка регулятора!");//SendMsg(("Ошибка регулятора!"), ALARM_MSG);
      if (power_err_cnt > 8) {
        set_current_power(target_power_volt);
        SWRead = true; // Разрешеаем чтение статуса
        time_to_status = 0; // Отменяем задержку чтения статуса
      }
      if (power_err_cnt > 20) { // За 20 секунд ничего сгореть не должно
        delay(1000);  //Пауза на всякий случай, чтобы прошли все другие команды
        set_buzzer(ON);
        set_power(OFF);
        SendMsg(("Аварийное отключение! Ошибка управления нагревателем."), ALARM_MSG);
      }
    } else
      power_err_cnt = 0;

    if (SamSetup.CheckPower && PowerOn && !isOnlineStab()) {
          set_buzzer(ON);
          set_power(OFF);
          SendMsg(("Аварийное отключение! Потеряна связь с нагревателем."), ALARM_MSG); 
    }
  }
  void set_power(bool On) {
    if (alarm_event && On) {
      return;
    }
    PowerOn = On;
    if (On) {
      digitalWrite(RELE_CHANNEL1, SamSetup.rele1);
      if (SamSetup.PwrType != NO_POVER_REG) {

          set_power_mode(POWER_SPEED_MODE);
      } else {
          current_power_mode = POWER_SPEED_MODE;
          digitalWrite(RELE_CHANNEL4, SamSetup.rele4);
      }
    } else {
      digitalWrite(RELE_CHANNEL4, !SamSetup.rele4);
      acceleration_heater = false;
      if (SamSetup.PwrType != NO_POVER_REG) {
        //vTaskDelay(700 / portTICK_PERIOD_MS);
        set_power_mode(POWER_SLEEP_MODE);
        vTaskDelay(200 / portTICK_PERIOD_MS);
      } else {
        current_power_mode = POWER_SLEEP_MODE;
      }
      sam_command_sync = SAMOVAR_RESET;
      digitalWrite(RELE_CHANNEL1, !SamSetup.rele1);
    }
  }
  float toPower(float value) { // конвертер в мощность ( V | W ) => W
    if (SamSetup.PwrType >= STAB_AVR) 
      return value; // если нечто иное возвращаем неизменным
    else {
        float R = SamSetup.HeaterResistant > 1 ? SamSetup.HeaterResistant : 15;
        return value * value / R; //если от kvic или RMVK пересчитываем в P
    }
  }
  float SQRT(float num) { // псевдо корень
    if (num < 0) {
      return -1.0f;
    }
    if (num == 0) {
        return 0.0f;
    }
    float guess = num;
    float prev_guess;
    do {
      prev_guess = guess;
      guess = (guess + num / guess) / 2;
    } while (abs(guess - prev_guess) > 0.01);
    return guess;
  }
  float fromPower(float value) { // конвертер из мощности: W => ( V | W )
    if (SamSetup.PwrType >= STAB_AVR)
        return value;
    else {
          static float prev_W = 0.0f;
          static float prev_V = 0.0f;
          if (value != prev_W) {
              prev_W = value;
              float R = SamSetup.HeaterResistant > 1 && SamSetup.HeaterResistant < 200 ? SamSetup.HeaterResistant : 15;
              prev_V = SQRT(value * R);
          }
          return prev_V;
    }
  }
