#include <msp430x14x.h>
#include <math.h>
#include <limits.h>
  
#include "sht11.h"

#include "basic.h"
#include "sensor.h"

enum {TEMP,HUMI};

#define	SET_DATA          P4DIR &=  ~BIT4; 
#define CLR_DATA          P4OUT &= ~BIT4; P4DIR |=  BIT4; 
#define GET_DATA(result)  result = ((P4IN & BIT4) >> 4)
#define	SET_SCK           P4OUT |=  BIT3; 
#define CLR_SCK           P4OUT &= ~BIT3; 

#define noACK 0
#define ACK   1
                            //adr  command  r/w
#define STATUS_REG_W 0x06   //000   0011    0
#define STATUS_REG_R 0x07   //000   0011    1
#define MEASURE_TEMP 0x03   //000   0001    1
#define MEASURE_HUMI 0x05   //000   0010    1
#define RESET        0x1e   //000   1111    0

//
// Coefficients for linearing the humdity sensor output
//
static const float C1 = -4.0;      
static const float C2 = 0.0405;
static const float C3 = -0.0000028;

//
// Coefficients for converting measured temperature
// to Fahrenheit
//
static const float D1_c = -38.4;     
static const float D2_c = 0.0098;

//
// Coefficients for converting measured temperature
// to Fahrenheit
//
static const float D1_f = -37.1;     
static const float D2_f = 0.0176;

//
// Coefficients for converting linearized humdity to true
// relative humidity.
//
static const float T1 = 0.01;     
static const float T2 = 0.00008;

static float lastTempC = 0.0;

static unsigned char lastMsb;
static unsigned char lastLsb;
static unsigned char lastCksum;
static unsigned char lastStatus;

#define DELAY_VALUE (10)

static void mydelay(unsigned int delayCount)
{
  volatile unsigned int j;
  
  for (j=0;j<delayCount;j++)
      _NOP();
}

static char s_write_byte(unsigned char value)
{ 
  unsigned char i;
  unsigned char errorVal = 0;

  CLR_SCK; // tm debug: ensure initial condition
  mydelay(DELAY_VALUE);

  for (i=0x80; i > 0; i /= 2) {   //shift bit for masking
    if (i & value) { 
      SET_DATA;                     //masking value with i , write to SENSI-BUS
    } else { 
      CLR_DATA;
    } 

    mydelay(DELAY_VALUE); // wait for data to stabilize
    
    // symmetric clock pulse
    SET_SCK;                     
    mydelay(DELAY_VALUE);
    CLR_SCK;
    mydelay(DELAY_VALUE);
  }

  SET_DATA;                       //release DATA-line
  mydelay(DELAY_VALUE); // wait for data stabilize before rising edge
  
  SET_SCK;                        //clk #9 for ack 
  mydelay(DELAY_VALUE);
  GET_DATA(errorVal);                //check ack (DATA will be pulled down by SHT11)
  CLR_SCK; 
  mydelay(DELAY_VALUE);
  
  return errorVal;                   //error=1 in case of no acknowledge
}

static unsigned char s_read_byte(unsigned char ack)
{ 
  unsigned char i;
  unsigned char value = 0;
  unsigned char bit;

  CLR_SCK;       // tm debug: ensure initial condition
  SET_DATA;      // release DATA-line
  mydelay(DELAY_VALUE); // allow data to settle

  for (i = 0x80; i > 0; i /= 2) {     //shift bit for masking
    SET_SCK;    
    mydelay(DELAY_VALUE); 
    GET_DATA(bit);                    //clk for SENSI-BUS
    if (bit) 
      value = (value | i);            //read bit  
    CLR_SCK;  		
    mydelay(DELAY_VALUE);			 
  }

  if (ack) {
    CLR_DATA;                         //in case of "ack==1" pull down DATA-Line
  }     

  mydelay(DELAY_VALUE);                   
  SET_SCK;                            //clk #9 for ack
  mydelay(DELAY_VALUE);                             //pulswith approx. 5 us 
  CLR_SCK;
  mydelay(DELAY_VALUE);						    
  SET_DATA;                           //release DATA-line

  return value;
}


//----------------------------------------------------------------------------------
// generates a transmission start 
//       _____         ________
// DATA:      |_______|
//           ___     ___
// SCK : ___|   |___|   |______
//----------------------------------------------------------------------------------
void s_transstart(void)
{  
   CLR_SCK;                   //Initial state: low clock, data tri-state
   SET_DATA;
   mydelay(DELAY_VALUE); // wait for data to settle high if needbe
    
   SET_SCK;
   mydelay(DELAY_VALUE);
   
   CLR_DATA;
   mydelay(DELAY_VALUE);
   
   CLR_SCK;
   mydelay(DELAY_VALUE);
   
   SET_SCK;
   mydelay(DELAY_VALUE);
   
   SET_DATA;		   
   mydelay(DELAY_VALUE);
   
   CLR_SCK;		
   mydelay(DELAY_VALUE);   
}

//----------------------------------------------------------------------------------
// communication reset: DATA-line=1 and at least 9 SCK cycles followed by transstart
//       _____________________________________________________         ________
// DATA:                                                      |_______|
//          _    _    _    _    _    _    _    _    _        ___     ___
// SCK : __| |__| |__| |__| |__| |__| |__| |__| |__| |______|   |___|   |______
//----------------------------------------------------------------------------------

static void s_connectionreset(void)
{  
  int i; 

  SET_DATA; 
  CLR_SCK;                  //Initial state

  mydelay(DELAY_VALUE); // wait for data to go high if it was low
  
  for(i=0; i < 9; i++) {   // >= 9 SCK cycles
    SET_SCK;
    mydelay(DELAY_VALUE);
    CLR_SCK;
    mydelay(DELAY_VALUE);
  }

  s_transstart();           //transmission start
}

static char s_softreset(void)
{ 
  unsigned char errorVal = 0;  

  s_connectionreset();              //reset communication
  errorVal += s_write_byte(RESET);       //send RESET-command to sensor
  return error;                     //error=1 in case of no response form the sensor
}

static char s_read_status_reg(unsigned char *p_value, unsigned char *p_checksum)
{ 
  unsigned char errorVal = 0;

  s_transstart();                   //transmission start
  errorVal = s_write_byte(STATUS_REG_R); //send command to sensor
  *p_value = s_read_byte(ACK);        //read status register (8-bit)
  *p_checksum = s_read_byte(noACK);   //read checksum (8-bit)  

  return errorVal;                     //error=1 in case of no response form the sensor
}

static char s_write_status_reg(unsigned char *p_value)
{ 
  unsigned char errorVal = 0;

  s_transstart();                   //transmission start
  errorVal += s_write_byte(STATUS_REG_W);//send command to sensor
  errorVal += s_write_byte(*p_value);    //send value of status register

  return errorVal;                     //error>=1 in case of no response form the sensor
}

static char s_measure(unsigned int *value, unsigned char *checksum, unsigned char mode)
{ 
  unsigned errorVal = 0;
  unsigned int i;
  unsigned char bit;
  unsigned char high, low;

  s_transstart();                   //transmission start

  switch(mode){                     //send command to sensor
    case TEMP	: errorVal += s_write_byte(MEASURE_TEMP); 
                  break;
    case HUMI	: errorVal += s_write_byte(MEASURE_HUMI); 
                  break;
    default     : break;	 
  }

  for (i = 0; i < UINT_MAX; i++) {
    GET_DATA(bit);
    if (bit == 0) break;             //wait until sensor has finished the measurement
    mydelay(DELAY_VALUE);    
  }

  GET_DATA(bit);

  if (bit) 
    errorVal += 1;                     // or timeout (~2 sec.) is reached

  high      = s_read_byte(ACK);    //read the first byte (MSB)
  low       = s_read_byte(ACK);    //read the second byte (LSB)
  *checksum = s_read_byte(noACK);  //read checksum

  lastMsb = high;
  lastLsb = low;
  lastCksum = *checksum;
  
  *value = (unsigned int)((high << 8) | low);

  return errorVal;
}

static float linearize_humidity(unsigned int humidity_from_sensor)
{
  float linearized_rh;

  // linearized_rh = C3 * pow(humdity_from_sensor,2) + C2 * humdity_from_sensor + C1;

  linearized_rh =  C3 * ((float)(humidity_from_sensor * humidity_from_sensor));
  linearized_rh += C2 * ((float)humidity_from_sensor);
  linearized_rh += C1;

  return linearized_rh;
}

static float true_humidity(float temperature_in_celsius, unsigned int measured_humidity)
{
  float linearized_rh;
  float true_rh;

  linearized_rh = linearize_humidity(measured_humidity);
  
  true_rh = (temperature_in_celsius - 25.0) * (T1 + (T2 * ((float)measured_humidity))) + linearized_rh; 

  if(true_rh > 100.0)
    true_rh = 100.0;                  //cut if the value is outside of   

  if(true_rh < 0.1)
    true_rh = 0.1;                  //the physical possible range

  return true_rh;  
}

static float Fahrenheit(unsigned int measured_temperature)
{
  return (D1_f + D2_f * ((float) measured_temperature));
}

static float Celsius(unsigned int measured_temperature)
{
  return (D1_c + D2_c * ((float) measured_temperature));
}

int SHT11_Temperature_In_Celsius(float *measurement)
{
  float temperature;
  unsigned int errorVal;
  unsigned int sensor_temperature;
  unsigned char status, checksum;

  errorVal = 0;
  s_connectionreset();
  s_read_status_reg(&lastStatus,&checksum);
  errorVal = s_measure(&sensor_temperature,&checksum,TEMP); 
  if (!errorVal) {
    temperature = Celsius(sensor_temperature);
    lastTempC = temperature;
    *measurement = temperature;
  }

  return errorVal;
}

int SHT11_Temperature_In_Fahrenheit(float* measurement)
{
  float temperature;
  unsigned int errorVal;
  unsigned int sensor_temperature;
  unsigned char checksum;

  errorVal = 0;
  s_connectionreset();
  s_read_status_reg(&lastStatus,&checksum);
  errorVal = s_measure(&sensor_temperature,&checksum,TEMP); 
  if (!errorVal) {
    temperature = Fahrenheit(sensor_temperature);
    *measurement = temperature;
  }

  return errorVal;  
}

int SHT11_Humidity(float * pTrueRH)
{
  return SHT11_True_Relative_Humidity(pTrueRH, lastTempC);
}

int SHT11_True_Relative_Humidity(float* measurement, float temperature_in_celsius)
{
  float relative_humidity;
  unsigned int errorVal;
  unsigned int sensor_humidity;
  unsigned char checksum;

  errorVal = 0;
  s_connectionreset();
  errorVal = s_measure(&sensor_humidity,&checksum,HUMI);

  if (!errorVal) {
    relative_humidity = true_humidity(temperature_in_celsius, sensor_humidity);
    *measurement = relative_humidity;
  }

  return errorVal;    
}

void SHT11_Init(void)
{
  sensorRegisterSampleMethod(SENSOR_ID_TEMPERATURE,
                             0,
                             SHT11_Temperature_In_Celsius);
                             
  sensorRegisterSampleMethod(SENSOR_ID_HUMIDITY,
                             0,
                             SHT11_Humidity);
                      
}

