//#define ZCDEBUG

volatile uint32_t delay_ticks = 250;

 void IRAM_ATTR R_zeroCrossISR();

void set_delay(uint32_t dt) {
    delayTime = dt;
    // Проверка на максимальное значение для timer1 (0x7FFFFF)
    uint32_t ticks = 5 * (dt + dtTime - 12); //преобразуем в тики (задержка + смещение 0 + поправка на расчёты)
    if (ticks > 0x7FFFFF) ticks = 0x7FFFFF;  // Максимум для timer1
    if (ticks < 250) ticks = 250;  // Минимум для стабильности
    delay_ticks = ticks;
}
uint16_t PowerToDelay(uint16_t Power) {// Перевод значения из мощности в задержку
    uint16_t P = (Power * 10000) / maxPower; // 0-10000    
    // PL[0] - макс. мощность (мин. задержка), PL[31] - мин. мощность
    if (P >= PL[0]) return delayLow; // >= макс. мощности
    if (P <= PL[31]) return delayHigh; // <= мин. мощности    
    for (uint8_t i = 0; i < 31; i++) {
        if (P <= PL[i] && P > PL[i+1]) { // Обратный порядок!
            float fraction = (float)(PL[i] - P) / (PL[i] - PL[i+1]);
            uint16_t delay = delayLow + (i * TimeStep) + round(TimeStep * fraction);
            return constrain(delay, delayLow, delayHigh);
        }
    }
    return delayHigh;
}
void calculateSensitivityTable() {// Рассчет таблицы чувствительности реакции в зависимости от относительной мощности
  float step = ((delayHigh - delayLow) / 31);
  for (uint8_t i = 0; i < 31; i++) {
      uint16_t delay_i = delayLow + i * step;
      uint16_t delay_i1 = delayLow + (i+1) * step;      
      if (PL[i] != PL[i+1]) sensitivity_table[i] = (float)(delay_i1 - delay_i) / (PL[i+1] - PL[i]);
        else sensitivity_table[i] = (float)(delayHigh - delayLow) / 10000.0;
  }
}
void heat(bool SW) { // Переключатель нагрева
 if (error && SW) return; // Не даём включить при ошибке ТЭНа
 heatEnabled = SW;
 if (!heatEnabled && boostMode) boost(OFF); //отключаем разгон если отключили нагрев
 if (!heatEnabled) { 
  set_delay(delayHigh); 
  powerSetpoint = 0;
 }
}
void boost(bool SW) { // Переключатель разгона
 static uint16_t powerSPtemp=0; 
 static int16_t delayTimeT;
  if (error && SW) return;
	boostMode = SW;
    updateOLEDDisplay_BoostMode();
    if (boostMode) {
      powerSPtemp = powerSetpoint;
      TrueMAXPowerTEN=false; //Старт прогрузки
      //maxPower = 1;
      tm2 = millis(); //Время на прогрузку для определения мощности ТЭНа
      delayTimeT = delayTime; // Запомнили задержку
      set_delay(delayLow); // ТЭН на полную
      heat(ON); // Включили нагрев, контактор включим в регуляторе через tm2 мс
    } else {
      powerSetpoint = powerSPtemp;//При отключении разгона восстанавливаем исходное задание мощности
      set_delay(delayTimeT); // и задержку
      digitalWrite(boostPin, LOW);  // Отключили контактор
    }
}
void setPower(int16_t newPower) {
  newPower = constrain(newPower, 0, 10000);  // Ограничиваем мощность в пределах 0-10000 Вт
  if (boostMode) boost(OFF); // Если был разгон отключаем
  heat(newPower != 0); // Включаем нагрев если уставка мощности больше 0, иначе выключаем 
  if (REG) { // если регулятор включен// Блокируем изменение delayTime, если активна прогрузка ТЭНа
    if (heatEnabled) { // если включен нагрев
      if (powerSetpoint != newPower) {// Если задание изменилось 
        powerSetpoint = newPower;
        power = newPower;
        //Serial.println("Расчетная задержка = " + String(PowerToDelay(powerSetpoint)));
        set_delay(PowerToDelay(powerSetpoint));// Расчет начального delayTime для приближения к powerSetpoint
        //Serial.println("Power =>> "+ String(powerSetpoint) + ", Delay =>> " + String(delayTime));
        lastDelayChange = millis()+1000;//после прыжка ждем на секунду больше
        timeJump = millis();//задержка начала проверки симистора на пробитие
      }
    }
  }
}
void updateDelay() { // Регулятор
  if (!boostMode && heatEnabled) { // если не разгон и включен нагрев
    // Если с последней коррекции прошло меньше timeReg сек - попуск 
    if (millis() - lastDelayChange < timeReg*1000) return;

    // Адаптивная коррекция
    if (abs(power - powerSetpoint) > (maxPower * 0.002)) {// Если отклонение от задания больше 0.2% (на ТЭН 2 кВт это 4 Вт)
      uint16_t P_current = (powerSetpoint * 10000) / maxPower; //Перевод в относительные еденицы
      for (uint8_t i = 0; i < 31; i++) { // Ищем индекс в таблице
        if (P_current >= PL[i+1] && P_current <= PL[i]) {//нашли
          float error_percent = (float)(powerSetpoint - power) * 100.0 / maxPower; //процент ошибки
          int16_t correction = (int16_t)(error_percent * sensitivity_table[i] * (K_upr * 1.6));  // умножение на чувствительность и на силу воздействия
          correction = constrain(correction, -500, 500);// но не более 500 мкс чтоб не вылететь за края???
          set_delay(constrain(delayTime + correction, delayLow, delayHigh)); //подравняем по краям
          lastDelayChange = millis();
          //Serial.println("Адаптивная:"+String(correction));
          return;
        }
      }
    } else {// При меньшем отклонении плавная коррекция ±1 мкс
      int16_t dTinc = 0;
      if (power < powerSetpoint + 0.0005 * maxPower) dTinc = -1;//0.05 % зона нечуствительности для снижения болтанки (на ТЭН 2 кВт это +-1Вт)
      else if (power > powerSetpoint - 0.0005 * maxPower) dTinc = 1;
      set_delay(constrain(delayTime + dTinc, delayLow, delayHigh));
      lastDelayChange = millis();
      //if (abs(dTinc) > 0) Serial.println("Плавная:"+String(dTinc));
      return;
    }
  } 
  if (boostMode) {// В режиме разгона полностью открываем симистор
    set_delay(delayLow);
    powerSetpoint = power;
    if (tm2!=0 && (millis() - tm2 >= 6000)) {//через 6 сек после включения разгона сохраняем максимальную мощность ТЭН-а и включаем разгонный ТЭН
      if (power > 10) maxPower = power;
      digitalWrite(boostPin, HIGH);
      tm2 = 0;
    }
  }
}
void initModeFromEEPROM() {// Инициализация режима из EEPROM
  EEPROM.begin(EEPROM_SIZE);
    if (EEPROM.read(MODE_SAVED_FLAG_ADDR) == MODE_SAVED_FLAG_VALUE) {
    EEPROM.get(TIME_REG_ADDR, timeReg);//free
    EEPROM.get(K_UPR_ADDR, K_upr);
    EEPROM.get(DELAY_LOW_ADDR, delayLow);
    EEPROM.get(DELAY_HIGH_ADDR, delayHigh);
    EEPROM.get(PMAX_TEN_ADDR, maxPower);
    EEPROM.get(PL_ADDR, PL);    
    EEPROM.get(UDP_PORT_ADDR, UDP_Port);
    // Проверяем допустимые пределы:
    if (maxPower < 1 || maxPower > 10000) maxPower = PMAX_TEN;//
    if (timeReg < 1 || timeReg > 8) timeReg = DEFAULT_TIME_REG;//
    if (K_upr < 1 || K_upr > 10) K_upr = DEFAULT_K_UPR;
    if (delayLow < 0 || delayLow > 1500) delayLow = DEFAULT_DELAY_LOW;
    if (delayHigh < 7000 || delayHigh > 13000) delayHigh = DEFAULT_DELAY_HIGH;
    TimeStep = round((delayHigh - delayLow) / 31);
  }
  EEPROM.end();
}
void Check_Triak() { // Проверка на пробитие симистора
  if (heatEnabled && !boostMode && !boostLoadActive) {
    if ((millis() - timeJump >= TIME_CHECK_TRIAK) && ((power - powerSetpoint) >= maxPower * K_CHECK) && maxPower > 100) {
      heat(OFF);
      digitalWrite(heatPin, LOW);//отключаем контактор
      error = true;
      Serial.println("Ошибка более "+ String(maxPower * K_CHECK) +" мощности за " + String(TIME_CHECK_TRIAK/1000) + " сек, возможно пробит симистр!");
      LD.printString_6x8("=== Triak error! ===", 1, 7);
    }
  }
}
void CalibrateLoop() {
  if (millis() - tm >= 6000) { //через 6 сек запись в калибровочную таблицу
  tm = millis();
  if (!heatEnabled) { boostLoadActive = false; return; }//если вдруг нагрев отключен калибровку отменяем
  if (maxPower < power && power > 10) maxPower = power; 
  PLt[nPL]=power;
  Serial.println("delayTime= " + String(delayTime) + ", Power = " + String(PLt[nPL]));
  set_delay(delayTime - round((delayHigh - delayLow) / 31));
  nPL--;
  if (nPL<0) {
    EndCalibrateTEN();
    }
  } 
}
void StartCalibrateTEN() {// Начинаем прогрузку ТЭНа
  boostLoadActive = true;  // Активируем режим прогрузки
  digitalWrite(heatPin, HIGH);
  heatEnabled = true;  // Включаем нагрев
  set_delay(delayHigh);  // Устанавливаем начальную мощность
  TrueMAXPowerTEN=false; //Старт прогрузки
  maxPower = 1;
  tm = millis();
  nPL=31;
}
void EndCalibrateTEN() {
    Serial.println("Exit calibrate TEN.");
    TrueMAXPowerTEN = true;
    boostLoadActive = false;
    for (uint8_t i=0; i<32; i++) {//Пересчет в относительные единицы чтобы отвязаться от мощности ТЭНа
      PL[i] = (uint16_t)round((PLt[i]*10000)/maxPower);
      Serial.print(String(PL[i]) + ", ");
    }
    Serial.println();
    EEPROM.begin(EEPROM_SIZE);
    EEPROM.put(PL_ADDR, PL);
    EEPROM.put(PMAX_TEN_ADDR, maxPower);
    EEPROM.commit();
    EEPROM.end();
    calculateSensitivityTable();
    heatEnabled = false;  // Отключаем нагрев
    digitalWrite(heatPin, LOW);
}
// Обработчик прерывания таймера
void ICACHE_RAM_ATTR timerISR() {
    static bool triac_state = false;
    if (heatEnabled && !triac_state) {
        GPOS = (1 << triacPin);   // Включаем симистор
        timer1_write(500); // 100 мкс * 5 = 500 тиков
        triac_state = true;
        #ifdef ZCDEBUG
        delayTrue = micros() - ZeroCrossTime_F;//измерение задержки
        #endif
    } else {
        GPOC = (1 << triacPin); // Выключаем симистор
        triac_state = false;
        timer_armed = false;        
        //timer1_disable();
    }
}
void timerInit() {
    timer1_isr_init();
    timer1_attachInterrupt(timerISR);
    timer1_enable(TIM_DIV16, TIM_EDGE, TIM_SINGLE);
    // TIM_DIV16 = 80MHz/16 = 5MHz (1 тик = 0.2 мкс)
    // TIM_EDGE - считает до 0 и генерирует прерывание
    // TIM_SINGLE - одноразовый таймер
}
void ICACHE_RAM_ATTR F_zeroCrossISR() {// Обработчик прерывания по детектору нуля
  static uint32_t lastZeroCrossTime_F = 0;
  lastZeroCrossTime_F = ZeroCrossTime_F;
  ZeroCrossTime_F = micros();
  if (ZeroCrossTime_F-lastZeroCrossTime_F > 5000) {//антидребезг
    if (!heatEnabled) return;
    if (heatEnabled && !timer_armed) {
        // Устанавливаем таймер на delayTime микросекунд
        // Для timer1: 1 тик = 0.2 мкс при TIM_DIV16
        timer1_write(delay_ticks); 
        timer_armed = true;// Взвели таймер
    }
    if (StartZC) attachInterrupt(digitalPinToInterrupt(zeroCrossPin), R_zeroCrossISR, RISING ); // Настройка прерывания по детектору нуля
  }
}

void ICACHE_RAM_ATTR R_zeroCrossISR() {// Обработчик прерывания по детектору нуля для определения смещения
  static uint32_t ZeroCrossTime_R = 0, lastZeroCrossTime_R = 0;
  static uint8_t ZCSCount = 100;
  lastZeroCrossTime_R = ZeroCrossTime_R;
  ZeroCrossTime_R = micros();
  if (ZeroCrossTime_R-lastZeroCrossTime_R < 50) return;//антидребезг
  if (ZCSCount) {
      // Используем атомарное чтение ZeroCrossTime_F
      uint32_t zc_f;
      noInterrupts();
      zc_f = ZeroCrossTime_F;
      interrupts();
      dtTime = (dtTime + ((ZeroCrossTime_R - zc_f) >> 1)) >> 1;
      ZCSCount--;
  } else {
      StartZC = false;
  }
  attachInterrupt(digitalPinToInterrupt(zeroCrossPin), F_zeroCrossISR, FALLING ); // Настройка прерывания по детектору нуля  
}
void zeroCrossInit() {
  calculateSensitivityTable();
  attachInterrupt(digitalPinToInterrupt(zeroCrossPin), F_zeroCrossISR, FALLING ); // Настройка прерывания по детектору нуля
  timerInit(); // Инициализируем таймер
  Serial.println("Работа..."); Serial.println();
}
void triakLoop() {
  if (boostLoadActive) {
    CalibrateLoop();// Обновляем таблицу мощностей ТЭН-а в процессе его прогрузки
    return;
  }
  else if (REG) updateDelay();
  static bool lastHeatState = false;
  if (heatEnabled != lastHeatState) {
      if (!heatEnabled) {
          digitalWrite(triacPin, LOW);
      }
      lastHeatState = heatEnabled;
  }
  
  Check_Triak();                          // Проверка симистора на пробитие
  //установка флага невозможности выполнить задачу по мощности
  insufficientVoltage = (powerSetpoint > power && delayTime == delayLow);
  #ifdef ZCDEBUG
  if (heatEnabled)    Serial.println("Задержка: " + String(delayTime) + " - " + String(delayTrue)
  + " мкс, Смещение нуля: " + String(dtTime)+ " , delay_ticks: " + String(delay_ticks));
  #endif
}