/*  
 *  L.A. Riley 11/18/99
 *  
 *  This is a greatly modified miniterm.c 
 *  by Sven Goldt (goldt@math.tu-berlin.de) 
 *
 *  This program reads the serial interface expecting to find a Davis 
 *  Systems WeatherLink interface.  This interface provides an RS232
 *  port to a weather station console made by the same company.
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  as published by the Free Software Foundation; either version 2
 *  of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  20-Nov-1999   Lew Riley       First release reading data from the 
 *                                Davis hardware.  
 * 
 *  24-Nov-1999   Charlie Peck    Cleaned-up a bit.  Removed terminal, 
 *                                process, and signal handling code that 
 *                                we don't need.
*/

#include <termios.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include "crc-table.h"

#define BUFFER_SIZE 50
#define BAUDRATE B2400
#define SERIALDEVICE "/dev/ttyS1"
#define FALSE 0
#define TRUE 1
#define ACK 6 

/* Danger Will Robinson, a global.
*/
unsigned char *receive_buffer;

int getInTemp(int fd, int debug_level);
int getOutTemp(int fd, int debug_level);
int getWindSpeed(int fd, int debug_level);
int getWindDir(int fd, int debug_level);
int getPressure(int fd, int debug_level);
int getInHum(int fd, int debug_level);
int getOutHum(int fd, int debug_level);
int getRain(int fd, int debug_level);

int put_serial_char(int fd, int c, int debug_level);
int put_serial_string(int fd, char *s, int debug_level);
int send_unsigned(int fd, unsigned ui, int debug_level);
unsigned char get_serial_char(int fd, int debug_level);
static int get_acknowledge(int fd, int debug_level);
int fill_buffer(int fd, int n, int debug_level);
int fill_crc_buffer(int fd, int n, int debug_level);
    
main(int argc, char *argv[]) {
   int fd, c, record, debug_level = 0;
   struct termios oldtio, newtio, oldstdtio, newstdtio;
   unsigned char header;
 
   /* Set the debug level.  0 = off, 1 = messages from weather value 
      functions, 2 = 1 plus messages from the serial interface functions.
   */ 
   if (argc < 2) {
      debug_level = 0; 
   } else {
      debug_level = atoi(argv[1]); 
   } 
 
   if (debug_level > 0) {
      printf("getWeather: debug_level = %d\n", debug_level); 
   }

   if ((receive_buffer = (unsigned char *)malloc(BUFFER_SIZE)) == NULL) {
      printf("getWeather: error allocating receive buffer (%d bytes).\n", 
         BUFFER_SIZE);
      exit(-1);
   }

   /* Open serial port for reading and writing and not as controlling tty
      because we don't want to get killed if linenoise sends CTRL-C.  
   */

   fd = open(SERIALDEVICE, O_RDWR | O_NOCTTY);
   if (fd < 0) {
      printf("getWeather: error opening serial device.\n");
      perror(SERIALDEVICE); 
      exit(-1); 
   }

 
   /* Save the current serial port settings.
   */
   tcgetattr(fd, &oldtio); 

   /* Set bps rate and hardware flow control and 8n1 (8bit,no parity,1 stopbit).
      Also don't hangup automatically and ignore modem status.  Finally enable 
      receiving characters.
   */
   newtio.c_cflag = BAUDRATE | CS8 | CLOCAL | CREAD;
 
   /* Don't echo characters, we just want output from the device.  Don't 
      generate signals.  This is the quick and dirty way to do this, we should 
      use the specific masks for ECHO and ISIG but since we don't care about 
      having any of the items in this vector enabled we can just zero it.
   */
   newtio.c_lflag = 0;
    
   /* Ignore bytes with parity errors and leave port raw and dumb.
   */
   newtio.c_iflag = IGNPAR;

   /* Flush the I/O queue for the port and enable the new settings.
   */
   tcflush(fd, TCIFLUSH);
   tcsetattr(fd, TCSANOW, &newtio);

   /* Save the old settings and then stop echo and buffering on stdin.
   */
   tcgetattr(0, &oldstdtio);
   tcgetattr(0, &newstdtio); /* get working stdtio definition */
   newstdtio.c_lflag &= ~(ICANON | ECHO); /* change the flags */ 
   tcsetattr(0, TCSANOW, &newstdtio); /* change the configuration */ 

   /* Call the functions that collect the data from the Davis interface
      and write the results to stdout in metric form where appropriate.
   */
   printf("%3.1f, %3.1f", (float)5/9*((float)getInTemp(fd, debug_level)/10-32), 
      (float)5/9*((float)getOutTemp(fd, debug_level)/10-32));

   printf(", %d, %d", (int)(getWindSpeed(fd, debug_level)*1.609), 
      getWindDir(fd, debug_level));

   printf(", %5.0f", (float)getPressure(fd, debug_level)*2.54);

   printf(", %d, %d", getInHum(fd, debug_level), getOutHum(fd, debug_level));

   printf(", %4.3f\n", (float)getRain(fd, debug_level)/100*2.54);
   
   /* Put the serial port and the terminal back the way the were when
      we started.
   */
   tcsetattr(fd, TCSANOW, &oldtio); 
   tcsetattr(0, TCSANOW, &oldstdtio); 

   close(fd);
   exit(0);
}

/* --------- Functions to read particular weather data values -------- */
	
int getInTemp(int fd, int debug_level) {
   int temp, cal;

   put_serial_string(fd, "WRD", debug_level); // Send read command.
   put_serial_char(fd, 0x44, debug_level);    // read 4 nibbles from bank 1.
   put_serial_char(fd, 0x30, debug_level);    // inside raw temp
   put_serial_char(fd, 0xd, debug_level);     // Send CR...
   get_acknowledge(fd, debug_level); // verify that the command was received and got ACK

   memset(receive_buffer, 0, BUFFER_SIZE); 
   fill_buffer(fd, 2, debug_level);  // get the returned data (in bytes)
   temp = *((int *)receive_buffer);  // read the raw temperature

   if (debug_level > 0) {
      printf("getInTemp: raw inside temp = %d\n", temp);
   }
  
   put_serial_string(fd, "WRD", debug_level); // Send read command.
   put_serial_char(fd, 0x44, debug_level);    // read 4 nibbles from bank 1.
   put_serial_char(fd, 0x52, debug_level);    // inside temp CAL number
   put_serial_char(fd, 0xd, debug_level);     // Send CR... 
   get_acknowledge(fd, debug_level); // verify that the command was received and got ACK
 
   memset(receive_buffer, 0, BUFFER_SIZE); 
   fill_buffer(fd, 2, debug_level);  // get the returned data (in bytes)
   cal = *((int *)receive_buffer);   // read the Cal number out of the buffer

   if (debug_level > 0) {
      printf("getInTemp: inside temp calibration = %d\n", cal);
   }

   return(temp + cal);
}

int getOutTemp(int fd, int debug_level) {
   int temp, cal;

   put_serial_string(fd, "WRD", debug_level); // Send read command.
   put_serial_char(fd, 0x44, debug_level);    // read 3 nibbles from bank 1.
   put_serial_char(fd, 0x56, debug_level);    // outside raw temp
   put_serial_char(fd, 0xd, debug_level);     // Send CR...
   get_acknowledge(fd, debug_level); // verify that the command was received and got ACK

   memset(receive_buffer, 0, BUFFER_SIZE); 
   fill_buffer(fd, 2, debug_level);  // get the returned data (in bytes)
   temp = *((int *)receive_buffer);

   if (debug_level > 0) {
      printf("getOutTemp: raw outside temp = %d\n", temp);
   }

   put_serial_string(fd, "WRD", debug_level); // Send read command.
   put_serial_char(fd, 0x44, debug_level);    // read 4 nibbles from bank 1.
   put_serial_char(fd, 0x78, debug_level);    // outside temp CAL number
   put_serial_char(fd, 0xd, debug_level);     // Send CR...
   get_acknowledge(fd, debug_level); // verify that the command was received and got ACK

   memset(receive_buffer, 0, BUFFER_SIZE); 
   fill_buffer(fd, 2, debug_level);  // get the returned data (in bytes)
   cal = *((int *)receive_buffer); 

   if (debug_level > 0) {
      printf("getOutTemp: outside temp calibration = %d\n", temp);
   }

   return(temp + cal);
}

int getWindSpeed(int fd, int debug_level) {
   int speed;

   put_serial_string(fd, "WRD", debug_level); // Send read command.
   put_serial_char(fd, 0x22, debug_level);    // read 2 nibbles from bank 0.
   put_serial_char(fd, 0x5e, debug_level);    // wind speed
   put_serial_char(fd, 0xd, debug_level);     // Send CR...
   get_acknowledge(fd, debug_level); // verify that the command was received and got ACK
   
   memset(receive_buffer, 0, BUFFER_SIZE); 
   fill_buffer(fd, 1, debug_level);  // get the returned data (in bytes)
   speed = *((unsigned char *)receive_buffer);  // read wind speed

   if (debug_level > 0) {
      printf("getWindSpeed: raw wind speed = %d\n", speed);
   }

   return(speed);
}

int getWindDir(int fd, int debug_level) {
   int direction;

   put_serial_string(fd, "WRD", debug_level); // Send read command.
   put_serial_char(fd, 0x44, debug_level);    // read 4 nibbles from bank 1.
   put_serial_char(fd, 0xb4, debug_level);    // wind direction
   put_serial_char(fd, 0xd, debug_level);     // Send CR...
   get_acknowledge(fd, debug_level); // verify that the command was received and got ACK

   memset(receive_buffer, 0, BUFFER_SIZE); 
   fill_buffer(fd, 2, debug_level);  // get the returned data (in bytes)
   direction = *((int *)receive_buffer); 

   if (debug_level > 0) {
      printf("getWindDir: raw wind direction = %d\n", direction);
   }

   return(direction);
}

int getPressure(int fd, int debug_level) {
   int pressure, cal;

   put_serial_string(fd, "WRD", debug_level); // Send read command.
   put_serial_char(fd, 0x44, debug_level);    // read 4 nibbles from bank 1.
   put_serial_char(fd, 0x00, debug_level);    // barometric pressure
   put_serial_char(fd, 0xd, debug_level);     // Send CR...
   get_acknowledge(fd, debug_level); // verify that the command was received and got ACK

   memset(receive_buffer, 0, BUFFER_SIZE); 
   fill_buffer(fd, 2, debug_level);  // get the returned data (in bytes)
   pressure = *((int *)receive_buffer); 

   if (debug_level > 0) {
      printf("getPressure: raw pressure = %d\n", pressure);
   }

   put_serial_string(fd, "WRD", debug_level); // Send read command.
   put_serial_char(fd, 0x44, debug_level);    // read 4 nibbles from bank 1.
   put_serial_char(fd, 0x2c, debug_level);    // barometer offset
   put_serial_char(fd, 0xd, debug_level);     // Send CR...
   get_acknowledge(fd, debug_level); // verify that the command was received and got ACK

   memset(receive_buffer, 0, BUFFER_SIZE); 
   fill_buffer(fd, 2, debug_level);  // get the returned data (in bytes)
   cal = *((int *)receive_buffer);  

   if (debug_level > 0) {
      printf("getPressure: pressure calibration = %d\n", cal);
   }

   return(pressure - cal);
}

int getInHum(int fd, int debug_level) {
   int hum;

   put_serial_string(fd, "WRD", debug_level); // Send read command.
   put_serial_char(fd, 0x24, debug_level);    // read 2 nibbles from bank 1.
   put_serial_char(fd, 0x80, debug_level);    // inside raw hum
   put_serial_char(fd, 0xd, debug_level);     // Send CR...
   get_acknowledge(fd, debug_level); // verify that the command was received and got ACK

   memset(receive_buffer, 0, BUFFER_SIZE); 
   fill_buffer(fd, 1, debug_level);  // get the returned data (in bytes)
   hum = *((unsigned char *)receive_buffer);

   if (hum == 128) {
      printf("getInHum: inside sensor not reachable\n");
      return(-1);
   }

   if (debug_level > 0) {
      printf("getInHum: raw inside humidity = %d\n", hum);
   }

   // xxx - does inside humidity have a calibration?

   return(hum);
}

int getOutHum(int fd, int debug_level) {
   int hum, cal;

   put_serial_string(fd, "WRD", debug_level); // Send read command.
   put_serial_char(fd, 0x24, debug_level);    // read 2 nibbles from bank 1.
   put_serial_char(fd, 0x98, debug_level);    // outside raw hum
   put_serial_char(fd, 0xd, debug_level);     // Send CR...
   get_acknowledge(fd, debug_level); // verify that the command was received and got ACK

   memset(receive_buffer, 0, BUFFER_SIZE); 
   fill_buffer(fd, 1, debug_level);  // get the returned data (in bytes)
   hum = *((unsigned char *)receive_buffer); 

   if (hum == 128) {
      printf("getOutHum: outside sensor not reachable\n");
      return(-1);
   }
 
   if (debug_level > 0) {
      printf("getOutHum: raw outside humidity = %d\n", hum);
   }

   put_serial_string(fd, "WRD", debug_level); // Send read command.
   put_serial_char(fd, 0x44, debug_level);    // read 4 nibbles from bank 1.
   put_serial_char(fd, 0xda, debug_level);    // outside hum CAL number
   put_serial_char(fd, 0xd, debug_level);     // Send CR...
   get_acknowledge(fd, debug_level); // verify that the command was received and got ACK

   memset(receive_buffer, 0, BUFFER_SIZE); 
   fill_buffer(fd, 2, debug_level);  // get the returned data (in bytes)
   cal = *((int *)receive_buffer);

   if (debug_level > 0) {
      printf("getOutHum: outside humidity calibration = %d\n", cal);
   }

   hum = hum + cal; // hum is now calibrated, but possibly out of range.
  
   if (hum >100)
      hum = 100;
   else if (hum < 1)
      hum = 1;      // hum has been clipped to the range 1 - 100.

   return(hum);
}

int getRain(int fd, int debug_level) {
   int rain, cal;

   put_serial_string(fd, "WRD", debug_level); // Send read command.
   put_serial_char(fd, 0x44, debug_level);    // read 3 nibbles from bank 1.
   put_serial_char(fd, 0xCE, debug_level);    // Read beginning at C9h.
                                              // (Daily Rain Data on GroWeather)
   put_serial_char(fd, 0xd, debug_level);     // Send CR...
   get_acknowledge(fd, debug_level); // verify that the command was received and got ACK

   memset(receive_buffer, 0, BUFFER_SIZE); 
   fill_buffer(fd, 2, debug_level);  // get the returned data (in bytes)
   rain = *((int *)receive_buffer); 

   if (debug_level > 0) {
      printf("getRain: raw rain clicks = %d\n", rain);
   }

   // xxx - Are we reading the accumulated rain or the daily?  We'll 
   // need some code in cron to reset it at midnight each day.
   
   put_serial_string(fd, "WRD", debug_level); // Send read command.
   put_serial_char(fd, 0x44, debug_level);    // read 3 nibbles from bank 1.
   put_serial_char(fd, 0xD6, debug_level);    // Read beginning at d6h.
   put_serial_char(fd, 0xd, debug_level);     // Send CR...
   get_acknowledge(fd, debug_level); // verify that the command was received and got ACK
   
   memset(receive_buffer, 0, BUFFER_SIZE); 
   fill_buffer(fd, 2, debug_level); // get the returned data (in bytes)
   cal = *((int *)receive_buffer);  // read the Cal number out of the buffer

   if (debug_level > 0) {
      printf("getRain: rain calibration = %d\n", cal);
   }
  
   return (int)((float)rain / (float)cal * 100);
}

/* ---- Serial I/O functions based on templates from Davis ---- */

int put_serial_char(int fd, int c, int debug_level)  {
   unsigned char uc = c;

   if (debug_level > 1) {
      printf("put_serial_char: char = %x\n", uc);
   }

   return write(fd, &uc, 1);
}

int send_unsigned(int fd, unsigned ui, int debug_level) {
   unsigned char *uc;
   int j = 0; 
  
   uc = (unsigned char *)&ui;
   j += put_serial_char(fd, *uc, debug_level);
   j += put_serial_char(fd, *(uc+1), debug_level);
  
   return(j); 
}

int put_serial_string(int fd, char * s, int debug_level) {
   int i, j = 0;
   
   for (i = 0; s[i] != '\0'; i++)
      j += put_serial_char(fd, s[i], debug_level);
   
   return(j);
}

unsigned char get_serial_char(int fd, int debug_level) {
   unsigned char c;
  
   read(fd, &c, 1);
   
   if (debug_level > 1) {
      printf("get_serial_char: char = %x\n", c);
   }
      
    return(c);
}

static int get_acknowledge(int fd, int debug_level) {
   int c;

   if ((c = get_serial_char(fd, debug_level)) == ACK) {

      if (debug_level > 1) {
         printf("get_acknowledge: got ACK!\n");
      }
      
      return(1);   
   
   } else { 
   
      printf("get_acknowledge: Looking for ACK and got %x\n", c);
      return(0);
   }
}

int fill_buffer(int fd, int n, int debug_level) {
   int i,c;
  
   for (i=0; i<n; i++) {

      c = get_serial_char(fd, debug_level);
      
      if (debug_level > 1) {
         printf("fill_buffer: position = %d, value = %x\n", i, c);
      }

      if (c >= 0) {
      
         receive_buffer[i] = c;
      
      } else {
	  
	     printf("fill_buffer: error reading serial port\n");
	     return(-1);
      
      }
   }
   
   return(1);
}

int fill_crc_buffer(int fd, int n, int debug_level) { // n includes CRC bytes
   int i,c;
   int crc = 0;   // initialize the CRC checksum to 0
  
   for (i=0; i<n; i++) {

      c = get_serial_char(fd, debug_level);

      if (debug_level > 1) {
         printf("fill_crc_buffer: position = %d, value = %x\n", i, c);
      }
 
      if (c >= 0) {
	  
	     receive_buffer[i] = c;   // Store the data
	     crc = crc_table[(crc >> 8) ^ c] ^ (crc << 8);
	     
	  } else {
	  
	     printf("fill_crc_buffer: error reading serial port\n");
	     return(-1);
      }

      if (crc == 0) {
      
         return(1);         // we have read in n bytes and the CRC is OK
         
      } else {
      
         printf("fill_crc_buffer: checksum error\n");
         return(-1);       // The CRC check failed.
      }
   }
}
