/*
 * This program measures the temperatures of my pool, one solar panel, and the
 * hot water leaving the panels.  It generates ASCII records that are input
 * to gnuplot, when given the "-g" option.  When not given "-g", this generates
 * HTML for a small web page that shows the current values.
 *
 * This program also turns on and off a valve that sends pool water up through the 
 * panels and back.  It turns the value on when the panel temperature goes above 150 F
 * and turns it off when the panel falls below 90 F.
 *
 * (c) Robert Bedichek, 2003, 2005, 2006
 */
                                                                                                            
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <math.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/poll.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
#include<sys/mman.h>
#include<sys/types.h>
#include<unistd.h>

int verbose = 0;

// This defines which range of A/D channels are connected
// to which sensors
const int current_first_channel = 0;
const int current_last_channel = 2;
const int pressure_sensor_input = 3;
const int sewer_first_channel = 4;
const int sewer_last_channel = 7;


// The minimum number of calibration points is 2, there must be one with a raw
// value of 0 and another with a raw value as high as the highest raw value that
// can occur.  We use 100 AMPS, that should be safely higher than any value encountered

#define calibration_points     (4)
#define channels_to_calibrate  (3)

#define LEFT_CHANNEL    (0)
#define RIGHT_CHANNEL   (1)
#define NEUTRAL_CHANNEL (2)

struct calib {
  double raw_value;
  double calibrated_value; };

// I've been unable to figure out how to initialize this table statically
// So I do it in C code below
struct calib calibration_table[channels_to_calibrate][calibration_points];

typedef int bool;
enum {
  false = 0, true = 1};

int calibrate_mode = 0;

#define GPIOBASE	0x80840000
#define PADR	0
#define PADDR	(0x10 / sizeof(unsigned int))
#define PHDR	(0x40 / sizeof(unsigned int))
#define PHDDR	(0x44 / sizeof(unsigned int))

#define DIO_DATA (4/sizeof(unsigned int))
#define DIO_DIRECTION  (0x14/sizeof(unsigned int))
volatile unsigned int *dio_data;
volatile unsigned int *dio_direction;

int pulse_fd;

// These delay values are calibrated for the EP9301 
// CPU running at 166 Mhz, but should work also at 200 Mhz
#define SETUP	15
#define PULSE	36
#define HOLD	22

#define COUNTDOWN(x)	asm volatile (				\
				      "1:\n"			\
				      "subs %1, %1, #1;\n"	\
				      "bne 1b;\n"		\
				      : "=r" ((x)) : "r" ((x))	\
				       );

volatile unsigned int *gpio;
volatile unsigned int *phdr;
volatile unsigned int *phddr;
volatile unsigned int *padr;
volatile unsigned int *paddr;

volatile unsigned short *complete, *data;
volatile unsigned char *control;
volatile unsigned char *present;

int adinit(void)
{
  int fd = open("/dev/mem", O_RDWR);
  if (fd <= 0)
    {
      fprintf(stderr, "Unable to open /dev/mem in adinit\n");
      exit(1);
    }
  present = (unsigned char *)mmap(0, 
				  getpagesize(), 
				  PROT_READ|PROT_WRITE, 
				  MAP_SHARED, 
				  fd, 
				  0x22400000);

  if (present == (unsigned char *)-1)
    {
      fprintf(stderr, "Unable to mmap the A/D presence register\n");
      exit(1);
    }

  if ((*present & 1) == 0)
    {
      fprintf(stderr, "MAX197 A/D converter not present in TS-7250\n");
      exit(1);
    }

  control = (unsigned char *)mmap(0, 
				  getpagesize(), 
				  PROT_READ|PROT_WRITE, 
				  MAP_SHARED, 
				  fd, 
				  0x10f00000);
  if (control == (unsigned char *)-1)
    {
      fprintf(stderr, "Unable to mmap the A/D control register\n");
      exit(1);
    }

  data = (unsigned short *)control;

  complete = (unsigned short *)mmap(0, 
				    getpagesize(), 
				    PROT_READ|PROT_WRITE, 
				    MAP_SHARED, 
				    fd, 
				    0x10800000);
  if (complete == (unsigned short *)-1)
    {
      fprintf(stderr, "Unable to mmap the A/D 'complete' register\n");
      exit(1);
    }
  close(fd);
}


double measure_voltage(int channel)
{
  int cnt; 
  int res;
  const int max_count = 100000;

  *control = 0x40 | channel;// channel 0
  cnt = 0;
  while ((*complete & 0x80) != 0 && cnt < max_count) cnt++;
  if (cnt >= max_count)
    printf("%s: cnt=%d\n", __func__, cnt);

  res = *data;
  return ((((double)res * 50000.0)/4096.0) + 5.0) / 10000.0;
}


/*
 * Return true if the passed temperature is a reasonable value.
 */
static int inrange(double x)
{
  return (-10.0 < x && x < 300.0);
}

/*
 * Return the current time in minutes.
 */
static double current_time_in_minutes()
{
  time_t t = time(0);
  return (double)t / 60.0;
}

// Convert sensor voltage to pressure in PSI.
// Assume a 6VDC source to the sensor.

static double v_to_p(double v)
{
  return (v - 0.822) / 0.0353;
}


/*
 * Top level function.
 */
int main (int argc, char *argv[])
{
  adinit();

  long number_of_samples = 5 * 60 * 20;

  if (argc == 2) {
    number_of_samples = atol(argv[1]);
    calibrate_mode = 1;
  }

  int channel, i;
  for (channel = 0; channel < channels_to_calibrate ; channel++) {
    for (i = 0 ; i < (calibration_points - 1) ; i++) {
      calibration_table[channel][i].raw_value = 0.0;
      calibration_table[channel][i].calibrated_value = 0.0;
    }
    calibration_table[channel][i].raw_value = 100.0;
    calibration_table[channel][i].calibrated_value = 100.0;
  }
  calibration_table[LEFT_CHANNEL][1].raw_value = 7.276;
  calibration_table[LEFT_CHANNEL][1].calibrated_value = 4.75;

  calibration_table[RIGHT_CHANNEL][1].raw_value = 6.070;
  calibration_table[RIGHT_CHANNEL][1].calibrated_value = 2.74;

  calibration_table[NEUTRAL_CHANNEL][1].raw_value = 5.406;
  calibration_table[NEUTRAL_CHANNEL][1].calibrated_value = 1.62;

  calibration_table[LEFT_CHANNEL][2].raw_value = 18.69;
  calibration_table[LEFT_CHANNEL][2].calibrated_value = 21.2;

  calibration_table[RIGHT_CHANNEL][2].raw_value = 27.32;
  calibration_table[RIGHT_CHANNEL][2].calibrated_value = 33.9;

  calibration_table[NEUTRAL_CHANNEL][2].raw_value = 6.944;
  calibration_table[NEUTRAL_CHANNEL][2].calibrated_value = 4.07;

  double start_time = current_time_in_minutes();
  double accumulated_amps[8] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
  double accumulated_sewer_volts[8] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
  double pressure_sensor_min = measure_voltage(pressure_sensor_input);
  double pressure_sensor_max = pressure_sensor_min;
  long samples;

  for (samples = 0 ; samples < number_of_samples ; samples++) {
    double time_now = current_time_in_minutes();
    double time_passed = time_now - start_time;
    for (i = current_first_channel ; i <= current_last_channel ; i++) {
      double voltage_from_transducer = measure_voltage(i);
      double amps = (voltage_from_transducer / 5.0) * 100.0;
      accumulated_amps[i] += amps;
    }
    for (i = sewer_first_channel ; i <= sewer_last_channel  ; i++) {
      accumulated_sewer_volts[i] += measure_voltage(i);
    }
    double pressure_sensor = measure_voltage(pressure_sensor_input);
    if (pressure_sensor < pressure_sensor_min)
      pressure_sensor_min = pressure_sensor;
    if (pressure_sensor > pressure_sensor_max)
      pressure_sensor_max = pressure_sensor;
    if (time_passed >= 5.0) {
      break;
    }
    usleep(1);
  }
  // Print the three current values
  for (i = current_first_channel ; i <= current_last_channel ; i++) {
    double amps = accumulated_amps[i] / (double)samples;
    if  (calibrate_mode) {
      printf("(%d: %5.3lfA uncalibrated)", i, amps);
    }
    int j;
    int found = 0;

    for (j = 0 ; j < (calibration_points - 1) ; j++) {
      if (amps < 0)
	amps = 0.0;
      else if (amps > 100.0)
	amps = 100.0;
      struct calib *this_p = &calibration_table[i - current_first_channel][ j];
      struct calib *next_p = &calibration_table[i - current_first_channel][ j + 1];
      if (verbose) {
	printf("%d:%d: this_p: %5.3lf,%5.3lf  next_p: %5.3lf,%5.3lf\n", 
	       i, j, this_p->raw_value, this_p->calibrated_value, next_p->raw_value, next_p->calibrated_value);
      }
      if (this_p->raw_value <= amps && amps <= next_p->raw_value) {
	amps -= this_p->raw_value;
	double interpolation_value = amps / (next_p->raw_value - this_p->raw_value);
	if (interpolation_value < 0.0 || interpolation_value > 1.0) {
	  printf("%s: interplation value not in 0..1: %5.3lf\n", argv[0], interpolation_value);
	  exit(1);
	}
	amps = this_p->calibrated_value + 
	  interpolation_value * (next_p->calibrated_value - this_p->calibrated_value);
	found = 1;
      }
    }
    if (!found && !verbose) {
      printf("%s: Failed to find interpolation values, amps=%5.3lf\n", argv[0], amps);
      exit(1);
    }

    printf("%5.3lf ", amps);
  }

  // Print the min and max pressures
  printf("%5.3lf %5.3lf", v_to_p(pressure_sensor_min), v_to_p(pressure_sensor_max));

  // Print the sewer conductivity voltages
  for (i = sewer_first_channel ; i <= sewer_last_channel ; i++) {
    printf(" %5.3lf", accumulated_sewer_volts[i] / (double)samples);
  }

  printf("\n");
}

