/*
    $Id: tsl.c,v 1.4 2001/01/29 09:23:29 root Exp $

    tsl, temperature sensor data logger
    Copyright (C) 2000  James Cameron (quozl@us.netrek.org)

    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.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

/* to do

set audio alarm on long duration increase over threshold

*/

#ifndef lint
static char vcid[] = "$Id: tsl.c,v 1.4 2001/01/29 09:23:29 root Exp $";
#endif /* lint */

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <termios.h>
#include <unistd.h>
#include <linux/kd.h>
#include <ncurses.h>
#include <signal.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>

#define MAXSENSOR 4
#define MAX_NONE -50.0
#define MIN_NONE 100.0

/* line number where matrix starts */
#define MATRIX 7

#define COLUMN_WIDTH 12
#define COLUMN (80-(COLUMN_WIDTH*MAXSENSOR))
#define PLACE(number) (COLUMN+(COLUMN_WIDTH*number))

int woken;

int lines[8] = { 0, 2, 3, 4, 6, 7, 9, 11 };
float datum[MAXSENSOR], max[MAXSENSOR], min[MAXSENSOR];
float lo[MAXSENSOR], hi[MAXSENSOR], acc[MAXSENSOR], sam[MAXSENSOR];
int loset[MAXSENSOR], hiset[MAXSENSOR], seen[MAXSENSOR];
int x, y;
char old[256] = { 0 };

/* port access functions borrowed from prog84 */
int dev_port_fd = -1;

#ifdef __alpha
int port = 0x3bc;
#else
int port = 0x378; /* lpt1 /dev/lp0 */
#endif

void open_io()
{
  dev_port_fd = open("/dev/port", O_RDWR);
  if(dev_port_fd < 0)
  {
    perror("/dev/port");
    exit(errno);
  }
}

void close_io()
{
  close(dev_port_fd);
  dev_port_fd = -1;
}

void out_byte(int port, unsigned char byte)
{
  off_t s;
  int r;

  s = lseek(dev_port_fd, port, 0);
  if (s < 0) perror("lseek");
  else if (s != port)
    fprintf(stderr, "hmmm: seeking to %d, went to %ld.\n", port, (long)s);

  r = write(dev_port_fd, &byte, 1);
  if (r != 1)
  {
    fprintf(stderr, "hmmm: write returned %d\n", r);
    if (r < 0) perror("write");
  }
}

unsigned char in_byte(int port)
{
  off_t s;
  int r;
  unsigned char ch = 0;

  s = lseek(dev_port_fd, port, 0);
  if (s < 0)
    perror("lseek");
  else if (s != port)
    fprintf(stderr, "hmmm: seeking to %d, went to %ld.\n", port, (long)s);

  r = read(dev_port_fd, &ch, 1);
  if (r != 1)
  {
    fprintf(stderr, "hmmm: read returned %d\n", r);
    if (r < 0) perror("read");
  }

  return ch;
}

void reaper() {
  wait(NULL);
}

void graph() {
  if (strlen(old) == 0) return;
  signal(SIGCHLD, reaper);
  if (fork() != 0) return;
  execl("graph.sh", "graph.sh", old, NULL);
  execlp("graph.sh", "graph.sh", old, NULL);
  exit(0);
}

void beeper_on() {
  out_byte(port,0xff);
}

void beeper_off() {
  out_byte(port,0x00);
}

void wake(int x)
{
  signal(SIGALRM, wake);
  woken++;
}

void cursor(int x, int y) {
  mvprintw(lines[y]+MATRIX, COLUMN+COLUMN_WIDTH*x-1, " ");
  mvprintw(lines[y]+MATRIX, COLUMN+COLUMN_WIDTH*x+COLUMN_WIDTH-3, " ");
}

void cursor_on(int x, int y) {
  standout();
  cursor(x, y);
  standend();
}

void cursor_off(int x, int y) {
  cursor(x, y);
}

void background() {
  int i;

  mvprintw(0, 0, "Temperature Sensor Logger, 0.4-macanbar");
  mvprintw(1, 0, "Copyright (C) 2001 James Cameron (quozl@us.netrek.org)");
  mvhline(2, 0, 0, 80);


  mvhline(MATRIX-3, COLUMN+COLUMN_WIDTH*0-2, 0, COLUMN_WIDTH*MAXSENSOR);
  mvprintw(MATRIX-2, COLUMN+COLUMN_WIDTH*0, "Truck");
  mvprintw(MATRIX-2, COLUMN+COLUMN_WIDTH*1, "Big");
  mvprintw(MATRIX-2, COLUMN+COLUMN_WIDTH*2, "Little");
  mvprintw(MATRIX-2, COLUMN+COLUMN_WIDTH*3, "Shed");
  mvhline(MATRIX-1, 0, 0, 78);

  mvprintw(MATRIX+0, 0, "Current Temperature");
  mvhline(MATRIX+1, 0, 0, 78);

  mvprintw(MATRIX+2, 0, "Minimum Temperature");
  mvprintw(MATRIX+3, 0, "Maximum Temperature");
  mvprintw(MATRIX+4, 0, "Average Temperature");
  mvhline(MATRIX+5, 0, 0, 78);

  mvprintw(MATRIX+6, 0, "Alarm if hotter than");
  mvprintw(MATRIX+7, 0, "Alarm if colder than");
  mvhline(MATRIX+8, 0, 0, 78);
  mvprintw(MATRIX+9, 0, "Alarm status");
  mvhline(MATRIX+10, 0, 0, 78);

  /* vertical bars */
  for(i=0;i<(MAXSENSOR+1);i++) {
    chtype ttee = (i == MAXSENSOR) ? ACS_URCORNER : ((i == 0) ? ACS_ULCORNER : ACS_TTEE);
    chtype plus = (i == MAXSENSOR) ? ACS_RTEE : ACS_PLUS;
    chtype btee = (i == MAXSENSOR) ? ACS_LRCORNER : ACS_BTEE;

    mvvline(MATRIX-3, COLUMN+COLUMN_WIDTH*i-2, ttee, 1);
    mvvline(MATRIX-2, COLUMN+COLUMN_WIDTH*i-2, 0, 1);
    mvvline(MATRIX-1, COLUMN+COLUMN_WIDTH*i-2, plus, 1);
    mvvline(MATRIX+0, COLUMN+COLUMN_WIDTH*i-2, 0, 1);
    mvvline(MATRIX+1, COLUMN+COLUMN_WIDTH*i-2, plus, 1);
    mvvline(MATRIX+2, COLUMN+COLUMN_WIDTH*i-2, 0, 1);
    mvvline(MATRIX+3, COLUMN+COLUMN_WIDTH*i-2, 0, 1);
    mvvline(MATRIX+4, COLUMN+COLUMN_WIDTH*i-2, 0, 1);
    mvvline(MATRIX+5, COLUMN+COLUMN_WIDTH*i-2, plus, 1);
    mvvline(MATRIX+6, COLUMN+COLUMN_WIDTH*i-2, 0, 1);
    mvvline(MATRIX+7, COLUMN+COLUMN_WIDTH*i-2, 0, 1);
    mvvline(MATRIX+8, COLUMN+COLUMN_WIDTH*i-2, plus, 1);
    mvvline(MATRIX+9, COLUMN+COLUMN_WIDTH*i-2, 0, 1);
    mvvline(MATRIX+10, COLUMN+COLUMN_WIDTH*i-2, btee, 1);
  }

}

void redo(int x, int y, float z) {
  mvprintw(MATRIX+lines[y], PLACE(x), "%5.2f  ", z);
}

void text(int x, int y, char *z) {
  mvprintw(lines[y]+MATRIX, PLACE(x), z);
}

void wipe(int x, int y) {
  text(x, y, "       ");
}

/* if keyboard used, process it */
void keyboard() {
  static char value[COLUMN_WIDTH+1] = { 0 };
  int z, c;

  alarm(6);
  c = getch ( );
  while ( c != ERR ) {
    switch ( c ) {
    case ' ':
      beeper_off();
      mvprintw(22, 0, " ");
      clrtoeol();
      break;
    case 'g':
      graph();
      break;
    case '\f':
      clear();
      background();
      for (z=0;z<MAXSENSOR;z++) {
	if (seen[z]) {
	  redo(z, 0, datum[z]);
	  if (min[z] != MIN_NONE) redo(z, 1, min[z]);
	  if (max[z] != MAX_NONE) redo(z, 2, max[z]);
	  if (sam[z] > 0) redo(z, 3, (float)(acc[z]/sam[z]));
	  if (hiset[z]) redo(z, 4, hi[z]);
	  if (loset[z]) redo(z, 5, lo[z]);
	}
      }
      cursor_on(x, y);
      refresh();
      break;
    case KEY_LEFT:
      if (x>0) { cursor_off(x, y); x--; cursor_on(x, y); }
      break;
    case KEY_RIGHT:
      if (x<(MAXSENSOR-1)) { cursor_off(x, y); x++; cursor_on(x, y); }
      break;
    case KEY_UP:
      if (y>0) { cursor_off(x, y); y--; cursor_on(x, y); }
      break;
    case KEY_DOWN:
      if (y<(7-1)) { cursor_off(x, y); y++; cursor_on(x, y); }
      break;
    case KEY_NPAGE:
      if (y == 4) {
	hi[x]++;
	hiset[x] = 1;
	redo(x, y, hi[x]);
      } else if (y == 5) {
	lo[x]++;
	loset[x] = 1;
	redo(x, y, lo[x]);
      }
      break;
    case KEY_PPAGE:
      if (y == 4) {
	hi[x]--;
	hiset[x] = 1;
	redo(x, y, hi[x]);
      } else if (y == 5) {
	lo[x]--;
	loset[x] = 1;
	redo(x, y, lo[x]);
      }
      break;
    case KEY_END:
      if (y == 4) {
	hi[x] = datum[x];
	hiset[x] = 1;
	redo(x, y, hi[x]);
      } else if (y == 5) {
	lo[x] = datum[x];
	loset[x] = 1;
	redo(x, y, lo[x]);
      }
      break;
    case '-': case '.':
    case '0': case '1': case '2': case '3': case '4':
    case '5': case '6': case '7': case '8': case '9':
      if (y == 4 || y == 5) {
	int len = strlen(value);
	if (len < (COLUMN_WIDTH - 2)) {
	  value[len++] = c;
	  value[len++] = 0;
	  text(x, 7, value);
	}
      }
      break;
    case KEY_BACKSPACE:
      if (y == 4 || y == 5) {
	int len = strlen(value);
	if (len > 0) {
	  value[len-1] = 0;
	  wipe(x, 7);
	  text(x, 7, value);
	}
      }
      break;
    case KEY_IC:
    case '\n':
    case '\r':
    case 0x09:
      if (y == 4 || y == 5) {
	int len = strlen(value);
	if (len > 0) {
	  if (y == 4) {
	    hi[x] = atof(value);
	    hiset[x] = 1;
	    redo(x, y, hi[x]);
	  } else if (y == 5) {
	    lo[x] = atof(value);
	    loset[x] = 1;
	    redo(x, y, lo[x]);
	  }
	  value[0] = 0;
	  wipe(x, 7);
	}
      }
      break;
    case KEY_DC:
    case 0xff:
    case 0x7f:
      switch (y) {
      case 0:
	break;
      case 1:
	min[x] = MIN_NONE;
	break;
      case 2:
	max[x] = MAX_NONE;
	break;
      case 3:
	acc[x] = 0.0;
	sam[x] = 0.0;
	break;
      case 4:
	hiset[x] = 0;
	break;
      case 5:
	loset[x] = 0;
	break;
      }
      wipe(x, y);
      break;
    default:
      break;
    }
    c = getch ( );
  }
  alarm(0);

}

int main ( int argc, char *argv[] )
{
  char *device;
  FILE *input, *output = NULL, *current;
  int fd, sample = 60, was, number, cursor = 0;
  time_t now;
  struct tm *tm;
  int i;
  struct termios termios;
  char *p, buffer[128];                 /* buffer for data from sensor */

  open_io();

  /* reset sensor result array */
  for (i=0; i<MAXSENSOR; i++) {
    datum[i] = 0.0;
    max[i] = MAX_NONE;
    min[i] = MIN_NONE;
    lo[i] = MAX_NONE;
    hi[i] = MIN_NONE;
    acc[i] = 0.0;
    sam[i] = 0.0;
    loset[i] = 0;
    hiset[i] = 0;
    seen[i] = 0;
  }

  hi[0] = -1.0;
  hi[1] = -1.0;
  hi[2] = -1.0;

  /* allow user to specify alternate serial port as first argument */
  device = "/dev/ttyS0";
  if (argc > 1) device = argv[1];

  /* initialise curses screen handling */
  initscr();
  cbreak();
  noecho();
  nonl();
  nodelay(stdscr, TRUE);
  keypad(stdscr, TRUE);
  /* leaveok(stdscr, TRUE); */

  background();

  /* position cursor */
  x = 0;
  y = 0;
  cursor_on(x, y);

  /* open the serial port, causing DTR to be raised and the sensor to run */
  input = fopen(device, "r" );
  if (input == NULL) {
    perror("fopen");
  }

  /* set the serial port characteristics */
  fd = fileno(input);
  if (tcgetattr(fd, &termios) < 0) {
    perror("tcgetattr");
    exit(1);
  }

  /* useful at remote end to see echo'd data */
  termios.c_lflag |= ECHO;

  if (cfsetospeed(&termios, B2400) < 0) {
    perror("cfsetospeed");
    exit(1);
  }

  if (cfsetispeed(&termios, B2400) < 0) {
    perror("cfsetispeed");
    exit(1);
  }

  if (tcsetattr(fd, TCSANOW, &termios) < 0) {
    perror("tcsetattr");
    exit(1);
  }

  woken = 0;
  signal(SIGALRM, wake);

  /* infinite loop */
  for(;;) {

    /* refresh the screen */
    refresh();

    /* wait for data */
    {
      struct timeval tv;
      fd_set in;
      int status;

      tv.tv_sec = 2;
      tv.tv_usec = 0;
      FD_ZERO(&in);
      FD_SET(fileno(input), &in);
      FD_SET(fileno(stdin), &in);

      status = select(fileno(input)+1, &in, NULL, NULL, &tv);
      if (status == 0) {
	standout();
	mvprintw(22, 0, "Communication breakdown, check power and cables.");
	standend();
	beep();
	beeper_on();
	continue;
      }
      if (FD_ISSET(fileno(stdin), &in)) {
	keyboard();
	continue;
      }
      mvprintw(23, 0, "Receiving okay.");
      clrtoeol();
      if (status < 0) {
	perror("select");
	exit(1);
      }
    }

    alarm(2);

    /* get a line of data from the sensor */
    p = fgets(buffer, 128, input);
    if (p == NULL) {
      perror("fgets");
      exit(1);
    }

    alarm(0);

    /* remove the terminating new-line */
    buffer[strlen(buffer)-1] = '\0';

    /* remove preceeding carriage return */
    p = buffer;
    while (*p == '\r' || *p == '\n') p++;

    /* establish timestamp for this sample */
    now = time(NULL);
    tm = localtime(&now);

    /* check packet header for reset detection */
    if (*p == 'R') {
      mvprintw(MATRIX-2, 0, "%s", p);
      continue;
    }

    /* ignore any other packet header other than sensor reports */
    if (!isdigit(*p)) continue;

    /* decode the sensor number */
    sscanf(p, "%d", &number);
    number--;
    if (number < 0 || number > MAXSENSOR-1) continue;

    /* check for missing blank */
    p++;
    if (!isblank(*p)) continue;

    /* check for verbose mode additional data */
    if (strlen(p) > 9) {
#ifdef PRINT_VERBOSE
      mvprintw(22, 0, p);
#endif
      p += 10;
    }

    /* read and decode the data */
    sscanf(p, "%f", &datum[number]);
    redo(number, 0, datum[number]);

    if (datum[number] < min[number]) {
      min[number] = datum[number];
      redo(number, 1, min[number]);
    }

    if (datum[number] > max[number]) {
      max[number] = datum[number];
      redo(number, 2, max[number]);
    }

    acc[number] += datum[number];
    sam[number]++;
    redo(number, 3, (float)(acc[number]/sam[number]));

    /* check for limit */
    if (hiset[number] && (datum[number] > hi[number])) {
      standout();
      text(number, 6, "TOO HOT");
      standend();
      beep();
      mvprintw(22, 0, "Overtemperature alarm for sensor %d", number+1);
      clrtoeol();
      beeper_on();
    } else if (loset[number] && (datum[number] < lo[number])) {
      standout();
      text(number, 6, "TOO COLD");
      standend();
      beep();
      mvprintw(22, 0, "Undertemperature alarm for sensor %d", number+1);
      clrtoeol();
      beeper_on();
    } else {
      text(number, 6, "Okay    ");
    }

    seen[number] = 5;
    for(i=0;i<MAXSENSOR;i++) {
      if (seen[i]) {
	seen[i]--;
      }
    }

    for(i=0;i<MAXSENSOR;i++) {
      if (!seen[i]) {
	wipe(i, 0);
	/* sensor gone */
	if (hiset[i] || loset[i]) {
	  /* user is monitoring it */
	  standout();
	  text(i, 6, "Broken  ");
	  standend();
	  beep();
	}
      }
    }

    /* save this sample to output log if we have reached sample time */
    if ( ( now / sample ) != was )
    {
      char new[256];

      was = now / sample;

      sprintf ( new, "%03.3d.log", tm->tm_yday );
      if ( output == NULL ) {
	output = fopen ( new, "a" );
	if (output == NULL) { perror("fopen"); exit(1); }
	strcpy ( old, new );
      } else {
	if ( strcmp ( old, new ) ) {
	  fclose ( output );
	  output = fopen ( new, "a" );
	  if (output == NULL) { perror("fopen"); exit(1); }
	  strcpy ( old, new );
	}
      }

      fprintf(output, "%7.4f",
	      (float) tm->tm_hour + 
	      (float) tm->tm_min / 60.0 + 
	      (float) tm->tm_sec / 3600.0);
      for(i=0;i<MAXSENSOR;i++) {
	if (seen[i]) {
	  fprintf(output, ",%5.2f", datum[i]);
	} else {
	  fprintf(output, ",");
	}
      }
      fprintf(output, "\n");
      if (fflush(output) != 0) { perror("fflush"); exit(1); }
    }
  }
}

