/*
  Name:    SauronPlus.cpp
  Author:  Roman Schönfeld & Daniel Hausherr
*/

/***************************************************************************************************
  This work is licensed under a Creative Commons Attribution 4.0 International (CC BY 4.0) License.

  You are free to:

  Share — copy and redistribute the material in any medium or format
  Adapt — remix, transform, and build upon the material
  for any purpose, even commercially.

  The licensor cannot revoke these freedoms as long as you follow the license terms.

  Under the following terms:

  Attribution — You must give appropriate credit, provide a link to the license, and indicate if
  changes were made. You may do so in any reasonable manner, but not in any way that suggests
  the licensor endorses you or your use.

  No additional restrictions — You may not apply legal terms or technological measures that
  legally restrict others from doing anything the license permits.

  Credits:
  Laboratory for Physics and Illumination Technologies,
  University of Applied Sciences Southern Westfalia
  Germany

  Make Light! Support by the BMBF in the development of this device is gratefully acknowledged
***************************************************************************************************/
#include "SauronPlus.h"

SoftWire Wire = SoftWire(SOFT_SDA,SOFT_SCL);
char swTxBuffer[16];
char swRxBuffer[16];

/***************************************************************************************************
**  Constanten
***************************************************************************************************/
static const int I2C_MCDC04_ADD = 0x74, I2C_IOEXPANDER_ADD = 0x41;

/***************************************************************************************************
**  locale
***************************************************************************************************/
referenceCurrent        current_ref;
io_expander_direction   current_direction;

/***************************************************************************************************
**  locale Variablen
***************************************************************************************************/
static int8_t tmp_current_attenuation = -1, tmp_current_time = -1, tmp_current_io = -1, tmp_ref = -1;
static double max_range = 0.0d;
/***************************************************************************************************
**  locale Functions
***************************************************************************************************/

static int8_t pinmode_io_expander();
static int8_t write_to_io_expander_analog_pin(uint8_t);
static int8_t raw_counts(uint16_t*);
static int8_t device_op_state(bool);
static int8_t i2c_write_register_device(uint8_t*, uint8_t, uint8_t);
static int8_t i2c_read_register_device(uint8_t*, uint8_t, uint8_t, uint8_t);

/***************************************************************************************************
**  external Functions
***************************************************************************************************/
// initialized devices
void init_device() {
  Wire.setTxBuffer(swTxBuffer, sizeof(swTxBuffer));
  Wire.setRxBuffer(swRxBuffer, sizeof(swRxBuffer));
  Wire.setDelay_us(5);
  Wire.setTimeout(1000);
  Wire.begin();

  current_ref =               (referenceCurrent)IREF_NA_5120;
  current_time =              (integrationTime)TINT_MS_64;
  current_direction =         (io_expander_direction)IO_OUTPUT;
  current_attenuation =       (attenuation)ATTENUATION_1;

  pinmode_io_expander();

  set_settings_device();
}

int8_t set_settings_device() {

  if ((tmp_ref != (int8_t)current_ref) || (tmp_current_attenuation != (int8_t)current_attenuation) || ((current_io == IO_HIGH_MAZET) && (tmp_current_time != (int8_t)current_time))) {

    switch (current_attenuation) {
      case ATTENUATION_1:                    // 100µA
        current_io =                (io_expander)IO_HIGH_VFC;

        digitalWrite(_SW_A0, LOW);
        digitalWrite(_SW_A1, LOW);
        max_range = 100.0d;
        break;
      case ATTENUATION_10:                    // 1mA
        current_io =                (io_expander)IO_HIGH_VFC;

        digitalWrite(_SW_A0, LOW);
        digitalWrite(_SW_A1, HIGH);

        max_range = 1000.0d;
        break;
      case ATTENUATION_100:                    // 10mA
        current_io =                (io_expander)IO_HIGH_VFC;

        digitalWrite(_SW_A0, HIGH);
        digitalWrite(_SW_A1, LOW);

        max_range = 10000.0d;
        break;
      case ATTENUATION_1000:                    // 50mA
        current_io =                (io_expander)IO_HIGH_VFC;

        digitalWrite(_SW_A0, HIGH);
        digitalWrite(_SW_A1, HIGH);

        max_range = 50000.0d;
        break;
      case ATTENUATION_X: {                   // < 5 µA
          current_io =                (io_expander)IO_HIGH_MAZET;

          uint8_t configx[] = { _CREGL, 0,  _CMD };

          if (current_ref == 4)
            max_range = 5.120d;
          if (current_ref == 3)
            max_range = 1.280d;
          if (current_ref == 2)
            max_range = 0.320d;
          if (current_ref == 1)
            max_range = 0.080d;
          if (current_ref == 0)
            max_range = 0.020d;


          if (device_op_state(STOP))
            return -1;

          configx[1] = (((uint8_t)current_ref << 4) | (uint8_t)current_time);
          configx[1] = (configx[1] | 0x80); // setze die Anode der Photodiode an den Eingang des ICs

          if (i2c_write_register_device(configx, sizeof(configx), I2C_MCDC04_ADD) != 0)  // 0  succes
            return -1;

          break;
        }
      default:
        break;
    }

    if (tmp_current_io != current_io) {

      if (current_io == IO_HIGH_VFC)
        digitalWrite(_SW_EN, HIGH);
      else if (current_io == IO_HIGH_MAZET)
        digitalWrite(_SW_EN, LOW);

      write_to_io_expander_analog_pin((uint8_t)current_io);
      write_to_io_expander_analog_pin((uint8_t)IO_LOW);

      tmp_current_io = (int8_t)current_io;
    }

    tmp_current_attenuation = (int8_t)current_attenuation;
    tmp_current_time = (int8_t)current_time;
    tmp_ref = (int8_t)current_ref;
  }

  delay(75);

  return 0;
}

double get_max_range() {
  return max_range;
}

uint8_t get_range() {
  uint8_t range = 0;

  if (max_range == 0.020d)
    range = 1;

  if (max_range == 0.080d)
    range = 2;

  if (max_range == 0.320d)
    range = 3;

  if (max_range == 1.280d)
    range = 4;

  if (max_range == 5.120d)
    range = 5;

  if (max_range == 100.00d)
    range = 6;

  if (max_range == 1000.00d)
    range = 7;

  if (max_range == 10000.00d)
    range = 8;

  if (max_range == 50000.00d)
    range = 9;

  return range;
}

int8_t get_value_in_current(double * value) {
  uint16_t counts, int_time = 0;

  int_time = (uint16_t)((double)(pow(2.0d, (double)current_time) + 0.5d));

  if ((raw_counts(&counts) == 0)) 
  {
    switch (current_io) {
      case IO_HIGH_VFC: {

          (*value) = ((double)counts / (double)(int_time * 100.0d));
          (*value) *= 100.0d;
          // max. 50 mA
          if ((uint8_t)current_attenuation == 3)
            (*value) *= (double)((uint16_t)(pow(10.0d, (double)current_attenuation) + 0.5d)) / 2.0d;
          else
            (*value) *= (double)((uint16_t)(pow(10.0d, (double)current_attenuation) + 0.5d));
          break;
        }
      case IO_HIGH_MAZET: {

          (*value) = ((double)counts  / (double)((int_time * 1024.0d) - 1));
          (*value) =  value[0] * (double)((uint16_t)(20.0d * pow(4.0d, (double)current_ref) + 0.5d));
          (*value) =  value[0] / 1000.0d;

          break;
        }
      default:
        break;
    }
  } 
  else
  {
    return -1;
  }
  
  return 0;
}

int8_t define_matching_range() {

  bool loop_end = false;
  uint8_t save_time = 0;
  uint16_t counts_current = 0, max_value = 0, min_value = 0, time_offset = 1;

  save_time = (uint8_t)current_time;
  
  current_time = (integrationTime)TINT_MS_4;

  while (!loop_end) {
    
    if (set_settings_device() != 0)
      return -1;

    if (current_io == IO_HIGH_VFC)
      time_offset = 100;
    else
      time_offset = 1024;

    max_value = (uint16_t)(pow(2.0d, (double)current_time) + 0.5d) * time_offset;
    max_value = (uint16_t)(((double)max_value /  100.0d) * 95.0d);

    if (current_attenuation == ATTENUATION_1 || current_attenuation == ATTENUATION_X)
      min_value =  (uint16_t)(((double)max_value  / 100.0d) * 3.0d);
    else
      min_value =  (uint16_t)(((double)max_value  / 100.0d) * 10.0d);

    if (raw_counts(&counts_current) == 0) 
    {
      switch (current_io) {
        case IO_HIGH_VFC:
        
          if (counts_current >= max_value) {
            if ((uint8_t)current_attenuation < 3)
              current_attenuation = (attenuation)((uint8_t)current_attenuation + 1);
            if ((uint8_t)current_attenuation == 3)
              loop_end = true;
          }
          else {
            if (counts_current < min_value ) {
              if ((uint8_t)current_attenuation > 0)
                current_attenuation = (attenuation)((uint8_t)current_attenuation - 1);
              else if ((uint8_t)current_attenuation == 0)
                current_attenuation = (attenuation)ATTENUATION_X;//loop_end = true;
            }
            else
              loop_end = true;
          }

          break;
        case IO_HIGH_MAZET:
        
          if (counts_current >= max_value) {
            if ((uint8_t)current_ref < 4)
              current_ref = (referenceCurrent)((uint8_t)current_ref + 1);
            else if ((uint8_t)current_ref == 4)
            {
              current_attenuation = (attenuation)ATTENUATION_1;//Serial.println(current_attenuation); loop_end = true;
            }
          }
          else {
            if (counts_current < min_value) {
              if ((uint8_t)current_ref > 0)
                current_ref = (referenceCurrent)((uint8_t)current_ref - 1);
              if ((uint8_t)current_ref == 0)
                loop_end = true;
            }
            else
              loop_end = true;
          }

          break;
        default:
        
          break;
      }

      if (loop_end)
        break;

    } 
    else 
    {     
      return -1;
    }
      
  } // end while

  current_time = (integrationTime)save_time;

  set_settings_device();

  return 0;
}
/***************************************************************************************************
**  internal Functions
***************************************************************************************************/
/***************************************************************************************************
**  device_op_state
**
**
**
**  Input:
**  Output: -1 - No Succes
**           0 - Succes
***************************************************************************************************/
int8_t device_op_state(bool state) {
  uint8_t osr_reg[] = { _OSR, 0};

  if (state)
    osr_reg[1] = _MEASUREMENT_MODE;
  else
    osr_reg[1] = _CONFIG_MODE;

  if (i2c_write_register_device(osr_reg, sizeof(osr_reg), I2C_MCDC04_ADD)) // ungleich 0 no succes
    return -1;

  return 0;
}
/***************************************************************************************************
**  write_to_io_expander_analog_pin
**
**
**
**  Input:
**  Output: -1 - No Succes
**           0 - Succes
***************************************************************************************************/
static int8_t write_to_io_expander_analog_pin(uint8_t state) {
  uint8_t conf[2];

  conf[0] = 0x01;
  conf[1] = state;

  if (i2c_write_register_device(conf, sizeof(conf), I2C_IOEXPANDER_ADD))  // ungleich 0 no succes
    return -1;

  return 0;
}
/***************************************************************************************************
**  pinmode_io_expander
**
**
**
**  Input:
**  Output: -1 - No Succes
**           0 - Succes
***************************************************************************************************/
static int8_t pinmode_io_expander() {

  uint8_t conf[] = { 0x03, 0x00 };

  // initialize io port
  conf[1] = (uint8_t)current_direction;

  if (i2c_write_register_device(conf, sizeof(conf), I2C_IOEXPANDER_ADD) != 0)  // 0 succes
    return -1;

  return 0;
}
/***************************************************************************************************
**  raw_counts
**
**
**
**  Input:
**  Output: -1 - No Succes
**           0 - Succes
***************************************************************************************************/
static int8_t raw_counts(uint16_t *value) {
  switch (current_io) {
    case IO_HIGH_VFC: {

        uint8_t f_compensation = 8;
        uint16_t int_time = 0;

        int_time = (uint16_t)((double)(pow(2.0d, (double)current_time) + 0.5d));

        FreqCounter::f_comp = f_compensation;     // Set compensation to 8
        FreqCounter::start(int_time);

        while (FreqCounter::f_ready == 0);         // wait until counter ready

        (*value) = (uint16_t)FreqCounter::f_freq;

        break;
      }
    case IO_HIGH_MAZET: {
        uint8_t data[2];
        uint16_t ready_pin_timeouts = 0;

        if (device_op_state(START) == 0) {  // Parameter TRUE falls Messung starten und FALSE falls Messung beenden

          while (true) {
            ready_pin_timeouts++;
            if (ready_pin_state_high) break;
            if (ready_pin_timeouts > 100) {
              return -1;
            }
            else {
              delay(1);
            }
          }
          
          ready_pin_state_high = false;

          if (i2c_read_register_device(data, sizeof(data), (uint8_t)_OUTX, I2C_MCDC04_ADD) != 0) // ungleich 0 no succes
            return -1;

          (*value) = (uint16_t)(((data[1]) << 8) | data[0]);
        }
        else
          return -1;

        break;
      }
    default:
      (*value) = 0;
      break;
  }
  return 0;
}
/***************************************************************************************************
**  i2c_write_register_device
**
**  schreibt in die Registern
**
**  Input:   command Buffer beinhaltet die Registerwerte, size Größe des Buffers
**  Output: -1 - No Succes
**           0 - Succes
***************************************************************************************************/
static int8_t i2c_write_register_device(uint8_t* conf, uint8_t b_size, uint8_t address ) {

  delay(1);

  Wire.beginTransmission(address);
  Wire.write(conf, b_size);

  return (int8_t)Wire.endTransmission(); // 0 succes
}
/***************************************************************************************************
**  i2c_read_register_device
**
**  liest aus den Registern
**
**  Input:   value Buffer zum einlesen der Werte, t_size Größe des Buffers, register_number Register Nummer aus dem gelesen werden soll
**  Output: -1 - No Succes
**           0 - Succes
***************************************************************************************************/
static int8_t i2c_read_register_device(uint8_t *data, uint8_t b_size, uint8_t reg, uint8_t address) {
  uint8_t reg_data[] = { reg };
  uint16_t i2c_read_timeouts = 0;

  if (i2c_write_register_device(reg_data, sizeof(reg_data), address) != 0) // ungleich 0 no succes
    return -1;

  Wire.requestFrom(address, b_size);

  while (Wire.available() < b_size) {
    i2c_read_timeouts++;
    if (i2c_read_timeouts > 100) {
      return -1;
    }
    else {
      delay(1);
    }
  }

  for (uint8_t i = 0; i < b_size; i++)
    data[i] = (uint8_t)Wire.read();

  return 0;
}
