/*
 * Файл: estimate.c
 * Модуль идентификации параметров асинхронного двигателя
 */

#include "estimate.h"
#include "def.h"
#include <math.h>
#include <string.h>

#ifndef M_PI
#define M_PI 3.14159265358979323846f
#endif

/*
 * Настройки эксперимента по определению активного сопротивления статора Rs
 * Метод: разностный метод с двумя уровнями постоянного тока
 */
float RS_IREF1            = 0.1f;     /* первый уровень тока, А */
float RS_IREF2            = 0.2f;     /* второй уровень тока, А */
float RS_SETTLE_TIME      = 0.5f;     /* время установления тока, с */
float RS_THRESHOLD        = 0.98f;    /* относительный порог достижения тока */

/*
 * Настройки эксперимента по определению сопротивления ротора Rr
 * и индуктивностей рассеяния Llr, Lls
 * Метод: подача переменного тока по оси q, RMS измерения
 */
float RRL_IQ_AMP          = 0.2f;     /* амплитуда тока, А */
float RRL_RAMP_TIME       = 2.0f;     /* время нарастания амплитуды, с */
float RRL_FREQ            = 1.0f;     /* частота тестового сигнала, Гц (дефолт) */
int   RRL_SAMPLES         = 200;      /* количество выборок для RMS */
int   RRL_AVG             = 10;       /* количество усреднений на частоте */

/* Частоты для многоточечной идентификации (Гц) */
#define RRL_FREQ_COUNT 5
static const float RRL_FREQS[RRL_FREQ_COUNT] = { 1.0f, 2.0f, 3.0f, 5.0f, 10.0f };

/*
 * Настройки эксперимента по определению взаимной индуктивности Lm
 * Метод: подача постоянного тока по оси d, интегрирование ЭДС на фронте
 */
float LM_IDREF            = 0.3f;     /* заданный постоянный ток, о.е. */
float LM_SETTLE_THRESHOLD = 0.95f;    /* порог окончания переходного процесса */
float LM_SETTLE_TIME      = 0.5f;     /* выдержка для усреднения Id, с */
float LM_DEMAG_TIME       = 0.3f;     /* размагничивание между циклами, с */
int   LM_AVG              = 5;        /* количество измерений для усреднения */

/*
 * Настройки переключения между экспериментами
 */
float STEP_PAUSE_TIME = 0.5f;

/* Текущий шаг идентификации */
static Estimate_Test_t test_step = ESTIMATE_TEST_IDLE;

/* Структура с вычисленными параметрами двигателя */
static Params_t params = { 0 };

/* ============================================
 *   
 * ============================================ */
static RsState_t rs_state = {0};
static RrlState_t rrl_state = {0};
static LmState_t lm_state = {0};
static int in_pause;
static float pause_timer;

/* ============================================
 *  
 * ============================================ */

/*
 *    Vd/Vq  
 * : v -   ( )
 * :   
 */
static inline float vdq_to_volts(float vq) {
    float vq_oe = vq / T1_PRD;
    float dt_oe = DT / T_PWM;
    if (vq_oe > dt_oe) vq_oe -= dt_oe;
    else if (vq_oe < -dt_oe) vq_oe += dt_oe;
    return vq_oe * U_BAZ / SQRT3;
}

/*
 *    Iq  
 * : iq -  
 * :   
 */
static inline float idq_to_amps(float iq) {
    return iq * I_BAZ;
}

/* ============================================
 *  
 * ============================================ */

/*
 *   
 *       
 */
void estimate_init(void) {
    params.Rs = 0.0192f;
    params.Rr = 0.0085f;
    params.Lls = 0.0019364f;
    params.Llr = 0.0019364f;
    params.Lk = params.Llr + params.Lls;
    params.Zk = 0;
    params.Rk = params.Rs+ params.Rr;
    params.Xk = 0;
    params.Lm = 0;
    
    memset(&rs_state, 0, sizeof(RsState_t));
    memset(&rrl_state, 0, sizeof(RrlState_t));
    memset(&lm_state, 0, sizeof(LmState_t));
    in_pause = 0;
    pause_timer = 0;
}

void estimate_reset(void) {
  memset(&rs_state, 0, sizeof(RsState_t));
  memset(&rrl_state, 0, sizeof(RrlState_t));
  memset(&lm_state, 0, sizeof(LmState_t));
  test_step = ESTIMATE_TEST_IDLE;
}

void estimate_start(Estimate_Test_t start_test) {
  estimate_reset();
  test_step = start_test;
}

/*
 *    
 *   
 */
Estimate_Test_t estimate_get_step(void) {
    return test_step;
}

/*
 *      
 *    params
 */
Params_t* estimate_get_params(void) {
    return &params;
}

/* ============================================
 *   
 * ============================================ */

/*
 * :     Rs
 * 
 * :
 *   step0:   IREF1,   
 *   step1:  ,  U1  I1
 *   step2:   IREF2,   
 *   step3:  ,  U2  I2
 *   step4:  Rs = (U2-U1)/(I2-I1), 
 * 
 * :
 *   vq, iq -     (. .)
 *   dt -  
 *   iq_ref -    
 *   freq_ref -     (0   )
 */
static void process_Rs(float vq, float iq, float dt, float* iq_ref, float* freq_ref) {
  if (freq_ref)
    *freq_ref = 0;

  switch (rs_state.step) {
  case 0:
    *iq_ref = RS_IREF1;
    rs_state.sum_v = 0;
    rs_state.sum_i = 0;
    rs_state.sample_cnt = 0;
    if (fabsf(iq) >= RS_IREF1 * RS_THRESHOLD) {
      rs_state.step = 1;
      rs_state.timer = 0;
    }
    break;

  case 1:
    *iq_ref = RS_IREF1;
    rs_state.timer += dt;
    rs_state.sum_v += vdq_to_volts(vq);
    rs_state.sum_i += idq_to_amps(iq);
    rs_state.sample_cnt++;
    if (rs_state.timer >= RS_SETTLE_TIME) {
      if (rs_state.sample_cnt > 0) {
        rs_state.meas1 = rs_state.sum_v / rs_state.sample_cnt;
        rs_state.val1 = rs_state.sum_i / rs_state.sample_cnt;
      }
      rs_state.step = 2;
    }
    break;

  case 2:
    *iq_ref = RS_IREF2;
    rs_state.sum_v = 0;
    rs_state.sum_i = 0;
    rs_state.sample_cnt = 0;
    if (fabsf(iq) >= RS_IREF2 * RS_THRESHOLD) {
      rs_state.step = 3;
      rs_state.timer = 0;
    }
    break;

  case 3:
    *iq_ref = RS_IREF2;
    rs_state.timer += dt;
    rs_state.sum_v += vdq_to_volts(vq);
    rs_state.sum_i += idq_to_amps(iq);
    rs_state.sample_cnt++;
    if (rs_state.timer >= RS_SETTLE_TIME) {
      if (rs_state.sample_cnt > 0) {
        rs_state.meas2 = rs_state.sum_v / rs_state.sample_cnt;
        rs_state.val2 = rs_state.sum_i / rs_state.sample_cnt;
      }
      rs_state.step = 4;
    }
    break;

  case 4:
    *iq_ref = 0;
    if (fabsf(rs_state.val2 - rs_state.val1) > 0.001f) {
      params.Rs = (rs_state.meas2 - rs_state.meas1) / (rs_state.val2 - rs_state.val1);
      if (params.Rs < 0) params.Rs = 0;
    }
    rs_state.step = 5;
    rs_state.done = 1;  /*    */
    break;

  default:
    *iq_ref = 0;
    break;
  }
}



/*
 * :    Rr   
 *
 * :
 *   -      0  RRL_IQ_AMP   RRL_RAMP_TIME
 *   -      
 *   -  RRL_SAMPLES  , Vq^2, Iq^2
 *   -  RMS ,   Zk   Rk
 *   -  Rr = Rk - Rs, Xk, Lk
 *   -  RRL_AVG 
 *
 * :
 *   vq, iq -     (. .)
 *   dt -  
 *   iq_ref -    
 *   freq_ref -    
 */
static void process_RrL(float vq, float iq, float dt, float* iq_ref, float* freq_ref) {

  float f = RRL_FREQS[(rrl_state.freq_idx < RRL_FREQ_COUNT) ? rrl_state.freq_idx : (RRL_FREQ_COUNT - 1)];

  /*    (0...1) */
  float amp_k = rrl_state.ramp_timer / RRL_RAMP_TIME;
  if (amp_k > 1.0f) amp_k = 1.0f;

  /*     */
  float current_amp = RRL_IQ_AMP * amp_k;

  /*     */
  *iq_ref = current_amp * sinf(2.0f * M_PI * f * rrl_state.timer);
  if (freq_ref) *freq_ref = f;
  rrl_state.timer += dt;
  rrl_state.ramp_timer += dt;

  /*        -   */
  if (amp_k < 1.0f) {
    return;
  }

  /*  ,   */
  float vq_oe = vq / T1_PRD;
  float dt_oe = DT / T_PWM;
  if (vq_oe > dt_oe) vq_oe -= dt_oe;
  else if (vq_oe < -dt_oe) vq_oe += dt_oe;

  float vq_V = vdq_to_volts(vq);
  float iq_A = idq_to_amps(iq);

  rrl_state.sum_p += vq_V * iq_A;
  rrl_state.sum_vq2 += vq_V * vq_V;
  rrl_state.sum_iq2 += iq_A * iq_A;
  rrl_state.sample_count++;

  if (rrl_state.sample_count >= RRL_SAMPLES) {
    float P = rrl_state.sum_p / (float)RRL_SAMPLES;
    float Vq_rms = sqrtf(rrl_state.sum_vq2 / (float)RRL_SAMPLES);
    float Iq_rms = sqrtf(rrl_state.sum_iq2 / (float)RRL_SAMPLES);

    if (Iq_rms > 0.001f) {
      float Zk = Vq_rms / Iq_rms;
      float Rk = P / (Iq_rms * Iq_rms);
      float Rr = Rk - params.Rs;
      if (Rr < 0) Rr = 0;
      float Xk = (Zk > Rk) ? sqrtf(Zk * Zk - Rk * Rk) / 2 : 0;
      float Lk = (f > 1e-6f) ? (Xk / (2.0f * M_PI * f)) : 0.0f;

      (void)Rr;
      rrl_state.avg_Zk += Zk;
      rrl_state.avg_Xk += Xk;
      rrl_state.avg_Lk += Lk;
      rrl_state.avg_Rk += Rk;
      rrl_state.avg_count++;
    }

    rrl_state.sum_p = 0;
    rrl_state.sum_vq2 = 0;
    rrl_state.sum_iq2 = 0;
    rrl_state.sample_count = 0;
  }

  if (rrl_state.avg_count >= RRL_AVG) {
    /*      */
    int k = rrl_state.freq_idx;
    if (k < RRL_FREQ_COUNT) {
      rrl_state.freq_Zk[k] = rrl_state.avg_Zk / (float)RRL_AVG;
      rrl_state.freq_Xk[k] = rrl_state.avg_Xk / (float)RRL_AVG;
      rrl_state.freq_Lk[k] = rrl_state.avg_Lk / (float)RRL_AVG;
      rrl_state.freq_Rk[k] = rrl_state.avg_Rk / (float)RRL_AVG;
      rrl_state.freq_ready = k + 1;
    }

    /*     */
    rrl_state.freq_idx++;
    rrl_state.timer = 0.0f;
    rrl_state.ramp_timer = 0.0f;
    rrl_state.sum_p = 0.0f;
    rrl_state.sum_vq2 = 0.0f;
    rrl_state.sum_iq2 = 0.0f;
    rrl_state.sample_count = 0;
    rrl_state.avg_Zk = 0.0f;
    rrl_state.avg_Xk = 0.0f;
    rrl_state.avg_Lk = 0.0f;
    rrl_state.avg_Rk = 0.0f;
    rrl_state.avg_count = 0;

    /*       0  */
    if (rrl_state.freq_idx >= RRL_FREQ_COUNT) {
      float s1 = 0.0f, sf = 0.0f, sf2 = 0.0f;
      float sR = 0.0f, sfR = 0.0f;
      float sfX = 0.0f;
      int n = rrl_state.freq_ready;
      for (int i = 0; i < n; i++) {
        float fi = RRL_FREQS[i];
        float Ri = rrl_state.freq_Rk[i];
        float Xi = rrl_state.freq_Xk[i];
        s1 += 1.0f;
        sf += fi;
        sf2 += fi * fi;
        sR += Ri;
        sfR += fi * Ri;
        sfX += fi * Xi;
      }

      /* Rk(f) = a + b f */
      float den = (s1 * sf2 - sf * sf);
      float aR = (fabsf(den) > 1e-9f) ? ((sR * sf2 - sf * sfR) / den) : (n > 0 ? (sR / (float)n) : 0.0f);

      /* Xk(f) = c f ( 0) */
      float cX = (sf2 > 1e-9f) ? (sfX / sf2) : 0.0f;

      params.Rk = aR;
      params.Xk = 0.0f; /*  f=0   -> 0 */
      params.Zk = params.Rk;
      params.Lk = cX / (2.0f * M_PI);

      params.Rr = params.Rk - params.Rs;
      if (params.Rr < 0.0f) params.Rr = 0.0f;
      params.Lls = params.Lk / 2.0f;
      params.Llr = params.Lk / 2.0f;

      rrl_state.done = 1;
    }
  }
}



/*
 * :    Lm
 *
 * :
 *   -    Idref
 *   -  Ed = Vd - Rs*Id     
 *   -      LM_SETTLE_THRESHOLD -  
 *   - Ls = integral(Ed*dt) / Id_steady
 *   - Lm = Ls - Lls
 *
 * :
 *   vd, id -     (. .)
 *   dt -  
 *   id_ref -    
 *   freq_ref -    
 */
static void process_Lm(float vd, float id, float dt, float* id_ref, float* freq_ref) {
  float vd_abs = vdq_to_volts(vd);
  float id_abs = idq_to_amps(id);
  float ed = vd_abs - params.Rs * id_abs;
  float id_pu = fabsf(id);
  float id_thresh = LM_IDREF * LM_SETTLE_THRESHOLD;

  if (freq_ref)
    *freq_ref = 0.0f;

  switch (lm_state.step) {
  case 0:
    *id_ref = 0.0f;
    lm_state.timer += dt;
    if (lm_state.timer >= LM_DEMAG_TIME) {
      lm_state.timer = 0.0f;
      lm_state.integral_psi = 0.0f;
      lm_state.first_sample = 0;
      lm_state.prev_ed = ed;
      lm_state.step = 1;
    }
    break;

  case 1:
    *id_ref = LM_IDREF;
    if (!lm_state.first_sample) {
      lm_state.prev_ed = ed;
      lm_state.first_sample = 1;
    }
    else {
      lm_state.integral_psi += (ed + lm_state.prev_ed) * 0.5f * dt;
      lm_state.prev_ed = ed;
    }

    if (id_pu >= id_thresh) {
      lm_state.step = 2;
      lm_state.timer = 0.0f;
      lm_state.sum_id = 0.0f;
      lm_state.sample_cnt = 0;
    }
    break;

  case 2:
    *id_ref = LM_IDREF;
    lm_state.timer += dt;
    lm_state.sum_id += id_abs;
    lm_state.sample_cnt++;
    if (lm_state.timer >= LM_SETTLE_TIME) {
      lm_state.step = 3;
    }
    break;

  case 3: {
    *id_ref = LM_IDREF;
    int measure_ok = 0;
    if (lm_state.sample_cnt > 0 && lm_state.integral_psi > 0.0f) {
      float id_avg = lm_state.sum_id / (float)lm_state.sample_cnt;
      if (id_avg > 0.001f) {
        float Ls = lm_state.integral_psi / id_avg;
        float Lm = Ls - params.Lls;
        if (Lm > 0.0f) {
          lm_state.avg_Lm += Lm;
          lm_state.avg_count++;
          measure_ok = 1;
        }
      }
    }

    if (lm_state.avg_count >= LM_AVG) {
      params.Lm = lm_state.avg_Lm / (float)LM_AVG;
      lm_state.done = 1;
      lm_state.step = 4;
    }
    else if (measure_ok) {
      lm_state.timer = 0.0f;
      lm_state.step = 0;
    }
    else {
      lm_state.timer = 0.0f;
      lm_state.step = 0;
    }
    break;
  }

  case 4:
    *id_ref = 0.0f;
    break;

  default:
    *id_ref = 0.0f;
    lm_state.step = 0;
    break;
  }
}

/* ============================================
 *  
 * ============================================ */

 /*
  * estimate_process -   
  *
  * :     ,  
  *                  
  *
  *  :
  *   vd, vq  -     d,q ( )
  *   id, iq  -     d,q ( )
  *   dt      -   ()
  *
  *  :
  *   vd_ref  -     d (. .)
  *   vq_ref  -     q (. .)
  *   freq_ref -     ()
  */
void estimate_process(float vd, float vq, float id, float iq, float dt,
  float* vd_ref, float* vq_ref, float* freq_ref) {

  *vd_ref = 0;
  *vq_ref = 0;
  if (freq_ref) *freq_ref = 0;

  /*    IDLE -    */
  if (test_step == ESTIMATE_TEST_IDLE) {
    return;
  }

  /*    */
  if (in_pause) {
    pause_timer += dt;
    if (pause_timer >= STEP_PAUSE_TIME) {
      in_pause = 0;
      /*     */
      if (test_step == ESTIMATE_TEST_RS) test_step = ESTIMATE_TEST_RR_L;
      else if (test_step == ESTIMATE_TEST_RR_L) test_step = ESTIMATE_TEST_LM;
      else if (test_step == ESTIMATE_TEST_LM) test_step = ESTIMATE_TEST_DONE;
    }
    return;
  }

  switch (test_step) {
  case ESTIMATE_TEST_RS:
    process_Rs(vq, iq, dt, vq_ref, freq_ref);
    if (rs_state.done) {
      in_pause = 1;
      pause_timer = 0;
    }
    break;

  case ESTIMATE_TEST_RR_L:
    process_RrL(vq, iq, dt, vq_ref, freq_ref);
    if (rrl_state.done) {
      in_pause = 1;
      pause_timer = 0;
    }
    break;

  case ESTIMATE_TEST_LM:
    if (vq_ref)
      *vq_ref = 0.0f;
    process_Lm(vd, id, dt, vd_ref, freq_ref);
    if (lm_state.done) {
      in_pause = 1;
      pause_timer = 0;
    }
    break;

  case ESTIMATE_TEST_DONE:
    /*   */
    break;

  default:
    break;
  }
}