/****************************************************************************/
/* Copyright 1991 - 2003 MBARI												*/
/****************************************************************************/
/* Summary	: User Interface Commands for OASIS Mooring Controller			*/
/* Filename : usrcmds.c														*/
/* Author	: Robert Herlien (rah)											*/
/* Project	: OASIS3 Mooring												*/
/* $Revision: 1.2 $															*/
/* Created	: 02/03/2003 rah												*/
/*																			*/
/* MBARI provides this documentation and code "as is", with no warranty,	*/
/* express or implied, of its quality or consistency. It is provided without*/
/* support and without obligation on the part of the Monterey Bay Aquarium	*/
/* Research Institute to assist in its use, correction, modification, or	*/
/* enhancement. This information should not be published or distributed to	*/
/* third parties without specific written permission from MBARI.			*/
/*																			*/
/****************************************************************************/
/* Modification History:													*/
/* 03feb2003 rah - created from OASIS usrcmds.c								*/
/* $Log: usrcmds.c,v $
/* Revision 1.2	 2012/03/16 16:29:00  bobh
/* Fixed timeout problem on connect
/*
/* Revision 1.1	 2011/10/25 22:39:22  bobh
/* Initial oasis4 revision
/*
/* Revision 1.9	 2010/11/16 23:38:24  bobh
/* Fix 'reset' code that could cause file corruption
/*
/* Revision 1.8	 2010/10/25 16:42:50  bobh
/* Changes to support scripting
/*
/* Revision 1.7	 2010/05/06 22:25:46  bobh
/* Added user functions to manipulate remote I/O, changed function parser to parse and pass 4 parameters
/*
/* Revision 1.6	 2009/11/19 19:05:58  bobh
/* Changes to support con to remote ports and fixes to file I/O due to testing
/*
/* Revision 1.5	 2009/11/04 17:32:02  mrisi
/* Fixed Analog command to work on ports other than console
/*
/* Revision 1.4	 2009/10/28 21:52:28  mrisi
/* Fixed calibration value for can pressure
/*
/* Revision 1.3	 2009/06/18 22:38:48  bobh
/* Modifications for 2nd rev of controller board
/*
/* Revision 1.2	 2009/04/17 18:05:04  bobh
/* Changes leading up to CDR
/*
/* Revision 1.1	 2009/03/25 20:34:41  bobh
/* Initial checkin
/*
/* Revision 1.6	 2006/09/11 23:15:25  mrisi
/* added CVS tag string to version
/*
/* Revision 1.5	 2004/08/18 21:08:35  bobh
/* Diagnostic routines added, minor bug fixes, blinky added as parm
/*
/* Revision 1.4	 2003/09/09 00:01:15  bobh
/* Misc tweaks after demo
/*
/* Revision 1.3	 2003/09/03 17:48:16  bobh
/* Further mods for config file changes
/*
 */
/****************************************************************************/

#include <mbariTypes.h>					/* MBARI type definitions			*/
#include <mbariConst.h>					/* MBARI constants					*/
#include <oasis.h>						/* OASIS controller definitions		*/
#include <custom.h>						/* PWR_PERM definition				*/
#include <io.h>							/* OASIS I/O definitions			*/
#include <serial.h>						/* OASIS serial I/O definitions		*/
#include <task.h>						/* OASIS task dispatcher			*/
#include <malloc.h>						/* OASIS malloc routines			*/
#include <file.h>						/* OASIS File I/O Routines			*/
#include <log.h>						/* OASIS data logging definitions	*/
#include <syslog.h>						/* Respirometer system logger		*/
#include <usrcmds.h>					/* User Command function definitions*/
#include <drvr.h>						/* OASIS driver functions			*/
#include <userif.h>						/* OASIS User Interface routines	*/
#include <utils.h>						/* OASIS Utility routines			*/
#include <clock.h>						/* OASIS Clock module				*/
#include <sensors.h>					/* OASIS Utility routines			*/
#include <debug.h>
#include <remote.h>						/* Remote I/O library			*/
#include <swdiag.h>						/* Software diagnostic func protos	*/
#include <syslog.h>						/* Respirometer system logger		*/

#include <cfxpico.h>					/* Persistor PicoDOS definitions	*/
#include <stdio.h>						/* Standard I/O						*/
#include <string.h>						/* String library functions			*/
#include <ctype.h>						/* Standard ctype.h defs			*/

#define ANALOG_DELAY 30


/********************************/
/*		External Data			*/
/********************************/

	/* serPort and serMode are special variables:  they get saved/restored	*/
	/* during task swap, so they're always valid							*/
Extern Nat16			serPort;		/* Dflt ser port, saved by dispatch */
Extern Word				serMode;		/* Serial mode (see ser_init())		*/

Extern Semaphore		ser_sem[];		/* Mutex sems for serial ports		*/
Extern Word				error;			/* Error vector						*/
Extern Nat32			pwr_perm;		/* Devices to leave powered on		*/
Extern struct tm		now;			/* Current date & time				*/
Extern Parm_t			con_dsc;		/* Disconnect character for CONNECT */
Extern Parm_t			con_tmout;		/* Timeout during CONNECT			*/
Extern Parm_t			send_ascii;		/* TRUE to send samples in ASCII	*/
Extern Parm_t			tncchr;			/* TNC command character			*/
Extern Parm_t			brkchr;			/* Character that sends break		*/
Extern const char * const signon;		/* Signon message					*/


/********************************/
/*		Global Data				*/
/********************************/

Global MBool			tnc_connected;	/* TRUE if we're connected to TNC	*/


/********************************/
/*		Module Local Data		*/
/********************************/

MLocal Byte				*dispnext;		/* Where to display memory next		*/


/************************************************************************/
/* Function	   : analog													*/
/* Purpose	   : Get A/D port value										*/
/* Inputs	   : Parm Mask, Port number									*/
/* Outputs	   : OK														*/
/************************************************************************/
		Int16
analog(Nat16 pmask, Parm_t port)
{
  Nat16			cnt;
  Nat16			val[4];
  double		adval;

  if (pmask == 0)
  {
	get_analog(0, 4, 100, val);
	adval = val[0] * AD_SCALE;
	xprintf("Chan 0 %d cnts %5.3f volts, Batt volts = %6.3f\n",
		   val[0], adval, 6.8708 * adval);

	adval = val[1] * AD_SCALE;
	xprintf("Chan 1 %d cnts %5.3f volts, Can Temp = %5.2f deg C\n",
		   val[1], adval, 100 * (adval - 0.6));

	adval = val[2] * AD_SCALE;
	xprintf("Chan 2 %d cnts %5.3f volts, Pressure = %5.2f PSIA\n",
		   val[2], adval, 14.212 * adval + 1.48);

	adval = val[3] * AD_SCALE;
	xprintf("Chan 3 %d cnts %5.3f volts\n", val[3], adval);
  }

  else if (port >= NAD_PORTS)
  {
	xprintf("Bad A/D port number\n");
	return(ERROR);
  }
  else
  {
	get_analog((Nat16)port, 1, 100, &cnt);
	xprintf("A/D %d = %d counts, (%5.3f volts at 2.5V ref)\n",
			(Nat16)port, cnt, (Flt32)cnt * AD_SCALE);
  }

  return(OK);

} /* analog() */


/************************************************************************/
/* Function	   : quit													*/
/* Purpose	   : User command to exit User I/F							*/
/* Inputs	   : None													*/
/* Outputs	   : ABORT													*/
/************************************************************************/
		Int16
quit(Void)
{
  return( ABORT );

} /* quit() */


#ifdef TNC
/************************************************************************/
/* Function	   : radiomsg												*/
/* Purpose	   : Process TNC message, which begins with "***"			*/
/* Inputs	   : Parm Mask, tail of TNC message							*/
/* Outputs	   : OK or ABORT											*/
/************************************************************************/
		Int16
radiomsg( Nat16 pmask, char *tail )
{
#pragma unused(pmask)
  if ( strstr(tail, "DISCONNECT") != NULL )
  {
	tnc_connected = FALSE;
	return( ABORT );
  }

	return( OK );

} /* radiomsg() */

#endif

/************************************************************************/
/* Function	   : reset													*/
/* Purpose	   : Reset System											*/
/* Inputs	   : Parm Mask, String										*/
/* Outputs	   : OK														*/
/* Comments	   : If not "LOG", never returns, resets all RAM and system */
/************************************************************************/
		Int16
reset(Nat16 pmask, char *s, char *unused1, char *unused2, char *unused3, char *cmdline)
{
#pragma unused(unused1, unused2, unused3)

  sysLog(cmdline);

  if ((pmask & 1) == 0)
  {
	takeFileSem();								/* Prevent further file I/O*/
	io_power(pwr_perm);							/* Set quiescent pwr bits*/
	logDoFlush();								/* Flush log file		*/
	BIOSReset();								/* Reset the system		*/
  }
  else if ( cmp_ulc(s, "LOG") != NULL )
	logClear();									/* Delete all log files */
  else if ( cmp_ulc(s, "ALL") != NULL )
  {
	logClear();									/* Delete all log files */
	takeFileSem();								/* Prevent further file I/O*/
	io_power(pwr_perm);							/* Set quiescent pwr bits*/
	BIOSReset();								/* Reset the system		*/
  }
  else if ( cmp_ulc(s, "PICO") != NULL )
  {
	takeFileSem();								/* Prevent further file I/O*/
	io_power(pwr_perm);							/* Set quiescent pwr bits*/
	logDoFlush();								/* Flush log file		*/
	BIOSResetToPicoDOS();						/* Go to PicoDOS		*/
  }

  return( OK );

} /* reset() */


/************************************************************************/
/* Function	   : usr_power												*/
/* Purpose	   : Temporarily turn on/off power bits						*/
/* Inputs	   : Parm Mask, Power bit vector							*/
/* Outputs	   : OK														*/
/************************************************************************/
		Int16
usr_power( Nat16 pmask, Parm_t pwrvec )
{
  if (pmask & 1)
	io_power(pwrvec);

  xprintf("Power vector = %lx hex\n", ioGetPwrVec());

	return( OK );

} /* usr_power() */


/************************************************************************/
/* Function	   : usrSemWait												*/
/* Purpose	   : Wait until semaphore becomes available, allow user abort*/
/* Inputs	   : Semaphore ID											*/
/* Outputs	   : OK or ABORT											*/
/************************************************************************/
		Errno
usrSemWait(SemID sem)
{
	if (sem->sem_cnt < 1)
	{
		xputs("Serial line busy.  Waiting...");

		while (sem->sem_cnt < 1)
		{
			if (xgetc_tmout(1) != ERROR)
			{
				xputs("Aborted\n");
				return(ABORT);
			}
		}
	}
	return(OK);
}

/************************************************************************/
/* Function	   : localSerConnect										*/
/* Purpose	   : Open a connection to local serial port					*/
/* Inputs	   : Local serial port number								*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
		MLocal Int16
localSerConnect(Nat16 serport)
{
	Int16		ch;
	Nat32		startTick, n;
#define BUFSIZE 16
	char		buffer[BUFSIZE];

	startTick = ioGetTick();

	while ((ioGetTick() - startTick) < (con_tmout*TICKS_PER_SECOND))
	{
		// We can do this loop one char at a time, since we know the instrument port
		// is local (no penalty for char at a time), and although user port
		// MAY be remote, remote reads are already buffered
		while ((ser_sndsts(serport) < TXBUFSIZE-1) &&
			   ((ch = xgetc_tmout(0)) != ERROR))
		{										/* Drain user serial buffer */
			if ( ch == brkchr )					/* If BREAK char			*/
				ser_break(serport, TICKS_PER_SECOND); /* send a BREAK to instr*/
			else if (ch == con_dsc)				/* If disconnect char		*/
				return(OK);						/*	return OK				*/
			else								/* If not break or disconn	*/
				ser_putc(serport, ch);			/*	char, send to instr		*/
			startTick = ioGetTick();			/* Reset timeout			*/
		}

		// We optimize this loop for multi-chars, since user port MAY be remote
		// and remote writes are not buffered
		if ((n = ser_read(serport, buffer, BUFSIZE, 0)) > 0)
		{
			startTick = ioGetTick();			/* Reset timeout		*/
			xputn(buffer, n);
		}

		task_delay( 2 );						/* Run loop at 50 Hz   */

	} /* for */

	xputs("TIMEOUT\n");
	return(ABORT);

} /* localSerConnect() */


/************************************************************************/
/* Function	   : connect												*/
/* Purpose	   : Connect user to an instrument's serial port			*/
/* Inputs	   : Parm Mask, Instrument name								*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
		Int16
connect(Nat16 pmask, char *name, Parm_t unused1, Parm_t unused2,
		Parm_t unused3, Byte *buffer)
{
#pragma unused(unused1, unused2, unused3)
  Driver		*idp;					/* Instrument driver pointer	*/
  Nat16			iport, oport;			/* Instrument ser port, ours	*/
  Word			omode;					/* Our serial mode				*/
  Errno			rtn;					/* Return code					*/
  RemSerHandle	rsh = NULL;				/* Handle for remote serial port*/

  if ( !(pmask & 1) )							/* If no instr. name,	*/
	return( ERROR );							/*	abort				*/

  if ( (idp = drvr_find(name)) != DRV_NULL )
	iport = idp->drv_parms[SER_PORT];			/* Get drvr ptr to instr*/
  else
	return( ERROR );

  if (iport == NO_SERIAL)						/* If no serial port,	*/
	return( ERROR );							/*	 return ERROR		*/

  if (isPhysSer(iport))
	  rtn = usrSemWait(&ser_sem[iport]);
  else if ((rsh = getVirtSerPort(iport)) != NULL)
	  rtn = usrSemWait(&rsh->sem);
  else
	  return(ERROR);

  // If user aborted waiting for sem, just return OK
  if (rtn != OK)
	  return(OK);

  /* Removed alloc of bigger Tx buffer.	 Instead, you can set the driver to */
  /* have a large buffer via the BIG_TX_BUF serial mode bit.  We'll want to */
  /* set this bit on the radio driver, which was the concern in the first	*/
  /* place (ADCP can shove more characters at the radio than it can send)	*/
  oport = serPort;								/* Save our serial port		*/
  omode = serMode;								/* Save our serial mode		*/
  drv_ser_port(idp);							/* Acquire instrument's port*/

  if (!(*idp->drv_serwake)(idp,TRUE,buffer))	/* Wake up instrument		*/
	  xputs("Wakeup failed\n");					/* If failed, tell user		*/

  serPort = oport;								/* Restore output port		*/
  serMode = omode;								/* Restore serial mode		*/
  xprintf( "Connected\n" );						/* Print connected message	*/

  if (isPhysSer(iport))
	  localSerConnect(iport);
  else
	  remSerConnect(rsh);

  serPort = iport;								/* Set instr port for wake	*/
  (*idp->drv_serwake)(idp,FALSE,buffer);		/* Put instr. back to sleep */
  drv_ser_release(idp);							/* Release instrument's port*/
  drv_pwroff(idp);								/* Turn instr power off		*/
  serPort = oport;								/* Restore our serial port	*/
  newline();									/* Start new line			*/

  return( OK );

} /* connect() */


/************************************************************************/
/* Function	   : time													*/
/* Purpose	   : Get/Set Time and Date									*/
/* Inputs	   : Parm Mask, String										*/
/* Outputs	   : OK														*/
/************************************************************************/
		Int16
usr_time(Nat16 pmask, char *s)
{
  Nat32			oldSecs, newSecs;
  Nat16			oldTicks, newTicks;
  struct tm		*newtime;
  Int16			rtn;

  if ( pmask & 1 )
  {
	RTCGetTime(&oldSecs, &oldTicks);
	newtime = gmtime(&oldSecs);

	if ((rtn = sscanf(s, "%d %d %d %d %d %d", &newtime->tm_hour,
					  &newtime->tm_min, &newtime->tm_sec, &newtime->tm_year,
					  &newtime->tm_mon, &newtime->tm_mday)) >= 3)
	{
	  if (rtn >= 5)
		newtime->tm_mon--;
	  if (rtn >= 4)
	  {
		if	(newtime->tm_year < YEAR0)			/* If 2 digit year & < 70 */
		  newtime->tm_year += 100;				/*	use 21st century	  */
		else if (newtime->tm_year >= 1900)		/* If 4 digit year, adjust*/
		  newtime->tm_year -= 1900;				/*	for mktime (offset 1900)*/
	  }

	  clk_write(newtime);
	  RTCGetTime(&newSecs, &newTicks);

	  xprintf("Sending time diff of %ld/%d seconds/ticks to drivers and tasks\n",
			  (Int32)(newSecs-oldSecs), (Int16)(newTicks-oldTicks));
	  drvr_timechange((Int32)(newSecs - oldSecs));
	  task_timechange((Int32)(newSecs - oldSecs), (Int32)newTicks - (Int32)oldTicks);
	}
  }

  clk_printtime(clk_read());
  newline();

  return( OK );

} /* usr_time() */


/************************************************************************/
/* Function	   : tnc_cmd												*/
/* Purpose	   : Send command string to Packet Radio TNC				*/
/* Inputs	   : Parm Mask, String										*/
/* Outputs	   : OK														*/
/************************************************************************/
		Int16
tnc_cmd(Nat16 pmask, char *cmd, Parm_t unused1, Parm_t unused2, 
		Parm_t unused3, Byte *buffer)
{
#pragma unused(unused1, unused2, unused3)
  Int16 i;
	
  if (pmask & 1)
  {
	xprintf("%c%s\r", tncchr, cmd );	/* Put TNC in cmd mode, send the cmd*/
	i = xgetn_tmout((char *)buffer, USR_BUFSIZE-1, 3);
	xflush_ser(TICKS_PER_SECOND/2);
	buffer[i] = '\0';					/* End of string delimiter		   */
	tnc_converse();						/* Back to converse mode		   */
	xprintf("%s\n", buffer );			/* Send user the reply			   */
	return( OK );
  }
  else
	return( ERROR );

} /* tnc_cmd() */


/************************************************************************/
/* Function	   : isValidMemory											*/
/* Purpose	   : Check if address is valid CF2 Memory					*/
/* Inputs	   : Memory ptr												*/
/* Outputs	   : TRUE if OK												*/
/************************************************************************/
		MLocal MBool
isValidMemory(Byte *p)
{
  Nat32			addr;

  addr = (Nat32)p;
  if (addr <= RAM_END_ADDR)				/* RAM							*/
	return(TRUE);
  if (addr < 0xe00000)					/* Addr's below FLASH are bad	*/
	return(FALSE);
  if (addr < 0xefffff)					/* FLASH						*/
	return(TRUE);
   return(addr >= 0xfffff000);			/* CPU32 hardware registers		*/

} /* isValidMemory() */


/************************************************************************/
/* Function	   : display												*/
/* Purpose	   : Display Memory											*/
/* Inputs	   : Parm Mask, Start address, Length						*/
/* Outputs	   : OK														*/
/************************************************************************/
		Int16
display( Nat16 pmask, Parm_t start, Parm_t len )
{
  Int16			lines, i, displen;

  if ( pmask & 1 )
	dispnext = (Byte *)start;

  displen = (pmask & 2) ? ((len + 15) & ~0x0f) : 0x80;
  lines = displen >> 4;

  if (!isValidMemory(dispnext) || !isValidMemory(dispnext + displen))
	return(ERROR);

  for ( ; lines > 0; lines--)
  {
	xprintf("%08lx	", (Nat32)dispnext);
  
	for ( i = 0; i < 16; i++ )
	  xprintf("%02x ", dispnext[i]);

	xputs("	 ");

	for ( i = 0; i < 16; i++, dispnext++ )
	  xputc(isprint(*dispnext) ? *dispnext : '.');

	newline();
	if ( xgetc_tmout(0) != ERROR )
	  break;
	dispatch();
  }

  return(OK);

} /* display() */


/************************************************************************/
/* Function	   : set													*/
/* Purpose	   : Set Memory												*/
/* Inputs	   : Parm Mask, Memory address								*/
/* Outputs	   : OK or ERROR											*/
/* Comments	   : Uses 20 byte buffer for user to enter an 8 bit number	*/
/*				 If user wants to enter a lot of white space, he's SOL	*/
/************************************************************************/
		Int16
set( Nat16 pmask, Byte *addr)
{
  Byte			*p;
  char			reply[20];
  Int16			c, rtn;

  if ((pmask & 1) == 0)
	return(ERROR);

  p = addr;

  for (rtn = 1; rtn > 0; )
  {
	if (!isValidMemory(p))
		return(ERROR);

	xprintf("%08lx	%02x ", (Nat32)p, *p);

	if ((rtn = xgets_tmout(reply, 20, 60)) > 0)
	  if ((rtn = sscanf(reply, " %x", &c)) > 0)
		*p++ = (Byte)c;
  }

  return( OK );

} /* set() */


/************************************************************************/
/* Function	   : version												*/
/* Purpose	   : Echo OASIS version number								*/
/* Inputs	   : None													*/
/* Outputs	   : OK														*/
/************************************************************************/
Int16 version(Nat16 pmask, char *arg)
{
#pragma unused(pmask)
  
  xprintf("\n%s %s %s \n", signon, __DATE__, __TIME__);
  
  /* spit out CVS tag */
  if ( cmp_ulc("CVS", arg) )
	   xprintf("CVS tag \"%s\"\n", "$Name:	$");
  
  
  return OK;
  
} /* version() */


/************************************************************************/
/* Function	   : usrSysLog												*/
/* Purpose	   : Write to system log									*/
/* Inputs	   : String and Parms										*/
/* Outputs	   : OK														*/
/************************************************************************/
		Int16
usrSysLog(Nat16 pmask, char *fmt, Parm_t parm1, Parm_t parm2, Parm_t parm3)
{
#pragma unused(pmask)

	sysLogPrintf(fmt, parm1, parm2, parm3);
	return(OK);
  
} /* usrSysLog() */
