/****************************************************************************/
/* Copyright 1991 - 2016 MBARI												*/
/****************************************************************************/
/* Summary	: User Interface Routines for OASIS5 Mooring Controller			*/
/* Filename : userif.c														*/
/* Author	: Robert Herlien (rah)											*/
/* Project	: OASIS5 Mooring												*/
/* Revision: 1.0															*/
/* Created	: 07/19/2016 from OASIS3 userif.c								*/
/*																			*/
/* 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 - OASIS3 version created from OASIS userif.c				*/
/* 19jul2016 rah - OASIS5 version created from OASIS3 userif.c				*/
/* $Log$
 */
/****************************************************************************/

#include <mbariTypes.h>					/* MBARI type definitions			*/
#include <mbariConst.h>					/* MBARI constants					*/
#include <oasis.h>						/* OASIS controller definitions		*/
#include <custom.h>						/* Customizations for implementation*/
#include <debug.h>						/* Turns on debugging features		*/
#include <otask.h>						/* OASIS Multitasking definitions	*/
#include <serial.h>						/* OASIS Serial I/O definitions		*/
#include <malloc.h>						/* OASIS malloc routines			*/
#include <drvr.h>						/* OASIS driver functions			*/
#include <log.h>						/* OASIS data logging definitions	*/
#include <userif.h>						/* OASIS User Interface routines	*/
#include <usrcmds.h>					/* User Command function definitions*/
#include <parm.h>						/* Parameter table definitions		*/
#include <file.h>						/* usrDir, etc						*/
#include <utils.h>						/* OASIS Utility routines			*/
#include <ymodem.h>						/* Ymodem command send command		*/
#include <o5debug.h>					/* OASIS diagnostic routines		*/
#include <swdiag.h>						/* Software diagnostic func protos	*/
#include <variable.h>					/* Variable definitions				*/
#include <modbus.h>
#include <usrRemoteCmds.h>				/* User funcs for Remote I/O		*/
#include <cfgsave.h>					/* OASIS Config file save routines	*/
#include <parse.h>						/* Generic parser declarations		*/
#include <script.h>						/* Scripting Language definitions	*/
#include <usrCmdTbl.h>					/* User Command table				*/

#include <FreeRTOS.h>					/* FreeRTOS definitions				*/
#include <semphr.h>						/* FreeRTOS semaphore functions		*/

#include <ctype.h>						/* Standard ctype.h defs			*/
#include <stdio.h>						/* Standard I/O						*/
#include <string.h>						/* Standard string.h defs			*/

#define TNC_PWRUP_TIME	(2 * TICKS_PER_SEC)	/* Wait 2 secs after pwr up */
#define TNC_REPLY_FLUSH_TIME (3*TICKS_PER_SEC/10)
										/* Wait 300 ms to flush TNC replies */

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

Extern time_t			radio_time;		/* Last time connected on radio		*/
Extern Parm_t			tncchr;			/* TNC command character			*/
Extern const char * const signon;		/* Signon message					*/

Extern char				*tnc_name;		/* TNC node name					*/
Extern MBool			tnc_connected;	/* TRUE if we're connected to TNC	*/


/****************************************************/
/*	Drive Descriptors for Drivers in this module	*/
/****************************************************/

const DrvDesc usrDrvDesc =
	{ "UserIF", usr_drv, nullWakeFunc, LS_NULL, NULL, 
	  SECS_PER_DAY, 0, 38400, (MODE_N81 | ECHO | AUTOCR), CONSOLE_MODE,
	  LOG_BIN, 0, 120, 0, 0, 0};

const char *freewaveParmDesc[] = 
	{"Connect Time", "Disconnect Time", NULL, NULL, NULL, NULL, NULL, NULL};

const DrvDesc freewaveDrvDesc =
	{ "Freewave", freewave_drv, nullWakeFunc, LS_NULL, freewaveParmDesc,
	  600, 1, 19200, (MODE_N81 | NEWCR), 0x02, LOG_BIN, 0, 240, 20, 0, 0};

const DrvDesc teledesignDrvDesc =
	{ "Teledesign", freewave_drv, nullWakeFunc, LS_NULL, freewaveParmDesc, 
	  600, 1, 9600, (MODE_N81 | ECHO | AUTOCR), 0x02, LOG_BIN, 0, 240, 20, 2, 0};

const DrvDesc viperDrvDesc =
	{ "Viper", freewave_drv, nullWakeFunc, LS_NULL, freewaveParmDesc, 
	  600, 1, 9600, (MODE_N81 | ECHO | AUTOCR), 0x02, LOG_BIN, 0, 240, 20, 15, 0};


/********************************/
/*		Module Local Data		*/
/********************************/
/* Note that, since we removed "cmdAdd" because we can't incrementally	*/
/* compile and link a small piece of code to a fixed address (and it's	*/
/* not terribly	useful anyway), we no longer need a linked list of user	*/
/* commands.  This not only does away with malloc'ing the funcList, it	*/
/* also does away with the need to lock that list, which is problematical*/
/* if e.g. someone does a "help" and XOFF's, thus locking out another	*/
/* user from executing any commands.									*/

MLocal const char		prompt[] = PROMPT;

MLocal const char * const tnc_cmds[] =
{ "NO ON", "EC OFF", "AX ON", "AU OFF", "BBS ON", "P 0", "PACT After 3",
  "DW 33", "TXD 33", "FR 5", "MAXF 4", "CP ON", "CR OFF", "M 0", 
  "CONM C", "SE $1A", "TXU OFF", "F OFF", "STR 0", "ADR OFF"
};


/************************************************************************/
/* Function	   : get_parms												*/
/* Purpose	   : Parse parameters from user command tail				*/
/* Inputs	   : Command tail ptr, Ptr to array of parms, ptr to funcEntry*/
/* Outputs	   : Bit vector of parameters found							*/
/************************************************************************/
MLocal ParmMask_t get_parms(char *tailp, Parm_t *parmp, const FuncEntry *fep)
{
	Tokenizer		tokenizer;
	Reg char		*tok;
	Reg ParmMask_t	pmask;
	Reg Nat32		i;

	pmask = 0;
	memset(parmp, 0, NPARMS * sizeof(Parm_t)); /* Init parms to 0 */
	deblank(tailp);
	initTokenizer(&tokenizer, tailp);

	for ( i = 0; i < NPARMS; i++ )
	{
		switch( fep->fe_parmtype[i] )
		{
		  case STRING:					/* String type. Go til next		*/
			  tok = getNextToken(&tokenizer);
			  if (isaTok(tok))
			  {
				  parmp[i] = (Parm_t)getStringVal(tok);
				  pmask |= (1 << i);
			  }
			  break;

		  case ALL:						/* Get all of command tail		*/
			  parmp[i] = (Parm_t)(tokenizer.next);
			  tokenizer.next = NULL;
			  if (parmp[i] != NULL)
				  pmask |= (1 << i);
			  break;

		  case DEC:						/* Get decimal number			*/
			  tok = getNextToken(&tokenizer);
			  if (isaTok(tok) && (getLongVal(tok, &parmp[i])))
				  pmask |= (1 << i);
			  break;

		  case HEX:						/* Get hex number				*/
			  tok = getNextToken(&tokenizer);
			  if (isaTok(tok) && (sscanf(tok, " %x", &parmp[i]) >= 1))
				  pmask |= (1 << i);
			  break;
		}
	}

	return( pmask );

} /* get_parms() */


/************************************************************************/
/* Function	   : checkVariable											*/
/* Purpose	   : See if command line is a variable assignment			*/
/* Inputs	   : Command Line ptr										*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
MLocal Errno checkVariable(char *cmd)
{
	char		*keyword, *tail;

	if ((tail = strchr(cmd, '=')) == NULL)
		return(ERROR);

	keyword = strtok(cmd, "		=\n");
	tail++;
	deblank(tail);

	if (*keyword != '$')
		return(ERROR);

	return(parseVariable(keyword, tail));

} /* checkVariable() */


/************************************************************************/
/* Function	   : executeCmdLIne											*/
/* Purpose	   : Execute the given command line (from user)				*/
/* Inputs	   : Command Line ptr										*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
CmdRtn executeCmdLine(char *cmdLine)
{
	Reg const FuncEntry	*fep;
	Reg Nat32		i;
	Reg char		*tail;				/* Ptr to tail of cmd string	*/
	Reg ParmMask_t	pmask;				/* Parameter mask				*/
	Parm_t			parms[NPARMS];		/* Function parameters			*/

	if (checkVariable(cmdLine) == OK)
		return(OK);

	for (i = 0, fep = functbl; i < NumberOf(functbl); i++, fep++)
	{											/* Find functbl entry	*/
		if ((tail = cmp_ulc(cmdLine, fep->fe_cs)) != NULL)
		{
			pmask = get_parms(tail, parms, fep);

			return((*fep->fe_cmd)(pmask, parms[0], parms[1], parms[2],
								  parms[3], cmdLine));
		}
	}

	return(ERROR);

} /* executeCmdLine() */


/************************************************************************/
/* Function	   : one_usr_cmd											*/
/* Purpose	   : Do one cycle of user cmd & response					*/
/* Inputs	   : Buffer ptr, len, timeout in seconds					*/
/* Outputs	   : OK, ERROR, or ABORT									*/
/************************************************************************/
MLocal CmdRtn one_usr_cmd(char *buffer, Nat16 buflen, Nat16 tmout)
{
	Reg Int16		rtn;				/* Function return code			*/

	xputs(prompt);						/* Print prompt					*/
	if ((rtn = xgets_tmout(buffer, buflen, tmout)) == ERROR)
	{									/* If timed out, abort			*/
		xputs("Timeout\n");
		xflush_ser(0);
		return(ABORT);
	}
	
	if (rtn == 0)						/* If no command, return OK*/
		return(OK);

	if ((rtn = executeCmdLine(buffer)) == ERROR)
		xputs("Bad Command\n");

	return(rtn);

} /* one_usr_cmd() */


/************************************************************************/
/* Function	   : usrif													*/
/* Purpose	   : User I/F Main routine.	 Called by usr_drv or radio_drv */
/* Inputs	   : Timeout in seconds										*/
/* Outputs	   : OK, ERROR if got too many bad cmds, ABORT if no memory */
/************************************************************************/
MLocal CmdRtn usrif(Nat32 tmout)
{
	CmdRtn		rtn;
	Nat32		badcnt;
	char		*buffer;

	if ((buffer = pmalloc(USR_BUFSIZE)) == NULL)
		return(ABORT);					/* If no memory, return ABORT	*/
  
#ifdef FULL_SIGNON
	xprintf("%s\n%s\n", signon, tnc_name); /* TNC Name=Deployment		*/
#endif

	badcnt = 0;							/* Init bad cmd counter			*/

	while ((rtn = one_usr_cmd(buffer, USR_BUFSIZE, tmout)) != ABORT)
	{									/* Do user interface			*/
		if ((rtn == ERROR) && (++badcnt > 50))
			break;						/* Check for infinite loop		*/
	}
	free(buffer);						/* Free buffer					*/
	return(rtn);

} /* usrif() */


/************************************************************************/
/* Function	   : usr_drv												*/
/* Purpose	   : User I/F Main routine									*/
/* Inputs	   : Driver Pointer											*/
/* Outputs	   : None													*/
/************************************************************************/
void usr_drv(Driver *dp)
{
	drv_ser_port(dp);					/* Setup serial port			*/
	usrif(dp->drv_parms[TIMEOUT]);
	drv_ser_release(dp);				/* Release serial port			*/
	drv_pwroff(dp);						/* Turn off power				*/
  
} /* usr_drv() */


/************************************************************************/
/* Function	   : freewave_drv											*/
/* Purpose	   : Freewave Radio Driver									*/
/* Inputs	   : Driver Pointer											*/
/* Outputs	   : None													*/
/************************************************************************/
void freewave_drv(Driver *dp)
{
	drv_ser_port(dp);					/* Setup serial port			*/
 
	/* Wait for radio to come alive		*/
	task_delay(dp->drv_parms[WARMUP_TIME] * TICKS_PER_SEC);
 
	xflush_ser(TNC_REPLY_FLUSH_TIME);	/* Throw away signon message	*/

	if (xgetc_tmout(dp->drv_parms[CONNECT_TIME]) != ERROR)
	{									/* Wait for connection			*/
		radio_time = clkTime();			/* Remember when connected		*/

		usrif(dp->drv_parms[TIMEOUT]);	/* Do the user interface		*/

		while (dp->drv_wakeup <= clkTime())	/* Reset wakeup				*/
			dp->drv_wakeup += dp->drv_parms[INTERVAL];
	}

	drv_ser_release(dp);				/* Release serial port			*/
	drv_pwroff(dp);						/* Turn off radio power			*/

} /* freewave_drv() */


/************************************************************************/
/* Function	   : user_init												*/
/* Purpose	   : Reset userif module due to cold (full) init			*/
/* Inputs	   : None													*/
/* Outputs	   : None													*/
/************************************************************************/
void user_init(void)
{
} /* user_init() */


/************************************************************************/
/* Function	   : help													*/
/* Purpose	   : User Help Function										*/
/* Inputs	   : None													*/
/* Outputs	   : OK														*/
/* Comments	   : Placed here, rather than in usrcmds.c, because it needs*/
/*				 access to the user function table						*/
/************************************************************************/
CmdRtn help(ParmMask_t pmask, char *tail)
{
	Reg Nat32		i;
	Reg const FuncEntry	*fep;

	for (i = 0, fep = functbl; i < NumberOf(functbl); i++, fep++)
	{
		if (((pmask & 1) == 0) || (cmp_ulc(tail, fep->fe_cs) != NULL))
			if (fep->fe_info != NULL)
				xprintf("%-12s %s\n", fep->fe_cs, fep->fe_info);
	}

	return( OK );

} /* help() */


#ifdef TNC

/************************************************************************/
/* Function	   : tnc_cmd_mode											*/
/* Purpose	   : Put TNC in Command mode								*/
/* Inputs	   : None													*/
/* Outputs	   : None													*/
/************************************************************************/
MLocal void tnc_cmd_mode(void)
{
	xputc(tncchr);								/* Go to cmd mode		*/
	xflush_ser(TNC_REPLY_FLUSH_TIME);			/* Clear incoming buffer*/

} /* tnc_cmd_mode() */


/************************************************************************/
/* Function	   : tnc_converse											*/
/* Purpose	   : Put TNC in Converse mode								*/
/* Inputs	   : None													*/
/* Outputs	   : None													*/
/************************************************************************/
void tnc_converse(void)
{
	xputs("K\r");						/* Go to converse mode			*/
	xflush_ser(TNC_REPLY_FLUSH_TIME);	/* Throw away reply				*/

} /* tnc_converse() */


/************************************************************************/
/* Function	   : sendTncCmd												*/
/* Purpose	   : Send a command to the TNC								*/
/* Inputs	   : Command string to send									*/
/* Outputs	   : None													*/
/************************************************************************/
MLocal void sendTncCmd(char *cmd)
{
	tnc_cmd_mode();
	xprintf("%s\r", cmd);
	xflush_ser(TNC_REPLY_FLUSH_TIME);

} /* sendTncCmd() */


/************************************************************************/
/* Function	   : tnc_status												*/
/* Purpose	   : Get STATus message from TNC							*/
/* Inputs	   : None													*/
/* Outputs	   : Status of TNC, or ERROR								*/
/* Comment	   : Leaves TNC in command mode								*/
/************************************************************************/
MLocal CmdRtn tnc_status(Void)
{
	char		*p;
	CmdRtn		res;
	Int32		i;
	char		buff[32];

	res = ERROR;								/* Init result			*/
	tnc_cmd_mode();								/* Go to command mode	*/
	xputs("STAT\r");							/* Ask for status		*/

	while (xgets_tmout(buff, sizeof(buff), 1) != ERROR)
		if ((p = strchr(buff, '$')) != NULL)
			if ((i = sscanf(p+1, "%x", &res)) >= 1)
				break;

	xflush_ser(TNC_REPLY_FLUSH_TIME);			/* Throw away extra "cmd:"*/

	return(res);

} /* tnc_status() */


/************************************************************************/
/* Function	   : wait_connect											*/
/* Purpose	   : Wait for (dis)connect status							*/
/* Inputs	   : Boolean (TRUE to look for connect, FALSE to look for	*/
/*				  disconnect), timeout in seconds						*/
/* Outputs	   : MBool, TRUE if found appropriate status				*/
/************************************************************************/
MLocal MBool wait_connect(MBool connect, Nat32 tmout)
{
	Nat32 startTick;

	for ( startTick = xTaskGetTickCount(); 
		  (xTaskGetTickCount() - startTick) < ((Nat32)tmout * TICKS_PER_SEC); )
	{
		task_delay(TICKS_PER_SEC/2);
		if (connect)
		{
			if ((tnc_status() & 0xfffd) == 4)
				return( TRUE );
		}
		else
		{
			if (tnc_status() == 0)
				return(TRUE);
		}
	}

	return( FALSE );

} /* wait_connect() */


/************************************************************************/
/* Function	   : tnc_init												*/
/* Purpose	   : Initialize TNC parameters								*/
/* Inputs	   : None													*/
/* Outputs	   : None													*/
/************************************************************************/
MLocal void tnc_init(void)
{
	Nat32	i;

	tnc_cmd_mode();						/* Go to command mode			*/
	xputs( "RESET\r" );					/* Go to command mode, Reset TNC*/
	xflush_ser(TICKS_PER_SEC/2);		/* Clear incoming buffer		*/
	
	for ( i = 0; i < NumberOf(tnc_cmds); i++ )
	{
		xprintf("%s\r", tnc_cmds[i]);	/* Send command					*/
		xflush_ser(TNC_REPLY_FLUSH_TIME); /* Throw away reply				*/
	}
	xprintf("MY %s\r", tnc_name);		/* Set up TNC name string		*/
	xflush_ser( TNC_REPLY_FLUSH_TIME );	/* Throw away reply				*/

} /* tnc_init() */


/************************************************************************/
/* Function	   : tnc_drv												*/
/* Purpose	   : TNC Packet Radio Driver								*/
/* Inputs	   : Driver Pointer											*/
/* Outputs	   : None													*/
/************************************************************************/
void tnc_drv(Driver *dp)
{
	drv_ser_port( dp );					/* Setup serial port			*/
	task_delay(TNC_PWRUP_TIME);			/* Wait for TNC to come alive	*/
	xflush_ser(TNC_REPLY_FLUSH_TIME);	/* Throw away signon message	*/

	if (dp->drv_runflags & DO_INIT)
	{									/* Initialize TNC if needed		*/
		tnc_init();						/* Init TNC						*/
		dp->drv_runflags &= ~DO_INIT;	/* Turn off init flag			*/
	}

	if (wait_connect(TRUE, dp->drv_parms[CONNECT_TIME]))
	{									/* Wait for connection			*/
		tnc_connected = TRUE;			/* Show we're connected			*/
		radio_time = clkTime();			/* Remember when connected		*/
		tnc_converse();					/* Go to converse mode			*/

		if (usrif(dp->drv_parms[TIMEOUT]) == ERROR)
										/* Do the user interface		*/
			dp->drv_runflags |= DO_INIT; /* If lots of bad cmds, re-init */

		while (dp->drv_wakeup <= clkTime())	/* Reset wakeup				*/
			dp->drv_wakeup += dp->drv_parms[INTERVAL];

		if (tnc_connected)				/* If still connected, disconnect*/
			sendTncCmd("DISC");			/* Request disconnect			*/

		wait_connect(FALSE, dp->drv_parms[DISCONNECT_TIME]);
		task_delay(3 * TICKS_PER_SEC);
	}

	drv_ser_release(dp);				/* Release serial port			*/
	drv_pwroff(dp);						/* Turn off radio power			*/

} /* tnc_drv() */

#endif /* #ifdef TNC */
