/*
    $Id: warner.c,v 1.5 1999/07/06 05:33:22 root Exp $

    warner, speedometer, odometer and data logger for laptop
    Copyright (C) 1999  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
*/

#ifndef lint
static char vcid[] = "$Id: warner.c,v 1.5 1999/07/06 05:33:22 root Exp $";
#endif /* lint */

#include <stdio.h>
#include <time.h>
#include <sys/time.h>
#include <termios.h>
#include <unistd.h>
#include <linux/kd.h>
#include <ncurses.h>

#define MAXMARK 13 /* mark stack size */

main() {
  FILE *input, *calibrate, *output = NULL, *current;
  int fd, sample, was, c, wm, sound = 1;
  time_t now;
  struct tm *tm;
  int i;
  char *p, buffer[128];                 /* buffer for data from sensor */
  int ct, cd, ot, od, dt, dd;
  float mm, range, speed, previous;
  struct termios termios;
  struct mark {
    int entering;
    float range;
    int ct, cd;
    char name[128];
  } mark[MAXMARK];

  for(i=0;i<MAXMARK;i++) {
    mark[i].name[0] = '\0';
    mark[i].entering = 0;
    mark[i].range = 0.0;
  }
  wm = 1;

  initscr ( );
  cbreak ( );
  noecho ( );
  nonl ( );
  nodelay ( stdscr, TRUE );
  keypad ( stdscr, TRUE );
  mvprintw ( 0, 0, "Prototype Automobile Speed Logger, 0.2" );

  /* default calibration constant */  
  mm = 392.4646;
  previous = 0.0;
  sample = 10;

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

  fd = fileno ( input );

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

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

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

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

  mvprintw ( 2, 0,
	     "   10   20   30   40   50   60   70   80   90   100  110  120" );
           /* 1234567890123456789012345678901234567890123456789012345678901234567890 */
  mvprintw ( 3, 0,
	     "    |    |    |    |    |    |    |    |    |    |    |    |" );
  mvprintw ( 23, 0, "Waiting for data from sensor ... is it plugged in?" );
  attron(A_BOLD);
  mvprintw ( 2, 28, "60" );
  mvprintw ( 2, 48, "100" );
  attroff(A_BOLD);
  standout();
  mvprintw ( 3, 29, "|" );
  mvprintw ( 3, 49, "|" );
  standend();
 
  ot = -1;
  od = -1;

  for(;;) {

    refresh();

    /* get a line of data from the sensor */
    fgets ( buffer, 128, input );

    /* 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 );

    /* reset detection, output blank line, clear previous data */
    if ( *p == 'a' ) {
      mvprintw ( 23, 0, "%02d:%02d:%02d sensor has reset", 
		tm->tm_hour, tm->tm_min, tm->tm_sec );
      clrtoeol ( );
      if ( output != NULL )
        fprintf ( output, "\n\n" );
      ot = -1;
      od = -1;
      continue;
    }

    /* ignore any empty lines from sensor */
    if ( strlen(p) < 8 ) continue;

    /* perform conversion from decimal */
    sscanf ( p, "%d %d", &ct, &cd );

    /* avoid calculation if no data yet, need two data points to proceed */
    if ( ot == -1 ) {
      ot = ct;
      od = cd;
      continue;
    }

    /* calculate offsets, delta time, delta distance */
    dt = ct - ot;
    dd = cd - od;

    /* remember old numbers for next time */
    ot = ct;
    od = cd;

    /* sanity check, if time or distance goes down, reset */
    if ( dt < 0 ) {
      mvprintw ( 23, 0, "%02d:%02d:%02d insane delta time %d", 
		tm->tm_hour, tm->tm_min, tm->tm_sec, dt );
      clrtoeol ( );
      continue;
    }

    if ( dd < 0 ) {
      mvprintw ( 23, 0, "%02d:%02d:%02d insane delta distance %d", 
		tm->tm_hour, tm->tm_min, tm->tm_sec, dd );
      clrtoeol ( );
      continue;
    }

    /* check for new calibration constant */
#ifdef notdef
    calibrate = fopen ( "calibrate.dat", "r" );
    if ( calibrate != NULL ) {
      fscanf ( calibrate, "%f", &mm );
      mvprintw ( 23, 0, "%02d:%02d:%02d recalibrate to %f mm/pulse", 
		tm->tm_hour, tm->tm_min, tm->tm_sec, mm );
      clrtoeol ( );
      fclose ( calibrate );
      remove ( "calibrate.dat" );
    }
#endif

    /* calculate speed (mm/ms -> km/h) */
    speed = (float) dd * mm / dt * 3.6;

    /* suppress low speed data */
    if ( speed < 3.0 ) {
      if ( output != NULL ) {
	fprintf ( output, "\n" );
      }
      mvprintw ( 23, 0, "%02d:%02d:%02d below threshold (%3.1f km/h)", 
		tm->tm_hour, tm->tm_min, tm->tm_sec, speed );
      clrtoeol ( );
      continue;
    }

    /* ignore clearly bogus data */
    if ( speed > 200.0 ) {
      mvprintw ( 23, 0, "%02d:%02d:%02d bogus speed (%f km/h)",
                tm->tm_hour, tm->tm_min, tm->tm_sec, speed );
      clrtoeol ( );
      continue;
    }

    /* calculate odometer reading */
    range = (float) ( cd * mm / 1000000 );

    /* if keyboard used, process it */
    c = getch ( );
    while ( c != ERR ) {
      switch ( c ) {
      case '\f':
	redrawwin(stdscr);
	break;
      case KEY_PPAGE:
	sound = 0;
	mvprintw ( 23, 0, "Speed warning sounds : off" ); clrtoeol ( );
	break;
      case KEY_NPAGE:
	sound = 1;
	mvprintw ( 23, 0, "Speed warning sounds : ON" ); clrtoeol ( );
	break;
      case KEY_DC:
	mark[wm].range = 0.0;
	mark[wm].name[0] = '\0';
	mark[wm].entering = 0;
	move ( 10, 0 ); clrtoeol ( );
	move ( 12, 0 ); clrtoeol ( );
        move ( 13, 0 ); clrtoeol ( );
	move ( 14, 0 ); clrtoeol ( );
	move ( 15, 0 ); clrtoeol ( );
	move ( 16, 0 ); clrtoeol ( );
        mvprintw ( 23, 0, "Mark F%d deleted.", wm ); clrtoeol ( );
	break;
      case KEY_F(1):
      case KEY_F(2):
      case KEY_F(3):
      case KEY_F(4):
      case KEY_F(5):
      case KEY_F(6):
      case KEY_F(7):
      case KEY_F(8):
      case KEY_F(9):
      case KEY_F(10):
      case KEY_F(11):
      case KEY_F(12):
	wm = c - KEY_F0;
	if ( mark[wm].range == 0.0 ) {
	  move ( 10, 21 );
	  clrtoeol ( );
	} else {
	  mvprintw ( 10, 21, "Mark = %7.3f km", mark[wm].range );
	}
        move ( 12, 0 ); clrtoeol ( );
	move ( 13, 0 ); clrtoeol ( );
	move ( 14, 0 ); clrtoeol ( );
	move ( 15, 0 ); clrtoeol ( );
	move ( 16, 0 ); clrtoeol ( );
	mvprintw ( 23, 0, "Mark F%d.", wm ); clrtoeol ( );
	break;
      case KEY_IC:
      case '\n':
      case '\r':
      case '\\':
      case 0x09:
	fprintf ( output, "mark\t%7.3f\t%d\t%d\t%s\n", mark[wm].range, 
		  mark[wm].ct, mark[wm].cd, mark[wm].name );
	mark[wm].entering = 0;
	mvprintw ( 23, 0, "Mark F%d recorded to log.", wm ); clrtoeol ( );
	break;
      case KEY_BACKSPACE:
      case 0x08:
      case 0xff:
      case 0x7f:
	if ( strlen ( mark[wm].name ) > 0 ) {
	  mark[wm].name[strlen(mark[wm].name)-1] = '\0';
	}
	break;
      default:
	if ( ! mark[wm].entering ) {
	  mark[wm].name[0] = '\0';
	  mark[wm].range = range;
	  mark[wm].ct = ct;
	  mark[wm].cd = cd;
	  mvprintw ( 10, 21, "Mark = %7.3f km", range );
	  move ( 12, 0 ); clrtoeol ( );
          move ( 13, 0 ); clrtoeol ( );
	  move ( 14, 0 ); clrtoeol ( );
	  move ( 15, 0 ); clrtoeol ( );
	  move ( 16, 0 ); clrtoeol ( );
	  mark[wm].entering++;
	  mvprintw ( 23, 0, "Mark F%d set.", wm ); clrtoeol ( );
	}
	mark[wm].name[strlen(mark[wm].name)+1] = '\0';
	mark[wm].name[strlen(mark[wm].name)] = c;
        break;
      }
      move ( 10, 40 );
      if ( strlen ( mark[wm].name ) > 0 ) printw ( "F%d \"%s\"", 
						   wm, mark[wm].name );
      clrtoeol ( );
      c = getch ( );
    }

    mvprintw (  4, 0, "##############################################################################" );
    mvprintw (  4, (int) ( speed / 2.0 ), "#" );
    clrtoeol ( );
    mvprintw (  7, 21, "Time =  %02d:%02d:%02d",
		tm->tm_hour, tm->tm_min, tm->tm_sec );
    mvprintw (  6, 20, "Speed = %6.2f  km/h", speed );
    mvprintw (  8, 17, "Odometer = %7.3f km", range );
               /* 12345678901234567890 (28 peanut butter sangers) */
    if ( mark[wm].range != 0 ) {
      char *c = mark[wm].name;
      int remain;
      float since;

      /* ? */
      remain = atoi ( mark[wm].name );
      since = range - mark[wm].range;

      mvprintw ( 12, 6, "Distance since mark = %7.3f km", since );
      if ( remain != 0 ) {
	struct tm tm;
	float until = (float) remain - since;
	int dd = cd - mark[wm].cd;
	int dt = ct - mark[wm].ct;
	float speed = ( dd * mm / dt * 3.6 );
	float guess = until / speed; /* hours */
	time_t then = now + ( (float) guess * 3600.0 );
        localtime_r ( &then, &tm );

	if ( until < 0 ) {
	  move ( 13, 0 ); clrtoeol ( );
	  mvprintw ( 13, 28, "(passed)" );
	  move ( 14, 0 ); clrtoeol ( );
	  move ( 15, 0 ); clrtoeol ( );
	  move ( 16, 0 ); clrtoeol ( );
	} else {
	  mvprintw ( 13,  1, "Distance to destination  = %7.3f km", until );
	  mvprintw ( 14,  1, "Average speed since mark = %6.2f  km/h", speed );
	                   /* 1234567890123456789012345678901234567890 */
	  mvprintw ( 16,  1, "Predicted arrival time   =  %02d:%02d:%02d", 
		     tm.tm_hour, tm.tm_min, tm.tm_sec );
	  mvprintw ( 15,  1, "Predicted travel time    = %6.2f hours", 
		     (float) guess );
	}
      }
    }

    /*
    mvprintw ( 18, 20, "Ct:Cd = %d:%d", ct, cd ); clrtoeol ( );
    mvprintw ( 19, 20, "Dt:Dd = %d:%d", dt, dd ); clrtoeol ( );
    */

    /* detect overspeed conditions */
    if ( sound ) for(i=60;i<120;i+=10) {
      if ( previous < i && speed > i ) {
	/* rising tone, major chord, C, E, G */
	ioctl(0, KDMKTONE, (100<<16 | (1193180/1000)));
	usleep(100000);
	ioctl(0, KDMKTONE, (100<<16 | (1193180/1260)));
	usleep(100000);
	ioctl(0, KDMKTONE, (200<<16 | (1193180/1498)));
	usleep(200000);
      }
      if ( previous > i && speed < i ) {
	/* falling tone, minor chord, G, D#, C */
	ioctl(0, KDMKTONE, (100<<16 | (1193180/1498)));
	usleep(100000);
	ioctl(0, KDMKTONE, (100<<16 | (1193180/1189)));
	usleep(100000);
	ioctl(0, KDMKTONE, (200<<16 | (1193180/1000)));
	usleep(200000);
      }
    }
    previous = speed;

    /* announce we are working fine in the first few metres of travel */
    if ( sound ) if ( cd * mm < 10000 ) {
	ioctl(0, KDMKTONE, (100<<16 | (1193180/4000)));
	usleep(100000);
    }

    /* format line for output */
#ifdef notdef
    sprintf ( buffer, 
	      "%07.4f\t%06.2f\t%d\t%d\t%d\t%d\t%f\tcar-%04.4d-%03.3d.log\n",
	      (float) tm->tm_hour + 
	      (float) tm->tm_min / 60.0 + 
	      (float) tm->tm_sec / 3600.0,
	      speed,
	      ct,
	      cd,
	      dt,
	      dd,
	      mm,
	      tm->tm_year+1900, 
	      tm->tm_yday );

    /* write current sample to new output file */
    current = fopen ( "warner.tmp", "w" );
    fclose ( current );
    rename ( "warner.tmp", "warner.dat" );
#endif

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

      was = now / sample;

      sprintf ( new, "car-%04.4d-%03.3d.log", tm->tm_year+1900,
		tm->tm_yday );

      mvprintw ( 21, 21, "File = %s", new );
 
      if ( output == NULL ) {
	output = fopen ( new, "a" );
        lines = 0;
      } else {
	if ( strcmp ( old, new ) ) {
	  mvprintw ( 23, 70, "(new file)" ); clrtoeol ( );
	  fclose ( output );
	  output = fopen ( new, "a" );
	  strcpy ( old, new );
          lines = 0;
	}
      }

      fprintf ( output, "%.4f %.2f %d %d %d %d %f\n",
		(float) tm->tm_hour + 
		(float) tm->tm_min / 60.0 + 
		(float) tm->tm_sec / 3600.0,
		speed,
		ct,
		cd,
		dt,
		dd,
		mm );
      fflush ( output );
      lines++;

      mvprintw ( 21, 60, "Lines = %d", lines );
      clrtoeol ( );
    }
  }
}

