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

#include <mbariTypes.h>					/* MBARI type definitions			*/
#include <mbariConst.h>					/* MBARI constants					*/
#include <oasis.h>						/* OASIS controller definitions		*/
#include <custom.h>						/* PWR_PERM definition				*/
#include <serial.h>						/* OASIS serial I/O definitions		*/
#include <otask.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 <timer.h>						/* OASIS Clock module				*/
#include <adc.h>						/* OASIS5 analog definitions		*/
#include <debug.h>
#include <remote.h>						/* Remote I/O library				*/
#include <swdiag.h>						/* Software diagnostic func protos	*/
#include <syslog.h>						/* Respirometer system logger		*/
#include <dig_io.h>
#include <picConfig.h>

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

#include <istdio.h>						/* Standard I/O						*/
#include <string.h>						/* String library functions			*/
#include <ctype.h>						/* Standard ctype.h defs			*/
#include <time.h>

#define ANALOG_DELAY 30

/* Definitions of RAM space on PIC32MX470F512L  */
#define RAM_START	0x0
#define RAM_LEN		0x20000
#define FLASH_START 0x1D000000
#define FLASH_LEN	0x80000
#define BOOT_START	0x1FC00000
#define BOOT_LEN	0x3FFF
#define SFR_START	0x1F800000
#define	SFR_LEN		0xFFFFF


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

Extern Semaphore		ser_sem[];		/* Mutex sems for serial ports		*/
Extern Nat32			pwr_perm;		/* Devices to leave powered on		*/
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			brkchr;			/* Character that sends break		*/
Extern const char * const signon;		/* Signon message					*/


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

#ifdef TNC
Extern Parm_t			tncchr;			/* TNC command character			*/
Global MBool			tnc_connected;	/* TRUE if we're connected to TNC	*/
#endif

/********************************/
/*		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														*/
/************************************************************************/
CmdRtn	analog(ParmMask_t pmask, Parm_t chan, Parm_t nchans)
{
    Nat32	stChan, numChans, j;
    Nat16	adresult[ADC_CHANS];

	stChan = (pmask & 1) ? chan : 0;
	numChans = (pmask & 2) ? nchans : 1;

	if (pmask == 0)
		numChans = ADC_CHANS;
	
	if (adcConvertChans(stChan, numChans, 64, adresult) != OK)
	{
		xputs("A/D error.  Chan/numChans OK?\n");
		return(ERROR);
	}

    for (j = 0; j < numChans; j++)
        printAnalogChan(stChan+j, adresult[j]);
	
	return(OK);

} /* analog() */


/************************************************************************/
/* Function	   : quit													*/
/* Purpose	   : User command to exit User I/F							*/
/* Inputs	   : None													*/
/* Outputs	   : ABORT													*/
/************************************************************************/
CmdRtn 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											*/
/************************************************************************/
CmdRtn radiomsg(ParmMask_t pmask, char *tail )
{
	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 */
/************************************************************************/
CmdRtn	reset(ParmMask_t pmask, char *s, char *unused1, char *unused2,
			  char *unused3, char *cmdline)
{
	sysLog(cmdline);

	if ((pmask & 1) == 0)
	{
		takeFileSem();							/* Prevent further file I/O*/
		digPwrVecSet(pwr_perm);					/* Set quiescent pwr bits*/
		logDoFlush();							/* Flush log file		*/
		picSoftReset();							/* Reset the PIC		*/
	}
	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*/
		digPwrVecSet(pwr_perm);					/* Set quiescent pwr bits*/
		picSoftReset();							/* Reset the PIC		*/
	}
	else if ( cmp_ulc(s, "PICO") != NULL )
	{
		xprintf("There is no \"Reset PICO\" on the PIC32\r\n");
	}

	return( OK );

} /* reset() */


CmdRtn	forceException(void)
{
	Nat32 i = 0;

	return(256/i);
}


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

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

	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 (xSemaphoreGetMutexHolder(sem->semHandle) == NULL)
		return(OK);

	xputs("Serial line busy.  Waiting...");

	while (xSemaphoreGetMutexHolder(sem->semHandle) != NULL)
	{
		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 CmdRtn localSerConnect(Port_t serport)
{
	Int16		ch;
	Nat32		startTick, n;
#define BUFSIZE 16
	char		buffer[BUFSIZE];

	startTick = xTaskGetTickCount();

	while ((xTaskGetTickCount() - startTick) < (con_tmout*TICKS_PER_SEC))
	{
		// 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) < TX_BUFF_SIZE-1) &&
			   ((ch = xgetc_tmout(0)) != ERROR))
		{										/* Drain user serial buffer */
			if ( ch == brkchr )					/* If BREAK char			*/
				ser_break(serport, TICKS_PER_SEC); /* 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 = xTaskGetTickCount();		/* 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 = xTaskGetTickCount();	/* Reset timeout		*/
			xputn(buffer, n);
		}

		task_delay(TICKS_PER_SEC/50);			/* 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											*/
/************************************************************************/
CmdRtn connect(ParmMask_t pmask, char *name, Parm_t unused1, Parm_t unused2,
			   Parm_t unused3, Byte *buffer)
{
	Driver		*idp;					/* Instrument driver pointer	*/
	Port_t		iport;					/* Instrument serial port		*/
	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 BIG_TX_BUF support.  Do we need to add it back in?				*/
	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		*/

	xprintf( "Connected\n" );					/* Print connected message	*/

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

	(*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		*/
	newline();									/* Start new line			*/

	return( OK );

} /* connect() */


/************************************************************************/
/* Function	   : time													*/
/* Purpose	   : Get/Set Time and Date									*/
/* Inputs	   : Parm Mask, String										*/
/* Outputs	   : OK														*/
/************************************************************************/
CmdRtn usr_time(ParmMask_t pmask, char *s)
{
	time_t			oldSecs, newSecs;
	Nat16			oldMs;
	struct tm		*newtime;
	int				year;
	CmdRtn			rtn = 0;

	if ( pmask & 1 )
	{
		oldSecs = clkTime();					/* For drvr_timechange	*/
		newtime = gmtime(&oldSecs);				/* Init newtime in case	*/
												/* user input has no date*/
		newtime->tm_sec = 0;					/* In case no seconds	*/

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

		if (rtn >= 2)
		{
			if ((newSecs = mktime(newtime)) != -1)
			{
				clkSetTime(newSecs);

				xprintf("Sending time diff of %d seconds to drivers\n",
						(Int32)(newSecs-oldSecs));

				drvr_timechange((Int32)(newSecs - oldSecs));
			}
			else
				xputs("Error in mktime()\n");
		}
    }

	clkPrintTime(NULL);
	newline();

	return( OK );

} /* usr_time() */


#ifdef TNC
/************************************************************************/
/* Function	   : tnc_cmd												*/
/* Purpose	   : Send command string to Packet Radio TNC				*/
/* Inputs	   : Parm Mask, String										*/
/* Outputs	   : OK														*/
/************************************************************************/
CmdRtn tnc_cmd(ParmMask_t pmask, char *cmd, Parm_t unused1, Parm_t unused2, 
			   Parm_t unused3, Byte *buffer)
{
	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() */
#endif


/************************************************************************/
/* Function	   : isValidMemory											*/
/* Purpose	   : Check if address is valid PIC32 Memory					*/
/* Inputs	   : Memory ptr												*/
/* Outputs	   : 0 if invalid, 1 if readable, 2 if writable				*/
/************************************************************************/
MLocal int isValidMemory(Byte *p)
{
	Nat32		addr, virtBits;

	addr = (Nat32)p;
	virtBits = (addr >> 29) & 0x7;
	addr &= 0x1FFFFFFFF;
	if ((virtBits != 0) && (virtBits != 4) && (virtBits != 5))
		return(0);

	if (addr < (RAM_START + RAM_LEN))
		return(2);

	if (addr < (FLASH_START + FLASH_LEN))
		return(1);
	
	if (addr < (BOOT_START + BOOT_LEN))
		return(1);
	
	if (addr < (SFR_START + SFR_LEN))
		return(1);
	
	return(0);

} /* isValidMemory() */


/************************************************************************/
/* Function	   : display												*/
/* Purpose	   : Display Memory											*/
/* Inputs	   : Parm Mask, Start address, Length						*/
/* Outputs	   : OK														*/
/************************************************************************/
CmdRtn display(ParmMask_t pmask, Parm_t start, Parm_t len)
{
	Int32			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	*/
/************************************************************************/
CmdRtn set(ParmMask_t 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) < 2)
			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														*/
/************************************************************************/
CmdRtn version(ParmMask_t pmask, char *arg)
{
	xprintf("\n%s %s %s \n", signon, __DATE__, __TIME__);
  
	/* spit out CVS tag */
	if ((pmask & 1) && 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														*/
/************************************************************************/
CmdRtn usrSysLog(ParmMask_t pmask, char *msg)
{
	sysLogPrintf(msg);
	return(OK);
  
} /* usrSysLog() */
