// Thermometer declarations ************************************************
int thermometer_initialized = FALSE;

enum ds18s20_power{
        NotChecked = 0,
        Parasitic = 1,
        External = 2,
        CheckAmbiguous = 3,
        CheckFailed = 4
};

enum temperature_scale{
        Celsius = 0,
        Fahrenheit = 1
};

// Temperature Measurement
struct sigaction conversion_done;
double latest_T;
enum dlp_port latest_T_port;
enum port_pin latest_T_pin;
int T_i_started  = 0;
int T_i_finished = 0;
#define MAX_T_POINTS 10000
#define MAX_THERMOMETERS 35

enum temperature_scale preferred_T_scale = Fahrenheit;

struct T_reading {
  double         T_Celsius;
  struct timeval t;
  enum dlp_port  port;
  enum port_pin  pin;
  int            matching;
  unsigned char  id[8];
} T[MAX_T_POINTS];

int thermometer_count = 0;

struct T_specs {
  unsigned char	id[8];
  double a;
  double b;
  double c;
  char location[22];
} thermometerSpecs[36] = {
  {{'\x10','\x48','\xb6','\x35','\x01','\x08','\x00','\xe8'},  0.0000106,  0.00288, -0.090,{"Server Room           "}},
  {{'\x10','\x18','\xbf','\x35','\x01','\x08','\x00','\x35'}, -0.0000160,  0.00285,  0.013,{"Server Room Floor     "}},
  {{'\x10','\xec','\xbe','\x35','\x01','\x08','\x00','\x56'}, -0.0000436,  0.00428,  0.088,{"Server Room Ceiling   "}},
  {{'\x10','\xe2','\xae','\x35','\x01','\x08','\x00','\x39'},  0.0000096,  0.00206,  0.054,{"Master Bedroom        "}},
  {{'\x10','\x96','\xb4','\x35','\x01','\x08','\x00','\xbc'},  0.0000170, -0.00048,  0.079,{"Attic High East       "}},
  {{'\x10','\xf5','\xad','\x35','\x01','\x08','\x00','\xa9'}, -0.0000087,  0.00180, -0.127,{"Attic High Center     "}},
  {{'\x10','\xc3','\x9c','\x35','\x01','\x08','\x00','\xbf'}, -0.0000135,  0.00104,  0.025,{"Front Porch Air       "}},
  {{'\x10','\x4b','\xb5','\x35','\x01','\x08','\x00','\xff'},  0.0000269,  0.00179,  0.022,{"Pump Water            "}},
  {{'\x10','\xc7','\xab','\x35','\x01','\x08','\x00','\xb6'},  0.0001737, -0.00822,  0.127,{"Pumphouse Air         "}},
  {{'\x10','\x7f','\xb1','\x35','\x01','\x08','\x00','\xd1'},  0.0000148,  0.00111, -0.087,{"Pumphouse Sky         "}},
  {{'\x28','\xe0','\xe4','\x14','\x01','\x00','\x00','\x6f'}, -0.0000007, -0.00204,  0.126,{"Pumphouse Ground High "}},
  {{'\x28','\x18','\xe0','\x14','\x01','\x00','\x00','\xa3'}, -0.0001028,  0.00735, -0.110,{"30\" Underground       "}},
  {{'\x28','\xb4','\xf9','\x14','\x01','\x00','\x00','\x0d'},  0.0000104, -0.00146,  0.016,{"Attic High West       "}},
  {{'\x28','\x9c','\xfd','\x14','\x01','\x00','\x00','\x05'}, -0.0000241,  0.00064,  0.003,{"Living Room           "}},
  {{'\x28','\xc2','\xf8','\x14','\x01','\x00','\x00','\xea'}, -0.0000017, -0.00410,  0.095,{"Back Patio            "}},
  {{'\x28','\xd2','\xde','\x14','\x01','\x00','\x00','\xd5'}, -0.0000343,  0.00346, -0.190,{"Garden Tub            "}},
  {{'\x28','\xde','\xec','\x14','\x01','\x00','\x00','\xaf'}, -0.0000390,  0.00206, -0.064,{"Garden Pond           "}},
  {{'\x28','\xa1','\xe2','\x14','\x01','\x00','\x00','\xb1'}, -0.0000292,  0.00227,  0.048,{"Platform              "}},
  {{'\x28','\x85','\xf7','\x14','\x01','\x00','\x00','\x75'}, -0.0000011, -0.00427,  0.160,{"Chicken House         "}},
  {{'\x28','\xd7','\xe4','\x14','\x01','\x00','\x00','\x07'},  0.0000415, -0.00373, -0.040,{"Lower Greenhouse      "}},
  {{'\x28','\xef','\x03','\x15','\x01','\x00','\x00','\x4f'},  0.0000034, -0.00383,  0.226,{"Upper Greenhouse      "}},
  {{'\x28','\x18','\xee','\x14','\x01','\x00','\x00','\x01'}, -0.0000076,  0.00344, -0.083,{"Vegetable Garden      "}},
  {{'\x28','\xd8','\xf4','\x14','\x01','\x00','\x00','\x5f'}, -0.0000046,  0.00053,  0.014,{"Garden Mullberry Tree "}},
  {{'\x28','\xd8','\xfd','\x14','\x01','\x00','\x00','\xac'},  0.0000379, -0.00658,  0.205,{"                      "}},
  {{'\x28','\x94','\xfc','\x14','\x01','\x00','\x00','\x69'}, -0.0000523,  0.00613, -0.111,{"                      "}},
  {{'\x28','\xf4','\x00','\x15','\x01','\x00','\x00','\xa2'},  0.0000259, -0.00558,  0.035,{"                      "}},
  {{'\x28','\xcc','\x00','\x15','\x01','\x00','\x00','\xee'}, -0.0000286, -0.00361,  0.016,{"                      "}},
  {{'\x28','\x96','\xfb','\x14','\x01','\x00','\x00','\x56'}, -0.0000689,  0.00185, -0.108,{"                      "}},
  {{'\x28','\x11','\xde','\x14','\x01','\x00','\x00','\x13'},  0.0000294,  0.00105, -0.145,{"                      "}},
  {{'\x28','\x89','\xf4','\x14','\x01','\x00','\x00','\x46'}, -0.0000412,  0.00159, -0.073,{"                      "}},
  {{'\x28','\xb9','\xe7','\x14','\x01','\x00','\x00','\x99'},  0.0000306, -0.00273,  0.058,{"                      "}},
  {{'\x28','\xc5','\xe2','\x14','\x01','\x00','\x00','\xae'},  0.0000560, -0.00415, -0.018,{"                      "}},
  {{'\x28','\xfd','\xef','\x14','\x01','\x00','\x00','\x0e'},  0.0000584, -0.00018, -0.124,{"                      "}},
  {{'\x28','\xcb','\xea','\x14','\x01','\x00','\x00','\x83'}, -0.0000235,  0.00318, -0.081,{"                      "}},
  {{'\x28','\x97','\xef','\x14','\x01','\x00','\x00','\x02'},  0.0000080, -0.00043,  0.076,{"                      "}},
  {{'\x00'}}
};

struct ds18s20 {
  enum dlp_port      port;
  enum port_pin      pin;
  enum ds18s20_power power;
  unsigned char      id[8];
} thermometer[MAX_THERMOMETERS];

struct timespec remaining_time;
struct timespec delay = {(time_t)0, (long int)1000*1000000};

void thermometer_init();
int check_CRC(unsigned char chars[],int count);
void showThermometerLocation(unsigned char* id);
double thermometerAdjustment(unsigned char* id, double T);
void search_temp_sensors(enum dlp_port port, enum port_pin pin);
int already_started(enum dlp_port port, enum port_pin pin);
void read_ds18s20(enum dlp_port port, enum port_pin pin, char* sensor_id);
void printPortPin(enum dlp_port port, enum port_pin pin);
void get_ds18s20_result();

void thermometer_init(){
  sigset_t mask;
  int i;
  for (i=0; i<40; i++)
    thermometer_port[i] = false;

  T[0].T_Celsius = 0.0;
  T[0].t.tv_sec = 0;
  T[0].t.tv_usec = 0;
  T[0].port = 0;
  T[0].pin  = 0;

  // Seting up signal handler for thermometer reading delays
  conversion_done.sa_handler = get_ds18s20_result;
  sigemptyset(&mask);
  sigaddset(&mask, SIGALRM);
  conversion_done.sa_mask = mask;
  conversion_done.sa_flags = 0;
  conversion_done.sa_restorer = NULL;
  sigaction(SIGALRM, &conversion_done, NULL);

  thermometer_initialized = true;
}

void thermometer_deinit(){
  if (!thermometer_initialized)
    return;
  while (T_i_started > T_i_finished) sleep(1);
}

int check_CRC(unsigned char chars[],int count){
  int i;
  int crc;
  unsigned int lookup_table[256] = {
    0, 94, 188, 226, 97, 63, 221, 131, 194, 156, 126, 32, 163, 253, 31, 65,
    157, 195, 33, 127, 252, 162, 64, 30, 95, 1, 227, 189, 62, 96, 130, 220,
    35, 125, 159, 193, 66, 28, 254, 160, 225, 191, 93, 3, 128, 222, 60, 98,
    190, 224, 2, 92, 223, 129, 99, 61, 124, 34, 192, 158, 29, 67, 161, 255,
    70, 24, 250, 164, 39, 121, 155, 197, 132, 218, 56, 102, 229, 187, 89, 7,
    219, 133, 103, 57, 186, 228, 6, 88, 25, 71, 165, 251, 120, 38, 196, 154,
    101, 59, 217, 135, 4, 90, 184, 230, 167, 249, 27, 69, 198, 152, 122, 36,
    248, 166, 68, 26, 153, 199, 37, 123, 58, 100, 134, 216, 91, 5, 231, 185,
    140, 210, 48, 110, 237, 179, 81, 15, 78, 16, 242, 172, 47, 113, 147, 205,
    17, 79, 173, 243, 112, 46, 204, 146, 211, 141, 111, 49, 178, 236, 14, 80,
    175, 241, 19, 77, 206, 144, 114, 44, 109, 51, 209, 143, 12, 82, 176, 238,
    50, 108, 142, 208, 83, 13, 239, 177, 240, 174, 76, 18, 145, 207, 45, 115,
    202, 148, 118, 40, 171, 245, 23, 73, 8, 86, 180, 234, 105, 55, 213, 139,
    87, 9, 235, 181, 54, 104, 138, 212, 149, 203, 41, 119, 244, 170, 72, 22,
    233, 183, 85, 11, 136, 214, 52, 106, 43, 117, 151, 201, 74, 20, 246, 168,
    116, 42, 200, 150, 21, 75, 169, 247, 182, 232, 10, 84, 215, 137, 107, 53
  };

  crc = 0;
  for (i=0; i<count; i++){
    crc = lookup_table[crc ^ chars[i]];
  }
  return crc;
}

void showThermometerLocation(unsigned char* id){
  int i, j;
  i = 0;
  while (thermometerSpecs[i].id[0] != 0x00){
    j = 0;
    while (j < 8 && thermometerSpecs[i].id[j] == id[j]) j++;
    if (j == 8){
      printf("%23s ", (char*)thermometerSpecs[i].location);
      return;
    }
    i++;
  }
  printf("*** Unlisted thermometer ***\n");
}

double thermometerAdjustment(unsigned char* id, double T){
  int i, j;
  i = 0;
  while (thermometerSpecs[i].id[0] != 0x00){
    j = 0;
    while (j < 8 && thermometerSpecs[i].id[j] == id[j]) j++;
    if (j == 8){
      return thermometerSpecs[i].a*T*T + thermometerSpecs[i].b*T + thermometerSpecs[i].c;
    }
    i++;
  }
  printf("*** Unlisted thermometer ***\n");
  return 0.0;
}

void search_temp_sensors(enum dlp_port port, enum port_pin pin){
  int i,j,k, found, last_group;

  pthread_mutex_lock(&dlp_command_mutex);
  outbuf[1] = SearchDS18S20s;
  outbuf[2] = (unsigned char)(port+pin);
  send_dlp_command();
  last_group = false;
  i = 0;
  while (!last_group){
    k = 0;           
    found = false;
    if (inbuf[i] == 0x02 || inbuf[i] == 0x08 || inbuf[i] == 0x00 || inbuf[i] == 0xc0){
      last_group = true;
      for (j = 1; j < 8; j++){
        if (inbuf[i+j] != inbuf[i]){
          last_group = false;
          break;
        }
      }
      if (last_group){
        switch (inbuf[i]){
          case 0x02:
            printf("Sensor search error: Temperature sensor not detected on pin %d of port %c.\n", pin, portToLetter(port));
            break;
          case 0x08:
            printf("Sensor search error: Short-circuit detected or no pull-up resistor at pin %d of port %c.\n", pin, portToLetter(port));
            break;
          case 0xC0:
            printf("Search algorithm reported 0x11 result for test bit pair on pin %d of port %c.\n", pin, portToLetter(port));
            break;
          case 0x00:
            break;
          default:
            last_group = false;
        }
      }
    }
    if (!last_group){
      if (check_CRC(&inbuf[i],8)){
        printf("CRC check failed during thermometer search: ");
        for (j=0; j<8; j++)
          printf("%02X", inbuf[i+j]);
        printf("\n");
        exit(-1);
      }
      while (k < thermometer_count){
        if (inbuf[i] == 0x10 || inbuf[i] == 0x28){  // Make sure only work with thermometers
          found = true;                             // Be sure to test this fully.
          for (j = 0; j < 8; j++){
            if (thermometer[k].id[j] != inbuf[i+j])
              found = false;
          }
        }
        k++;
      }
      if (!found)
        k = thermometer_count;
      else
        k--;
      thermometer[k].port = port;
      thermometer[k].pin  = pin;
      switch (inbuf[i+8]){
        case 0:
          thermometer[k].power = Parasitic;
          break;
        case 0x80:
          thermometer[k].power = External;
          break;
        default:
          thermometer[k].power = CheckFailed;
          printf("Power check failed: inbuf=%d\n",inbuf[i+8]);
      }
      if (!found){
        for (j=0; j<8; j++)
          thermometer[k].id[j] = inbuf[i+j];
        thermometer_count++;
      }
      i += 9;
    }
  }
  pthread_mutex_unlock(&dlp_command_mutex);

  pthread_mutex_lock(&verbose_mutex);
  if (verbose){
    for (i = 0; i < thermometer_count; i++){
      if (thermometer[i].port == port && thermometer[i].pin == pin) {
        showThermometerLocation(thermometer[i].id);
        printf("Thermometer %d is on port %c, pin %d, has id=", i, portToLetter(port), pin);
        for (j = 0; j < 8; j++)
          printf("%02x", thermometer[i].id[j]);
        switch (thermometer[i].power){
          case Parasitic:
            printf(", and uses parasitic power.");
            break;
          case External:
            printf(", and uses external power.");
            break;
          case CheckFailed:
            printf(" - power check failed.");
            break;
          case CheckAmbiguous:
            printf(" - power check was ambiguous.");
            break;
          case NotChecked:
            printf(", and power not been checked.");
          default:
            printf(", and is in impossible state.");
        } 
        printf("\n");
      }
    }
  }
  pthread_mutex_unlock(&verbose_mutex);
}


int already_started(enum dlp_port port, enum port_pin pin){
  int i;
  for (i = T_i_finished; i < T_i_started; i++)
    if (T[i].port == port && T[i].pin == pin)
      return true;
  return false;
}


void read_ds18s20(enum dlp_port port, enum port_pin pin, char* sensor_id){
  struct itimerval conversion_wait;
  struct timeval t;
  unsigned char   response;
  int i,j, first_T_i_started;
  switch (port){
    case PortA:
      if (port == PortA && analog_port[pin]){
        printf("Port A, pin %d must be set to digital for DS18S20 measurement.\n", pin);
        exit(-1);
      }
      if (pin > 5){
        printf("Port A, pins 5-7 are not available for temperature measurement.\n");
        exit(-1);
      }
      break;
    case PortB:
      if (pin != 0 && pin !=5){
        printf("Only pins 0 and 5 of port B can be used for temperature measurement.\n");
        exit(-1);
      }
      break;
    case PortC:
      break;
    case PortD:
      printf("Note: Port D is shared with Channel B of the FTDI USB interface chip.\n");
      break;
    case PortE:
      if (pin < 3)
        printf("Note: Port E pins 0-2 are shared with the LED and control lines of Channel B of the FTDI USB interface chip.\n");
      else {
        printf("Pins 3-7 of Port E are not available.\n");
        exit(-1);
      }
      break;
    }
  while (already_started(port, pin)) sleep(1);  
  first_T_i_started = T_i_started + 1;
  pthread_mutex_lock(&dlp_command_mutex);
  outbuf[2] = (unsigned char)(port+pin);
  if (!sensor_id){   // Sensor id is NULL - measure all sensors on this port and pin
     for (j = 0; j < thermometer_count; j++){
       if (thermometer[j].port == port && thermometer[j].pin == pin){
         T_i_started++;
         T[T_i_started].matching = true;
          outbuf[1] = StartMatchDS18S20;
         for (i=3; i< 11; i++){
           outbuf[i] = thermometer[j].id[i-3];
           T[T_i_started].id[i-3] = outbuf[i];
         }
         send_dlp_command();
         response = inbuf[0];
       }
     }
  }
  else if (strlen(sensor_id) != 0){  // 8-byte sensor id is given - measure specific thermometer
    T_i_started++;
    T[T_i_started].matching = true;
    outbuf[1] = StartMatchDS18S20;
    for (i=3; i< 11; i++){
      outbuf[i] = *(sensor_id+i-3);
      T[T_i_started].id[i-3] = outbuf[i];
    }
    send_dlp_command();
    response = inbuf[0];
  }
  else{    // Sensor id is not NULL and not 8-bytes - assume one thermometer on this port and pin
    T_i_started++;
    T[T_i_started].matching = false;
    outbuf[1] = StartDS18S20;
    send_dlp_command();
    response = inbuf[0];
    outbuf[1] = ReadDS18S20Id;
    send_dlp_command();
    for (i=0; i < 8; i++){
      T[T_i_started].id[i] = inbuf[i];
    }
  } 
  pthread_mutex_unlock(&dlp_command_mutex);
  thermometer_port[port+pin-0x28] = false;
  switch (response){
    case 99:
      thermometer_port[port+pin-0x28] = true;
      break;
    case 8:
      printf("\n **** Short-circuit or no pull-up resistor at pin %d of port %c ****\n", pin, portToLetter(port));
      exit(-1);
    case 2:
      printf("\n **** Temperature sensor ");
      if (T[T_i_started].matching){
        printf("(");
        for (i=0; i<8; i++)
          printf("%02x",T[T_i_started].id[i]);
        printf(") ");
      }
      printf("not detected at pin %d of port %c ****\n", pin, portToLetter(port));
      exit(-1);
    default:
      printf("\n **** Unknown Temperature sensor status byte of %02X ****\n", response);
      exit(-1);
  }
  pthread_mutex_lock(&verbose_mutex);
  if (verbose)
//    printf("Temperature sensor ready at pin %d of port %c:\n", pin, portToLetter(port));
  pthread_mutex_unlock(&verbose_mutex);

  // A valid temperature will only be available 750 milliseconds after the above initiation.
  // We now prepare a timer that will send a SIGALRM signal after 750 mS and have that signal
  // cause get_ds18s20() to harvest the temperature result.
  gettimeofday(&t,NULL);
  for (i = first_T_i_started; i < T_i_started + 1; i++){
    T[i].t.tv_sec = t.tv_sec;
    T[i].t.tv_usec = t.tv_usec;
    T[i].port = port;
    T[i].pin  = pin;
  }

  conversion_wait.it_interval.tv_sec  = 0;
  conversion_wait.it_interval.tv_usec = 0;
  conversion_wait.it_value.tv_sec     = 0;
  conversion_wait.it_value.tv_usec    = 750000;
  setitimer(ITIMER_REAL, &conversion_wait, NULL);
  // When this timer counts down to zero, get_ds18s20_result() will be run.
  // Until then, we can do other things as long as they don't use the port/pin
  // being used for the temperature measurement.
}


// This is only to be called by the setitimer() call in read_ds18s20().
// Do not call it separately.  read_ds18s20 initiates a new reading and 
// get_ds18s20_result() gets the answer 750 milliseconds later.
void get_ds18s20_result(int signal){
  int res;
  struct timeval t;
  struct itimerval conversion_wait;
  int additional_time;
  char time_string[26];
  int i;
  T_i_finished++;
  pthread_mutex_lock(&dlp_command_mutex);
  outbuf[2] = (unsigned char)(T[T_i_finished].port + T[T_i_finished].pin);
  if(T[T_i_finished].matching){
    outbuf[1] = ReadMatchDS18S20;
    for (i=3; i < 11; i++)
      outbuf[i] = T[T_i_finished].id[i-3];
  }
  else{
    outbuf[1] = ReadDS18S20;
  }
  send_dlp_command();

  if (check_CRC(inbuf,9)){
    printf("CRC check failed during thermometer reading - received bytes: ");
    for (i=0; i<9; i++)
      printf("%02X", inbuf[i]);
    printf("\n");
    exit(-1);
  }
  if (T[T_i_finished].id[0] == 0x10){
    switch ((int)inbuf[1]){
      case 0:
        res = (inbuf[0] & 0xFE);
        break;
      case 255:
        res = -1 - 255 + inbuf[0];
        break;
      default:
        printf("Temperature high byte is %02X, but should be 0 or 0xFF.\n", inbuf[1]);
        exit(-1);
    }
    T[T_i_finished].T_Celsius = 0.5*(double)res - 0.25 + (double)(inbuf[7]-inbuf[6])/inbuf[7];
    T[T_i_finished].T_Celsius += thermometerAdjustment(T[T_i_finished].id, T[T_i_finished].T_Celsius);
  } else if(T[T_i_finished].id[0] == 0x28){
    if (inbuf[1] > 0x0F)
      res = (0xFFFF0000 | (inbuf[1] << 8)) | inbuf[0];
    else
      res = (inbuf[1] << 8) | inbuf[0];
    T[T_i_finished].T_Celsius = (double)res/16;
    T[T_i_finished].T_Celsius += thermometerAdjustment(T[T_i_finished].id, T[T_i_finished].T_Celsius);
  }

  pthread_mutex_unlock(&dlp_command_mutex);
  pthread_mutex_lock(&verbose_mutex);
  if (verbose){
    asctime_r(localtime(&T[T_i_finished].t.tv_sec), time_string);
    time_string[19] = 0;
    printf("%2d: %s: Thermometer (id=", T_i_finished, &time_string[4]);
    for (i = 0; i< 8; i++)
      printf("%02x", T[T_i_finished].id[i]);
    printf(") at port %c pin %d reports T = ", portToLetter(T[T_i_finished].port),  T[T_i_finished].pin);
    if (preferred_T_scale == Celsius)
      printf("%5.2f degrees C ", T[T_i_finished].T_Celsius);
    else
      printf("%5.1f degrees F ", 9*T[T_i_finished].T_Celsius/5 + 32);
    showThermometerLocation(T[T_i_finished].id);
    printf("\n");
  }
  pthread_mutex_unlock(&verbose_mutex);

  // Set up timer for any additional thermometer readings that have been started.
  gettimeofday(&t,NULL);
  if (T_i_finished < T_i_started){
    additional_time = 750 - ((int)(t.tv_sec  - T[T_i_finished].t.tv_sec )*1000
                          +  (int)(t.tv_usec - T[T_i_finished].t.tv_usec)/1000);
    if (additional_time <= 0)
      additional_time = 1;

    conversion_wait.it_interval.tv_sec  = 0;
    conversion_wait.it_interval.tv_usec = 0;
    conversion_wait.it_value.tv_sec     = 0;
    conversion_wait.it_value.tv_usec    = additional_time*1000;
    setitimer(ITIMER_REAL, &conversion_wait, NULL);
  }
}

