/*
 * Author: klaute -Kai Lauterbach - @kailauterbach - me@klaute.de
 * Date: 09/2016
 * License: GPLv3
 */

/*****************************************************************************/

#include <stdint.h>
#include <stdio.h>
#include <string.h>

#include <avr/wdt.h>

/*****************************************************************************/

void cc_init(void);
void cc_abort(void);
void cc_processData(uint8_t);
void cc_clearReadDataBuffer(void);

/*****************************************************************************/

void cc_setStartFreq(void);
void cc_setEndFreq(void);
void cc_setIntervall(void);
void cc_setFreqStep(void);
void cc_setDriveStrength(void);
void cc_startMeasurement(void);
void cc_getConfig(void);

/*****************************************************************************/

extern "C" {
  #include "globals.h"
}

/*****************************************************************************/

uint8_t cc_commands[] = { 
    CC_CMD_SET_START_FREQ,
    CC_CMD_SET_END_FREQ,
    CC_CMD_SET_INTERVALL,
    CC_CMD_SET_DRIVE_STRENGTH,
    CC_CMD_SET_FREQ_STEP,
    CC_CMD_START_MEASUREMENT,
    CC_CMD_GET_CONFIG,
  };

void (*cc_cmd_functions[])() = {
    CC_CMD_SET_START_FREQ_FUNC,
    CC_CMD_SET_END_FREQ_FUNC,
    CC_CMD_SET_INTERVALL_FUNC,
    CC_CMD_SET_DRIVE_STRENGTH_FUNC,
    CC_CMD_SET_FREQ_STEP_FUNC,
    CC_CMD_START_MEASUREMENT_FUNC,
    CC_CMD_GET_CONFIG_FUNC,
  };

uint8_t cc_cmd_data_to_read[] = {
    CC_CMD_SET_START_FREQ_DATA_TO_READ,
    CC_CMD_SET_END_FREQ_DATA_TO_READ,
    CC_CMD_SET_INTERVALL_DATA_TO_READ,
    CC_CMD_SET_DRIVE_STRENGTH_DATA_TO_READ,
    CC_CMD_SET_FREQ_STEP_DATA_TO_READ,
    CC_CMD_START_MEASUREMENT_DATA_TO_READ,
    CC_CMD_GET_CONFIG_DATA_TO_READ,
  };

uint8_t cc_read_data[CC_READ_DATA_MAX];

/*****************************************************************************/

uint8_t cc_state = CC_STATE_READ_SOM1;

uint8_t cc_cmd_to_call = CC_CMD_NO_CMD;

uint8_t cc_cmd_received_correct = MSG_INCOMPLETE;

uint8_t cc_cmd_data_read_cnt = 0;

/*****************************************************************************/

void cc_setStartFreq()
{
  uint32_t tmp_start_freq  = (uint32_t)cc_read_data[0] << 24;
           tmp_start_freq += (uint32_t)cc_read_data[1] << 16;
           tmp_start_freq += (uint32_t)cc_read_data[2] <<  8;
           tmp_start_freq += (uint32_t)cc_read_data[3];
  if (tmp_start_freq < 1)
    tmp_start_freq = 1;
  if (tmp_start_freq > 150000000)
    tmp_start_freq = 150000000;

  start_freq = tmp_start_freq;

  char* tmp = "     ";
  sprintf(tmp, "%c%c%c%c%c", MSG_SOM1, MSG_SOM2,
                             MSG_TYPE_ANSWER_OK,
                             MSG_EOM1, MSG_EOM2);
  Serial.write(tmp);
}

void cc_setEndFreq()
{
  uint32_t tmp_end_freq  = (uint32_t)cc_read_data[0] << 24;
           tmp_end_freq += (uint32_t)cc_read_data[1] << 16;
           tmp_end_freq += (uint32_t)cc_read_data[2] <<  8;
           tmp_end_freq += (uint32_t)cc_read_data[3];
  if (tmp_end_freq < 1)
    tmp_end_freq = 1;
  if (tmp_end_freq > 150000000)
    tmp_end_freq = 150000000;

  end_freq = tmp_end_freq;

  char* tmp = "     ";
  sprintf(tmp, "%c%c%c%c%c", MSG_SOM1, MSG_SOM2,
                             MSG_TYPE_ANSWER_OK,
                             MSG_EOM1, MSG_EOM2);
  Serial.write(tmp);
}

void cc_setFreqStep()
{
  uint32_t tmp_step_freq  = (uint32_t)cc_read_data[0] << 24;
           tmp_step_freq += (uint32_t)cc_read_data[1] << 16;
           tmp_step_freq += (uint32_t)cc_read_data[2] <<  8;
           tmp_step_freq += (uint32_t)cc_read_data[3];
  if (tmp_step_freq < 1)
    tmp_step_freq = 1;
  if (tmp_step_freq > 150000000)
    tmp_step_freq = 150000000;

  step_freq = tmp_step_freq;

  char* tmp = "     ";
  sprintf(tmp, "%c%c%c%c%c", MSG_SOM1, MSG_SOM2,
                             MSG_TYPE_ANSWER_OK,
                             MSG_EOM1, MSG_EOM2);
  Serial.write(tmp);
}

void cc_setIntervall()
{
  uint16_t tmp_intervall  = (uint16_t)cc_read_data[0] << 8;
           tmp_intervall += (uint16_t)cc_read_data[1];
  if (tmp_intervall < 1)
    tmp_intervall = 1;
  if (tmp_intervall > 150000000)
    tmp_intervall = 150000000;

  intervall = tmp_intervall;

  char* tmp = "     ";
  sprintf(tmp, "%c%c%c%c%c", MSG_SOM1, MSG_SOM2,
                             MSG_TYPE_ANSWER_OK,
                             MSG_EOM1, MSG_EOM2);
  Serial.write(tmp);
}

void cc_setDriveStrength()
{
  enum si5351_drive tmp_ds  = (enum si5351_drive)cc_read_data[0];
  if (tmp_ds == SI5351_DRIVE_2MA ||
      tmp_ds == SI5351_DRIVE_4MA ||
      tmp_ds == SI5351_DRIVE_6MA ||
      tmp_ds == SI5351_DRIVE_8MA)
  {
    drive_str = tmp_ds;

    char* tmp = "     ";
    sprintf(tmp, "%c%c%c%c%c", MSG_SOM1, MSG_SOM2,
                               MSG_TYPE_ANSWER_OK,
                               MSG_EOM1, MSG_EOM2);
    Serial.write(tmp);
  } else {

    char* tmp = "     ";
    sprintf(tmp, "%c%c%c%c%c", MSG_SOM1, MSG_SOM2,
                               MSG_TYPE_ANSWER_NOK,
                               MSG_EOM1, MSG_EOM2);
    Serial.write(tmp);
  }
}

void cc_startMeasurement()
{

  // 1. send measurement info to host
  //    MSG_TYPE_CONFIG
  cc_getConfig();

  // 2. start a for loop from the frequence to start to the end frequence
  if (start_freq > 0 && start_freq <= 150000000 &&
      end_freq > 0 && end_freq <= 150000000 &&
      start_freq < end_freq &&
      intervall > 0 &&
      step_freq > 0)
  {
    si5351.drive_strength(SI5351_CLK0, drive_str); // 2 4 6 8ma

    uint32_t freq = 0;
    for (freq = start_freq; freq < end_freq; freq += step_freq)
    {
      uint32_t a0_sum  = 0;
      uint32_t a1_sum  = 0;
      uint16_t i = 0;

      si5351.set_freq(freq * 100, SI5351_PLL_FIXED, SI5351_CLK0);
      si5351.output_enable(SI5351_CLK0, 1); // enable clock output 0
      delay(1);

      for (i = 0; i < intervall; i++)
      {
        // 3. on every loop read the analog input A0 and A1 for the in intervall (milliseconds)
        //    and generate the average value, read the ADC value every milli second.
        a0_sum += analogRead(A0);
        a1_sum += analogRead(A1);

        delay(1);
      }

      a0_sum = a0_sum / intervall;
      a1_sum = a1_sum / intervall;

      // 4. send the current output frequency, the drive strength and the measured ADC values from A0 and A1 to the host
      //    MSG_TYPE_MEAS_FREQ_INFO
      Serial.write(MSG_SOM1);
      Serial.write(MSG_SOM2);
      Serial.write(MSG_TYPE_MEAS_FREQ_INFO);
      Serial.write((uint8_t)((freq & 0xff000000) >> 24));
      Serial.write((uint8_t)((freq & 0x00ff0000) >> 16));
      Serial.write((uint8_t)((freq & 0x0000ff00) >>  8));
      Serial.write((uint8_t) (freq & 0x000000ff));
      Serial.write((uint8_t)((a0_sum & 0xff00) >> 8));
      Serial.write((uint8_t) (a0_sum & 0x00ff));
      Serial.write((uint8_t)((a1_sum & 0xff00) >> 8));
      Serial.write((uint8_t) (a1_sum & 0x00ff));
      Serial.write(MSG_EOM1);
      Serial.write(MSG_EOM2);

      si5351.output_enable(SI5351_CLK0, 0); // disable clock output 0
    }

    // 5. send a measurement end message to the host
    //    MSG_TYPE_MEAS_END_INFO
    char* tmp = "     ";
    sprintf(tmp, "%c%c%c%c%c", MSG_SOM1, MSG_SOM2,
                               MSG_TYPE_MEAS_END_INFO,
                               MSG_EOM1, MSG_EOM2);
    Serial.write(tmp);

  } else {
    // on error
    char* tmp = "     ";
    sprintf(tmp, "%c%c%c%c%c", MSG_SOM1, MSG_SOM2,
                               MSG_TYPE_ANSWER_NOK,
                               MSG_EOM1, MSG_EOM2);
    Serial.write(tmp);
  }

}

void cc_getConfig()
{
  Serial.write(MSG_SOM1);
  Serial.write(MSG_SOM2);
  Serial.write(MSG_TYPE_CONFIG);
  Serial.write((uint8_t)((start_freq & 0xff000000) >> 24));
  Serial.write((uint8_t)((start_freq & 0x00ff0000) >> 16));
  Serial.write((uint8_t)((start_freq & 0x0000ff00) >>  8));
  Serial.write((uint8_t) (start_freq & 0x000000ff));
  Serial.write((uint8_t)((end_freq & 0xff000000) >> 24));
  Serial.write((uint8_t)((end_freq & 0x00ff0000) >> 16));
  Serial.write((uint8_t)((end_freq & 0x0000ff00) >>  8));
  Serial.write((uint8_t) (end_freq & 0x000000ff));
  Serial.write((uint8_t)((step_freq & 0xff000000) >> 24));
  Serial.write((uint8_t)((step_freq & 0x00ff0000) >> 16));
  Serial.write((uint8_t)((step_freq & 0x0000ff00) >>  8));
  Serial.write((uint8_t) (step_freq & 0x000000ff));
  Serial.write((uint8_t)((intervall & 0xff00) >> 8));
  Serial.write((uint8_t) (intervall & 0x00ff));
  Serial.write((uint8_t) drive_str);
  Serial.write(MSG_EOM1);
  Serial.write(MSG_EOM2);
}

/*****************************************************************************/

void cc_init()
{
  cc_state                = CC_STATE_READ_SOM1;
  cc_cmd_to_call          = CC_CMD_NO_CMD;
  cc_cmd_data_read_cnt    = 0;
  cc_cmd_received_correct = MSG_INCOMPLETE;
}

/*****************************************************************************/

void cc_abort()
{
  // send abort message, then init
  char* tmp = "     ";
  sprintf(tmp, "%c%c%c%c%c", MSG_SOM1, MSG_SOM2,
                             MSG_TYPE_ANSWER_NOK,
                             MSG_EOM1, MSG_EOM2);
  Serial.write(tmp);

  cc_init();
}

/*****************************************************************************/

void cc_processData(uint8_t c)
{
  uint8_t i = 0;

  switch (cc_state)
  {
    //*********************************//
    case CC_STATE_READ_SOM1:;
      if (c == MSG_SOM1)
        cc_state = CC_STATE_READ_SOM2;
      else
        cc_abort();
      break;

    //*********************************//
    case CC_STATE_READ_SOM2:;
      if (c == MSG_SOM2)
        cc_state = CC_STATE_READ_CMD;
      else
        cc_abort();
      break;

    //*********************************//
    case CC_STATE_READ_CMD:;
      for (i = 0; i < sizeof(cc_commands)/sizeof(uint8_t); i++)
      {
        if (cc_commands[i] == c)
        {
          if (cc_cmd_data_to_read[i] > 0)
            cc_state = CC_STATE_READ_DATA;
          else
            cc_state = CC_STATE_READ_EOM1;

          cc_cmd_to_call = i; // remember the index of command to call

          break; // break the loop
        }
      }
      break;

    //*********************************//
    case CC_STATE_READ_DATA:;
      // write the variable c to the input buffer
      cc_read_data[cc_cmd_data_read_cnt] = c;

      if (cc_cmd_data_read_cnt >= cc_cmd_data_to_read[cc_cmd_to_call]-1)
      {
        cc_state = CC_STATE_READ_EOM1;
      }

      cc_cmd_data_read_cnt++;

      break;

    //*********************************//
    case CC_STATE_READ_EOM1:;
      if (c == MSG_EOM1)
        cc_state = CC_STATE_READ_EOM2;
      else
        cc_abort();
      break;

    //*********************************//
    case CC_STATE_READ_EOM2:;
      if (c == MSG_EOM2)
      {
        cc_cmd_received_correct = MSG_COMPLETE;
      } else
        cc_abort();
      break;

    default:
      cc_abort();
  }

  //*********************************//
  if (cc_cmd_received_correct == MSG_COMPLETE)
  {
    // call the function using the received data
    (*cc_cmd_functions[cc_cmd_to_call])();
    // clear the read buffer
    cc_clearReadDataBuffer();
    cc_init();
  }
}

/*****************************************************************************/

void cc_clearReadDataBuffer()
{
  uint8_t i = 0;
  for (i = 0; i < CC_READ_DATA_MAX; i++)
  {
    cc_read_data[i] = 0x00;
  }
}

/*****************************************************************************/

