/*
  charger.c

  Copyright 20-Nov-2002 MBARI
  Written: 20-Nov-2002 Mark Sibenac
  Last mod: 01-Apr-2004 sib - added manual input for batts online

  Bluefin Battery Charger
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef _QNX
#include <unistd.h>
#include <sys/select.h>
#else
#include <sys/unistd.h>
#endif
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>

#include "error.h"
#include "xfr.h"
#include "smart_battery.h"
#include "serial.h"
#include "charger.h"
#include "util.h"

typedef enum {MENU_TOP, MENU_BATT, MENU_CHARGER, MENU_AUTO, MENU_NUM} MENU_t;

#define ALARM_PERIOD (30) /* seconds */
#define MAX_ENERGY (4000.0) /* WHr */
#define CHARGE_VOLTAGE (35.5) /* volts */
#define NOMINAL_VOLTAGE (35.5) /* volts */
#define NOMINAL_CURRENT (1.0) /* Amps for Hotel */
#define HOTEL_PEAK_CURRENT (5.0) /* Amps extra peak for Hotel */

int DEBUG_LEVEL = 0; // default to no debug

void printTopMenu (void)
{
  printf ("\nCharger Software\n");
  printf ("compiled: %s\n\n", __DATE__);
  printf ("1.  Low-level battery commands\n");
  printf ("2.  Low-level charger commands\n");
  printf ("3.  Automated charging\n");
  printf ("Q.  Quit\n");
  printf ("?.  This menu\n");
}

void printBattMenu (void)
{
  printf ("\nBattery Menu\n\n");
  printf ("1.  Set Address\n");
  printf ("2.  Set State\n");
  printf ("3.  Set Energy Level\n");
  printf ("4.  Set Streaming Mode\n");
  printf ("5.  Set Master Mode\n");
  printf ("6.  Crash Battery uController\n");
  printf ("7.  Set all batteries to Off State\n");
  printf ("8.  Get battery version\n");
  printf ("9.  Enable all batts and disable watchdogs\n");
  printf ("0.  Set watchdog state\n");
  printf ("B.  Manually enter batts-online\n");
  printf ("I.  Print All Batt Info\n");
  printf ("T.  Top Menu\n");
  printf ("?.  This Menu\n");
}

void printChargerMenu (void)
{
  printf ("\nCharger Menu\n\n");
  printf ("1.  Initialize\n");
  printf ("2.  Reset\n");
  printf ("3.  Trigger\n");
  printf ("4.  Enable Output\n");
  printf ("5.  Disable Output\n");
  printf ("6.  Set OVP\n");
  printf ("7.  Set VMax Limit\n");
  printf ("8.  Set Voltage\n");
  printf ("9.  Set IMax Limit\n");
  printf ("0.  Set Current\n");
  printf ("I.  Print All Charger Info\n");
  printf ("T.  Top Menu\n");
  printf ("?.  This Menu\n");
}

void printAutomatedChargingMenu (void)
{
  printf ("\nAutomated Charging Menu\n\n");
  printf ("1.  Set Vtrip for charging\n");
  printf ("2.  Set 3 Currents\n");
  printf ("3.  Set Batts to Charge or Balance\n");
  printf ("4.  Charge\n");
  printf ("5.  Stop Charge\n");
  printf ("6.  Set Vmin for Balancing/Discharging\n");
  printf ("7.  Balance Packs\n");
  printf ("8.  Stop Balance/Discharge - Set Idle State\n");
  printf ("9.  Discharge Pack (enable all batts)\n");
  printf ("0.  Set Hotel Voltage/Current\n");
  printf ("D.  Enable (Discharge) all batts and disable watchdogs\n");
  printf ("I.  Print Charging Info\n");
  printf ("T.  Top Menu\n");
  printf ("?.  This Menu\n");
}

void getBattNumsStr (char *str, SmartBattery_t *sm_batts)
{
  int i;
  char buff[5];

  for (i=0; i<MAX_BATTS; ++i)
    {
      if (sm_batts[i].batt_alive)
	{
	  sprintf (buff, "%02Xh ", i);
	  strcat (str, buff);
	}
    }
}

void printBattsOnLine (SmartBattery_t *sm_batts)
{
  char batts[256*4] = "";

  getBattNumsStr (batts, sm_batts);
  printf ("Batts Online: %s\n", batts);
}

char getKeyBoard (void)
{
  char str_in[2] = "\000";
  int ret;
  struct timeval tv;
  fd_set readFds;

  tv.tv_sec = 0; tv.tv_usec = 100000; // sleep for 100ms waiting for keyboard
  FD_ZERO (&readFds);
  FD_SET (0/*stdin*/, &readFds);
  
  if ((ret = select (1, &readFds, 0, 0, &tv)) < 0)
    { 
      ERROR ("err: getKeyBoard select failed %d: %s\n", ret, strerror(errno));
      return 0;
    }
  else if (ret > 0) // did not timeout; got a character
    {
      if ((ret = read (0/*stdin*/, str_in, 1)) < 0)
	{
	  ERROR ("err: getKeyBoard read failed %d: %s\n", ret, 
		 strerror(errno));
	}
      DEBUG (5, "getKeyBoard read got '%c'\n", *str_in);
      return *str_in;
    }

  return 0;
}
int getBattNum (SmartBattery_t  *sm_batts)
{
  char batts[256*4] = "";
  int batt;
  getBattNumsStr(batts, sm_batts);
  printf ("Enter batt number in hex [%s]: ", batts);
  scanf ("%x", &batt);
  if (!sm_batts[batt].batt_alive)
    {
      ERROR ("err: Batt %02X is not alive\n", batt);
      return -1;
    }
  return batt;
}

int updateBattInfo (int batt_port, int batt, SmartBattery_t *sm_batt)
{
  sm_batt->address = batt;
  battQueryCellVolts (batt_port, batt, &sm_batt->volts);
  battQuerySummary (batt_port, batt, &sm_batt->summary);
  battQueryTemps (batt_port, batt, &sm_batt->temps);
  
  return 0;
}

// returns 0 if no faults
int checkBattFaults (int batt_port, int xfr_port, SmartBattery_t *sm_batt, 
		     AutoCharger_t *autocharger)
{
  char fault=0;

  if (sm_batt->summary.error != BATT_ERROR_CLEAR)
    {
      fault = 1;
      printf ("\nbattery %02X error state=%s -- taking off charge\n", sm_batt->address,
	      BattErrorStr(sm_batt->summary.error));
    }
  else if (sm_batt->summary.state!=BATT_STATE_CHARGE)
    return 0;

  if (sm_batt->summary.max_volt >= autocharger->vtrip)
    {
      fault = 1;
      printf ("\nbattery %02X max_volt=%f >= vtrip=%f -- taking off charge\n", sm_batt->address,
	      sm_batt->summary.max_volt, autocharger->vtrip);
    }
  if (sm_batt->summary.pack_I <
      (-1.10 * autocharger->currents[autocharger->current_state] - 0.5))
    {
      fault = 1;
      printf ("\nbattery %02X I=%f < max=%f -- taking off charge\n", sm_batt->address,
	      sm_batt->summary.pack_I, 
	      -1.10 * autocharger->currents[autocharger->current_state] - 0.5);
    }
  
  return fault;
}

void printBattInfo (SmartBattery_t *sm_batt)
{
  int h,m,s,i;

  printf ("\nBattery info for %02Xh = #%d\n", sm_batt->address, 
	  sm_batt->address);
  h = (sm_batt->summary.time)/60/60;
  m = (sm_batt->summary.time-h*60*60)/60;
  s = (sm_batt->summary.time-h*60*60-m*60);
  printf ("state=%s, error=%s, mode=%s\n", 
	  BattStateStr(sm_batt->summary.state),
	  BattErrorStr(sm_batt->summary.error),
	  BattModeStr(sm_batt->summary.mode));
  printf ("%0.2fV %0.2fA MaxTemp=%0.2f MinVolt=%0.2f MaxVolt=%0.2f\n",
	  sm_batt->summary.pack_V,
	  sm_batt->summary.pack_I,
	  sm_batt->summary.max_temp,
	  sm_batt->summary.min_volt,
	  sm_batt->summary.max_volt);
  printf ("water leak=%d C=%0.2fWHr time=%02d:%02d:%02d\n",
	  sm_batt->summary.water_leak,
	  sm_batt->summary.capacity,
	  h, m, s);
  printf ("Volts: ");
  for (i=0; i<NUM_CELLS; ++i)
    printf ("%0.2f ", sm_batt->volts.cell_volts[i]);
  printf ("\n");
  printf ("Temps: ");
  for (i=0; i<NUM_TEMPS; ++i)
    printf ("%0.2f ", sm_batt->temps.pack_temps[i]);
  printf ("<-- oil\n");
}

void printAllBattInfo (SmartBattery_t *sm_batt)
{
  int i;

  for (i=0; i < MAX_BATTS; ++i)
    {
      if (sm_batt[i].batt_alive)
	printBattInfo (&sm_batt[i]);
    }
}

void enableAllBatts (SmartBattery_t *sm_batts)
{
  int i;

  for (i=0; i < MAX_BATTS; ++i)
    {
      if (sm_batts[i].batt_alive)
	{
	  printf ("Enabling (Discharging) batt %02x (#%03d)\n",sm_batts[i].address,
		  sm_batts[i].address);
	  battSetState (sm_batts[i].port, sm_batts[i].address,
			BATT_STATE_DISCHARGE);
	  printf ("Disabling watchdog for batt %02x (#%03d)\n",sm_batts[i].address,
		  sm_batts[i].address);
	  battSetWatchdog (sm_batts[i].port, sm_batts[i].address,
			   BATT_WD_DISABLE);
	}
    }
}

int updateChargerInfo (int xfr_port, XFRState_t *xfr)
{
  int ret;

  if (!(ret = xfrGetVOut (xfr_port, &xfr->VOut)))
    return ret;
  if (!(ret = xfrGetVSet (xfr_port, &xfr->VSet)))
    return ret;
  if (!(ret = xfrGetIOut (xfr_port, &xfr->IOut)))
    return ret;
  if (!(ret = xfrGetISet (xfr_port, &xfr->ISet)))
    return ret;
  if (!(ret = xfrGetOut (xfr_port, &xfr->OutEnabled)))
    return ret;
  if (!(ret = xfrGetErr (xfr_port, &xfr->err)))
    return ret;
  if (!(ret = xfrGetStatus (xfr_port, &xfr->asts, &xfr->sts, &xfr->fault)))
    return ret;
  
  return ret;
}

void printChargerInfo (int xfr_port, XFRState_t *xfr)
{
  printf ("Charger info\n");
  updateChargerInfo (xfr_port, xfr);
  printf ("Vout=%0.4fV Iout=%0.4fA OutputEnable=%d\n", xfr->VOut, xfr->IOut,
	  xfr->OutEnabled);
  printf ("Vset=%0.4fV Iset=%0.4fA\n", xfr->VSet, xfr->ISet);
  printf ("Err=%d Asts=0x%04X Sts=0x%04X Fault=0x%04X\n\n", xfr->err,
	  xfr->asts, xfr->sts, xfr->fault);
}

void initAutoCharger (AutoCharger_t *autocharger, SmartBattery_t *sm_batts)
{
  int i;
  for (i=0; i<MAX_BATTS; ++i)
    {
      sm_batts[i].batt_alive = 0; // not alive
      autocharger->batts[i] = 0; // do not charge
    }
  autocharger->vtrip = CHARGER_VTRIP_DEFAULT;
  autocharger->vmin = CHARGER_VMIN_DEFAULT;
  for (i=0; i<MAX_CHARGE_STAGES; ++i)
    {
      autocharger->currents[i] = CHARGER_CURRENT_DEFAULTS(i);
    }
  autocharger->state = CHARGER_IDLE;
}

void printAutoChargerInfo (AutoCharger_t *autocharger)
{
  int i;
  printf ("\nAuto Charger Info:\n");
  printf ("vtrip = %0.4f, vmin = %0.4f\n", autocharger->vtrip, autocharger->vmin);
  printf ("Charge states: ");
  for (i=0; i<MAX_CHARGE_STAGES; ++i)
    {
      printf ("%0.4f ", autocharger->currents[i]);
    }
  printf ("\nstate = %s\n", ChargerState(autocharger->state));
  printf ("Batts to Charge: ");
  for (i=0; i<MAX_BATTS; ++i)
    {
      if (autocharger->batts[i])
	printf ("%02Xh ", i);
    }
  printf ("\n\n");
}

void flushStdin (void)
{
  char str[256];
  int ret;
  struct timeval tv;
  fd_set readFds;

  tv.tv_sec = 0; tv.tv_usec = 0; // sleep for 0ms waiting for keyboard
  FD_ZERO (&readFds);
  FD_SET (0/*stdin*/, &readFds);
  
  if ((ret = select (1, &readFds, 0, 0, &tv)) < 0)
    { 
      ERROR ("err: flushStdin select failed %d: %s\n", ret, strerror(errno));
      return;
    }
  else if (ret > 0) // did not timeout; got a character
    {
      if ((ret = read (0/*stdin*/, str, 255)) < 0)
	{
	  ERROR ("err: flushStdin read failed %d: %s\n", ret, 
		 strerror(errno));
	}
    }
}

int main (int argc, char **argv)
{
  int xfr_port, batt_port;
  int i;
  MENU_t menu = MENU_TOP;
  char keyboard = 0;
  char shutdown = 0;
  struct termios termios_p;
  time_t t1, talarm;
  SmartBattery_t sm_batts[MAX_BATTS];
  XFRState_t xfr;
  AutoCharger_t autocharger;
  double nominal_current = NOMINAL_CURRENT; // for hotel current
  double nominal_voltage = NOMINAL_VOLTAGE; // for hotel and charge max voltage
  char cdevname[128]="tcp:ets8p-1:3002";
  char bdevname[128]="/dev/ser10";

  printf ("Press return when batteries are enabled... ");
  tcflush(0, TCIOFLUSH);
  printf ("%d\n", getchar());

  for (i=1; i < argc; ++i) 
  {
    if (*argv[i] == '-') // got something
    {
      if (!strcmp(argv[i]+1, "cdev")) // power supply/charger device name
	strncpy (cdevname, argv[i+1], 128);
      else if (!strcmp(argv[i]+1, "bdev")) // battery device name
	strncpy (bdevname, argv[i+1], 128);
      else if (!strcmp(argv[i]+1, "debug")) // debug level
	DEBUG_LEVEL = atoi(argv[i+1]);
      else // print usage
	{
	  printf ("usage: charger [-cdev charger_dev_name] [-bdev batt_dev_name] [-debug "
		  "debug_level]\n");
	  printf ("eg.    charger -cdev tcp:ets8p-1:3002 -bdev /dev/ser10 -debug 2\n");
	  printf ("default cdev='%s'\n", cdevname);
	  printf ("default bdev='%s'\n", bdevname);
	  printf ("default debug level=%d\n", DEBUG_LEVEL);
	  return -1;
	}
    }
  }

  printf ("cdev='%s'\n", cdevname);
  printf ("bdev='%s'\n", bdevname);
  printf ("debug level=%d\n", DEBUG_LEVEL);

  initAutoCharger (&autocharger, sm_batts); // zero and set fields to defaults

  if (tcgetattr (0/*stdin*/, &termios_p) < 0)
    {
      ERROR ("err: main() could not tcgetattr on stdin: %s\n",
	     strerror (errno));
      return -1;
    }

  termios_p.c_lflag |= ICANON;	// Enable Line Oriented Mode 

  if (tcsetattr (0/*stdin*/, TCSANOW, &termios_p) < 0)
    {
      ERROR ("err: main() tcsetattr failed on stdin: %s\n", strerror(errno));
      return -1;
    }

  DEBUG (1, "Debug Level=%d\n", DEBUG_LEVEL);
  DEBUG (1, "time = %d\n", (int)time(0));

#ifdef _QNX
  if (openPort (cdevname, &xfr_port, 9600, 8, 1, "NONE") < 0)
    {
      ERROR ("err: Could not open '%s'\n", cdevname);
      return -1;
    }
  if (openPort (bdevname, &batt_port, 9600, 8, 1, "NONE") < 0)
    {
      ERROR ("err: Could not open '%s'\n", bdevname);
      return -1;
    }
#else /* _QNX */
  if (openPort (cdevname, &xfr_port, B9600, 8, 1, "NONE") < 0)
    {
      ERROR ("err: Could not open '%s'\n", cdevname);
      return -1;
    }
  if (openPort (bdevname, &batt_port, B9600, 8, 1, "NONE") < 0)
    {
      ERROR ("err: Could not open '%s'\n", bdevname);
      return -1;
    }
#endif /* _QNX */

  if (openLogFile (xfr_port, "xfr") < 0)
    {
      ERROR ("err: Could not open xfr_ logfile\n");
      return -1;
    }

  if (openLogFile (batt_port, "battery") < 0)
    {
      ERROR ("err: Could not open battery_ logfile\n");
      return -1;
    }
		
  DEBUG (1, "xfr_port fd= %d\n" , xfr_port);
  DEBUG (1, "batt_port fd = %d\n" , batt_port);
  xfrGetErr (xfr_port, &xfr.err); // initial comms for xfr power supply
  xfrOverVoltSet (xfr_port, 45.0); // set OVP to 45.0 V
  xfrVMax (xfr_port, 36.0); // set VMAX to 36.0 V

  if (battSync (batt_port, sm_batts) <= 0) // find all batteries
    { // if there was a problem, signify shutdown
      //shutdown = 1;
    }

  for (i=0; i<MAX_BATTS; ++i)
    {
      if (sm_batts[i].batt_alive)
	{
	  autocharger.batts[i] = 1; // set charging flag
	  sm_batts[i].port = batt_port;
	}
    }

  printBattsOnLine (sm_batts);
  printTopMenu();

  t1 = time(0);
  talarm = t1 + ALARM_PERIOD;

  while (!shutdown) {
    // first check for keyboard hit
    if ((keyboard = getKeyBoard ())) {
      switch (menu) {
      case MENU_TOP: 
	switch (keyboard) {
	case '1': // low-level battery commands
	  menu = MENU_BATT;
	  printBattMenu();
	  break;
	case '2': // low-level charger commands
	  menu = MENU_CHARGER;
	  printChargerMenu();
	  break;
	case '3': // automated charging commands
	  menu = MENU_AUTO;
	  printAutomatedChargingMenu();
	  break;
	case 'Q':
	case 'q': // Quit
	  // shutdown properly
	  shutdown = 1;
	  break;
	case 'T':  
	case 't':
	case '?': // Print this menu
	  printTopMenu();
	  break;
	case 0x0a: // line feed
	case 0x0d: // carriage return 
	  break; // do nothing
	default : // uh-oh
	  printf ("Command '%c' not recognized...\n", keyboard);
	} // case MENU_TOP: switch (keyboard)
	break;

      case MENU_BATT:
	switch (keyboard) {
	case '1': {// set address
	  unsigned int old_add=0, new_add=0;
	  printBattsOnLine(sm_batts);
	  printf ("Enter old address in hex(0-all): ");
	  scanf ("%x", &old_add);
	  printf ("Old address = %02Xh\n", old_add);
	  printf ("Enter new address in hex: ");
	  scanf ("%x", &new_add);
	  printf ("New address = %02Xh\n", new_add);
	  battSetAddress (batt_port, old_add, new_add);
	  break;
	}
	case '2': { // set state
	  BattState_t state=BATT_STATE_OFF;
	  int batt = 0;
	  if ((batt = getBattNum (sm_batts)) < 0)
	    break;
	  printf ("Enter State	1)Off, 2)Discharge, 3)Charge, 4)Balance: ");
	  scanf ("%d", (int*)&state);
	  if (state < 0 || state >= BATT_STATE_UNKNOWN)
	    {
	      ERROR ("err: Batt state %d is not known\n", state);
	      break;
	    }
	  battSetState (batt_port, batt, state-1);
	  break;
	}
	case '3': { // set energy level
	  double C=0.0;
	  int batt = 0;
	  if ((batt = getBattNum (sm_batts)) < 0)
	    break;
	  printf ("Enter Energy Level (%0.2fC): ", 
		  sm_batts[batt].summary.capacity);
	  scanf ("%lf", &C);
	  if (C < 0.0 || C >= MAX_ENERGY)
	    {
	      ERROR ("err: Batt energy=%0.2f is out of range\n", C);
	      break;
	    }
	  battSetEnergy (batt_port, batt, C);
	  break;
	}
	case '4': { // set streaming level
	  int stream = 0;
	  int batt = 0;
	  if ((batt = getBattNum (sm_batts)) < 0)
	    break;
	  printf ("Enter Streaming Level (1-on, 0-off): ");
	  scanf ("%d", &stream);
	  if (stream != 1 && stream != 0)
	    {
	      ERROR ("err: Batt stream mode=%d is out of range\n", stream);
	      break;
	    }
	  battSetStreaming (batt_port, batt, stream);
	  break;
	}
	case '5': { // set master mode
	  int mode = 0;
	  int batt = 0;
	  if ((batt = getBattNum (sm_batts)) < 0)
	    break;
	  printf ("Enter Master mode (%d-master, %d-slave): ", 
		  BATT_MODE_MASTER, BATT_MODE_SLAVE);
	  scanf ("%d", &mode);
	  if (mode != BATT_MODE_MASTER && mode != BATT_MODE_SLAVE)
	    {
	      ERROR ("err: Batt mode=%d is out of range\n", mode);
	      break;
	    }
	  battSetMaster (batt_port, batt, mode);
	  break;
	}
	case '6': { // crash battery ucontroller
	  int batt = 0;
	  if ((batt = getBattNum (sm_batts)) < 0)
	    break;
	  battCrash (batt_port, batt);
	  break;
	}
	case '7': { // set all batts to Off state
	  int i;
	  for (i=0; i<MAX_BATTS; ++i)
	    if (sm_batts[i].batt_alive)
	      {
	        battSetState (batt_port, sm_batts[i].address, BATT_STATE_OFF);
	        printf ("Battery %02Xh = #%d is off\n", sm_batts[i].address,
	                sm_batts[i].address);
	      }
	  break;
	}
	case '8': { // print batt version
	  int i;
	  char ver[80];
	  for (i=0; i<MAX_BATTS; ++i)
	    if (sm_batts[i].batt_alive) 
	      {
		battGetVersion (batt_port, i, ver, 80);
		printf ("Battery %02Xh = #%d reports: %s\n", sm_batts[i].address,
			sm_batts[i].address, ver);
	      }
	  break;
	}
	case '9': // enable (discharge) all batts and disable all watchdogs
	  printf ("Enabling all batts and disabling all watchdogs.\n");
	  enableAllBatts(sm_batts);
	  break;
	case '0': { // set watchdog mode (disable/enable)
	  int mode = 0;
	  int batt = 0;
	  if ((batt = getBattNum (sm_batts)) < 0)
	    break;
	  printf ("Enter watchdog state (%d-disable, %d-enable): ", 
		  BATT_WD_DISABLE, BATT_WD_ENABLE);
	  scanf ("%d", &mode);
	  if (mode != BATT_WD_DISABLE && mode != BATT_WD_ENABLE)
	    {
	      ERROR ("err: Batt WDmode=%d is out of range\n", mode);
	      break;
	    }
	  battSetWatchdog (batt_port, batt, mode);
	  break;
	}
	case 'B':
	case 'b': { // manually enter batts-online
	  unsigned int n=0, add=0;
	  printf ("I think you have these batts online:\n  ");
	  printBattsOnLine(sm_batts);
	  initAutoCharger (&autocharger, sm_batts); // zero and set fields to defaults
	  for (n=0; n<MAX_BATTS; n++) { // clear out database
	    sm_batts[n].batt_alive = 0; // not alive
	    sm_batts[n].port = 0;
	    sm_batts[n].address = 0;
	  }
	  printf ("How many batts do you have? ");
	  scanf ("%d", &n);
	  printf ("Enter %d batts that you have online in hex, separated by spaces and end with x.\n  ", n);
	  for (n=n; n; n--) {
	    if (1 != scanf ("%x", &add))
	      break;
	    if (add > 0 && add <= 0xff) { // probably got something valid
	      printf ("Batt %02x is online.\n", add);
	      sm_batts[add].batt_alive = 1;
	      sm_batts[add].address = add;
	      autocharger.batts[add] = 1;
	      sm_batts[add].port = batt_port;
	    } else {
	      printf ("error: %02x was invalid. skipping...\n", add);
	    }
	  }
	  printf ("\n\nNow I think you have these batts online:\n  ");
	  printBattsOnLine(sm_batts);
	  break;
	}
	case 'I': 
	case 'i': { // print all batt info
	  int i;
	  for (i=0; i<MAX_BATTS; ++i)
	    if (sm_batts[i].batt_alive) 
	      {
		updateBattInfo (batt_port, i, &sm_batts[i]);
		printBattInfo (&sm_batts[i]);
	      }
	  break;
	}
	case 'T':  
	case 't': // go up to top menu
	  menu = MENU_TOP;
	  printTopMenu();
	  break;
	case '?': // print this menu
	  printBattMenu();
	  break;
	case 0x0a: // line feed
	case 0x0d: // carriage return 
	  break; // do nothing
	default : // uh-oh
	  printf ("Command '%c' not recognized...\n", keyboard);
	} // case MENU_BATT: switch (keyboard)
	break;

      case MENU_CHARGER:
	switch (keyboard) {
	case '1': // initialize
	  xfrInit (xfr_port);
	  break;
	case '2': // reset
	  xfrReset (xfr_port);
	  break;
	case '3': // trigger
	  xfrTrigger (xfr_port);
	  break;
	case '4': // enable output
	  xfrEnableOutput (xfr_port);
	  break;
	case '5': // disable output
	  xfrDisableOutput (xfr_port);
	  break;
	case '6': { // set ovp
	  double ovp = 0.0;
	  printf ("Enter OVP: ");
	  scanf ("%lf", &ovp);
	  xfrOverVoltSet (xfr_port, ovp);
	  break;
	}
	case '7': { // set vmax limit
	  double vmax = 0.0;
	  printf ("Enter VMAX: ");
	  scanf ("%lf", &vmax);
	  xfrVMax (xfr_port, vmax);
	  break;
	}
	case '8': { // set voltage
	  double vset = 0.0;
	  printf ("Enter Voltage Set Point: ");
	  scanf ("%lf", &vset);
	  xfrSetVolts (xfr_port, vset);
	  break;
	}
	case '9': { // set imax limit
	  double imax = 0.0;
	  printf ("Enter IMAX: ");
	  scanf ("%lf", &imax);
	  xfrIMax (xfr_port, imax);
	  break;
	}
	case '0': { // set current
	  double iset = 0.0;
	  printf ("Enter Current Set Point: ");
	  scanf ("%lf", &iset);
	  xfrSetCurrent (xfr_port, iset);
	  break;
	}
	case 'I': 
	case 'i': // print all charge info
	  printChargerInfo (xfr_port, &xfr);
	  break;
	case 'T':  
	case 't': // go up to top menu
	  menu = MENU_TOP;
	  printTopMenu();
	  break;
	case '?': // print this menu
	  printChargerMenu();
	  break;
	case 0x0a: // line feed
	case 0x0d: // carriage return 
	  break; // do nothing
	default : // uh-oh
	  printf ("Command '%c' not recognized...\n", keyboard);
	} // case MENU_CHARGER: switch (keyboard)
	break;

      case MENU_AUTO:
	switch (keyboard) {
	case '1': { // set vtrip
	  double vtrip;
	  printf ("Vtrip=%0.4f	Enter new Vtrip: ", autocharger.vtrip);
	  scanf ("%lf", &vtrip);
	  if (vtrip < BALANCE_VMIN || vtrip > MAX_VTRIP)
	    {
	      ERROR ("err: vtrip=%0.4f is not within range.\n", vtrip);
	    } else {
	      autocharger.vtrip = vtrip;
	    }
	  break;
	}
	case '2': { // set 3 currents
	  double current;
	  int i;
	  printf ("Charge currents are: ");
	  for (i=0; i<MAX_CHARGE_STAGES; ++i)
	    {
	      printf ("%d=%0.4f ", i, autocharger.currents[i]);
	    }
	  printf ("\nEnter new currents separated by spaces: ");
	  flushStdin(); // get that pesky newline
	  for (i=0; i<MAX_CHARGE_STAGES; ++i)
	    {
	      scanf ("%lf", &current);
	      if (current < 0.0 || current > MAX_CHARGE_CURRENT)
		{
		  ERROR ("err: current=%0.4f is out of range\n", current);
		  continue;
		}
	      autocharger.currents[i] = current;
	    }
	  flushStdin(); // get that pesky newline
	  printf ("\nNew currents are: ");
	  for (i=0; i<MAX_CHARGE_STAGES; ++i)
	    {
	      printf ("%d=%0.4f ", i, autocharger.currents[i]);
	    }
	  printf ("\n");
	  break;
	}
	case '3': { // set batts to charge
	  int i, batt, ret;
	  char str[MAX_BATTS*4], *pstr, *ppstr;
	  printf ("Batts to charge are set to: ");
	  for (i=0; i<MAX_BATTS; ++i)
	    {
	      if (autocharger.batts[i])
		printf ("%02Xh ", i);
	    }
	  printf ("\nEnter new batts to charge in hex separated by spaces: \n");
	  for (i=0; i<MAX_BATTS; ++i)
	    {
	      autocharger.batts[i] = 0;
	    }
	  fflush (stdout);
	  autocharger.num_batts_to_charge = 0;
	  flushStdin(); // get that pesky newline
	  ret = read (0, str, MAX_BATTS*4);
	  DEBUG (103, "str='%s'\n", str);
	  ppstr = str;
	  while ((pstr=(char *)strsep (&ppstr, " \012\015\n\r")))
	    {
	      sscanf(pstr, "%x", &batt);
	      if (batt < 0 || batt >= MAX_BATTS)
		{
		  ERROR ("err: batt=%02Xh is out of range\n", batt);
		  continue;
		}
	      autocharger.batts[batt] = 1;
	      ++autocharger.num_batts_to_charge;
	      DEBUG (103, "got %02Xh num=%d\n", batt, autocharger.num_batts_to_charge); 
	    }
	  --autocharger.num_batts_to_charge; // strsep counts the last 1 twice
	  flushStdin(); // get that pesky newline
	  printf ("New batts to charge are: ");
	  for (i=0; i<MAX_BATTS; ++i)
	    {
	      if (autocharger.batts[i])
		printf ("%02Xh ", i);
	    }
	  printf ("\n");
	  break;
	}
	case '4': // charge
	  autocharger.state = CHARGER_START;
	  printf ("\nPreparing to Charge packs\n");
	  break;
	case '5': // stop charge
	  autocharger.state = CHARGER_STOP;
	  printf ("\nPreparing to Stop Charging packs\n");
	  break;
	case '6': { // set vmin
	  double vmin;
	  printf ("Vmin=%0.4f	Enter new Vmin: ", autocharger.vmin);
	  scanf ("%lf", &vmin);
	  if (vmin < BALANCE_VMIN || vmin > MAX_VTRIP)
	    {
	      ERROR ("err: vmin=%0.4f is not within range.\n", vmin);
	    } else {
	      autocharger.vmin = vmin;
	    }
	  break;
	}
	case '7': // balance packs
	  autocharger.state = CHARGER_BALANCE_START;
	  printf ("\nPreparing to Balance packs\n");
	  break;
	case '8': { // stop balance/idle
	  int i;
	  for (i=0; i<MAX_BATTS; ++i) {
	    if (sm_batts[i].batt_alive) {
	      printf ("Turning battery %02Xh=#%d off\n", sm_batts[i].address, 
		      sm_batts[i].address);
	      if (!battSetState (sm_batts[i].port, sm_batts[i].address, BATT_STATE_OFF))
		sm_batts[i].summary.state = BATT_STATE_OFF;
	      printBattInfo (&sm_batts[i]);
	    }
	  }
	  autocharger.state = CHARGER_IDLE;
	  break;
	}
	case '9':  // discharge packs
	  autocharger.state = CHARGER_DISCHARGE_START;
	  printf ("\nPreparing to Discharge packs\n");
	  break;
	case '0': { // set hotel voltage/current
	  double volt, current;
	  printf ("nominal Voltage=%0.1f	Enter new Voltage: ", nominal_voltage);
	  scanf ("%lf", &volt);
	  if (volt < 0.0 || volt > 35.5)
	    {
	      ERROR ("err: voltage=%0.1f is not within range.\n", volt);
	    } else {
	      nominal_voltage = volt;
	    }
	  printf ("nominal Current=%0.1f	Enter new Current: ", nominal_current);
	  scanf ("%lf", &current);
	  if (current < 0.0 || current > 5.0)
	    {
	      ERROR ("err: current=%0.1f is not within range.\n", current);
	    } else {
	      nominal_current = current;
	    }
	  xfrSetVolts (xfr_port, nominal_voltage);
	  xfrSetCurrent (xfr_port, nominal_current + HOTEL_PEAK_CURRENT);
	  xfrEnableOutput (xfr_port);
	  break;
	}
	case 'I': 
	case 'i': // print charging info
	  printAutoChargerInfo (&autocharger);
	  break;
	case 'D':
	case 'd': // enable all batts and disable watchdogs
	  printf ("Enabling all batts and disabling all watchdogs.\n");
	  enableAllBatts(sm_batts);
	  break;
	case 'T':  
	case 't': // go up to top menu
	  menu = MENU_TOP;
	  printTopMenu();
	  break;
	case '?': // print this menu
	  printAutomatedChargingMenu();
	  break;
	case 0x0a: // line feed
	case 0x0d: // carriage return 
	  break; // do nothing
	default : // uh-oh
	  printf ("Command '%c' not recognized...\n", keyboard);
	} // case MENU_AUTO: switch (keyboard)
	break;

      default:
        printf("\n");
      } // switch (menu)
    } // if (keyboard = ...)
	
    // now see if alarm is here
    t1 = time(0);
    if (t1 >= talarm) // ring!! ring!!! ring!!!!
      {
	int i;
	talarm = t1 + ALARM_PERIOD;
	DEBUG (102, "ALARM!!! time=%d next alarm=%d\n", (int)t1, (int)talarm); 
	for (i=0; i<MAX_BATTS; ++i)
	  if (sm_batts[i].batt_alive) 
	    {
	      updateBattInfo (batt_port, i, &sm_batts[i]);
	    }
	switch (autocharger.state) {
	case CHARGER_IDLE :
	  // do nothing; chill out
	  DEBUG (5, "autocharger.state = CHARGER_IDLE\n");
	  break;
	case CHARGER_START :
	  // turn on charging flood gates and pump some current
	  DEBUG (2, "autocharger.state = CHARGER_START\n");
	  xfrSetVolts (xfr_port, CHARGE_VOLTAGE);
	  xfrSetCurrent (xfr_port, HOTEL_PEAK_CURRENT);
	  xfrEnableOutput (xfr_port);
	  autocharger.num_batts_to_charge = 0;
	  autocharger.num_batts_on_charge = 0;
	  autocharger.current_state = 0;
	  for (i=0; i<MAX_BATTS; ++i)
	    if (autocharger.batts[i]) 
	      {
		++autocharger.num_batts_to_charge;
		++autocharger.num_batts_on_charge;
		battSetState (batt_port, i, BATT_STATE_CHARGE);
		battSetWatchdog (batt_port, i, BATT_WD_ENABLE);
		xfrSetCurrent (xfr_port, nominal_current + 
			       autocharger.num_batts_on_charge *
			       autocharger.currents[0]);
		msleep (1000);
	      }
	  printf ("\n\n");
	  printAutoChargerInfo (&autocharger);
	  printChargerInfo (xfr_port, &xfr);
	  printAllBattInfo (sm_batts);
	  autocharger.state = CHARGER_STAGES;
	  break;
	case CHARGER_STAGES :
	  DEBUG (2, "autocharger.state = CHARGER_STAGES\n");
	  for (i=0; i<MAX_BATTS; ++i)
	    if (autocharger.batts[i]) // check only batts to charge 
	      {
		if (checkBattFaults (batt_port, xfr_port, &sm_batts[i], &autocharger))
		  {
		    if (autocharger.num_batts_on_charge)
		      --autocharger.num_batts_on_charge;
		    printf ("Reducing charging current by %0.1f Amps to %0.1f Amps\n", 
			    autocharger.currents[autocharger.current_state],
			    autocharger.currents[autocharger.current_state] *
			    autocharger.num_batts_on_charge);
		    if (autocharger.num_batts_on_charge)
			xfrSetCurrent (xfr_port, 
				   autocharger.currents[autocharger.current_state] * 
				   autocharger.num_batts_on_charge + nominal_current);
		    else
			xfrSetCurrent (xfr_port, HOTEL_PEAK_CURRENT);
 
		    printf ("Taking batt %02Xh=#%d off of charge\n", sm_batts[i].address,
                            sm_batts[i].address);
		    battSetState (batt_port, sm_batts[i].address, BATT_STATE_OFF);
		  }
	      }
	  if (!autocharger.num_batts_on_charge)
	    {
	      if (++autocharger.current_state >= MAX_CHARGE_STAGES)
		{
		  autocharger.state = CHARGER_STOP;
		  break;
		} else if (autocharger.currents[autocharger.current_state] < 0.20)
                {
                  autocharger.state = CHARGER_STOP;
                  break;
                } else {
		  autocharger.num_batts_on_charge = 0;
		  xfrSetCurrent (xfr_port, HOTEL_PEAK_CURRENT);
		  for (i=0; i<MAX_BATTS; ++i)
		    if (autocharger.batts[i]) 
		      {
			++autocharger.num_batts_on_charge;
			battSetState (batt_port, i, BATT_STATE_CHARGE);
			battSetWatchdog (batt_port, i, BATT_WD_ENABLE);
			xfrSetCurrent (xfr_port, nominal_current + 
				       autocharger.num_batts_on_charge *
				       autocharger.currents[autocharger.current_state]);
		      }
		}
	    }
	  printf ("\n\n");
	  printAutoChargerInfo (&autocharger);
	  printChargerInfo (xfr_port, &xfr);
	  printAllBattInfo (sm_batts);
	  break;
	case CHARGER_STOP :
	  DEBUG (2, "autocharger.state = CHARGER_STOP\n");
	  xfrSetVolts (xfr_port, nominal_voltage);
	  xfrSetCurrent (xfr_port, HOTEL_PEAK_CURRENT);
	  if (nominal_current < 0.1)
	    xfrDisableOutput (xfr_port);
	  autocharger.num_batts_on_charge = 0;
	  for (i=0; i<MAX_BATTS; ++i)
	    if (autocharger.batts[i]) 
	      {
		battSetState (batt_port, i, BATT_STATE_OFF);
	      }
	  printf ("\n\n");
	  printAutoChargerInfo (&autocharger);
	  printChargerInfo (xfr_port, &xfr);
	  printAllBattInfo (sm_batts);
	  autocharger.state = CHARGER_POST;
	  break;
	case CHARGER_POST :
	  DEBUG (2, "autocharger.state = CHARGER_POST\n");
	  autocharger.state = CHARGER_IDLE;
	  break;
	case CHARGER_BALANCE_START : {
	  int i;
	  DEBUG (2, "autocharger.state = CHARGER_BALANCE_START\n");
	  for (i=0; i<MAX_BATTS; ++i) {
	    if (autocharger.batts[i]) {
	      printBattInfo (&sm_batts[i]);
	      printf ("Putting battery %02Xh=#%d into Balancing state\n",
		      sm_batts[i].address, sm_batts[i].address);
	      if (!battSetState (sm_batts[i].port, sm_batts[i].address, BATT_STATE_BALANCE))
		{
		  battSetWatchdog (sm_batts[i].port, sm_batts[i].address,
				   BATT_WD_ENABLE);
		  sm_batts[i].summary.state = BATT_STATE_BALANCE;
		}
	    }
	  }
	  autocharger.state = CHARGER_BALANCE;
	  break;
	} // case CHARGER_BALANCE_START
	case CHARGER_BALANCE : {
	  int i;
	  DEBUG (2, "autocharger.state = CHARGER_BALANCE\n");
	  for (i=0; i<MAX_BATTS; ++i) {
	    if (sm_batts[i].batt_alive && sm_batts[i].summary.state == BATT_STATE_BALANCE) {
	      printBattInfo (&sm_batts[i]);
	      if (sm_batts[i].summary.min_volt <= autocharger.vmin) {
		printf ("Cutting off balancing of battery %02Xh\n", i);
		battSetState (sm_batts[i].port, sm_batts[i].address, BATT_STATE_OFF);
	      }
	    }
	  }
	  break;
	} // case CHARGER_BALANCE
	case CHARGER_DISCHARGE_START : {
	  int i;
	  DEBUG (2, "autocharger.state = CHARGER_DISCHARGE_START\n");
	  for (i=0; i<MAX_BATTS; ++i) {
	    if (sm_batts[i].batt_alive) {
	      printBattInfo (&sm_batts[i]);
	      printf ("Putting battery %02Xh=#%d into Discharge state\n",
		      sm_batts[i].address, sm_batts[i].address);
	      if (!battSetState (sm_batts[i].port, sm_batts[i].address, BATT_STATE_DISCHARGE))
		{
		  battSetWatchdog (sm_batts[i].port, sm_batts[i].address,
			   BATT_WD_ENABLE);
		  sm_batts[i].summary.state = BATT_STATE_DISCHARGE;
		}
	    }
	  }
	  autocharger.state = CHARGER_DISCHARGE;
	  break;
	} // case CHARGER_DISCHARGE_START
	case CHARGER_DISCHARGE : {
	  int i;
	  DEBUG (2, "autocharger.state = CHARGER_BALANCE\n");
	  for (i=0; i<MAX_BATTS; ++i) {
	    if (sm_batts[i].batt_alive && sm_batts[i].summary.state == BATT_STATE_DISCHARGE) {
	      printBattInfo (&sm_batts[i]);
	      if (sm_batts[i].summary.min_volt <= autocharger.vmin) {
		printf ("Cutting off discharge of battery %02Xh\n", i);
		battSetState (sm_batts[i].port, sm_batts[i].address, BATT_STATE_OFF);
	      }
	    }
	  }
	  break;
	} // case CHARGER_DISCHARGE
	} // switch (autocharger.state)
	DEBUG (101, "AutoCharger State=%s\n", ChargerState(autocharger.state));
      } // if (t1 > talarm)
  } // while

  closeLogFile (xfr_port);
		closeLogFile (batt_port);
  closePort (batt_port);
  closePort (xfr_port);

  termios_p.c_lflag |= ICANON;	// Enable Line Oriented Mode
	
  if (tcsetattr (0/*stdin*/, TCSANOW, &termios_p) < 0)
    {
      ERROR ("err: main() tcsetattr failed on stdin: %s\n", strerror(errno));
      return -1;
    }

  tcflush (0, TCIOFLUSH);
  printf ("\n\nHave a super day...\n");

  return 0;
}

